Index: sky/examples/city-list/city-list.sky |
diff --git a/sky/examples/city-list/city-list.sky b/sky/examples/city-list/city-list.sky |
deleted file mode 100644 |
index 4d8e55012f491f0ae6c8b6d31d1a1aeb30937c06..0000000000000000000000000000000000000000 |
--- a/sky/examples/city-list/city-list.sky |
+++ /dev/null |
@@ -1,532 +0,0 @@ |
-<!-- |
-// Copyright 2014 The Chromium Authors. All rights reserved. |
-// Use of this source code is governed by a BSD-style license that can be |
-// found in the LICENSE file. |
---> |
-<import src="../../framework/sky-element/sky-element.sky" as="SkyElement" /> |
-<import src="city-data-service.sky" as="CityDataService" /> |
-<import src="city-sequence.sky" as="CitySequence" /> |
- |
-<sky-element name="state-header"> |
-<template> |
- <style> |
- div { |
- font-size: 16px; |
- color: #FFF; |
- background-color: #333; |
- padding: 4px 4px 4px 12px; |
- display: paragraph; |
- } |
- </style> |
- <div>{{ state }}</div> |
-</template> |
-<script> |
-module.exports.StateHeaderElement = class extends SkyElement { |
- created() { |
- this.state = ""; |
- } |
- set datum(datum) { |
- this.state = datum.state; |
- } |
-}.register(); |
-</script> |
-</sky-element> |
- |
-<sky-element name="letter-header"> |
-<template> |
- <style> |
- div { |
- font-size: 12px; |
- font-weight: bold; |
- padding: 2px 4px 4px 12px; |
- background-color: #DDD; |
- display: paragraph; |
- } |
- </style> |
- <div>{{ letter }}</div> |
-</template> |
-<script> |
-module.exports.LetterHeaderElement = class extends SkyElement { |
- created() { |
- this.letter = ""; |
- } |
- set datum(datum) { |
- this.letter = datum.letter; |
- } |
-}.register(); |
-</script> |
-</sky-element> |
- |
-<sky-element name="city-item"> |
-<template> |
- <style> |
- :host { |
- display: flex; |
- font-size: 13px; |
- padding: 8px 4px 4px 12px; |
- border-bottom: 1px solid #EEE; |
- line-height: 15px; |
- overflow: hidden; |
- } |
- |
- div { |
- display: paragraph; |
- } |
- |
- span { |
- display: inline; |
- } |
- |
- #name { |
- font-weight: bold |
- } |
- |
- #population { |
- color: #AAA; |
- } |
- </style> |
- <div> |
- <span id="name">{{ name }}</span> |
- <t>, </t> |
- <span id="population">population {{ population }}</span> |
- </div> |
-</template> |
-<script> |
-module.exports.CityItemElement = class extends SkyElement { |
- created() { |
- this.name = ""; |
- this.population = ""; |
- } |
- set datum(datum) { |
- this.name = datum.name; |
- this.population = datum.population; |
- } |
-}.register(); |
-</script> |
-</sky-element> |
- |
-<sky-element name="city-list"> |
-<template> |
- <style> |
- |
- :host { |
- overflow: hidden; |
- position: absolute; |
- top: 0; |
- right: 0; |
- bottom: 0; |
- left: 0; |
- display: block; |
- background-color: #fff; |
- } |
- |
- #contentarea { |
- will-change: transform; |
- } |
- |
- .position { |
- position: absolute; |
- left: 0; |
- right: 0; |
- } |
- |
- </style> |
- <div id="contentarea"> |
- </div> |
-</template> |
-<script> |
- |
-(function(global) { |
- "use strict"; |
- |
- var LOAD_LENGTH = 20; |
- var LOAD_BUFFER_PRE = LOAD_LENGTH * 4; |
- var LOAD_BUFFER_POST = LOAD_LENGTH * 4; |
- |
- function Loader(cityList) { |
- this.cityList = cityList; |
- this.loadingData = false; |
- this.data = null; |
- this.zeroIndex = 0; |
- this.loadIndex = 0; |
- } |
- |
- Loader.prototype.localIndex = function(externalIndex) { |
- return externalIndex + this.zeroIndex; |
- } |
- |
- Loader.prototype.externalIndex = function(localIndex) { |
- return localIndex - this.zeroIndex; |
- } |
- |
- Loader.prototype.getItems = function() { |
- return this.data ? this.data.items : []; |
- } |
- |
- Loader.prototype.maybeLoadMoreData = |
- function(dataloadedCallback, firstVisible) { |
- if (this.loadingData) |
- return; |
- |
- if (firstVisible) { |
- this.loadIndex = this.externalIndex( |
- this.data.items.indexOf(firstVisible)); |
- } |
- |
- var localIndex = this.localIndex(this.loadIndex); |
- var loadedPre = 0; |
- var loadedPost = 0; |
- |
- if (this.data) { |
- loadedPre = localIndex; |
- loadedPost = this.data.items.length - loadedPre; |
- } |
- |
- var loadTime; |
- if (loadedPre >= LOAD_BUFFER_PRE && |
- loadedPost >= LOAD_BUFFER_POST) { |
- |
- var cityList = this.cityList; |
- setTimeout(function() { |
- cityList.dispatchEvent(new Event('load')); |
- }); |
- |
- if (window.startLoad) { |
- loadTime = new Date().getTime() - window.startLoad; |
- console.log('Load: ' + loadTime + 'ms'); |
- window.startLoad = undefined; |
- } |
- return; |
- } |
- |
- this.loadingData = true; |
- |
- var loadIndex; |
- |
- if (!this.data) { |
- // Initial batch |
- loadIndex = 0; |
- } else if (loadedPost < LOAD_BUFFER_POST) { |
- // Load forward first |
- loadIndex = this.data.items.length; |
- } else { |
- // Then load backward |
- loadIndex = -LOAD_LENGTH; |
- } |
- |
- var self = this; |
- var externalIndex = this.externalIndex(loadIndex); |
- |
- try { |
- CityDataService.service.then(function(cityService) { |
- return cityService.get(externalIndex, LOAD_LENGTH) |
- .then(function(cities) { |
- var indexOffset = 0; |
- var newData = new CitySequence(cities); |
- if (!self.data) { |
- self.data = newData; |
- } else if (loadIndex > 0) { |
- self.data.append(newData); |
- } else { |
- self.zeroIndex += LOAD_LENGTH; |
- indexOffset = LOAD_LENGTH; |
- newData.append(self.data); |
- self.data = newData; |
- } |
- |
- self.loadingData = false; |
- dataloadedCallback(self.data, indexOffset); |
- }); |
- }).catch(function(ex) { |
- console.log(ex.stack); |
- }); |
- } catch (ex) { |
- console.log(ex.stack); |
- } |
- } |
- |
- function Scroller() { |
- this.contentarea = null; |
- this.scrollTop = 0; |
- this.scrollHeight = -1; |
- } |
- |
- Scroller.prototype.setup = function(scrollHeight, contentarea) { |
- this.scrollHeight = scrollHeight; |
- this.contentarea = contentarea; |
- } |
- |
- Scroller.prototype.scrollBy = function(amount) { |
- this.scrollTop += amount; |
- this.scrollTo(); |
- } |
- |
- Scroller.prototype.scrollTo = function() { |
- var transform = 'translateY(' + -this.scrollTop.toFixed(2) + 'px)'; |
- this.contentarea.style.transform = transform; |
- } |
- |
- // Current position and height of the scroller, that could |
- // be used (by Tiler, for example) to reason about where to |
- // place visible things. |
- Scroller.prototype.getCurrentFrame = function() { |
- return { top: this.scrollTop, height: this.scrollHeight }; |
- } |
- |
- Scroller.prototype.hasFrame = function() { |
- return this.scrollHeight != -1; |
- } |
- |
- function Tile(datum, element, viewType, index) { |
- this.datum = datum; |
- this.element = element; |
- this.viewType = viewType; |
- this.index = index; |
- } |
- |
- function Tiler(contentArea, views, viewHeights) { |
- this.contentArea = contentArea; |
- this.drawTop = 0; |
- this.drawBottom = 0; |
- this.firstItem = -1; |
- this.tiles = []; |
- this.viewHeights = viewHeights; |
- this.views = views; |
- } |
- |
- Tiler.prototype.setupViews = function(scrollFrame) { |
- for (var type in this.viewHeights) { |
- this.initializeViewType(scrollFrame, type, this.viewHeights[type]); |
- } |
- } |
- |
- Tiler.prototype.initializeViewType = function(scrollFrame, viewType, |
- height) { |
- var count = Math.ceil(scrollFrame.height / height) * 2; |
- var viewCache = this.views[viewType] = { |
- indices: [], |
- elements: [] |
- }; |
- |
- var protoElement; |
- switch (viewType) { |
- case 'stateHeader': |
- protoElement = document.createElement('state-header'); |
- break; |
- case 'letterHeader': |
- protoElement = document.createElement('letter-header'); |
- break; |
- case 'cityItem': |
- protoElement = document.createElement('city-item'); |
- break; |
- default: |
- console.warn('Unknown viewType: ' + viewType); |
- } |
- protoElement.style.display = 'none'; |
- protoElement.style.height = height; |
- protoElement.classList.add('position'); |
- |
- for (var i = 0; i < count; i++) { |
- var clone = protoElement.cloneNode(false); |
- this.contentArea.appendChild(clone); |
- viewCache.elements.push(clone); |
- viewCache.indices.push(i); |
- } |
- } |
- |
- Tiler.prototype.checkoutTile = function(viewType, datum, top) { |
- var viewCache = this.views[viewType]; |
- var index = viewCache.indices.pop(); |
- var element = viewCache.elements[index]; |
- element.datum = datum; |
- element.style.display = ''; |
- element.style.top = top + 'px'; |
- |
- return new Tile(datum, element, viewType, index); |
- } |
- |
- Tiler.prototype.checkinTile = function(tile) { |
- if (!tile.element) |
- return; |
- |
- tile.element.style.display = 'none'; |
- this.views[tile.viewType].indices.push(tile.index); |
- } |
- |
- Tiler.prototype.getFirstVisibleDatum = function(scrollFrame) { |
- var tiles = this.tiles; |
- var viewHeights = this.viewHeights; |
- |
- var itemTop = this.drawTop - scrollFrame.top; |
- if (itemTop >= 0 && tiles.length) |
- return tiles[0].datum; |
- |
- var tile; |
- for (var i = 0; i < tiles.length && itemTop < 0; i++) { |
- tile = tiles[i]; |
- var height = viewHeights[tile.viewType]; |
- itemTop += height; |
- } |
- |
- return tile ? tile.datum : null; |
- } |
- |
- Tiler.prototype.viewType = function(datum) { |
- switch (datum.headerOrder) { |
- case 1: return 'stateHeader'; |
- case 2: return 'letterHeader'; |
- default: return 'cityItem'; |
- } |
- } |
- |
- Tiler.prototype.drawTiles = function(scrollFrame, data) { |
- var tiles = this.tiles; |
- var viewHeights = this.viewHeights; |
- |
- var buffer = Math.round(scrollFrame.height / 2); |
- var targetTop = scrollFrame.top - buffer; |
- var targetBottom = scrollFrame.top + scrollFrame.height + buffer; |
- |
- // Collect down to targetTop |
- var first = tiles[0]; |
- while (tiles.length && |
- targetTop > this.drawTop + viewHeights[first.viewType]) { |
- |
- var height = viewHeights[first.viewType]; |
- this.drawTop += height; |
- |
- this.firstItem++; |
- this.checkinTile(tiles.shift()); |
- |
- first = tiles[0]; |
- } |
- |
- // Collect up to targetBottom |
- var last = tiles[tiles.length - 1]; |
- while(tiles.length && |
- targetBottom < this.drawBottom - viewHeights[last.viewType]) { |
- |
- var height = viewHeights[last.viewType]; |
- this.drawBottom -= height; |
- |
- this.checkinTile(tiles.pop()); |
- |
- last = tiles[tiles.length - 1]; |
- } |
- |
- // Layout up to targetTop |
- while (this.firstItem > 0 && |
- targetTop < this.drawTop) { |
- |
- var datum = data[this.firstItem - 1]; |
- var type = this.viewType(datum); |
- var height = viewHeights[type]; |
- |
- this.drawTop -= height; |
- |
- var tile = targetBottom < this.drawTop ? |
- new Tile(datum, null, datum.viewType, -1) : // off-screen |
- this.checkoutTile(type, datum, this.drawTop); |
- |
- this.firstItem--; |
- tiles.unshift(tile); |
- } |
- |
- // Layout down to targetBottom |
- while (this.firstItem + tiles.length < data.length - 1 && |
- targetBottom > this.drawBottom) { |
- |
- var datum = data[this.firstItem + tiles.length]; |
- var type = this.viewType(datum); |
- var height = viewHeights[type]; |
- |
- this.drawBottom += height; |
- |
- var tile = targetTop > this.drawBottom ? |
- new Tile(datum, null, datum.viewType, -1) : // off-screen |
- this.checkoutTile(type, datum, this.drawBottom - height); |
- |
- tiles.push(tile); |
- } |
- |
- // Debug validate: |
- // for (var i = 0; i < tiles.length; i++) { |
- // if (tiles[i].datum !== data[this.firstItem + i]) |
- // throw Error('Invalid') |
- // } |
- } |
- |
- // FIXME: Needs better name. |
- Tiler.prototype.updateFirstItem = function(offset) { |
- var tiles = this.tiles; |
- |
- if (!tiles.length) { |
- this.firstItem = 0; |
- } else { |
- this.firstItem += offset; |
- } |
- } |
- |
- module.exports.CityListElement = class extends SkyElement { |
- |
- created() { |
- this.loader = null; |
- this.scroller = null; |
- this.tiler = null; |
- this.date = null; |
- this.month = null; |
- this.views = null; |
- } |
- |
- attached() { |
- this.views = {}; |
- this.loader = new Loader(this); |
- this.scroller = new Scroller(); |
- this.tiler = new Tiler( |
- this.shadowRoot.getElementById('contentarea'), this.views, { |
- stateHeader: 27, |
- letterHeader: 18, |
- cityItem: 30 |
- }); |
- |
- var self = this; |
- this.addEventListener('wheel', function(event) { |
- self.scrollBy(-event.offsetY) |
- }); |
- |
- setTimeout(function() { |
- self.domReady(); |
- self.loader.maybeLoadMoreData(self.dataLoaded.bind(self)); |
- }); |
- } |
- |
- domReady() { |
- this.scroller.setup(this.clientHeight, |
- this.shadowRoot.getElementById('contentarea')); |
- var scrollFrame = this.scroller.getCurrentFrame(); |
- this.tiler.setupViews(scrollFrame); |
- } |
- |
- updateView(data, scrollChanged) { |
- var scrollFrame = this.scroller.getCurrentFrame(); |
- this.tiler.drawTiles(scrollFrame, data); |
- var datum = scrollChanged ? |
- this.tiler.getFirstVisibleDatum(scrollFrame) : null; |
- this.loader.maybeLoadMoreData(this.dataLoaded.bind(this), datum); |
- } |
- |
- dataLoaded(data, indexOffset) { |
- var scrollFrame = this.scroller.getCurrentFrame(); |
- this.tiler.updateFirstItem(indexOffset); |
- this.updateView(data.items, false); |
- } |
- |
- scrollBy(amount) { |
- this.scroller.scrollBy(amount); |
- this.updateView(this.loader.getItems(), true); |
- } |
- }.register(); |
- |
-})(this); |
-</script> |
-</sky-element> |