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 // Safari 5.1 lacks Function.prototype.bind.
31 if (!('bind' in Function.prototype)) {
32 Function.prototype.bind = function(thisObject) {
34 var boundArguments = [];
35 for (var i = 1; i < arguments.length; ++i) {
36 boundArguments.push(arguments[i]);
39 var actualParameters = [];
40 for (var i = 0; i < boundArguments.length; ++i) {
41 actualParameters.push(boundArguments[i]);
43 for (var i = 0; i < arguments.length; ++i) {
44 actualParameters.push(arguments[i]);
46 return method.apply(thisObject, actualParameters);
51 base.asInteger = function(stringOrInteger)
53 if (typeof stringOrInteger == 'string')
54 return parseInt(stringOrInteger);
55 return stringOrInteger;
58 base.endsWith = function(string, suffix)
60 if (suffix.length > string.length)
62 var expectedIndex = string.length - suffix.length;
63 return string.lastIndexOf(suffix) == expectedIndex;
66 base.repeatString = function(string, count)
68 return new Array(count + 1).join(string);
71 base.joinPath = function(parent, child)
73 if (parent.length == 0)
75 return parent + '/' + child;
78 base.dirName = function(path)
80 var directoryIndex = path.lastIndexOf('/');
81 if (directoryIndex == -1)
83 return path.substr(0, directoryIndex);
86 base.trimExtension = function(url)
88 var index = url.lastIndexOf('.');
91 return url.substr(0, index);
94 base.uniquifyArray = function(array)
98 $.each(array, function(index, value) {
107 base.flattenArray = function(arrayOfArrays)
109 if (!arrayOfArrays.length)
111 return arrayOfArrays.reduce(function(left, right) {
112 return left.concat(right);
116 base.values = function(dictionary)
120 for (var key in dictionary) {
121 result.push(dictionary[key]);
127 base.filterDictionary = function(dictionary, predicate)
131 for (var key in dictionary) {
133 result[key] = dictionary[key];
139 base.mapDictionary = function(dictionary, functor)
143 for (var key in dictionary) {
144 var value = functor(dictionary[key]);
145 if (typeof value !== 'undefined')
152 base.filterTree = function(tree, isLeaf, predicate)
154 var filteredTree = {};
156 function walkSubtree(subtree, directory)
158 for (var childName in subtree) {
159 var child = subtree[childName];
160 var childPath = base.joinPath(directory, childName);
162 if (predicate(child))
163 filteredTree[childPath] = child;
166 walkSubtree(child, childPath);
170 walkSubtree(tree, '');
174 base.parseJSONP = function(jsonp)
176 var startIndex = jsonp.indexOf('(') + 1;
177 var endIndex = jsonp.lastIndexOf(')');
178 return JSON.parse(jsonp.substr(startIndex, endIndex - startIndex));
181 base.RequestTracker = function(requestsInFlight, callback, args)
183 this._requestsInFlight = requestsInFlight;
184 this._callback = callback;
185 this._args = args || [];
189 base.RequestTracker.prototype = {
190 _tryCallback: function()
192 if (!this._requestsInFlight && this._callback)
193 this._callback.apply(null, this._args);
195 requestComplete: function()
197 --this._requestsInFlight;
202 base.callInParallel = function(functionList, callback)
204 var requestTracker = new base.RequestTracker(functionList.length, callback);
206 $.each(functionList, function(index, func) {
208 requestTracker.requestComplete();
213 base.callInSequence = function(func, argumentList, callback)
219 if (nextIndex >= argumentList.length) {
224 func(argumentList[nextIndex++], callNext);
230 base.CallbackIterator = function(callback, listOfArgumentArrays)
232 this._callback = callback;
234 this._listOfArgumentArrays = listOfArgumentArrays;
237 base.CallbackIterator.prototype.hasNext = function()
239 return this._nextIndex < this._listOfArgumentArrays.length;
242 base.CallbackIterator.prototype.hasPrevious = function()
244 return this._nextIndex - 2 >= 0;
247 base.CallbackIterator.prototype.callNext = function()
251 var args = this._listOfArgumentArrays[this._nextIndex];
253 this._callback.apply(null, args);
256 base.CallbackIterator.prototype.callPrevious = function()
258 if (!this.hasPrevious())
260 var args = this._listOfArgumentArrays[this._nextIndex - 2];
262 this._callback.apply(null, args);
265 base.AsynchronousCache = function(fetch)
268 this._dataCache = {};
269 this._callbackCache = {};
272 base.AsynchronousCache.prototype.get = function(key, callback)
276 if (self._dataCache[key]) {
277 // FIXME: Consider always calling callback asynchronously.
278 callback(self._dataCache[key]);
282 if (key in self._callbackCache) {
283 self._callbackCache[key].push(callback);
287 self._callbackCache[key] = [callback];
289 self._fetch.call(null, key, function(data) {
290 self._dataCache[key] = data;
292 var callbackList = self._callbackCache[key];
293 delete self._callbackCache[key];
295 callbackList.forEach(function(cachedCallback) {
296 cachedCallback(data);
302 Maintains a dictionary of items, tracking their updates and removing items that haven't been updated.
303 An "update" is a call to the "update" method.
304 To remove stale items, call the "remove" method. It will remove all
305 items that have not been been updated since the last call of "remove".
307 base.UpdateTracker = function()
313 base.UpdateTracker.prototype = {
315 Update an {key}/{item} pair. You can make the dictionary act as a set and
316 skip the {item}, in which case the {key} is also the {item}.
318 update: function(key, object)
320 object = object || key;
321 this._items[key] = object;
322 this._updated[key] = 1;
324 exists: function(key)
326 return !!this.get(key);
330 return this._items[key];
334 return Object.keys(this._items).length;
337 Callback parameters are:
340 - updated, which is true if the item was updated after last purge() call.
342 forEach: function(callback, thisObject)
347 Object.keys(this._items).sort().forEach(function(key) {
348 var item = this._items[key];
349 callback.call(thisObject || item, item, key, !!this._updated[key]);
352 purge: function(removeCallback, thisObject) {
353 removeCallback = removeCallback || function() {};
354 this.forEach(function(item, key, updated) {
357 removeCallback.call(thisObject || item, item);
358 delete this._items[key];
364 // Based on http://src.chromium.org/viewvc/chrome/trunk/src/chrome/browser/resources/shared/js/cr/ui.js
365 base.extends = function(base, prototype)
367 var extended = function() {
368 var element = typeof base == 'string' ? document.createElement(base) : base.call(this);
369 extended.prototype.__proto__ = element.__proto__;
370 element.__proto__ = extended.prototype;
371 element.init && element.init.apply(element, arguments);
375 extended.prototype = prototype;
379 function createRelativeTimeDescriptor(divisorInMilliseconds, unit)
381 return function(delta) {
382 var deltaInUnits = delta / divisorInMilliseconds;
383 return (deltaInUnits).toFixed(0) + ' ' + unit + (deltaInUnits >= 1.5 ? 's' : '') + ' ago';
387 var kMinuteInMilliseconds = 60 * 1000;
388 var kRelativeTimeSlots = [
390 maxMilliseconds: kMinuteInMilliseconds,
391 describe: function(delta) { return 'Just now'; }
394 maxMilliseconds: 60 * kMinuteInMilliseconds,
395 describe: createRelativeTimeDescriptor(kMinuteInMilliseconds, 'minute')
398 maxMilliseconds: 24 * 60 * kMinuteInMilliseconds,
399 describe: createRelativeTimeDescriptor(60 * kMinuteInMilliseconds, 'hour')
402 maxMilliseconds: Number.MAX_VALUE,
403 describe: createRelativeTimeDescriptor(24 * 60 * kMinuteInMilliseconds, 'day')
408 Represent time as descriptive text, relative to now and gradually decreasing in precision:
409 delta < 1 minutes => Just Now
410 delta < 60 minutes => X minute[s] ago
411 delta < 24 hours => X hour[s] ago
412 delta < inf => X day[s] ago
414 base.relativizeTime = function(time)
417 var delta = new Date().getTime() - time;
418 kRelativeTimeSlots.some(function(slot) {
419 if (delta >= slot.maxMilliseconds)
422 result = slot.describe(delta);