Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / polymer / components / core-list / core-list.html
1 <!--\r
2 Copyright (c) 2014 The Polymer Project Authors. All rights reserved.\r
3 This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt\r
4 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt\r
5 The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt\r
6 Code distributed by Google as part of the polymer project is also\r
7 subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt\r
8 -->\r
9 \r
10 <!--\r
11 `core-list` displays a virtual, 'infinite' list. The template inside the \r
12 `core-list` element represents the dom to create for each list item. The\r
13 `data` property specifies an array of list item data. The `height` property\r
14 represents the height of a list item.\r
15 \r
16 By default, the list supports selection via tapping. Styling the selection \r
17 should be done via binding. The `selectedProperty` property is set on the \r
18 list view data for each selected item.\r
19 \r
20 `core-list` manages a viewport of data based on the current scroll position.\r
21 For performance reasons, not every item in the list is rendered at once.\r
22 \r
23     <core-list data="{{data}}" height="80">\r
24       <template>\r
25         <div class="{{ {selected: selected} | tokenList }}">List row: {{index}}</div>\r
26       </template>\r
27     </core-list>\r
28 \r
29 @group Polymer Core Elements\r
30 @element core-list\r
31 -->\r
32 <link rel="import" href="../polymer/polymer.html">\r
33 <link rel="import" href="../core-selection/core-selection.html">\r
34 \r
35 <polymer-element name="core-list" on-tap="{{tapHandler}}">\r
36 <template>\r
37   <core-selection id="selection" multi="{{multi}}" on-core-select="{{selectedHandler}}"></core-selection>\r
38   <link rel="stylesheet" href="core-list.css">\r
39   <div id="viewport" class="core-list-viewport"><content></content></div>\r
40 </template>\r
41 <script>\r
42 (function() {\r
43 \r
44   Polymer('core-list', {\r
45     \r
46     publish: {\r
47       /**\r
48        * Fired when an item element is tapped.\r
49        * \r
50        * @event core-activate\r
51        * @param {Object} detail\r
52        *   @param {Object} detail.item the item element\r
53        */\r
54 \r
55       /**\r
56        * \r
57        * An array of source data for the list to display.\r
58        *\r
59        * @attribute data\r
60        * @type array\r
61        * @default null\r
62        */\r
63       data: null,\r
64 \r
65       /**\r
66        * \r
67        * An optional element on which to listen for scroll events.\r
68        *\r
69        * @attribute scrollTarget\r
70        * @type Element\r
71        * @default core-list\r
72        */\r
73       scrollTarget: null,\r
74 \r
75       /**\r
76        * \r
77        * The height of a list item. `core-list` currently supports only fixed-height\r
78        * list items. This height must be specified via the height property.\r
79        *\r
80        * @attribute height\r
81        * @type number\r
82        * @default 80\r
83        */\r
84       height: 80,\r
85 \r
86       /**\r
87        * \r
88        * The number of extra items rendered above the minimum set required to\r
89        * fill the list's height.\r
90        *\r
91        * @attribute extraItems\r
92        * @type number\r
93        * @default 30\r
94        */\r
95       extraItems: 30,\r
96 \r
97       /**\r
98        * \r
99        * The property set on the list view data to represent selection state. \r
100        * This should set so that it does not conflict with other data properties.\r
101        * Note, selection data is not stored on the data in given in the data property.\r
102        *\r
103        * @attribute selectedProperty\r
104        * @type string\r
105        * @default 'selected'\r
106        */\r
107       selectedProperty: 'selected',\r
108 \r
109       // TODO(sorvell): experimental\r
110       /**\r
111        * \r
112        * If true, data is sync'd from the list back to the list's data.\r
113        *\r
114        * @attribute sync\r
115        * @type boolean\r
116        * @default false\r
117        */\r
118       sync: false,\r
119 \r
120       /**\r
121        * \r
122        * Set to true to support multiple selection.\r
123        *\r
124        * @attribute multi\r
125        * @type boolean\r
126        * @default false\r
127        */\r
128       multi: false\r
129 \r
130     },\r
131     \r
132     observe: {\r
133       'data template scrollTarget': 'initialize'\r
134     },\r
135 \r
136     ready: function() {\r
137       this.clearSelection();\r
138       this._boundScrollHandler = this.scrollHandler.bind(this);\r
139     },\r
140 \r
141     attached: function() {\r
142       this.template = this.querySelector('template');\r
143     },\r
144 \r
145     // TODO(sorvell): it'd be nice to dispense with 'data' and just use \r
146     // template repeat's model. However, we need tighter integration\r
147     // with TemplateBinding for this.\r
148     initialize: function() {\r
149       if (!this.data || !this.template) {\r
150         return;\r
151       }\r
152       var target = this.scrollTarget || this;\r
153       if (this._target !== target) {\r
154         if (this._target) {\r
155           this._target.removeEventListener('scroll', this._boundScrollHandler, false);\r
156         }\r
157         this._target = target;\r
158         this._target.addEventListener('scroll', this._boundScrollHandler, false);\r
159       }\r
160 \r
161       this.initializeViewport();\r
162       this.initalizeData();\r
163       this.onMutation(this, this.initializeItems);\r
164     },\r
165 \r
166     // TODO(sorvell): need to handle resizing\r
167     initializeViewport: function() {\r
168       this.$.viewport.style.height = this.height * this.data.length + 'px';\r
169       this._visibleCount = Math.ceil(this._target.offsetHeight / this.height);\r
170       this._physicalCount = Math.min(this._visibleCount + this.extraItems,\r
171           this.data.length);\r
172       this._physicalHeight = this.height * this._physicalCount;\r
173     },\r
174 \r
175     // TODO(sorvell): selection currently cannot be maintained when\r
176     // items are added or deleted.\r
177     initalizeData: function() {\r
178       var exampleDatum = this.data[0] || {};\r
179       this._propertyNames = Object.getOwnPropertyNames(exampleDatum);\r
180       this._physicalData = new Array(this._physicalCount);\r
181       for (var i = 0; i < this._physicalCount; ++i) {\r
182         this._physicalData[i] = {};\r
183         this.updateItem(i, i);\r
184       }\r
185       this.template.model = this._physicalData;\r
186       this.template.setAttribute('repeat', '');\r
187     },\r
188 \r
189     initializeItems: function() {\r
190       this._physicalItems = new Array(this._physicalCount);\r
191       for (var i = 0, item = this.template.nextElementSibling;\r
192            item && i < this._physicalCount;\r
193            ++i, item = item.nextElementSibling) {\r
194         this._physicalItems[i] = item;\r
195         item._transformValue = 0;\r
196       }\r
197       this.refresh(false);\r
198     },\r
199 \r
200     updateItem: function(virtualIndex, physicalIndex) {\r
201       var virtualDatum = this.data[virtualIndex];\r
202       var physicalDatum = this._physicalData[physicalIndex];\r
203       this.pushItemData(virtualDatum, physicalDatum);\r
204       physicalDatum._physicalIndex = physicalIndex;\r
205       physicalDatum._virtualIndex = virtualIndex;\r
206       if (this.selectedProperty) {\r
207         physicalDatum[this.selectedProperty] = this._selectedData.get(virtualDatum);\r
208       }\r
209     },\r
210 \r
211     pushItemData: function(source, dest) {\r
212       for (var i = 0; i < this._propertyNames.length; ++i) {\r
213         var propertyName = this._propertyNames[i];\r
214         dest[propertyName] = source[propertyName];\r
215       }\r
216     },\r
217 \r
218     // experimental: push physical data back to this.data.\r
219     // this is optional when scrolling and needs to be called at other times.\r
220     syncData: function() {\r
221       if (this.firstPhysicalIndex === undefined || \r
222           this.baseVirtualIndex === undefined) {\r
223         return;\r
224       }\r
225       var p, v;\r
226       for (var i = 0; i < this.firstPhysicalIndex; ++i) {\r
227         p = this._physicalData[i];\r
228         v = this.data[this.baseVirtualIndex + this._physicalCount + i];\r
229         this.pushItemData(p, v);\r
230       }\r
231       for (var i = this.firstPhysicalIndex; i < this._physicalCount; ++i) {\r
232         p = this._physicalData[i];\r
233         v = this.data[this.baseVirtualIndex + i];\r
234         this.pushItemData(p, v);\r
235       }\r
236     },\r
237 \r
238     scrollHandler: function(e, detail) {\r
239       this._scrollTop = e.detail ? e.detail.target.scrollTop : e.target.scrollTop;\r
240       this.refresh(false);\r
241     },\r
242 \r
243     /**\r
244      * Refresh the list at the current scroll position.\r
245      *\r
246      * @method refresh\r
247      */\r
248     refresh: function(force) {\r
249       var firstVisibleIndex = Math.floor(this._scrollTop / this.height);\r
250       var visibleMidpoint = firstVisibleIndex + this._visibleCount / 2;\r
251 \r
252       var firstReifiedIndex = Math.max(0, Math.floor(visibleMidpoint - \r
253           this._physicalCount / 2));\r
254       firstReifiedIndex = Math.min(firstReifiedIndex, this.data.length - \r
255           this._physicalCount);\r
256 \r
257       var firstPhysicalIndex = firstReifiedIndex % this._physicalCount;\r
258       var baseVirtualIndex = firstReifiedIndex - firstPhysicalIndex;\r
259 \r
260       var baseTransformValue = Math.floor(this.height * baseVirtualIndex);\r
261       var nextTransformValue = Math.floor(baseTransformValue + \r
262           this._physicalHeight);\r
263 \r
264       var baseTransformString = 'translate3d(0,' + baseTransformValue + 'px,0)';\r
265       var nextTransformString = 'translate3d(0,' + nextTransformValue + 'px,0)';\r
266       // TODO(sorvell): experiemental for sync'ing back to virtual data.\r
267       if (this.sync) {\r
268         this.syncData();\r
269       }\r
270       this.firstPhysicalIndex = firstPhysicalIndex;\r
271       this.baseVirtualIndex = baseVirtualIndex;\r
272 \r
273       for (var i = 0; i < firstPhysicalIndex; ++i) {\r
274         var item = this._physicalItems[i];\r
275         if (force || item._transformValue != nextTransformValue) {\r
276           this.updateItem(baseVirtualIndex + this._physicalCount + i, i);\r
277           setTransform(item, nextTransformString, nextTransformValue);\r
278         }\r
279       }\r
280       for (var i = firstPhysicalIndex; i < this._physicalCount; ++i) {\r
281         var item = this._physicalItems[i];\r
282         if (force || item._transformValue != baseTransformValue) {\r
283           this.updateItem(baseVirtualIndex + i, i);\r
284           setTransform(item, baseTransformString, baseTransformValue);\r
285         }\r
286       }\r
287     },\r
288 \r
289     // list selection\r
290     tapHandler: function(e) {\r
291       if (e.target === this) {\r
292         return;\r
293       }\r
294       if (this.sync) {\r
295         this.syncData();\r
296       }\r
297       var n = e.target;\r
298       var model = n.templateInstance && n.templateInstance.model;\r
299       if (model) {\r
300         var vi = model._virtualIndex, pi = model._physicalIndex;\r
301         var data = this.data[vi], item = this._physicalItems[pi];\r
302         this.$.selection.select(data);\r
303         this.asyncFire('core-activate', {data: data, item: item});\r
304       }\r
305     },\r
306 \r
307     selectedHandler: function(e, detail) {\r
308       if (this.selectedProperty) {\r
309         var i$ = this.indexesForData(detail.item);\r
310         // TODO(sorvell): we should be relying on selection to store the\r
311         // selected data but we want to optimize for lookup.\r
312         this._selectedData.set(detail.item, detail.isSelected);\r
313         if (i$.physical >= 0) {\r
314           this.updateItem(i$.virtual, i$.physical);\r
315         }\r
316       }\r
317     },\r
318 \r
319     /**\r
320      * Select the list item at the given index.\r
321      *\r
322      * @method selectItem\r
323      * @param {number} index \r
324      */\r
325     selectItem: function(index) {\r
326       var data = this.data[index];\r
327       if (data) {\r
328         this.$.selection.select(data);\r
329       }\r
330     },\r
331 \r
332     /**\r
333      * Set the selected state of the list item at the given index.\r
334      *\r
335      * @method setItemSelected\r
336      * @param {number} index \r
337      * @param {boolean} isSelected \r
338      */\r
339     setItemSelected: function(index, isSelected) {\r
340       var data = this.data[index];\r
341       if (data) {\r
342         this.$.selection.setItemSelected(data, isSelected);\r
343       }\r
344     },\r
345 \r
346     indexesForData: function(data) {\r
347       var virtual = this.data.indexOf(data);\r
348       var physical = this.virtualToPhysicalIndex(virtual);\r
349       return { virtual: virtual, physical: physical };\r
350     },\r
351 \r
352     virtualToPhysicalIndex: function(index) {\r
353       for (var i=0, l=this._physicalData.length; i<l; i++) {\r
354         if (this._physicalData[i]._virtualIndex === index) {\r
355           return i;\r
356         }\r
357       }\r
358       return -1;\r
359     },\r
360 \r
361     get selection() {\r
362       return this.$.selection.getSelection();\r
363     },\r
364 \r
365     selectedChanged: function() {\r
366       this.$.selection.select(this.selected);\r
367     },\r
368 \r
369     clearSelection: function() {\r
370       this._selectedData = new WeakMap();\r
371       if (this.multi) {\r
372         var s$ = this.selection;\r
373         for (var i=0, l=s$.length, s; (i<l) && (s=s$[i]); i++) {\r
374           this.$.selection.setItemSelected(s, false);\r
375         }\r
376       } else {\r
377         this.$.selection.setItemSelected(this.selection, false);\r
378       }\r
379       this.$.selection.clear();\r
380     },\r
381 \r
382     scrollToItem: function(index) {\r
383       this.scrollTop = index * this.height;\r
384     }\r
385 \r
386   });\r
387 \r
388   // determine proper transform mechanizm\r
389   if (document.documentElement.style.transform !== undefined) {\r
390     function setTransform(element, string, value) {\r
391       element.style.transform = string;\r
392       element._transformValue = value;\r
393     }\r
394   } else {\r
395     function setTransform(element, string, value) {\r
396       element.style.webkitTransform = string;\r
397       element._transformValue = value;\r
398     }\r
399   }\r
400 \r
401 })();\r
402 </script>\r
403 </polymer-element>\r