UNPKG

foam-framework

Version:
967 lines (810 loc) 25.8 kB
/** * @license * Copyright 2012 Google Inc. 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. */ var DEBUG = DEBUG || false; var _DOC_ = _DOC_ || false; var FLAGS = FLAGS || {}; FLAGS.javascript = true; FLAGS.debug = DEBUG; FLAGS.documentation = _DOC_; function FEATURE_ENABLED(labels) { for ( var i = 0 ; i < labels.length ; i++ ) { if ( FLAGS[labels[i]] ) return true; } } var GLOBAL = GLOBAL || this; Object.defineProperty_ = Object.defineProperty; Object.defineProperty = function() { this.defineProperty_.apply(this, arguments); }; function MODEL(model) { var proto; function defineProperty(proto, key, map) { if ( ! map.value || proto === Object.prototype || proto === Array.prototype ) Object.defineProperty_.apply(this, arguments); else proto[key] = map.value; } if ( model.name ) { if ( ! GLOBAL[model.name] ) { if ( model.extends ) { GLOBAL[model.name] = { __proto__: GLOBAL[model.extends] }; } else { GLOBAL[model.name] = {}; } } proto = GLOBAL[model.name]; } else { proto = model.extendsProto ? GLOBAL[model.extendsProto].prototype : GLOBAL[model.extendsObject] ; } if ( model.properties ) for ( var i = 0 ; i < model.properties.length ; i++ ) { var p = model.properties[i]; defineProperty( proto, p.name, { get: p.getter, enumerable: false }); } for ( key in model.constants ) defineProperty( proto, key, { value: model.constants[key], writable: true, enumerable: false }); if ( Array.isArray(model.methods) ) { for ( var i = 0 ; i < model.methods.length ; i++ ) { var m = model.methods[i]; defineProperty( proto, m.name, { value: m, writable: true, enumerable: false }); } } else { for ( var key in model.methods ) defineProperty( proto, key, { value: model.methods[key], writable: true, enumerable: false }); } } var MODEL0 = MODEL; MODEL({ extendsObject: 'GLOBAL', methods: [ function memoize(f) { var cache = {}; var g = function() { var key = argsToArray(arguments).toString(); if ( ! cache.hasOwnProperty(key) ) cache[key] = f.apply(this, arguments); return cache[key]; }; g.name = f.name; return g; }, function memoize1(f) { /** Faster version of memoize() when only dealing with one argument. **/ var cache = {}; var g = function(arg) { var key = arg ? arg.toString() : ''; if ( ! cache.hasOwnProperty(key) ) cache[key] = f.call(this, arg); return cache[key]; }; g.name = f.name; return g; }, function constantFn(v) { /* Create a function which always returns the supplied constant value. */ return function() { return v; }; }, function latchFn(f) { var tripped = false; var val; /* Create a function which always returns the supplied constant value. */ return function() { if ( ! tripped ) { tripped = true; val = f(); f = undefined; } return val; }; }, function argsToArray(args) { var array = new Array(args.length); for ( var i = 0; i < args.length; i++ ) array[i] = args[i]; return array; }, function StringComparator(s1, s2) { if ( s1 == s2 ) return 0; return s1 < s2 ? -1 : 1; }, function equals(a, b) { if ( a === b ) return true; if ( ! a || ! b ) return false; if ( a.equals ) return a.equals(b); return a == b; }, function compare(a, b) { if ( a === b ) return 0; if ( a == null ) return -1; if ( b == null ) return 1; if ( a.compareTo ) return a.compareTo(b); if ( b.compareTo ) return -b.compareTo(a); return a > b ? 1 : -1 ; }, function toCompare(c) { if ( Array.isArray(c) ) return CompoundComparator.apply(null, c); return c.compare ? c.compare.bind(c) : c; }, function CompoundComparator() { var args = argsToArray(arguments); var cs = []; // Convert objects with .compare() methods to compare functions. for ( var i = 0 ; i < args.length ; i++ ) cs[i] = toCompare(args[i]); var f = function(o1, o2) { for ( var i = 0 ; i < cs.length ; i++ ) { var r = cs[i](o1, o2); if ( r != 0 ) return r; } return 0; }; f.toSQL = function() { return args.map(function(s) { return s.toSQL(); }).join(','); }; f.toMQL = function() { return args.map(function(s) { return s.toMQL(); }).join(' '); }; f.toBQL = function() { return args.map(function(s) { return s.toBQL(); }).join(' '); }; f.toString = f.toSQL; return f; }, function randomAct() { /** * Take an array where even values are weights and odd values are functions, * and execute one of the functions with propability equal to it's relative * weight. */ // TODO: move this method somewhere better var totalWeight = 0.0; for ( var i = 0 ; i < arguments.length ; i += 2 ) totalWeight += arguments[i]; var r = Math.random(); for ( var i = 0, weight = 0 ; i < arguments.length ; i += 2 ) { weight += arguments[i]; if ( r <= weight / totalWeight ) { arguments[i+1](); return; } } }, // Workaround for crbug.com/258552 function Object_forEach(obj, fn) { for ( var key in obj ) if ( obj.hasOwnProperty(key) ) fn(obj[key], key); }, function predicatedSink(predicate, sink) { if ( predicate === TRUE || ! sink ) return sink; return { __proto__: sink, $UID: sink.$UID, put: function(obj, s, fc) { if ( sink.put && ( ! obj || predicate.f(obj) ) ) sink.put(obj, s, fc); }, remove: function(obj, s, fc) { if ( sink.remove && ( ! obj || predicate.f(obj) ) ) sink.remove(obj, s, fc); }, reset: function() { sink.reset(); }, toString: function() { return 'PredicatedSink(' + sink.$UID + ', ' + predicate + ', ' + sink + ')'; } }; }, function limitedSink(count, sink) { var i = 0; return { __proto__: sink, $UID: sink.$UID, put: function(obj, s, fc) { if ( i++ >= count && fc ) { fc.stop(); } else { sink.put(obj, s, fc); } }/*, eof: function() { sink.eof && sink.eof(); }*/ }; }, function skipSink(skip, sink) { var i = 0; return { __proto__: sink, $UID: sink.$UID, put: function(obj, s, fc) { if ( i++ >= skip ) sink.put(obj, s, fc); } }; }, function orderedSink(comparator, sink) { comparator = toCompare(comparator); return { __proto__: sink, $UID: sink.$UID, i: 0, arr: [], put: function(obj, s, fc) { this.arr.push(obj); }, eof: function() { this.arr.sort(comparator); this.arr.select(sink); } }; }, function defineLazyProperty(target, name, definitionFn) { Object.defineProperty(target, name, { get: function() { var definition = definitionFn.call(this); Object.defineProperty(this, name, definition); return definition.get ? definition.get.call(this) : definition.value; }, configurable: true }); }, // Function for returning multi-line strings from commented functions. // Ex. var str = multiline(function() { /* multi-line string here */ }); function multiline(f) { var s = f.toString(); var start = s.indexOf('/*'); var end = s.lastIndexOf('*/'); return s.substring(start+2, end); }, // Computes the XY coordinates of the given node // relative to the containing elements. // TODO: findViewportXY works better... but do we need to find parent? function findPageXY(node) { var x = 0; var y = 0; var parent; while ( node ) { parent = node; x += node.offsetLeft; y += node.offsetTop; node = node.offsetParent; } return [x, y, parent]; }, // Computes the XY coordinates of the given node // relative to the viewport. function findViewportXY(node) { var rect = node.getBoundingClientRect(); return [rect.left, rect.top]; }, function nop() { /** NOP function. **/ }, function stringtoutf8(str) { var res = []; for (var i = 0; i < str.length; i++) { var code = str.charCodeAt(i); var count = 0; if ( code < 0x80 ) { res.push(code); continue; } // while(code > (0x40 >> count)) { // res.push(code & 0x3f); // count++; // code = code >> 7; // } // var header = 0x80 >> count; // res.push(code | header) } return res; }, function createGUID() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random()*16|0, v = c === 'x' ? r : (r&0x3|0x8); return v.toString(16); }); } ] }); var labelize = memoize1(function(str) { if ( str === '' ) return str; return capitalize(str.replace(/[a-z][A-Z]/g, function (a) { return a.charAt(0) + ' ' + a.charAt(1); })); }); var constantize = memoize1(function(str) { // switchFromCamelCaseToConstantFormat to SWITCH_FROM_CAMEL_CASE_TO_CONSTANT_FORMAT // TODO: add property to specify constantization. For now catch special case to avoid conflict with context this.X and this.Y. if ( str === 'x' ) return 'X_'; if ( str === 'y' ) return 'Y_'; return str.replace(/[a-z][^0-9a-z_]/g, function(a) { return a.substring(0,1) + '_' + a.substring(1,2); }).toUpperCase(); }); var capitalize = memoize1(function(str) { // switchFromProperyName to //SwitchFromPropertyName return str[0].toUpperCase() + str.substring(1); }); var camelize = memoize1(function (str) { // change css-name-style or 'space separated words' to camelCase var ret = str.replace (/(?:[-\s_])(\w)/g, function (_, a) { return a ? a.toUpperCase () : ''; }); return ret[0].toLowerCase() + ret.substring(1); }); MODEL({ extendsProto: 'Object', properties: [ { name: '$UID', getter: (function() { var id = 1; return function() { if ( Object.hasOwnProperty.call(this, '$UID__') ) return this.$UID__; this.$UID__ = id; id++; return this.$UID__; }; })() } ], methods: [ function become(other) { var local = Object.getOwnPropertyNames(this); for ( var i = 0; i < local.length; i++ ) { delete this[local[i]]; } var remote = Object.getOwnPropertyNames(other); for ( i = 0; i < remote.length; i++ ) { Object.defineProperty( this, remote[i], Object.getOwnPropertyDescriptor(other, remote[i])); } this.__proto__ = other.__proto__; } ] }); MODEL({ extendsProto: 'Array', constants: { oldForEach_: Array.prototype.forEach }, methods: [ function clone() { return this.slice(); }, function deepClone() { var a = this.clone(); for ( var i = 0 ; i < a.length ; i++ ) { var o = a[i]; if ( o && o.deepClone ) a[i] = o.deepClone(); } return a; }, function forEach(f, opt_this) { /* Replace Array.forEach with a faster version. */ if ( ! this || ! f || opt_this ) return this.oldForEach_.call(this, f, opt_this); var l = this.length; for ( var i = 0 ; i < l ; i++ ) f(this[i], i, this); }, function diff(other) { var added = other.slice(0); var removed = []; for ( var i = 0 ; i < this.length ; i++ ) { for ( var j = 0 ; j < added.length ; j++ ) { if ( this[i].compareTo(added[j]) == 0 ) { added.splice(j, 1); j--; break; } } if ( j == added.length ) removed.push(this[i]); } return { added: added, removed: removed }; }, function binaryInsert(item) { /* binaryInsert into a sorted array, removing duplicates */ var start = 0; var end = this.length-1; while ( end >= start ) { var m = start + Math.floor((end-start) / 2); var c = item.compareTo(this[m]); if ( c == 0 ) return this; // already there, nothing to do if ( c < 0 ) { end = m-1; } else { start = m+1; } } this.splice(start, 0, item); return this; }, function union(other) { return this.concat( other.filter(function(o) { return this.indexOf(o) == -1; }.bind(this))); }, function intersection(other) { return this.filter(function(o) { return other.indexOf(o) != -1; }); }, function intern() { for ( var i = 0 ; i < this.length ; i++ ) if ( this[i].intern ) this[i] = this[i].intern(); return this; }, function compareTo(other) { if ( this.length !== other.length ) return -1; for ( var i = 0 ; i < this.length ; i++ ) { var result = this[i].compareTo(other[i]); if ( result !== 0 ) return result; } return 0; }, // Clone this Array and remove 'v' (only 1 instance) // TODO: make faster by copying in one pass, without splicing function deleteF(v) { var a = this.clone(); for ( var i = 0 ; i < a.length ; i++ ) { if ( a[i] === v ) { a.splice(i, 1); break; } } return a; }, // Remove 'v' from this array (only 1 instance removed) // return true iff the value was removed function deleteI(v) { for ( var i = 0 ; i < this.length ; i++ ) { if ( this[i] === v ) { this.splice(i, 1); return true; } } return false; }, // Clone this Array and remove first object where predicate 'p' returns true // TODO: make faster by copying in one pass, without splicing function removeF(p) { var a = this.clone(); for ( var i = 0 ; i < a.length ; i++ ) { if ( p.f(a[i]) ) { a.splice(i, 1); break; } } return a; }, // Remove first object in this array where predicate 'p' returns true function removeI(p) { for ( var i = 0 ; i < this.length ; i++ ) { if ( p.f(this[i]) ) { this.splice(i, 1); breeak; } } return this; }, function pushF(obj) { var a = this.clone(); a.push(obj); return a; }, function spliceF(start, end /*, args */) { /** Functional version of splice. **/ var r = [], i; for ( i = 0 ; i < start ; i++ ) r.push(this[i]); for ( i = 2 ; i < arguments.length ; i++ ) r.push(arguments[i]); for ( i = start+end ; i < this.length ; i++ ) r.push(this[i]); return r; }, function fReduce(comparator, arr) { compare = toCompare(comparator); var result = []; var i = 0; var j = 0; var k = 0; while ( i < this.length && j < arr.length ) { var a = compare(this[i], arr[j]); if ( a < 0 ) { result[k++] = this[i++]; continue; } if ( a == 0) { result[k++] = this[i++]; result[k++] = arr[j++]; continue; } result[k++] = arr[j++]; } if ( i != this.length ) result = result.concat(this.slice(i)); if ( j != arr.length ) result = result.concat(arr.slice(j)); return result; }, function pushAll(arr) { /** * Push an array of values onto an array. * @param arr array of values * @return new length of this array */ // TODO: not needed, port and replace with pipe() this.push.apply(this, arr); return this.length; }, function mapFind(map) { /** * Search for a single element in an array. * @param predicate used to determine element to find */ for ( var i = 0 ; i < this.length ; i++ ) { var result = map(this[i], i); if ( result ) return result; } }, function mapProp(prop) { // Called like myArray.mapProp('name'), that's equivalent to: // myArray.map(function(x) { return x.name; }); return this.map(function(x) { return x[prop]; }); }, function mapCall() { var args = Array.prototype.slice.call(arguments, 0); var func = args.shift(); return this.map(function(x) { return x[func] && x[func].apply(x[func], args); }); } ], properties: [ { name: 'memento', getter: function() { throw "Array's can not be memorized properly as a memento."; } } ] }); MODEL({ extendsProto: 'String', methods: [ function indexOfIC(a) { return ( a.length > this.length ) ? -1 : this.toUpperCase().indexOf(a.toUpperCase()); }, function equals(other) { return this.compareTo(other) === 0; }, function equalsIC(other) { return other && this.toUpperCase() === other.toUpperCase(); }, // deprecated, use global instead function capitalize() { return this.charAt(0).toUpperCase() + this.slice(1); }, // deprecated, use global instead function labelize() { return this.replace(/[a-z][A-Z]/g, function (a) { return a.charAt(0) + ' ' + a.charAt(1); }).capitalize(); }, function compareTo(o) { return ( o == this ) ? 0 : this < o ? -1 : 1; }, // Polyfil String.prototype.startsWith || function startsWith(a) { // This implementation is very slow for some reason return 0 == this.lastIndexOf(a, 0); }, // Polyfil String.prototype.endsWith || function endsWith(a) { return (this.length - a.length) == this.lastIndexOf(a); }, function startsWithIC(a) { if ( a.length > this.length ) return false; var l = a.length; for ( var i = 0 ; i < l; i++ ) { if ( this[i].toUpperCase() !== a[i].toUpperCase() ) return false; } return true; }, function put(obj) { return this + obj.toJSON(); }, (function() { var map = {}; return function intern() { /** Convert a string to an internal canonical copy. **/ return map[this] || (map[this] = this.toString()); }; })(), function hashCode() { var hash = 0; if ( this.length == 0 ) return hash; for (i = 0; i < this.length; i++) { var code = this.charCodeAt(i); hash = ((hash << 5) - hash) + code; hash &= hash; } return hash; } ] }); MODEL({ extendsProto: 'Function', methods: [ /** * Replace Function.bind with a version * which is ~10X faster for the common case * where you're only binding 'this'. **/ (function() { var oldBind = Function.prototype.bind; var simpleBind = function(f, self) { return function() { return f.apply(self, arguments); }; /* var ret = function() { return f.apply(self, arguments); }; ret.toString = function bind() { return f.toString(); }; return ret; */ }; return function bind(arg) { return arguments.length == 1 ? simpleBind(this, arg) : oldBind.apply(this, argsToArray(arguments)); }; })(), function equals(o) { return this === o; }, function compareTo(o) { return this === o ? 0 : ( this.name.compareTo(o.name) || 1 ); }, function o(f2) { var f1 = this; return function() { return f1.call(this, f2.apply(this, argsToArray(arguments))); }; }, function on$(X /*, Values[] */) { /* Convert a function into a Value whenever any of the dependent Values update. */ return OrValue.create({ values: Array.prototype.splice.call(arguments, 1), valueFactory: this }, X); } ] }); MODEL({ extendsObject: 'Math', methods: [ function sign(n) { return n > 0 ? 1 : -1; } ] }); MODEL({ extendsProto: 'Date', methods: [ function toRelativeDateString(){ var seconds = Math.floor((Date.now() - this.getTime())/1000); if ( seconds < 60 ) return 'moments ago'; var minutes = Math.floor((seconds)/60); if ( minutes == 1 ) return '1 minute ago'; if ( minutes < 60 ) return minutes + ' minutes ago'; var hours = Math.floor(minutes/60); if ( hours == 1 ) return '1 hour ago'; if ( hours < 24 ) return hours + ' hours ago'; var days = Math.floor(hours / 24); if ( days == 1 ) return '1 day ago'; if ( days < 7 ) return days + ' days ago'; if ( days < 365 ) { var year = 1900+this.getYear(); var noyear = this.toDateString().replace(' ' + year, ''); return noyear.substring(4); } return this.toDateString().substring(4); }, function equals(o) { if ( ! o ) return false; if ( ! o.getTime ) return false; return this.getTime() === o.getTime(); }, function compareTo(o){ if ( o === this ) return 0; if ( ! o ) return 1; var d = this.getTime() - o.getTime(); return d == 0 ? 0 : d > 0 ? 1 : -1; }, function toMQL() { return this.getFullYear() + '/' + (this.getMonth() + 1) + '/' + this.getDate(); }, function toBQL() { var str = this.toISOString(); // eg. 2014-12-04T16:37:33.420Z return str.substring(0, str.indexOf('.')); // eg. 2014-12-04T16:37:33 } ] }); MODEL({ extendsProto: 'Number', methods: [ function compareTo(o) { return ( o == this ) ? 0 : this < o ? -1 : 1; }, ] }); MODEL({ extendsProto: 'Boolean', methods: [ function compareTo(o) { return (this.valueOf() ? 1 : 0) - (o ? 1 : 0); } ] }); MODEL({ extendsProto: 'RegExp', methods: [ function quote(str) { return (str+'').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); } ] }); console.log.json = function() { var args = []; for ( var i = 0 ; i < arguments.length ; i++ ) { var arg = arguments[i]; args.push(arg && arg.toJSON ? arg.toJSON() : arg); } console.log.apply(console, args); }; console.log.str = function() { var args = []; for ( var i = 0 ; i < arguments.length ; i++ ) { var arg = arguments[i]; args.push(arg && arg.toString ? arg.toString() : arg); } console.log.apply(console, args); }; // Promote 'console.log' into a Sink console.log.put = console.log.bind(console); console.log.remove = console.log.bind(console, 'remove: '); console.log.error = console.log.bind(console, 'error: '); console.log.json.put = console.log.json.bind(console); console.log.json.reduceI = console.log.json.bind(console, 'reduceI: '); console.log.json.remove = console.log.json.bind(console, 'remove: '); console.log.json.error = console.log.json.bind(console, 'error: '); console.log.str.put = console.log.str.bind(console); console.log.str.remove = console.log.str.bind(console, 'remove: '); console.log.str.error = console.log.str.bind(console, 'error: '); document.put = function(obj) { if ( obj.write ) { obj.write(this.X); } else { this.write(obj.toString()); } }; // Promote webkit apis; fallback on Node.js alternatives // TODO(kgr): this should be somewhere web specific window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.setImmediate; if ( window.Blob ) { Blob.prototype.slice = Blob.prototype.slice || Blob.prototype.webkitSlice; } if ( window.XMLHttpRequest ) { /** * Add an afunc send to XMLHttpRequest */ XMLHttpRequest.prototype.asend = function(ret, opt_data) { var xhr = this; xhr.onerror = function() { console.log('XHR Error: ', arguments); }; xhr.onloadend = function() { ret(xhr.response, xhr); }; xhr.send(opt_data); }; } String.fromCharCode = (function() { var oldLookup = String.fromCharCode; var lookupTable = []; return function(a) { if ( arguments.length == 1 ) return lookupTable[a] || (lookupTable[a] = oldLookup(a)); var result = ''; for ( var i = 0 ; i < arguments.length ; i++ ) result += lookupTable[arguments[i]] || (lookupTable[arguments[i]] = oldLookup(arguments[i])); return result; }; })(); var MementoProto = {}; Object.defineProperty(MementoProto, 'equals', { enumerable: false, configurable: true, value: function(o) { var keys = Object.keys(this); var otherKeys = Object.keys(o); if ( keys.length != otherKeys.length ) { return false; } for ( var i = 0 ; i < keys.length ; i++ ) { if ( ! equals(this[keys[i]], o[keys[i]]) ) return false; } return true; } });