/** * Provides indentation and folder structure markup for a Tree taking into account * depth and position within the tree hierarchy. */Ext.define('Ext.tree.Column', { extend: 'Ext.grid.column.Column', alias: 'widget.treecolumn', tdCls: Ext.baseCSSPrefix + 'grid-cell-treecolumn', autoLock: true, lockable: false, draggable: false, hideable: false, iconCls: Ext.baseCSSPrefix + 'tree-icon', checkboxCls: Ext.baseCSSPrefix + 'tree-checkbox', elbowCls: Ext.baseCSSPrefix + 'tree-elbow', expanderCls: Ext.baseCSSPrefix + 'tree-expander', textCls: Ext.baseCSSPrefix + 'tree-node-text', innerCls: Ext.baseCSSPrefix + 'grid-cell-inner-treecolumn', customIconCls: Ext.baseCSSPrefix + 'tree-icon-custom', isTreeColumn: true, /** * @cfg {Function/String} renderer * A renderer is an 'interceptor' method which can be used to transform data (value, * appearance, etc.) before it is rendered. Note that a *tree column* renderer yields * the *text* of the node. The lines and icons are produced by configurations. See * below for more information. * * **NOTE:** In previous releases, a string was treated as a method on * `Ext.util.Format` but that is now handled by the {@link #formatter} config. * * @param {Object} value The data value for the current cell * * renderer: function(value){ * // evaluates `value` to append either `person' or `people` * return Ext.util.Format.plural(value, 'person', 'people'); * } * * @param {Object} metaData A collection of metadata about the current cell; can be * used or modified by the renderer. Recognized properties are: `tdCls`, `tdAttr`, * `tdStyle`, `icon`, `iconCls` and `glyph`. * * For the standard grid column metaData propertyes see * {@link Ext.grid.column.Column#cfg-renderer column renderer} * * To change the icon used in the node, you can use the glyph metaData property as below. * * You can see an example of using the metaData parameter below. * * renderer: function(v, metaData, record) { * metaData.glyph = record.glyph; * return v; * } * * or * * renderer: function(v, metaData, record) { * metaData.icon = record.icon; * return v; * } * * @param {Ext.data.Model} record The record for the current row * * renderer: function (value, metaData, record) { * // evaluate the record's `updated` field and if truthy return the value * // from the `newVal` field, else return value * var updated = record.get('updated'); * return updated ? record.get('newVal') : value; * } * * @param {Number} rowIndex The index of the current row * * renderer: function (value, metaData, record, rowIndex) { * // style the cell differently for even / odd values * var odd = (rowIndex % 2 === 0); * metaData.tdStyle = 'color:' + (odd ? 'gray' : 'red'); * } * * @param {Number} colIndex The index of the current column * * var myRenderer = function(value, metaData, record, rowIndex, colIndex) { * if (colIndex === 0) { * metaData.tdAttr = 'data-qtip=' + value; * } * // additional logic to apply to values in all columns * return value; * } * * // using the same renderer on all columns you can process the value for * // each column with the same logic and only set a tooltip on the first column * renderer: myRenderer * * _See also {@link Ext.tip.QuickTipManager}_ * * @param {Ext.data.Store} store The data store * * renderer: function (value, metaData, record, rowIndex, colIndex, store) { * // style the cell differently depending on how the value relates to the * // average of all values * var average = store.average('grades'); * metaData.tdCls = (value < average) ? 'needsImprovement' : 'satisfactory'; * return value; * } * * @param {Ext.view.View} view The data view * * renderer: function (value, metaData, record, rowIndex, colIndex, store, view) { * // style the cell using the dataIndex of the column * var headerCt = this.getHeaderContainer(), * column = headerCt.getHeaderAtIndex(colIndex); * * metaData.tdCls = 'app-' + column.dataIndex; * return value; * } * * @return {String} * The HTML string to be rendered into the text portion of the tree node. */ /* eslint-disable indent, max-len */ cellTpl: [ '<tpl for="lines">', '<div class="{parent.childCls} {parent.elbowCls}-img ', '{parent.elbowCls}-<tpl if=".">line<tpl else>empty</tpl>" role="presentation"></div>', '</tpl>', '<div class="{childCls} {elbowCls}-img {elbowCls}', '<tpl if="isLast">-end</tpl><tpl if="expandable">-plus {expanderCls}</tpl>" role="presentation"></div>', '<tpl if="checked !== null">', '<div role="checkbox" {ariaCellCheckboxAttr}', ' class="{childCls} {checkboxCls}<tpl if="checked"> {checkboxCls}-checked</tpl>"></div>', '</tpl>', '<tpl if="glyph">', '<span class="{baseIconCls}" ', '<tpl if="glyphFontFamily">', 'style="font-family:{glyphFontFamily}"', '</tpl>', '>{glyph}</span>', '<tpl else>', '<tpl if="icon">', '<img src="{blankUrl}"', '<tpl else>', '<div', '</tpl>', ' role="presentation" class="{childCls} {baseIconCls} {customIconCls} ', '{baseIconCls}-<tpl if="leaf">leaf<tpl else><tpl if="expanded">parent-expanded<tpl else>parent</tpl></tpl> {iconCls}" ', '<tpl if="icon">style="background-image:url({icon})"/><tpl else>></div></tpl>', '</tpl>', '<tpl if="href">', '<a href="{href}" role="link" target="{hrefTarget}" class="{textCls} {childCls}">{value}</a>', '<tpl else>', '<span class="{textCls} {childCls}">{value}</span>', '</tpl>' ], /* eslint-enable indent, max-len */ // fields that will trigger a change in the ui that aren't likely to be bound to a column uiFields: { checked: 1, icon: 1, iconCls: 1 }, // fields that requires a full row render rowFields: { expanded: 1, loaded: 1, expandable: 1, leaf: 1, loading: 1, qtip: 1, qtitle: 1, cls: 1 }, initComponent: function() { var me = this; me.rendererScope = me.scope; me.setupRenderer(); // This always uses its own renderer. // Any custom renderer is used as an inner renderer to produce the text node of a tree cell. me.innerRenderer = me.renderer; me.renderer = me.treeRenderer; me.callParent(); me.scope = me; }, treeRenderer: function(value, metaData, record, rowIdx, colIdx, store, view) { var me = this, cls = record.get('cls'), rendererData; // The initial render will inject the cls into the TD's attributes. // If cls is ever *changed*, then the full rendering path is followed. if (metaData && cls) { metaData.tdCls += ' ' + cls; } rendererData = me.initTemplateRendererData(value, metaData, record, rowIdx, colIdx, store, view); return me.lookupTpl('cellTpl').apply(rendererData); }, initTemplateRendererData: function(value, metaData, record, rowIdx, colIdx, store, view) { var me = this, innerRenderer = me.innerRenderer, data = record.data, parent = record.parentNode, rootVisible = view.rootVisible, lines = [], ariaDescribeIds = [], parentData, glyph, glyphFontFamily, cell, ariaCheckDescId, ariaUnCheckDescId, ariaExpandDescId, ariaCollapseDescId, checkCell; while (parent && (rootVisible || parent.data.depth > 0)) { parentData = parent.data; lines[rootVisible ? parentData.depth : parentData.depth - 1] = parent.isLastVisible() ? 0 : 1; parent = parent.parentNode; } // Clear down metadata properties which may be set by the user renderer // because we apply them if we find them set, and the metedata property // is a static object owned by the View. // metaData may not be present if the column is being rendered through a // default renderer with no extra params. if (metaData) { metaData.iconCls = metaData.icon = metaData.glyph = null; } else { metaData = {}; } // Call renderer now so that we can use metaData properties that it may set. value = innerRenderer ? innerRenderer.apply(me.rendererScope, arguments) : value; // If a glyph was specified, then // transform glyph to the useful parts glyph = metaData.glyph || data.glyph; if (glyph) { glyph = Ext.Glyph.fly(glyph); glyphFontFamily = glyph.fontFamily; glyph = glyph.character; } cell = view.getCell(record, me); if (cell && me.isTreeColumn) { ariaCheckDescId = view.id + '-aria-description-checked'; ariaUnCheckDescId = view.id + '-aria-description-unchecked'; ariaExpandDescId = view.id + '-aria-description-expanded'; ariaCollapseDescId = view.id + '-aria-description-collapsed'; if (!Ext.isEmpty(data.checked)) { checkCell = Ext.fly(cell).selectNode('.' + me.checkboxCls); ariaDescribeIds.push(data.checked ? ariaCheckDescId : ariaUnCheckDescId); if (checkCell) { checkCell.setAttribute( 'aria-checked', data.checked); } } if (data.expandable && !data.leaf) { ariaDescribeIds.push(data.expanded ? ariaExpandDescId : ariaCollapseDescId); if (Ext.isIE) { Ext.fly(cell).selectNode('.' + me.expanderCls).setAttribute( 'aria-labelledby', data.expanded ? ariaExpandDescId : ariaCollapseDescId); } } if (ariaDescribeIds.length) { cell.setAttribute('aria-describedby', ariaDescribeIds.join(" ")); } else { cell.removeAttribute('aria-describedby'); } } return { record: record, baseIconCls: me.iconCls, customIconCls: (data.icon || data.iconCls) ? me.customIconCls : '', glyph: glyph, glyphFontFamily: glyphFontFamily, iconCls: metaData.iconCls || data.iconCls, icon: metaData.icon || data.icon, checkboxCls: me.checkboxCls, checked: data.checked, elbowCls: me.elbowCls, expanderCls: me.expanderCls, textCls: me.textCls, leaf: data.leaf, expandable: record.isExpandable(), expanded: data.expanded, isLast: record.isLastVisible(), blankUrl: Ext.BLANK_IMAGE_URL, href: data.href, hrefTarget: data.hrefTarget, lines: lines, metaData: metaData, // subclasses or overrides can implement a getChildCls() method, which can // return an extra class to add to all of the cell's child elements (icon, // expander, elbow, checkbox). This is used by the rtl override to add the // "x-rtl" class to these elements. childCls: me.getChildCls ? me.getChildCls() + ' ' : '', value: value || store.defaultRootText }; }, shouldUpdateCell: function(record, changedFieldNames) { // For the TreeColumn, if any of the known tree column UI affecting fields are updated // the cell should be updated in whatever way. // 1 if a custom renderer (not our default tree cell renderer), else 2. var me = this, i = 0, len, field; // If the column has a renderer which peeks and pokes at other data, // return 1 which means that a whole new TableView item must be rendered. if (me.hasCustomRenderer) { return 1; } if (changedFieldNames) { len = changedFieldNames.length; for (; i < len; ++i) { field = changedFieldNames[i]; // Check for fields which always require a full row update. if (me.rowFields[field]) { return 1; } // Check for fields which require this column to be updated. // The TreeColumn's treeRenderer is not a custom renderer. if (me.uiFields[field]) { return 2; } } } return me.callParent([record, changedFieldNames]); }, privates: { shouldFlagCustomRenderer: function() { return this.hasCustomRenderer || (this.innerRenderer && this.innerRenderer.length > 1); } }});