Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(86)

Side by Side Diff: sky/examples/city-list/city-list.sky

Issue 980323003: Clean up examples directory (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « sky/examples/city-list/city-data-service.sky ('k') | sky/examples/city-list/city-sequence.sky » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 <!--
2 // Copyright 2014 The Chromium Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 -->
6 <import src="../../framework/sky-element/sky-element.sky" as="SkyElement" />
7 <import src="city-data-service.sky" as="CityDataService" />
8 <import src="city-sequence.sky" as="CitySequence" />
9
10 <sky-element name="state-header">
11 <template>
12 <style>
13 div {
14 font-size: 16px;
15 color: #FFF;
16 background-color: #333;
17 padding: 4px 4px 4px 12px;
18 display: paragraph;
19 }
20 </style>
21 <div>{{ state }}</div>
22 </template>
23 <script>
24 module.exports.StateHeaderElement = class extends SkyElement {
25 created() {
26 this.state = "";
27 }
28 set datum(datum) {
29 this.state = datum.state;
30 }
31 }.register();
32 </script>
33 </sky-element>
34
35 <sky-element name="letter-header">
36 <template>
37 <style>
38 div {
39 font-size: 12px;
40 font-weight: bold;
41 padding: 2px 4px 4px 12px;
42 background-color: #DDD;
43 display: paragraph;
44 }
45 </style>
46 <div>{{ letter }}</div>
47 </template>
48 <script>
49 module.exports.LetterHeaderElement = class extends SkyElement {
50 created() {
51 this.letter = "";
52 }
53 set datum(datum) {
54 this.letter = datum.letter;
55 }
56 }.register();
57 </script>
58 </sky-element>
59
60 <sky-element name="city-item">
61 <template>
62 <style>
63 :host {
64 display: flex;
65 font-size: 13px;
66 padding: 8px 4px 4px 12px;
67 border-bottom: 1px solid #EEE;
68 line-height: 15px;
69 overflow: hidden;
70 }
71
72 div {
73 display: paragraph;
74 }
75
76 span {
77 display: inline;
78 }
79
80 #name {
81 font-weight: bold
82 }
83
84 #population {
85 color: #AAA;
86 }
87 </style>
88 <div>
89 <span id="name">{{ name }}</span>
90 <t>, </t>
91 <span id="population">population {{ population }}</span>
92 </div>
93 </template>
94 <script>
95 module.exports.CityItemElement = class extends SkyElement {
96 created() {
97 this.name = "";
98 this.population = "";
99 }
100 set datum(datum) {
101 this.name = datum.name;
102 this.population = datum.population;
103 }
104 }.register();
105 </script>
106 </sky-element>
107
108 <sky-element name="city-list">
109 <template>
110 <style>
111
112 :host {
113 overflow: hidden;
114 position: absolute;
115 top: 0;
116 right: 0;
117 bottom: 0;
118 left: 0;
119 display: block;
120 background-color: #fff;
121 }
122
123 #contentarea {
124 will-change: transform;
125 }
126
127 .position {
128 position: absolute;
129 left: 0;
130 right: 0;
131 }
132
133 </style>
134 <div id="contentarea">
135 </div>
136 </template>
137 <script>
138
139 (function(global) {
140 "use strict";
141
142 var LOAD_LENGTH = 20;
143 var LOAD_BUFFER_PRE = LOAD_LENGTH * 4;
144 var LOAD_BUFFER_POST = LOAD_LENGTH * 4;
145
146 function Loader(cityList) {
147 this.cityList = cityList;
148 this.loadingData = false;
149 this.data = null;
150 this.zeroIndex = 0;
151 this.loadIndex = 0;
152 }
153
154 Loader.prototype.localIndex = function(externalIndex) {
155 return externalIndex + this.zeroIndex;
156 }
157
158 Loader.prototype.externalIndex = function(localIndex) {
159 return localIndex - this.zeroIndex;
160 }
161
162 Loader.prototype.getItems = function() {
163 return this.data ? this.data.items : [];
164 }
165
166 Loader.prototype.maybeLoadMoreData =
167 function(dataloadedCallback, firstVisible) {
168 if (this.loadingData)
169 return;
170
171 if (firstVisible) {
172 this.loadIndex = this.externalIndex(
173 this.data.items.indexOf(firstVisible));
174 }
175
176 var localIndex = this.localIndex(this.loadIndex);
177 var loadedPre = 0;
178 var loadedPost = 0;
179
180 if (this.data) {
181 loadedPre = localIndex;
182 loadedPost = this.data.items.length - loadedPre;
183 }
184
185 var loadTime;
186 if (loadedPre >= LOAD_BUFFER_PRE &&
187 loadedPost >= LOAD_BUFFER_POST) {
188
189 var cityList = this.cityList;
190 setTimeout(function() {
191 cityList.dispatchEvent(new Event('load'));
192 });
193
194 if (window.startLoad) {
195 loadTime = new Date().getTime() - window.startLoad;
196 console.log('Load: ' + loadTime + 'ms');
197 window.startLoad = undefined;
198 }
199 return;
200 }
201
202 this.loadingData = true;
203
204 var loadIndex;
205
206 if (!this.data) {
207 // Initial batch
208 loadIndex = 0;
209 } else if (loadedPost < LOAD_BUFFER_POST) {
210 // Load forward first
211 loadIndex = this.data.items.length;
212 } else {
213 // Then load backward
214 loadIndex = -LOAD_LENGTH;
215 }
216
217 var self = this;
218 var externalIndex = this.externalIndex(loadIndex);
219
220 try {
221 CityDataService.service.then(function(cityService) {
222 return cityService.get(externalIndex, LOAD_LENGTH)
223 .then(function(cities) {
224 var indexOffset = 0;
225 var newData = new CitySequence(cities);
226 if (!self.data) {
227 self.data = newData;
228 } else if (loadIndex > 0) {
229 self.data.append(newData);
230 } else {
231 self.zeroIndex += LOAD_LENGTH;
232 indexOffset = LOAD_LENGTH;
233 newData.append(self.data);
234 self.data = newData;
235 }
236
237 self.loadingData = false;
238 dataloadedCallback(self.data, indexOffset);
239 });
240 }).catch(function(ex) {
241 console.log(ex.stack);
242 });
243 } catch (ex) {
244 console.log(ex.stack);
245 }
246 }
247
248 function Scroller() {
249 this.contentarea = null;
250 this.scrollTop = 0;
251 this.scrollHeight = -1;
252 }
253
254 Scroller.prototype.setup = function(scrollHeight, contentarea) {
255 this.scrollHeight = scrollHeight;
256 this.contentarea = contentarea;
257 }
258
259 Scroller.prototype.scrollBy = function(amount) {
260 this.scrollTop += amount;
261 this.scrollTo();
262 }
263
264 Scroller.prototype.scrollTo = function() {
265 var transform = 'translateY(' + -this.scrollTop.toFixed(2) + 'px)';
266 this.contentarea.style.transform = transform;
267 }
268
269 // Current position and height of the scroller, that could
270 // be used (by Tiler, for example) to reason about where to
271 // place visible things.
272 Scroller.prototype.getCurrentFrame = function() {
273 return { top: this.scrollTop, height: this.scrollHeight };
274 }
275
276 Scroller.prototype.hasFrame = function() {
277 return this.scrollHeight != -1;
278 }
279
280 function Tile(datum, element, viewType, index) {
281 this.datum = datum;
282 this.element = element;
283 this.viewType = viewType;
284 this.index = index;
285 }
286
287 function Tiler(contentArea, views, viewHeights) {
288 this.contentArea = contentArea;
289 this.drawTop = 0;
290 this.drawBottom = 0;
291 this.firstItem = -1;
292 this.tiles = [];
293 this.viewHeights = viewHeights;
294 this.views = views;
295 }
296
297 Tiler.prototype.setupViews = function(scrollFrame) {
298 for (var type in this.viewHeights) {
299 this.initializeViewType(scrollFrame, type, this.viewHeights[type]);
300 }
301 }
302
303 Tiler.prototype.initializeViewType = function(scrollFrame, viewType,
304 height) {
305 var count = Math.ceil(scrollFrame.height / height) * 2;
306 var viewCache = this.views[viewType] = {
307 indices: [],
308 elements: []
309 };
310
311 var protoElement;
312 switch (viewType) {
313 case 'stateHeader':
314 protoElement = document.createElement('state-header');
315 break;
316 case 'letterHeader':
317 protoElement = document.createElement('letter-header');
318 break;
319 case 'cityItem':
320 protoElement = document.createElement('city-item');
321 break;
322 default:
323 console.warn('Unknown viewType: ' + viewType);
324 }
325 protoElement.style.display = 'none';
326 protoElement.style.height = height;
327 protoElement.classList.add('position');
328
329 for (var i = 0; i < count; i++) {
330 var clone = protoElement.cloneNode(false);
331 this.contentArea.appendChild(clone);
332 viewCache.elements.push(clone);
333 viewCache.indices.push(i);
334 }
335 }
336
337 Tiler.prototype.checkoutTile = function(viewType, datum, top) {
338 var viewCache = this.views[viewType];
339 var index = viewCache.indices.pop();
340 var element = viewCache.elements[index];
341 element.datum = datum;
342 element.style.display = '';
343 element.style.top = top + 'px';
344
345 return new Tile(datum, element, viewType, index);
346 }
347
348 Tiler.prototype.checkinTile = function(tile) {
349 if (!tile.element)
350 return;
351
352 tile.element.style.display = 'none';
353 this.views[tile.viewType].indices.push(tile.index);
354 }
355
356 Tiler.prototype.getFirstVisibleDatum = function(scrollFrame) {
357 var tiles = this.tiles;
358 var viewHeights = this.viewHeights;
359
360 var itemTop = this.drawTop - scrollFrame.top;
361 if (itemTop >= 0 && tiles.length)
362 return tiles[0].datum;
363
364 var tile;
365 for (var i = 0; i < tiles.length && itemTop < 0; i++) {
366 tile = tiles[i];
367 var height = viewHeights[tile.viewType];
368 itemTop += height;
369 }
370
371 return tile ? tile.datum : null;
372 }
373
374 Tiler.prototype.viewType = function(datum) {
375 switch (datum.headerOrder) {
376 case 1: return 'stateHeader';
377 case 2: return 'letterHeader';
378 default: return 'cityItem';
379 }
380 }
381
382 Tiler.prototype.drawTiles = function(scrollFrame, data) {
383 var tiles = this.tiles;
384 var viewHeights = this.viewHeights;
385
386 var buffer = Math.round(scrollFrame.height / 2);
387 var targetTop = scrollFrame.top - buffer;
388 var targetBottom = scrollFrame.top + scrollFrame.height + buffer;
389
390 // Collect down to targetTop
391 var first = tiles[0];
392 while (tiles.length &&
393 targetTop > this.drawTop + viewHeights[first.viewType]) {
394
395 var height = viewHeights[first.viewType];
396 this.drawTop += height;
397
398 this.firstItem++;
399 this.checkinTile(tiles.shift());
400
401 first = tiles[0];
402 }
403
404 // Collect up to targetBottom
405 var last = tiles[tiles.length - 1];
406 while(tiles.length &&
407 targetBottom < this.drawBottom - viewHeights[last.viewType]) {
408
409 var height = viewHeights[last.viewType];
410 this.drawBottom -= height;
411
412 this.checkinTile(tiles.pop());
413
414 last = tiles[tiles.length - 1];
415 }
416
417 // Layout up to targetTop
418 while (this.firstItem > 0 &&
419 targetTop < this.drawTop) {
420
421 var datum = data[this.firstItem - 1];
422 var type = this.viewType(datum);
423 var height = viewHeights[type];
424
425 this.drawTop -= height;
426
427 var tile = targetBottom < this.drawTop ?
428 new Tile(datum, null, datum.viewType, -1) : // off-screen
429 this.checkoutTile(type, datum, this.drawTop);
430
431 this.firstItem--;
432 tiles.unshift(tile);
433 }
434
435 // Layout down to targetBottom
436 while (this.firstItem + tiles.length < data.length - 1 &&
437 targetBottom > this.drawBottom) {
438
439 var datum = data[this.firstItem + tiles.length];
440 var type = this.viewType(datum);
441 var height = viewHeights[type];
442
443 this.drawBottom += height;
444
445 var tile = targetTop > this.drawBottom ?
446 new Tile(datum, null, datum.viewType, -1) : // off-screen
447 this.checkoutTile(type, datum, this.drawBottom - height);
448
449 tiles.push(tile);
450 }
451
452 // Debug validate:
453 // for (var i = 0; i < tiles.length; i++) {
454 // if (tiles[i].datum !== data[this.firstItem + i])
455 // throw Error('Invalid')
456 // }
457 }
458
459 // FIXME: Needs better name.
460 Tiler.prototype.updateFirstItem = function(offset) {
461 var tiles = this.tiles;
462
463 if (!tiles.length) {
464 this.firstItem = 0;
465 } else {
466 this.firstItem += offset;
467 }
468 }
469
470 module.exports.CityListElement = class extends SkyElement {
471
472 created() {
473 this.loader = null;
474 this.scroller = null;
475 this.tiler = null;
476 this.date = null;
477 this.month = null;
478 this.views = null;
479 }
480
481 attached() {
482 this.views = {};
483 this.loader = new Loader(this);
484 this.scroller = new Scroller();
485 this.tiler = new Tiler(
486 this.shadowRoot.getElementById('contentarea'), this.views, {
487 stateHeader: 27,
488 letterHeader: 18,
489 cityItem: 30
490 });
491
492 var self = this;
493 this.addEventListener('wheel', function(event) {
494 self.scrollBy(-event.offsetY)
495 });
496
497 setTimeout(function() {
498 self.domReady();
499 self.loader.maybeLoadMoreData(self.dataLoaded.bind(self));
500 });
501 }
502
503 domReady() {
504 this.scroller.setup(this.clientHeight,
505 this.shadowRoot.getElementById('contentarea'));
506 var scrollFrame = this.scroller.getCurrentFrame();
507 this.tiler.setupViews(scrollFrame);
508 }
509
510 updateView(data, scrollChanged) {
511 var scrollFrame = this.scroller.getCurrentFrame();
512 this.tiler.drawTiles(scrollFrame, data);
513 var datum = scrollChanged ?
514 this.tiler.getFirstVisibleDatum(scrollFrame) : null;
515 this.loader.maybeLoadMoreData(this.dataLoaded.bind(this), datum);
516 }
517
518 dataLoaded(data, indexOffset) {
519 var scrollFrame = this.scroller.getCurrentFrame();
520 this.tiler.updateFirstItem(indexOffset);
521 this.updateView(data.items, false);
522 }
523
524 scrollBy(amount) {
525 this.scroller.scrollBy(amount);
526 this.updateView(this.loader.getItems(), true);
527 }
528 }.register();
529
530 })(this);
531 </script>
532 </sky-element>
OLDNEW
« no previous file with comments | « sky/examples/city-list/city-data-service.sky ('k') | sky/examples/city-list/city-sequence.sky » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698