2 * Copyright (C) 2011 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
26 var base = base || {};
30 base.asInteger = function(stringOrInteger)
32 if (typeof stringOrInteger == 'string')
33 return parseInt(stringOrInteger);
34 return stringOrInteger;
37 base.endsWith = function(string, suffix)
39 if (suffix.length > string.length)
41 var expectedIndex = string.length - suffix.length;
42 return string.lastIndexOf(suffix) == expectedIndex;
45 base.repeatString = function(string, count)
47 return new Array(count + 1).join(string);
50 base.joinPath = function(parent, child)
52 if (parent.length == 0)
54 return parent + '/' + child;
57 base.dirName = function(path)
59 var directoryIndex = path.lastIndexOf('/');
60 if (directoryIndex == -1)
62 return path.substr(0, directoryIndex);
65 base.trimExtension = function(url)
67 var index = url.lastIndexOf('.');
70 return url.substr(0, index);
73 base.uniquifyArray = function(array)
77 $.each(array, function(index, value) {
86 base.flattenArray = function(arrayOfArrays)
88 if (!arrayOfArrays.length)
90 return arrayOfArrays.reduce(function(left, right) {
91 return left.concat(right);
95 base.values = function(dictionary)
99 for (var key in dictionary) {
100 result.push(dictionary[key]);
106 base.filterDictionary = function(dictionary, predicate)
110 for (var key in dictionary) {
112 result[key] = dictionary[key];
118 base.mapDictionary = function(dictionary, functor)
122 for (var key in dictionary) {
123 var value = functor(dictionary[key]);
124 if (typeof value !== 'undefined')
131 base.filterTree = function(tree, isLeaf, predicate)
133 var filteredTree = {};
135 function walkSubtree(subtree, directory)
137 for (var childName in subtree) {
138 var child = subtree[childName];
139 var childPath = base.joinPath(directory, childName);
141 if (predicate(child))
142 filteredTree[childPath] = child;
145 walkSubtree(child, childPath);
149 walkSubtree(tree, '');
153 base.parseJSONP = function(jsonp)
155 var startIndex = jsonp.indexOf('(') + 1;
156 var endIndex = jsonp.lastIndexOf(')');
157 return JSON.parse(jsonp.substr(startIndex, endIndex - startIndex));
160 base.RequestTracker = function(requestsInFlight, callback, args)
162 this._requestsInFlight = requestsInFlight;
163 this._callback = callback;
164 this._args = args || [];
168 base.RequestTracker.prototype = {
169 _tryCallback: function()
171 if (!this._requestsInFlight && this._callback)
172 this._callback.apply(null, this._args);
174 requestComplete: function()
176 --this._requestsInFlight;
181 base.callInParallel = function(functionList, callback)
183 var requestTracker = new base.RequestTracker(functionList.length, callback);
185 $.each(functionList, function(index, func) {
187 requestTracker.requestComplete();
192 base.callInSequence = function(func, argumentList, callback)
198 if (nextIndex >= argumentList.length) {
203 func(argumentList[nextIndex++], callNext);
209 base.CallbackIterator = function(callback, listOfArgumentArrays)
211 this._callback = callback;
213 this._listOfArgumentArrays = listOfArgumentArrays;
216 base.CallbackIterator.prototype.hasNext = function()
218 return this._nextIndex < this._listOfArgumentArrays.length;
221 base.CallbackIterator.prototype.hasPrevious = function()
223 return this._nextIndex - 2 >= 0;
226 base.CallbackIterator.prototype.callNext = function()
230 var args = this._listOfArgumentArrays[this._nextIndex];
232 this._callback.apply(null, args);
235 base.CallbackIterator.prototype.callPrevious = function()
237 if (!this.hasPrevious())
239 var args = this._listOfArgumentArrays[this._nextIndex - 2];
241 this._callback.apply(null, args);
244 base.AsynchronousCache = function(fetch)
247 this._dataCache = {};
248 this._callbackCache = {};
251 base.AsynchronousCache.prototype.get = function(key, callback)
255 if (self._dataCache[key]) {
256 // FIXME: Consider always calling callback asynchronously.
257 callback(self._dataCache[key]);
261 if (key in self._callbackCache) {
262 self._callbackCache[key].push(callback);
266 self._callbackCache[key] = [callback];
268 self._fetch.call(null, key, function(data) {
269 self._dataCache[key] = data;
271 var callbackList = self._callbackCache[key];
272 delete self._callbackCache[key];
274 callbackList.forEach(function(cachedCallback) {
275 cachedCallback(data);
281 Maintains a dictionary of items, tracking their updates and removing items that haven't been updated.
282 An "update" is a call to the "update" method.
283 To remove stale items, call the "remove" method. It will remove all
284 items that have not been been updated since the last call of "remove".
286 base.UpdateTracker = function()
292 base.UpdateTracker.prototype = {
294 Update an {key}/{item} pair. You can make the dictionary act as a set and
295 skip the {item}, in which case the {key} is also the {item}.
297 update: function(key, object)
299 object = object || key;
300 this._items[key] = object;
301 this._updated[key] = 1;
303 exists: function(key)
305 return !!this.get(key);
309 return this._items[key];
312 Callback parameters are:
315 - updated, which is true if the item was updated after last purge() call.
317 forEach: function(callback, thisObject)
322 Object.keys(this._items).forEach(function(key) {
323 var item = this._items[key];
324 callback.call(thisObject || item, item, key, !!this._updated[key]);
327 purge: function(removeCallback, thisObject) {
328 removeCallback = removeCallback || function() {};
329 this.forEach(function(item, key, updated) {
332 removeCallback.call(thisObject || item, item);
333 delete this._items[key];
339 // Based on http://src.chromium.org/viewvc/chrome/trunk/src/chrome/browser/resources/shared/js/cr/ui.js
340 base.extends = function(base, prototype)
342 var extended = function() {
343 var element = typeof base == 'string' ? document.createElement(base) : base.call(this);
344 extended.prototype.__proto__ = element.__proto__;
345 element.__proto__ = extended.prototype;
346 element.init && element.init.apply(element, arguments);
350 extended.prototype = prototype;
354 function createRelativeTimeDescriptor(divisorInMilliseconds, unit)
356 return function(delta) {
357 var deltaInUnits = delta / divisorInMilliseconds;
358 return (deltaInUnits).toFixed(0) + ' ' + unit + (deltaInUnits >= 1.5 ? 's' : '') + ' ago';
362 var kMinuteInMilliseconds = 60 * 1000;
363 var kRelativeTimeSlots = [
365 maxMilliseconds: kMinuteInMilliseconds,
366 describe: function(delta) { return 'Just now'; }
369 maxMilliseconds: 60 * kMinuteInMilliseconds,
370 describe: createRelativeTimeDescriptor(kMinuteInMilliseconds, 'minute')
373 maxMilliseconds: 24 * 60 * kMinuteInMilliseconds,
374 describe: createRelativeTimeDescriptor(60 * kMinuteInMilliseconds, 'hour')
377 maxMilliseconds: Number.MAX_VALUE,
378 describe: createRelativeTimeDescriptor(24 * 60 * kMinuteInMilliseconds, 'day')
383 Represent time as descriptive text, relative to now and gradually decreasing in precision:
384 delta < 1 minutes => Just Now
385 delta < 60 minutes => X minute[s] ago
386 delta < 24 hours => X hour[s] ago
387 delta < inf => X day[s] ago
389 base.relativizeTime = function(time)
392 var delta = new Date().getTime() - time;
393 kRelativeTimeSlots.some(function(slot) {
394 if (delta >= slot.maxMilliseconds)
397 result = slot.describe(delta);