UNPKG

ares-ide

Version:

A browser-based code editor and UI designer for Enyo 2 projects

261 lines (255 loc) 8.33 kB
/** _enyo.FlyweightRepeater_ is a control that displays a repeating list of rows, suitable for displaying medium-sized lists (up to ~100 items). A flyweight strategy is employed to render one set of row controls, as needed, for as many rows as are contained in the repeater. The FlyweightRepeater's _components_ block contains the controls to be used for a single row. This set of controls will be rendered for each row. You may customize row rendering by handling the _onSetupItem_ event. The controls inside a FlyweightRepeater are non-interactive. This means that calling methods that would normally cause rendering to occur (e.g., _set("content", value)_) will not do so. However, you may force a row to render by calling _renderRow(inRow)_. In addition, you may force a row to be temporarily interactive by calling _prepareRow(inRow)_. Call the _lockRow()_ method when the interaction is complete. For more information, see the documentation on [Lists](building-apps/layout/lists.html) in the Enyo Developer Guide. */ enyo.kind({ name: "enyo.FlyweightRepeater", published: { //* Number of rows to render count: 0, /** If true, the selection mechanism is disabled. Tap events are still sent, but items won't be automatically re-rendered when tapped. */ noSelect: false, //* If true, multiple selections are allowed multiSelect: false, //* If true, the selected item will toggle toggleSelected: false, /** Used to specify CSS classes for the repeater's wrapper component (client). Input is identical to enyo.Control.setClasses() */ clientClasses: '', /** Used to specify custom styling for the repeater's wrapper component (client). Input is identical to enyo.Control.setStyle() */ clientStyle: '', /** Offset applied to row number during generation. Used to allow a items to use a natural index instead of being forced to be 0-based. Must be positive, as row -1 is used for undefined rows in the event system. */ rowOffset: 0, /** Direction items will be laid out--either "v" for vertical or "h" for horizontal */ orient: "v" }, events: { /** Fires once per row at render time. _inEvent.index_ contains the current row index. _inEvent.selected_ is a boolean indicating whether the current row is selected. */ onSetupItem: "", //* Fires after an individual row has been rendered from a call to _renderRow()_. onRenderRow: "" }, //* design-time attribute, indicates if row indices run //* from [0.._count_-1] (false) or [_count_-1..0] (true) bottomUp: false, //* @protected components: [ {kind: "Selection", onSelect: "selectDeselect", onDeselect: "selectDeselect"}, {name: "client"} ], create: enyo.inherit(function(sup) { return function() { sup.apply(this, arguments); this.noSelectChanged(); this.multiSelectChanged(); this.clientClassesChanged(); this.clientStyleChanged(); }; }), noSelectChanged: function() { if (this.noSelect) { this.$.selection.clear(); } }, multiSelectChanged: function() { this.$.selection.setMulti(this.multiSelect); }, clientClassesChanged: function() { this.$.client.setClasses(this.clientClasses); }, clientStyleChanged: function() { this.$.client.setStyle(this.clientStyle); }, setupItem: function(inIndex) { this.doSetupItem({index: inIndex, selected: this.isSelected(inIndex)}); }, //* Renders the list. generateChildHtml: enyo.inherit(function(sup) { return function() { var h = ""; this.index = null; // note: can supply a rowOffset // and indicate if rows should be rendered top down or bottomUp for (var i=0, r=0; i<this.count; i++) { r = this.rowOffset + (this.bottomUp ? this.count - i-1 : i); this.setupItem(r); this.$.client.setAttribute("data-enyo-index", r); if (this.orient == "h") { this.$.client.setStyle("display:inline-block;"); } h += sup.apply(this, arguments); this.$.client.teardownRender(); } return h; }; }), previewDomEvent: function(inEvent) { var i = this.index = this.rowForEvent(inEvent); inEvent.rowIndex = inEvent.index = i; inEvent.flyweight = this; }, decorateEvent: enyo.inherit(function(sup) { return function(inEventName, inEvent, inSender) { // decorate event with index found via dom iff event does not already contain an index. var i = (inEvent && inEvent.index != null) ? inEvent.index : this.index; if (inEvent && i != null) { inEvent.index = i; inEvent.flyweight = this; } sup.apply(this, arguments); }; }), tap: function(inSender, inEvent) { // ignore taps if selecting is disabled or if they don't target a row if (this.noSelect || inEvent.index === -1) { return; } if (this.toggleSelected) { this.$.selection.toggle(inEvent.index); } else { this.$.selection.select(inEvent.index); } }, selectDeselect: function(inSender, inEvent) { this.renderRow(inEvent.key); }, //* @public //* Returns the repeater's _selection_ component. getSelection: function() { return this.$.selection; }, //* Gets the selection state for the given row index. isSelected: function(inIndex) { return this.getSelection().isSelected(inIndex); }, //* Renders the row specified by _inIndex_. renderRow: function(inIndex) { // do nothing if index is out-of-range if (inIndex < this.rowOffset || inIndex >= this.count + this.rowOffset) { return; } //this.index = null; // always call the setupItem callback, as we may rely on the post-render state this.setupItem(inIndex); var node = this.fetchRowNode(inIndex); if (node) { enyo.dom.setInnerHtml(node, this.$.client.generateChildHtml()); this.$.client.teardownChildren(); this.doRenderRow({rowIndex: inIndex}); } }, //* Fetches the DOM node for the given row index. fetchRowNode: function(inIndex) { if (this.hasNode()) { return this.node.querySelector('[data-enyo-index="' + inIndex + '"]'); } }, //* Fetches the row number corresponding with the target of a given event. rowForEvent: function(inEvent) { if (!this.hasNode()) { return -1; } var n = inEvent.target; while (n && n !== this.node) { var i = n.getAttribute && n.getAttribute("data-enyo-index"); if (i !== null) { return Number(i); } n = n.parentNode; } return -1; }, //* Prepares the row specified by _inIndex_ such that changes made to the //* controls inside the repeater will be rendered for the given row. prepareRow: function(inIndex) { // do nothing if index is out-of-range if (inIndex < this.rowOffset || inIndex >= this.count + this.rowOffset) { return; } // update row internals to match model this.setupItem(inIndex); var n = this.fetchRowNode(inIndex); enyo.FlyweightRepeater.claimNode(this.$.client, n); }, //* Prevents rendering of changes made to controls inside the repeater. lockRow: function() { this.$.client.teardownChildren(); }, //* Prepares the row specified by _inIndex_ such that changes made to the //* controls in the row will be rendered in the given row; then performs the //* function _inFunc_, and, finally, locks the row. performOnRow: function(inIndex, inFunc, inContext) { // do nothing if index is out-of-range if (inIndex < this.rowOffset || inIndex >= this.count + this.rowOffset) { return; } if (inFunc) { this.prepareRow(inIndex); enyo.call(inContext || null, inFunc); this.lockRow(); } }, statics: { //* Associates a flyweight rendered control (_inControl_) with a //* rendering context specified by _inNode_. claimNode: function(inControl, inNode) { var n; if (inNode) { if (inNode.id !== inControl.id) { n = inNode.querySelector("#" + inControl.id); } else { // inNode is already the right node, so just use it n = inNode; } } // FIXME: consider controls generated if we found a node or tag: null, the later so can teardown render inControl.generated = Boolean(n || !inControl.tag); inControl.node = n; if (inControl.node) { inControl.rendered(); } else { //enyo.log("Failed to find node for", inControl.id, inControl.generated); } // update control's class cache based on the node contents for (var i=0, c$=inControl.children, c; (c=c$[i]); i++) { this.claimNode(c, inNode); } } } });