Skip to content

Commit

Permalink
add amp-live-list manager (#3094)
Browse files Browse the repository at this point in the history
  • Loading branch information
erwinmombay committed May 5, 2016
1 parent c8ed624 commit a32a1db
Show file tree
Hide file tree
Showing 6 changed files with 447 additions and 10 deletions.
158 changes: 158 additions & 0 deletions extensions/amp-live-list/0.1/live-list-manager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/**
* Copyright 2016 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {Poller} from './poller';
import {getService} from '../../../src/service';
import {user} from '../../../src/log';
import {viewerFor} from '../../../src/viewer';
import {whenDocumentReady} from '../../../src/document-ready';
import {xhrFor} from '../../../src/xhr';


/**
* Manages registered AmpLiveList components.
* Primarily handles network requests and updates the components
* if necessary.
*
* @visibleForTesting
*/
export class LiveListManager {

constructor(win) {
this.win = win;

/** @private @const {!Object<string, !AmpLiveList>} */
this.liveLists_ = Object.create(null);

/** @private @const {!Viewer} */
this.viewer_ = viewerFor(this.win);

/** @private {number} */
this.interval_ = 15000;

/** @private @const {!Array<number>} */
this.intervals_ = [this.interval_];

/** @private {?Poller} */
this.poller_ = null;

/** @private @const {string} */
this.url_ = this.win.location.href;

/** @private @const {function(): Promise} */
this.work_ = this.fetchDocument_.bind(this, this.url_);

// Only start polling when doc is ready and when the viewer is visible.
this.whenDocReady_().then(() => {
// Switch out the poller interval if we can find a lower one and
// then make sure to stop polling if viewer is not visible.
this.interval_ = Math.min.apply(Math, this.intervals_);
this.poller_ = new Poller(this.win, this.interval_, this.work_);

if (this.viewer_.isVisible()) {
this.poller_.start();
}
this.setupVisibilityHandler_();
});
}

/**
* Makes a request to the given url for the latest document.
*
* @param {string} url
* @private
*/
fetchDocument_(url) {
return xhrFor(this.win)
// TODO(erwinm): add update time here when possible.
.fetchDocument(url)
.then(this.getLiveLists_.bind(this));
}

/**
* Queries the document for all `amp-live-list` tags.
*
* @param {!HTMLDocument} doc
*/
getLiveLists_(doc) {
const lists = Array.prototype.slice.call(
doc.getElementsByTagName('amp-live-list'));
lists.forEach(this.updateLiveList_.bind(this));
}

/**
* Updates the appropriate `amp-live-list` with its updates from the server.
*
* @param {!HTMLElement} liveList
*/
updateLiveList_(liveList) {
const id = liveList.getAttribute('id');
user.assert(id, 'amp-live-list must have an id.');
user.assert(id in this.liveLists_, `amp-live-ist#${id} found but did not ` +
`exist on original page load`);
this.liveLists_[id].update(liveList);
}

/**
* Register an `amp-live-list` instance for updates.
*
* @param {number} id
* @param {!AmpLiveList} liveList
*/
register(id, liveList) {
const isNotRegistered = !(id in this.liveLists_);
if (isNotRegistered) {
this.liveLists_[id] = liveList;
this.intervals_.push(liveList.getInterval());
}
}

/**
* Returns a promise that is resolved when the document is ready.
* @return {!Promise}
* @private
*/
whenDocReady_() {
return whenDocumentReady(this.win.document);
}

/**
* Listens to he viewer visibility changed event.
* @private
*/
setupVisibilityHandler_() {
// Polling should always be stopped when document is no longer visible.
this.viewer_.onVisibilityChanged(() => {
if (this.viewer_.isVisible()) {
// We use immediate so that the user starts getting updates
// right away when they've switched back to the page.
this.poller_.start(/** immediate */ true);
} else {
this.poller_.stop();
}
});
}
}

/**
* @param {!Window} window
* @return {!LiveListManager}
*/
export function installLiveListManager(win) {
return getService(win, 'liveListManager', () => {
return new LiveListManager(win);
});
}
20 changes: 15 additions & 5 deletions extensions/amp-live-list/0.1/poller.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,16 @@ export class Poller {

/**
* Initalize any work needed to start polling.
* @param {boolean=} opt_immediate execute current work instead of queueing
* it in a timeout.
*/
start() {
start(opt_immediate) {
if (this.isRunning_) {
return;
}

this.isRunning_ = true;
this.poll_();
this.poll_(opt_immediate);
}

/**
Expand Down Expand Up @@ -107,14 +109,16 @@ export class Poller {
/**
* Queues a timeout that executes the work and recursively calls
* itself on success.
* @param {boolean=} opt_immediate execute current work instead of queueing
* it in a timeout.
* @private
*/
poll_() {
poll_(opt_immediate) {
if (!this.isRunning_) {
return;
}

this.lastTimeoutId_ = timer.delay(() => {
const work = () => {
this.lastWorkPromise_ = this.work_()
.then(() => {
if (this.backoffClock_) {
Expand All @@ -131,6 +135,12 @@ export class Poller {
throw err;
}
});
}, this.getTimeout_());
};

if (opt_immediate) {
work();
} else {
this.lastTimeoutId_ = timer.delay(work, this.getTimeout_());
}
}
}
Loading

0 comments on commit a32a1db

Please sign in to comment.