| Index: third_party/polymer/components/core-list/core-list.html
|
| diff --git a/third_party/polymer/components/core-list/core-list.html b/third_party/polymer/components/core-list/core-list.html
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..d81510827b94821ddae7a6567b8754804edfc063
|
| --- /dev/null
|
| +++ b/third_party/polymer/components/core-list/core-list.html
|
| @@ -0,0 +1,403 @@
|
| +<!--
|
| +Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
| +This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
| +The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
| +The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
| +Code distributed by Google as part of the polymer project is also
|
| +subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
| +-->
|
| +
|
| +<!--
|
| +`core-list` displays a virtual, 'infinite' list. The template inside the
|
| +`core-list` element represents the dom to create for each list item. The
|
| +`data` property specifies an array of list item data. The `height` property
|
| +represents the height of a list item.
|
| +
|
| +By default, the list supports selection via tapping. Styling the selection
|
| +should be done via binding. The `selectedProperty` property is set on the
|
| +list view data for each selected item.
|
| +
|
| +`core-list` manages a viewport of data based on the current scroll position.
|
| +For performance reasons, not every item in the list is rendered at once.
|
| +
|
| + <core-list data="{{data}}" height="80">
|
| + <template>
|
| + <div class="{{ {selected: selected} | tokenList }}">List row: {{index}}</div>
|
| + </template>
|
| + </core-list>
|
| +
|
| +@group Polymer Core Elements
|
| +@element core-list
|
| +-->
|
| +<link rel="import" href="../polymer/polymer.html">
|
| +<link rel="import" href="../core-selection/core-selection.html">
|
| +
|
| +<polymer-element name="core-list" on-tap="{{tapHandler}}">
|
| +<template>
|
| + <core-selection id="selection" multi="{{multi}}" on-core-select="{{selectedHandler}}"></core-selection>
|
| + <link rel="stylesheet" href="core-list.css">
|
| + <div id="viewport" class="core-list-viewport"><content></content></div>
|
| +</template>
|
| +<script>
|
| +(function() {
|
| +
|
| + Polymer('core-list', {
|
| +
|
| + publish: {
|
| + /**
|
| + * Fired when an item element is tapped.
|
| + *
|
| + * @event core-activate
|
| + * @param {Object} detail
|
| + * @param {Object} detail.item the item element
|
| + */
|
| +
|
| + /**
|
| + *
|
| + * An array of source data for the list to display.
|
| + *
|
| + * @attribute data
|
| + * @type array
|
| + * @default null
|
| + */
|
| + data: null,
|
| +
|
| + /**
|
| + *
|
| + * An optional element on which to listen for scroll events.
|
| + *
|
| + * @attribute scrollTarget
|
| + * @type Element
|
| + * @default core-list
|
| + */
|
| + scrollTarget: null,
|
| +
|
| + /**
|
| + *
|
| + * The height of a list item. `core-list` currently supports only fixed-height
|
| + * list items. This height must be specified via the height property.
|
| + *
|
| + * @attribute height
|
| + * @type number
|
| + * @default 80
|
| + */
|
| + height: 80,
|
| +
|
| + /**
|
| + *
|
| + * The number of extra items rendered above the minimum set required to
|
| + * fill the list's height.
|
| + *
|
| + * @attribute extraItems
|
| + * @type number
|
| + * @default 30
|
| + */
|
| + extraItems: 30,
|
| +
|
| + /**
|
| + *
|
| + * The property set on the list view data to represent selection state.
|
| + * This should set so that it does not conflict with other data properties.
|
| + * Note, selection data is not stored on the data in given in the data property.
|
| + *
|
| + * @attribute selectedProperty
|
| + * @type string
|
| + * @default 'selected'
|
| + */
|
| + selectedProperty: 'selected',
|
| +
|
| + // TODO(sorvell): experimental
|
| + /**
|
| + *
|
| + * If true, data is sync'd from the list back to the list's data.
|
| + *
|
| + * @attribute sync
|
| + * @type boolean
|
| + * @default false
|
| + */
|
| + sync: false,
|
| +
|
| + /**
|
| + *
|
| + * Set to true to support multiple selection.
|
| + *
|
| + * @attribute multi
|
| + * @type boolean
|
| + * @default false
|
| + */
|
| + multi: false
|
| +
|
| + },
|
| +
|
| + observe: {
|
| + 'data template scrollTarget': 'initialize'
|
| + },
|
| +
|
| + ready: function() {
|
| + this.clearSelection();
|
| + this._boundScrollHandler = this.scrollHandler.bind(this);
|
| + },
|
| +
|
| + attached: function() {
|
| + this.template = this.querySelector('template');
|
| + },
|
| +
|
| + // TODO(sorvell): it'd be nice to dispense with 'data' and just use
|
| + // template repeat's model. However, we need tighter integration
|
| + // with TemplateBinding for this.
|
| + initialize: function() {
|
| + if (!this.data || !this.template) {
|
| + return;
|
| + }
|
| + var target = this.scrollTarget || this;
|
| + if (this._target !== target) {
|
| + if (this._target) {
|
| + this._target.removeEventListener('scroll', this._boundScrollHandler, false);
|
| + }
|
| + this._target = target;
|
| + this._target.addEventListener('scroll', this._boundScrollHandler, false);
|
| + }
|
| +
|
| + this.initializeViewport();
|
| + this.initalizeData();
|
| + this.onMutation(this, this.initializeItems);
|
| + },
|
| +
|
| + // TODO(sorvell): need to handle resizing
|
| + initializeViewport: function() {
|
| + this.$.viewport.style.height = this.height * this.data.length + 'px';
|
| + this._visibleCount = Math.ceil(this._target.offsetHeight / this.height);
|
| + this._physicalCount = Math.min(this._visibleCount + this.extraItems,
|
| + this.data.length);
|
| + this._physicalHeight = this.height * this._physicalCount;
|
| + },
|
| +
|
| + // TODO(sorvell): selection currently cannot be maintained when
|
| + // items are added or deleted.
|
| + initalizeData: function() {
|
| + var exampleDatum = this.data[0] || {};
|
| + this._propertyNames = Object.getOwnPropertyNames(exampleDatum);
|
| + this._physicalData = new Array(this._physicalCount);
|
| + for (var i = 0; i < this._physicalCount; ++i) {
|
| + this._physicalData[i] = {};
|
| + this.updateItem(i, i);
|
| + }
|
| + this.template.model = this._physicalData;
|
| + this.template.setAttribute('repeat', '');
|
| + },
|
| +
|
| + initializeItems: function() {
|
| + this._physicalItems = new Array(this._physicalCount);
|
| + for (var i = 0, item = this.template.nextElementSibling;
|
| + item && i < this._physicalCount;
|
| + ++i, item = item.nextElementSibling) {
|
| + this._physicalItems[i] = item;
|
| + item._transformValue = 0;
|
| + }
|
| + this.refresh(false);
|
| + },
|
| +
|
| + updateItem: function(virtualIndex, physicalIndex) {
|
| + var virtualDatum = this.data[virtualIndex];
|
| + var physicalDatum = this._physicalData[physicalIndex];
|
| + this.pushItemData(virtualDatum, physicalDatum);
|
| + physicalDatum._physicalIndex = physicalIndex;
|
| + physicalDatum._virtualIndex = virtualIndex;
|
| + if (this.selectedProperty) {
|
| + physicalDatum[this.selectedProperty] = this._selectedData.get(virtualDatum);
|
| + }
|
| + },
|
| +
|
| + pushItemData: function(source, dest) {
|
| + for (var i = 0; i < this._propertyNames.length; ++i) {
|
| + var propertyName = this._propertyNames[i];
|
| + dest[propertyName] = source[propertyName];
|
| + }
|
| + },
|
| +
|
| + // experimental: push physical data back to this.data.
|
| + // this is optional when scrolling and needs to be called at other times.
|
| + syncData: function() {
|
| + if (this.firstPhysicalIndex === undefined ||
|
| + this.baseVirtualIndex === undefined) {
|
| + return;
|
| + }
|
| + var p, v;
|
| + for (var i = 0; i < this.firstPhysicalIndex; ++i) {
|
| + p = this._physicalData[i];
|
| + v = this.data[this.baseVirtualIndex + this._physicalCount + i];
|
| + this.pushItemData(p, v);
|
| + }
|
| + for (var i = this.firstPhysicalIndex; i < this._physicalCount; ++i) {
|
| + p = this._physicalData[i];
|
| + v = this.data[this.baseVirtualIndex + i];
|
| + this.pushItemData(p, v);
|
| + }
|
| + },
|
| +
|
| + scrollHandler: function(e, detail) {
|
| + this._scrollTop = e.detail ? e.detail.target.scrollTop : e.target.scrollTop;
|
| + this.refresh(false);
|
| + },
|
| +
|
| + /**
|
| + * Refresh the list at the current scroll position.
|
| + *
|
| + * @method refresh
|
| + */
|
| + refresh: function(force) {
|
| + var firstVisibleIndex = Math.floor(this._scrollTop / this.height);
|
| + var visibleMidpoint = firstVisibleIndex + this._visibleCount / 2;
|
| +
|
| + var firstReifiedIndex = Math.max(0, Math.floor(visibleMidpoint -
|
| + this._physicalCount / 2));
|
| + firstReifiedIndex = Math.min(firstReifiedIndex, this.data.length -
|
| + this._physicalCount);
|
| +
|
| + var firstPhysicalIndex = firstReifiedIndex % this._physicalCount;
|
| + var baseVirtualIndex = firstReifiedIndex - firstPhysicalIndex;
|
| +
|
| + var baseTransformValue = Math.floor(this.height * baseVirtualIndex);
|
| + var nextTransformValue = Math.floor(baseTransformValue +
|
| + this._physicalHeight);
|
| +
|
| + var baseTransformString = 'translate3d(0,' + baseTransformValue + 'px,0)';
|
| + var nextTransformString = 'translate3d(0,' + nextTransformValue + 'px,0)';
|
| + // TODO(sorvell): experiemental for sync'ing back to virtual data.
|
| + if (this.sync) {
|
| + this.syncData();
|
| + }
|
| + this.firstPhysicalIndex = firstPhysicalIndex;
|
| + this.baseVirtualIndex = baseVirtualIndex;
|
| +
|
| + for (var i = 0; i < firstPhysicalIndex; ++i) {
|
| + var item = this._physicalItems[i];
|
| + if (force || item._transformValue != nextTransformValue) {
|
| + this.updateItem(baseVirtualIndex + this._physicalCount + i, i);
|
| + setTransform(item, nextTransformString, nextTransformValue);
|
| + }
|
| + }
|
| + for (var i = firstPhysicalIndex; i < this._physicalCount; ++i) {
|
| + var item = this._physicalItems[i];
|
| + if (force || item._transformValue != baseTransformValue) {
|
| + this.updateItem(baseVirtualIndex + i, i);
|
| + setTransform(item, baseTransformString, baseTransformValue);
|
| + }
|
| + }
|
| + },
|
| +
|
| + // list selection
|
| + tapHandler: function(e) {
|
| + if (e.target === this) {
|
| + return;
|
| + }
|
| + if (this.sync) {
|
| + this.syncData();
|
| + }
|
| + var n = e.target;
|
| + var model = n.templateInstance && n.templateInstance.model;
|
| + if (model) {
|
| + var vi = model._virtualIndex, pi = model._physicalIndex;
|
| + var data = this.data[vi], item = this._physicalItems[pi];
|
| + this.$.selection.select(data);
|
| + this.asyncFire('core-activate', {data: data, item: item});
|
| + }
|
| + },
|
| +
|
| + selectedHandler: function(e, detail) {
|
| + if (this.selectedProperty) {
|
| + var i$ = this.indexesForData(detail.item);
|
| + // TODO(sorvell): we should be relying on selection to store the
|
| + // selected data but we want to optimize for lookup.
|
| + this._selectedData.set(detail.item, detail.isSelected);
|
| + if (i$.physical >= 0) {
|
| + this.updateItem(i$.virtual, i$.physical);
|
| + }
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Select the list item at the given index.
|
| + *
|
| + * @method selectItem
|
| + * @param {number} index
|
| + */
|
| + selectItem: function(index) {
|
| + var data = this.data[index];
|
| + if (data) {
|
| + this.$.selection.select(data);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Set the selected state of the list item at the given index.
|
| + *
|
| + * @method setItemSelected
|
| + * @param {number} index
|
| + * @param {boolean} isSelected
|
| + */
|
| + setItemSelected: function(index, isSelected) {
|
| + var data = this.data[index];
|
| + if (data) {
|
| + this.$.selection.setItemSelected(data, isSelected);
|
| + }
|
| + },
|
| +
|
| + indexesForData: function(data) {
|
| + var virtual = this.data.indexOf(data);
|
| + var physical = this.virtualToPhysicalIndex(virtual);
|
| + return { virtual: virtual, physical: physical };
|
| + },
|
| +
|
| + virtualToPhysicalIndex: function(index) {
|
| + for (var i=0, l=this._physicalData.length; i<l; i++) {
|
| + if (this._physicalData[i]._virtualIndex === index) {
|
| + return i;
|
| + }
|
| + }
|
| + return -1;
|
| + },
|
| +
|
| + get selection() {
|
| + return this.$.selection.getSelection();
|
| + },
|
| +
|
| + selectedChanged: function() {
|
| + this.$.selection.select(this.selected);
|
| + },
|
| +
|
| + clearSelection: function() {
|
| + this._selectedData = new WeakMap();
|
| + if (this.multi) {
|
| + var s$ = this.selection;
|
| + for (var i=0, l=s$.length, s; (i<l) && (s=s$[i]); i++) {
|
| + this.$.selection.setItemSelected(s, false);
|
| + }
|
| + } else {
|
| + this.$.selection.setItemSelected(this.selection, false);
|
| + }
|
| + this.$.selection.clear();
|
| + },
|
| +
|
| + scrollToItem: function(index) {
|
| + this.scrollTop = index * this.height;
|
| + }
|
| +
|
| + });
|
| +
|
| + // determine proper transform mechanizm
|
| + if (document.documentElement.style.transform !== undefined) {
|
| + function setTransform(element, string, value) {
|
| + element.style.transform = string;
|
| + element._transformValue = value;
|
| + }
|
| + } else {
|
| + function setTransform(element, string, value) {
|
| + element.style.webkitTransform = string;
|
| + element._transformValue = value;
|
| + }
|
| + }
|
| +
|
| +})();
|
| +</script>
|
| +</polymer-element>
|
|
|