[{"data":1,"prerenderedAt":566},["ShallowReactive",2],{"blog-\u002Fblog\u002Fmastering-dynamic-data-tables-icolumn-class":3},{"id":4,"title":5,"body":6,"date":556,"description":557,"draft":558,"extension":559,"lang":560,"meta":561,"navigation":352,"path":562,"seo":563,"stem":564,"__hash__":565},"blog\u002Fblog\u002Fmastering-dynamic-data-tables-icolumn-class.md","Mastering dynamic data tables with the IColumn class",{"type":7,"value":8,"toc":550},"minimark",[9,13,21,26,71,75,244,248,258,514,518,521,532,535,546],[10,11,12],"p",{},"Building scalable and reusable table components is a common challenge. Every table wants slightly different headers, cells, props, and sorting rules — and before long the component that was supposed to save you time has grown a new prop for each new screen.",[10,14,15,16,20],{},"The ",[17,18,19],"code",{},"IColumn"," class is my attempt at a different approach: describe each column as an object, let the table component render it. The configuration moves out of the table and into plain data, which is much easier to generate, reuse, and test.",[22,23,25],"h2",{"id":24},"key-features","Key features",[27,28,29,37,43,53],"ul",{},[30,31,32,36],"li",{},[33,34,35],"strong",{},"Customizable headers and rows."," Any Vue component can render a header or a cell, with props and events wired in.",[30,38,39,42],{},[33,40,41],{},"Flexible props and events."," Pass a static object, or a function that receives the row and returns one.",[30,44,45,48,49,52],{},[33,46,47],{},"Built-in sorting."," Strings and numbers work out of the box; supply a ",[17,50,51],{},"sorter"," for anything else.",[30,54,55,58,59,62,63,66,67,70],{},[33,56,57],{},"Special column types."," ",[17,60,61],{},"default"," for normal data, ",[17,64,65],{},"actions"," for row buttons at the end, ",[17,68,69],{},"selection"," for checkboxes at the start.",[22,72,74],{"id":73},"options","Options",[76,77,78,94],"table",{},[79,80,81],"thead",{},[82,83,84,88,91],"tr",{},[85,86,87],"th",{},"Property",[85,89,90],{},"Type",[85,92,93],{},"Description",[95,96,97,113,127,142,157,171,186,201,216,230],"tbody",{},[82,98,99,105,110],{},[100,101,102],"td",{},[17,103,104],{},"title",[100,106,107],{},[17,108,109],{},"string | function",[100,111,112],{},"Column header text",[82,114,115,120,124],{},[100,116,117],{},[17,118,119],{},"rowKey",[100,121,122],{},[17,123,109],{},[100,125,126],{},"Data property key for the row",[82,128,129,134,139],{},[100,130,131],{},[17,132,133],{},"props",[100,135,136],{},[17,137,138],{},"Object | function",[100,140,141],{},"Component props (static or dynamic)",[82,143,144,149,154],{},[100,145,146],{},[17,147,148],{},"rowComponent",[100,150,151],{},[17,152,153],{},"Object | undefined",[100,155,156],{},"Cell rendering component",[82,158,159,164,168],{},[100,160,161],{},[17,162,163],{},"headerComponent",[100,165,166],{},[17,167,153],{},[100,169,170],{},"Header rendering component",[82,172,173,178,183],{},[100,174,175],{},[17,176,177],{},"type",[100,179,180],{},[17,181,182],{},"'default' | 'actions' | 'selection'",[100,184,185],{},"Column behavior type",[82,187,188,193,198],{},[100,189,190],{},[17,191,192],{},"disabled",[100,194,195],{},[17,196,197],{},"boolean | function",[100,199,200],{},"Disables column functionality",[82,202,203,208,213],{},[100,204,205],{},[17,206,207],{},"sortable",[100,209,210],{},[17,211,212],{},"boolean",[100,214,215],{},"Enables column sorting",[82,217,218,222,227],{},[100,219,220],{},[17,221,51],{},[100,223,224],{},[17,225,226],{},"function",[100,228,229],{},"Custom comparison function",[82,231,232,237,241],{},[100,233,234],{},[17,235,236],{},"sortValue",[100,238,239],{},[17,240,153],{},[100,242,243],{},"Tracks sorting state",[22,245,247],{"id":246},"the-constructor","The constructor",[10,249,250,251,253,254,257],{},"The constructor pulls the options apart, fills in sensible defaults, and normalizes ",[17,252,133],{}," and ",[17,255,256],{},"events"," so the table never has to care whether they were static or functions.",[259,260,265],"pre",{"className":261,"code":262,"language":263,"meta":264,"style":264},"language-javascript shiki shiki-themes github-light github-dark","export class IColumn {\n  constructor(options) {\n    const {\n      title,\n      rowKey,\n      type = 'default',\n      sortable = false,\n      sorter = undefined,\n      headerComponent = undefined,\n      rowComponent = undefined,\n      disabled = false,\n      ...extras\n    } = options;\n\n    this.title = title;\n    this.rowKey = rowKey;\n    this.type = type;\n    this.sortable = sortable;\n    this.sorter =\n      sorter || ((row1, row2) => row1[this.rowKey] > row2[this.rowKey]);\n    this.headerComponent = this.processComponent(headerComponent);\n    this.rowComponent = this.processComponent(rowComponent);\n    this.disabled = disabled;\n    this.extras = extras;\n  }\n\n  processComponent(component) {\n    if (!component) return undefined;\n    return {\n      is: component.is || '',\n      props:\n        typeof component.props === 'function'\n          ? component.props\n          : () => component.props,\n      events:\n        typeof component.events === 'function'\n          ? component.events\n          : () => component.events,\n    };\n  }\n}\n","javascript","",[17,266,267,275,281,287,293,299,305,311,317,323,329,335,341,347,354,360,366,372,378,384,390,396,402,408,414,420,425,431,437,443,449,455,461,467,473,479,485,491,497,503,508],{"__ignoreMap":264},[268,269,272],"span",{"class":270,"line":271},"line",1,[268,273,274],{},"export class IColumn {\n",[268,276,278],{"class":270,"line":277},2,[268,279,280],{},"  constructor(options) {\n",[268,282,284],{"class":270,"line":283},3,[268,285,286],{},"    const {\n",[268,288,290],{"class":270,"line":289},4,[268,291,292],{},"      title,\n",[268,294,296],{"class":270,"line":295},5,[268,297,298],{},"      rowKey,\n",[268,300,302],{"class":270,"line":301},6,[268,303,304],{},"      type = 'default',\n",[268,306,308],{"class":270,"line":307},7,[268,309,310],{},"      sortable = false,\n",[268,312,314],{"class":270,"line":313},8,[268,315,316],{},"      sorter = undefined,\n",[268,318,320],{"class":270,"line":319},9,[268,321,322],{},"      headerComponent = undefined,\n",[268,324,326],{"class":270,"line":325},10,[268,327,328],{},"      rowComponent = undefined,\n",[268,330,332],{"class":270,"line":331},11,[268,333,334],{},"      disabled = false,\n",[268,336,338],{"class":270,"line":337},12,[268,339,340],{},"      ...extras\n",[268,342,344],{"class":270,"line":343},13,[268,345,346],{},"    } = options;\n",[268,348,350],{"class":270,"line":349},14,[268,351,353],{"emptyLinePlaceholder":352},true,"\n",[268,355,357],{"class":270,"line":356},15,[268,358,359],{},"    this.title = title;\n",[268,361,363],{"class":270,"line":362},16,[268,364,365],{},"    this.rowKey = rowKey;\n",[268,367,369],{"class":270,"line":368},17,[268,370,371],{},"    this.type = type;\n",[268,373,375],{"class":270,"line":374},18,[268,376,377],{},"    this.sortable = sortable;\n",[268,379,381],{"class":270,"line":380},19,[268,382,383],{},"    this.sorter =\n",[268,385,387],{"class":270,"line":386},20,[268,388,389],{},"      sorter || ((row1, row2) => row1[this.rowKey] > row2[this.rowKey]);\n",[268,391,393],{"class":270,"line":392},21,[268,394,395],{},"    this.headerComponent = this.processComponent(headerComponent);\n",[268,397,399],{"class":270,"line":398},22,[268,400,401],{},"    this.rowComponent = this.processComponent(rowComponent);\n",[268,403,405],{"class":270,"line":404},23,[268,406,407],{},"    this.disabled = disabled;\n",[268,409,411],{"class":270,"line":410},24,[268,412,413],{},"    this.extras = extras;\n",[268,415,417],{"class":270,"line":416},25,[268,418,419],{},"  }\n",[268,421,423],{"class":270,"line":422},26,[268,424,353],{"emptyLinePlaceholder":352},[268,426,428],{"class":270,"line":427},27,[268,429,430],{},"  processComponent(component) {\n",[268,432,434],{"class":270,"line":433},28,[268,435,436],{},"    if (!component) return undefined;\n",[268,438,440],{"class":270,"line":439},29,[268,441,442],{},"    return {\n",[268,444,446],{"class":270,"line":445},30,[268,447,448],{},"      is: component.is || '',\n",[268,450,452],{"class":270,"line":451},31,[268,453,454],{},"      props:\n",[268,456,458],{"class":270,"line":457},32,[268,459,460],{},"        typeof component.props === 'function'\n",[268,462,464],{"class":270,"line":463},33,[268,465,466],{},"          ? component.props\n",[268,468,470],{"class":270,"line":469},34,[268,471,472],{},"          : () => component.props,\n",[268,474,476],{"class":270,"line":475},35,[268,477,478],{},"      events:\n",[268,480,482],{"class":270,"line":481},36,[268,483,484],{},"        typeof component.events === 'function'\n",[268,486,488],{"class":270,"line":487},37,[268,489,490],{},"          ? component.events\n",[268,492,494],{"class":270,"line":493},38,[268,495,496],{},"          : () => component.events,\n",[268,498,500],{"class":270,"line":499},39,[268,501,502],{},"    };\n",[268,504,506],{"class":270,"line":505},40,[268,507,419],{},[268,509,511],{"class":270,"line":510},41,[268,512,513],{},"}\n",[22,515,517],{"id":516},"why-bother","Why bother?",[10,519,520],{},"Because column definitions become data, you get three things for free:",[27,522,523,526,529],{},[30,524,525],{},"Dynamic behavior per row without branching inside the table.",[30,527,528],{},"A single place to reason about rendering and interactivity.",[30,530,531],{},"Cross-cutting features — sorting, actions, selection — that slot in once and work everywhere.",[10,533,534],{},"That's the whole idea: treat each column as a structured, dynamic, reusable object, and let the table stay boring.",[10,536,537,538,545],{},"Originally published on ",[539,540,544],"a",{"href":541,"rel":542},"https:\u002F\u002Fwww.linkedin.com\u002Fpulse\u002Fmastering-dynamic-data-tables-icolumn-class-ali-abdelbaqy-dzytc\u002F",[543],"nofollow","LinkedIn",".",[547,548,549],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":264,"searchDepth":277,"depth":277,"links":551},[552,553,554,555],{"id":24,"depth":277,"text":25},{"id":73,"depth":277,"text":74},{"id":246,"depth":277,"text":247},{"id":516,"depth":277,"text":517},"2024-12-04","A small utility class that turns table columns into reusable, dynamic, sortable building blocks.",false,"md",null,{},"\u002Fblog\u002Fmastering-dynamic-data-tables-icolumn-class",{"title":5,"description":557},"blog\u002Fmastering-dynamic-data-tables-icolumn-class","UVMV8RcMnlCwvQxgHfZjn5pE6g13595Z3W7JHV6nRIc",1776556161208]