UNPKG

can

Version:

MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.

1,502 lines (1,440 loc) 1.83 MB
(function(global, env) { // jshint ignore:line if (typeof process === "undefined") { global.process = { argv: [], cwd: function() { return ""; }, browser: true, env: { NODE_ENV: env || "development" }, version: "", platform: global.navigator && global.navigator.userAgent && /Windows/.test(global.navigator.userAgent) ? "win" : "" }; } })( typeof self == "object" && self.Object == Object ? self : typeof process === "object" && Object.prototype.toString.call(process) === "[object process]" ? global : window, "development" ); var canNamespace_1_0_0_canNamespace = {}; var supportsNativeSymbols = (function() { var symbolExists = typeof Symbol !== "undefined" && typeof Symbol.for === "function"; if (!symbolExists) { return false; } var symbol = Symbol("a symbol for testing symbols"); return typeof symbol === "symbol"; }()); var CanSymbol; if(supportsNativeSymbols) { CanSymbol = Symbol; } else { var symbolNum = 0; CanSymbol = function CanSymbolPolyfill(description){ var symbolValue = "@@symbol"+(symbolNum++)+(description); var symbol = {}; // make it object type Object.defineProperties(symbol, { toString: { value: function(){ return symbolValue; } } }); return symbol; }; var descriptionToSymbol = {}; var symbolToDescription = {}; /** * @function can-symbol.for for * @parent can-symbol/methods * @description Get a symbol based on a known string identifier, or create it if it doesn't exist. * * @signature `canSymbol.for(String)` * * @param { String } description The string value of the symbol * @return { CanSymbol } The globally unique and consistent symbol with the given string value. */ CanSymbol.for = function(description){ var symbol = descriptionToSymbol[description]; if(!symbol) { symbol = descriptionToSymbol[description] = CanSymbol(description); symbolToDescription[symbol] = description; } return symbol; }; /** * @function can-symbol.keyFor keyFor * @parent can-symbol * @description Get the description for a symbol. * * @signature `canSymbol.keyFor(CanSymbol)` * * @param { String } description The string value of the symbol * @return { CanSymbol } The globally unique and consistent symbol with the given string value. */ CanSymbol.keyFor = function(symbol) { return symbolToDescription[symbol]; }; ["hasInstance","isConcatSpreadable", "iterator","match","prototype","replace","search","species","split", "toPrimitive","toStringTag","unscopables"].forEach(function(name){ CanSymbol[name] = CanSymbol("Symbol."+name); }); } // Generate can. symbols. [ // ======= Type detection ========== "isMapLike", "isListLike", "isValueLike", "isFunctionLike", "isScopeLike", // ======= Shape detection ========= "getOwnKeys", "getOwnKeyDescriptor", "proto", // optional "getOwnEnumerableKeys", "hasOwnKey", "hasKey", "size", "getName", "getIdentity", // shape manipulation "assignDeep", "updateDeep", // ======= GET / SET "getValue", "setValue", "getKeyValue", "setKeyValue", "updateValues", "addValue", "removeValues", // ======= Call ========= "apply", "new", // ======= Observe ========= "onValue", "offValue", "onKeyValue", "offKeyValue", "getKeyDependencies", "getValueDependencies", "keyHasDependencies", "valueHasDependencies", "onKeys", "onKeysAdded", "onKeysRemoved", "onPatches" ].forEach(function(name){ CanSymbol.for("can."+name); }); var canSymbol_1_7_0_canSymbol = canNamespace_1_0_0_canNamespace.Symbol = CanSymbol; var helpers = { makeGetFirstSymbolValue: function(symbolNames){ var symbols = symbolNames.map(function(name){ return canSymbol_1_7_0_canSymbol.for(name); }); var length = symbols.length; return function getFirstSymbol(obj){ var index = -1; while (++index < length) { if(obj[symbols[index]] !== undefined) { return obj[symbols[index]]; } } }; }, // The `in` check is from jQuery’s fix for an iOS 8 64-bit JIT object length bug: // https://github.com/jquery/jquery/pull/2185 hasLength: function(list){ var type = typeof list; if(type === "string" || Array.isArray(list)) { return true; } var length = list && (type !== 'boolean' && type !== 'number' && "length" in list) && list.length; // var length = "length" in obj && obj.length; return typeof list !== "function" && ( length === 0 || typeof length === "number" && length > 0 && ( length - 1 ) in list ); } }; var plainFunctionPrototypePropertyNames = Object.getOwnPropertyNames((function(){}).prototype); var plainFunctionPrototypeProto = Object.getPrototypeOf( (function(){}).prototype ); /** * @function can-reflect.isConstructorLike isConstructorLike * @parent can-reflect/type * * @description Test if a value looks like a constructor function. * * @signature `isConstructorLike(func)` * * Return `true` if `func` is a function and has a non-empty prototype, or implements * [can-symbol/symbols/new `@@@@can.new`]; `false` otherwise. * * ```js * canReflect.isConstructorLike(function() {}); // -> false * * function Construct() {} * Construct.prototype = { foo: "bar" }; * canReflect.isConstructorLike(Construct); // -> true * * canReflect.isConstructorLike({}); // -> false * !!canReflect.isConstructorLike({ [canSymbol.for("can.new")]: function() {} }); // -> true * ``` * * @param {*} func maybe a function * @return {Boolean} `true` if a constructor; `false` if otherwise. */ function isConstructorLike(func){ /* jshint unused: false */ // if you can new it ... it's a constructor var value = func[canSymbol_1_7_0_canSymbol.for("can.new")]; if(value !== undefined) { return value; } if(typeof func !== "function") { return false; } // If there are any properties on the prototype that don't match // what is normally there, assume it's a constructor var prototype = func.prototype; if(!prototype) { return false; } // Check if the prototype's proto doesn't point to what it normally would. // If it does, it means someone is messing with proto chains if( plainFunctionPrototypeProto !== Object.getPrototypeOf( prototype ) ) { return true; } var propertyNames = Object.getOwnPropertyNames(prototype); if(propertyNames.length === plainFunctionPrototypePropertyNames.length) { for(var i = 0, len = propertyNames.length; i < len; i++) { if(propertyNames[i] !== plainFunctionPrototypePropertyNames[i]) { return true; } } return false; } else { return true; } } /** * @function can-reflect.isFunctionLike isFunctionLike * @parent can-reflect/type * @description Test if a value looks like a function. * @signature `isFunctionLike(obj)` * * Return `true` if `func` is a function, or implements * [can-symbol/symbols/new `@@@@can.new`] or [can-symbol/symbols/apply `@@@@can.apply`]; `false` otherwise. * * ```js * canReflect.isFunctionLike(function() {}); // -> true * canReflect.isFunctionLike({}); // -> false * canReflect.isFunctionLike({ [canSymbol.for("can.apply")]: function() {} }); // -> true * ``` * * @param {*} obj maybe a function * @return {Boolean} */ var getNewOrApply = helpers.makeGetFirstSymbolValue(["can.new","can.apply"]); function isFunctionLike(obj){ var result, symbolValue = !!obj && obj[canSymbol_1_7_0_canSymbol.for("can.isFunctionLike")]; if (symbolValue !== undefined) { return symbolValue; } result = getNewOrApply(obj); if(result !== undefined) { return !!result; } return typeof obj === "function"; } /** * @function can-reflect.isPrimitive isPrimitive * @parent can-reflect/type * @description Test if a value is a JavaScript primitive. * @signature `isPrimitive(obj)` * * Return `true` if `obj` is not a function nor an object via `typeof`, or is null; `false` otherwise. * * ```js * canReflect.isPrimitive(null); // -> true * canReflect.isPrimitive({}); // -> false * canReflect.isPrimitive(undefined); // -> true * canReflect.isPrimitive(1); // -> true * canReflect.isPrimitive([]); // -> false * canReflect.isPrimitive(function() {}); // -> false * canReflect.isPrimitive("foo"); // -> true * * ``` * * @param {*} obj maybe a primitive value * @return {Boolean} */ function isPrimitive(obj){ var type = typeof obj; if(obj == null || (type !== "function" && type !== "object") ) { return true; } else { return false; } } var coreHasOwn = Object.prototype.hasOwnProperty; var funcToString = Function.prototype.toString; var objectCtorString = funcToString.call(Object); function isPlainObject(obj) { // Must be an Object. // Because of IE, we also have to check the presence of the constructor property. // Make sure that DOM nodes and window objects don't pass through, as well if (!obj || typeof obj !== 'object' ) { return false; } var proto = Object.getPrototypeOf(obj); if(proto === Object.prototype || proto === null) { return true; } // partially inspired by lodash: https://github.com/lodash/lodash var Constructor = coreHasOwn.call(proto, 'constructor') && proto.constructor; return typeof Constructor === 'function' && Constructor instanceof Constructor && funcToString.call(Constructor) === objectCtorString; } /** * @function can-reflect.isBuiltIn isBuiltIn * @parent can-reflect/type * @description Test if a value is a JavaScript built-in type. * @signature `isBuiltIn(obj)` * * Return `true` if `obj` is some type of JavaScript native built-in; `false` otherwise. * * ```js * canReflect.isBuiltIn(null); // -> true * canReflect.isBuiltIn({}); // -> true * canReflect.isBuiltIn(1); // -> true * canReflect.isBuiltIn([]); // -> true * canReflect.isBuiltIn(function() {}); // -> true * canReflect.isBuiltIn("foo"); // -> true * canReflect.isBuiltIn(new Date()); // -> true * canReflect.isBuiltIn(/[foo].[bar]/); // -> true * canReflect.isBuiltIn(new DefineMap); // -> false * * ``` * * Not supported in browsers that have implementations of Map/Set where * `toString` is not properly implemented to return `[object Map]`/`[object Set]`. * * @param {*} obj maybe a built-in value * @return {Boolean} */ function isBuiltIn(obj) { // If primitive, array, or POJO return true. Also check if // it is not a POJO but is some type like [object Date] or // [object Regex] and return true. if (isPrimitive(obj) || Array.isArray(obj) || isPlainObject(obj) || (Object.prototype.toString.call(obj) !== '[object Object]' && Object.prototype.toString.call(obj).indexOf('[object ') !== -1)) { return true; } else { return false; } } /** * @function can-reflect.isValueLike isValueLike * @parent can-reflect/type * @description Test if a value represents a single value (as opposed to several values). * * @signature `isValueLike(obj)` * * Return `true` if `obj` is a primitive or implements [can-symbol/symbols/getValue `@@can.getValue`], * `false` otherwise. * * ```js * canReflect.isValueLike(null); // -> true * canReflect.isValueLike({}); // -> false * canReflect.isValueLike(function() {}); // -> false * canReflect.isValueLike({ [canSymbol.for("can.isValueLike")]: true}); // -> true * canReflect.isValueLike({ [canSymbol.for("can.getValue")]: function() {} }); // -> true * canReflect.isValueLike(canCompute()); // -> true * canReflect.isValueLike(new DefineMap()); // -> false * * ``` * * @param {*} obj maybe a primitive or an object that yields a value * @return {Boolean} */ function isValueLike(obj) { var symbolValue; if(isPrimitive(obj)) { return true; } symbolValue = obj[canSymbol_1_7_0_canSymbol.for("can.isValueLike")]; if( typeof symbolValue !== "undefined") { return symbolValue; } var value = obj[canSymbol_1_7_0_canSymbol.for("can.getValue")]; if(value !== undefined) { return !!value; } } /** * @function can-reflect.isMapLike isMapLike * @parent can-reflect/type * * @description Test if a value represents multiple values. * * @signature `isMapLike(obj)` * * Return `true` if `obj` is _not_ a primitive, does _not_ have a falsy value for * [can-symbol/symbols/isMapLike `@@@@can.isMapLike`], or alternately implements * [can-symbol/symbols/getKeyValue `@@@@can.getKeyValue`]; `false` otherwise. * * ```js * canReflect.isMapLike(null); // -> false * canReflect.isMapLike(1); // -> false * canReflect.isMapLike("foo"); // -> false * canReflect.isMapLike({}); // -> true * canReflect.isMapLike(function() {}); // -> true * canReflect.isMapLike([]); // -> false * canReflect.isMapLike({ [canSymbol.for("can.isMapLike")]: false }); // -> false * canReflect.isMapLike({ [canSymbol.for("can.getKeyValue")]: null }); // -> false * canReflect.isMapLike(canCompute()); // -> false * canReflect.isMapLike(new DefineMap()); // -> true * * ``` * * @param {*} obj maybe a Map-like * @return {Boolean} */ function isMapLike(obj) { if(isPrimitive(obj)) { return false; } var isMapLike = obj[canSymbol_1_7_0_canSymbol.for("can.isMapLike")]; if(typeof isMapLike !== "undefined") { return !!isMapLike; } var value = obj[canSymbol_1_7_0_canSymbol.for("can.getKeyValue")]; if(value !== undefined) { return !!value; } // everything else in JS is MapLike return true; } /** * @function can-reflect.isObservableLike isObservableLike * @parent can-reflect/type * @description Test if a value (or its keys) can be observed for changes. * * @signature `isObservableLike(obj)` * * Return `true` if `obj` is _not_ a primitive and implements any of * [can-symbol/symbols/onValue `@@@@can.onValue`], [can-symbol/symbols/onKeyValue `@@@@can.onKeyValue`], or * [can-symbol/symbols/onPatches `@@@@can.onKeys`]; `false` otherwise. * * ```js * canReflect.isObservableLike(null); // -> false * canReflect.isObservableLike({}); // -> false * canReflect.isObservableLike([]); // -> false * canReflect.isObservableLike(function() {}); // -> false * canReflect.isObservableLike({ [canSymbol.for("can.onValue")]: function() {} }); // -> true * canReflect.isObservableLike({ [canSymbol.for("can.onKeyValue")]: function() {} }); // -> true * canReflect.isObservableLike(canCompute())); // -> true * canReflect.isObservableLike(new DefineMap())); // -> true * ``` * * @param {*} obj maybe an observable * @return {Boolean} */ // Specially optimized var onValueSymbol = canSymbol_1_7_0_canSymbol.for("can.onValue"), onKeyValueSymbol = canSymbol_1_7_0_canSymbol.for("can.onKeyValue"), onPatchesSymbol = canSymbol_1_7_0_canSymbol.for("can.onPatches"); function isObservableLike( obj ) { if(isPrimitive(obj)) { return false; } return Boolean(obj[onValueSymbol] || obj[onKeyValueSymbol] || obj[onPatchesSymbol]); } /** * @function can-reflect.isListLike isListLike * @parent can-reflect/type * * @description Test if a value looks like a constructor function. * * @signature `isListLike(list)` * * Return `true` if `list` is a `String`, <br>OR `list` is _not_ a primitive and implements `@@@@iterator`, * <br>OR `list` is _not_ a primitive and returns `true` for `Array.isArray()`, <br>OR `list` is _not_ a primitive and has a * numerical length and is either empty (`length === 0`) or has a last element at index `length - 1`; <br>`false` otherwise * * ```js * canReflect.isListLike(null); // -> false * canReflect.isListLike({}); // -> false * canReflect.isListLike([]); // -> true * canReflect.isListLike("foo"); // -> true * canReflect.isListLike(1); // -> false * canReflect.isListLike({ [canSymbol.for("can.isListLike")]: true }); // -> true * canReflect.isListLike({ [canSymbol.iterator]: function() {} }); // -> true * canReflect.isListLike({ length: 0 }); // -> true * canReflect.isListLike({ length: 3 }); // -> false * canReflect.isListLike({ length: 3, "2": true }); // -> true * canReflect.isListLike(new DefineMap()); // -> false * canReflect.isListLike(new DefineList()); // -> true * ``` * * @param {*} list maybe a List-like * @return {Boolean} */ function isListLike( list ) { var symbolValue, type = typeof list; if(type === "string") { return true; } if( isPrimitive(list) ) { return false; } symbolValue = list[canSymbol_1_7_0_canSymbol.for("can.isListLike")]; if( typeof symbolValue !== "undefined") { return symbolValue; } var value = list[canSymbol_1_7_0_canSymbol.iterator]; if(value !== undefined) { return !!value; } if(Array.isArray(list)) { return true; } return helpers.hasLength(list); } /** * @function can-reflect.isSymbolLike isSymbolLike * @parent can-reflect/type * * @description Test if a value is a symbol or a [can-symbol]. * * @signature `isSymbolLike(symbol)` * * Return `true` if `symbol` is a native Symbol, or evaluates to a String with a prefix * equal to that of CanJS's symbol polyfill; `false` otherwise. * * ```js * /* ES6 *\/ canReflect.isSymbolLike(Symbol.iterator); // -> true * canReflect.isSymbolLike(canSymbol.for("foo")); // -> true * canReflect.isSymbolLike("@@symbol.can.isSymbol"); // -> true (due to polyfill for non-ES6) * canReflect.isSymbolLike("foo"); // -> false * canReflect.isSymbolLike(null); // -> false * canReflect.isSymbolLike(1); // -> false * canReflect.isSymbolLike({}); // -> false * canReflect.isSymbolLike({ toString: function() { return "@@symbol.can.isSymbol"; } }); // -> true * ``` * * @param {*} symbol maybe a symbol * @return {Boolean} */ var supportsNativeSymbols$1 = (function() { var symbolExists = typeof Symbol !== "undefined" && typeof Symbol.for === "function"; if (!symbolExists) { return false; } var symbol = Symbol("a symbol for testing symbols"); return typeof symbol === "symbol"; }()); var isSymbolLike; if(supportsNativeSymbols$1) { isSymbolLike = function(symbol) { return typeof symbol === "symbol"; }; } else { var symbolStart = "@@symbol"; isSymbolLike = function(symbol) { if(typeof symbol === "object" && !Array.isArray(symbol)){ return symbol.toString().substr(0, symbolStart.length) === symbolStart; } else { return false; } }; } /** * @function can-reflect.isScopeLike isScopeLike * @parent can-reflect/type * * @description Test if a value represents a can.view.Scope or its API equivalent * * @signature `isScopeLike(obj)` * * Return `true` if `obj` is _not_ a primitive, does _not_ have a falsy value for * [can-symbol/symbols/isScopeLike `@@@@can.isScopeLike`], or implements the public * API of [can-view-scope] along with `_context` and `_meta` objects; `false` otherwise. * * ```js * canReflect.isScopeLike(null); // -> false * canReflect.isScopeLike(1); // -> false * canReflect.isScopeLike("foo"); // -> false * canReflect.isScopeLike({}); // -> false * canReflect.isScopeLike(function() {}); // -> false * canReflect.isScopeLike([]); // -> false * canReflect.isScopeLike({ [canSymbol.for("can.isScopeLike")]: true }); // -> true * canReflect.isScopeLike({ * get(){}, set(){}, find(){}, peek(){}, computeData(){}, add(){}, getScope(){}, * getHelperOrPartial(){}, getTemplateContext(), addLetContext(){}, cloneFromRef(){}, * _meta: {}, _context: {} * }); // -> true * canReflect.isScopeLike(new can.view.Scope()); // -> true * * ``` * * @param {*} obj maybe a Map-like * @return {Boolean} */ // note: older can 2.x scopes do not implement find() or addLetContext() but these are required by later can-stache, so passing // this function is not a guarantee of interoperability. var fnKeys = ["get", "set", "peek", "computeData", "add", "getScope", "getHelperOrPartial", "getTemplateContext", "cloneFromRef"]; function isScopeLike(obj) { if(isPrimitive(obj)) { return false; } var isScopeLike = obj[canSymbol_1_7_0_canSymbol.for("can.isScopeLike")]; if(typeof isScopeLike !== "undefined") { return !!isScopeLike; } return fnKeys.every(function(key) { return typeof obj[key] === "function"; }) && "_context" in obj && obj._meta && typeof obj._meta === "object"; } var type = { isConstructorLike: isConstructorLike, isFunctionLike: isFunctionLike, isListLike: isListLike, isMapLike: isMapLike, isObservableLike: isObservableLike, isScopeLike: isScopeLike, isPrimitive: isPrimitive, isBuiltIn: isBuiltIn, isValueLike: isValueLike, isSymbolLike: isSymbolLike, /** * @function can-reflect.isMoreListLikeThanMapLike isMoreListLikeThanMapLike * @parent can-reflect/type * * @description Test if a value should be treated as a list instead of a map. * * @signature `isMoreListLikeThanMapLike(obj)` * * Return `true` if `obj` is an Array, declares itself to be more ListLike with * `@@@@can.isMoreListLikeThanMapLike`, or self-reports as ListLike but not as MapLike; `false` otherwise. * * ```js * canReflect.isMoreListLikeThanMapLike([]); // -> true * canReflect.isMoreListLikeThanMapLike(null); // -> false * canReflect.isMoreListLikeThanMapLike({}); // -> false * canReflect.isMoreListLikeThanMapLike(new DefineList()); // -> true * canReflect.isMoreListLikeThanMapLike(new DefineMap()); // -> false * canReflect.isMoreListLikeThanMapLike(function() {}); // -> false * ``` * * @param {Object} obj the object to test for ListLike against MapLike traits. * @return {Boolean} */ isMoreListLikeThanMapLike: function(obj){ if(Array.isArray(obj)) { return true; } if(obj instanceof Array) { return true; } if( obj == null ) { return false; } var value = obj[canSymbol_1_7_0_canSymbol.for("can.isMoreListLikeThanMapLike")]; if(value !== undefined) { return value; } var isListLike = this.isListLike(obj), isMapLike = this.isMapLike(obj); if(isListLike && !isMapLike) { return true; } else if(!isListLike && isMapLike) { return false; } }, /** * @function can-reflect.isIteratorLike isIteratorLike * @parent can-reflect/type * @description Test if a value looks like an iterator. * @signature `isIteratorLike(obj)` * * Return `true` if `obj` has a key `"next"` pointing to a zero-argument function; `false` otherwise * * ```js * canReflect.isIteratorLike([][Symbol.iterator]()); // -> true * canReflect.isIteratorLike(new DefineList()[canSymbol.iterator]()); // -> true * canReflect.isIteratorLike(new DefineMap()[canSymbol.iterator]()); // -> true * canReflect.isIteratorLike(null); // -> false * canReflect.isIteratorLike({ next: function() {} }); // -> true * canReflect.isIteratorLike({ next: function(foo) {} }); // -> false (iterator nexts do not take arguments) * ``` * * @param {Object} obj the object to test for Iterator traits * @return {Boolean} */ isIteratorLike: function(obj){ return obj && typeof obj === "object" && typeof obj.next === "function" && obj.next.length === 0; }, /** * @function can-reflect.isPromise isPromise * @parent can-reflect/type * @description Test if a value is a promise. * * @signature `isPromise(obj)` * * Return `true` if `obj` is an instance of promise or `.toString` returns `"[object Promise]"`. * * ```js * canReflect.isPromise(Promise.resolve()); // -> true * ``` * * @param {*} obj the object to test for Promise traits. * @return {Boolean} */ isPromise: function(obj){ return (obj instanceof Promise || (Object.prototype.toString.call(obj) === '[object Promise]')); }, /** * @function can-reflect.isPlainObject isPlainObject * @parent can-reflect/type * @description Test if a value is an object created with `{}` or `new Object()`. * * @signature `isPlainObject(obj)` * * Attempts to determine if an object is a plain object like those you would create using the curly braces syntax: `{}`. The following are not plain objects: * * 1. Objects with prototypes (created using the `new` keyword). * 2. Booleans. * 3. Numbers. * 4. NaN. * * ```js * var isPlainObject = require("can-reflect").isPlainObject; * * // Created with {} * console.log(isPlainObject({})); // -> true * * // new Object * console.log(isPlainObject(new Object())); // -> true * * // Custom object * var Ctr = function(){}; * var obj = new Ctr(); * * console.log(isPlainObject(obj)); // -> false * ``` * * @param {Object} obj the object to test. * @return {Boolean} */ isPlainObject: isPlainObject }; var call = { /** * @function {function(...), Object, ...} can-reflect/call.call call * @parent can-reflect/call * @description Call a callable, with a context object and parameters * * @signature `call(func, context, ...rest)` * * Call the callable `func` as if it were a function, bound to `context` and with any additional parameters * occurring after `context` set to the positional parameters. * * Note that `func` *must* either be natively callable, implement [can-symbol/symbols/apply @@@@can.apply], * or have a callable `apply` property to work with `canReflect.call` * * ```js * var compute = canCompute("foo"); * * canReflect.call(compute, null, "bar"); * canReflect.call(compute, null); // -> "bar" * ``` * * @param {function(...)} func the function to call with the supplied arguments * @param {Object} context the context object to set as `this` on the function call * @param {*} rest any arguments after `context` will be passed to the function call * @return {*} return types and values are determined by the call to `func` */ call: function(func, context){ var args = [].slice.call(arguments, 2); var apply = func[canSymbol_1_7_0_canSymbol.for("can.apply")]; if(apply) { return apply.call(func, context, args); } else { return func.apply(context, args); } }, /** * @function {function(...), Object, ...} can-reflect/call.apply apply * @parent can-reflect/call * @description Call a callable, with a context object and a list of parameters * * @signature `apply(func, context, args)` * * Call the callable `func` as if it were a function, bound to `context` and with any additional parameters * contained in the Array-like `args` * * Note that `func` *must* either be natively callable, implement [can-symbol/symbols/apply @@@@can.apply], * or have a callable `apply` property to work with `canReflect.apply` * * ```js * var compute = canCompute("foo"); * * canReflect.apply(compute, null, ["bar"]); * canReflect.apply(compute, null, []); // -> "bar" * ``` * * @param {function(...)} func the function to call * @param {Object} context the context object to set as `this` on the function call * @param {*} args arguments to be passed to the function call * @return {*} return types and values are determined by the call to `func` */ apply: function(func, context, args){ var apply = func[canSymbol_1_7_0_canSymbol.for("can.apply")]; if(apply) { return apply.call(func, context, args); } else { return func.apply(context, args); } }, /** * @function {function(...), ...} can-reflect/call.new new * @parent can-reflect/call * @description Construct a new instance of a callable constructor * * @signature `new(func, ...rest)` * * Call the callable `func` as if it were a function, bound to a new instance of `func`, and with any additional * parameters occurring after `func` set to the positional parameters. * * Note that `func` *must* either implement [can-symbol/symbols/new @@@@can.new], * or have a callable `apply` property *and* a prototype to work with `canReflect.new` * * ```js * canReflect.new(DefineList, ["foo"]); // -> ["foo"]<DefineList> * ``` * * @param {function(...)} func a constructor * @param {*} rest arguments to be passed to the constructor * @return {Object} if `func` returns an Object, that returned Object; otherwise a new instance of `func` */ "new": function(func){ var args = [].slice.call(arguments, 1); var makeNew = func[canSymbol_1_7_0_canSymbol.for("can.new")]; if(makeNew) { return makeNew.apply(func, args); } else { var context = Object.create(func.prototype); var ret = func.apply(context, args); if(type.isPrimitive(ret)) { return context; } else { return ret; } } } }; var setKeyValueSymbol = canSymbol_1_7_0_canSymbol.for("can.setKeyValue"), getKeyValueSymbol = canSymbol_1_7_0_canSymbol.for("can.getKeyValue"), getValueSymbol = canSymbol_1_7_0_canSymbol.for("can.getValue"), setValueSymbol = canSymbol_1_7_0_canSymbol.for("can.setValue"); var reflections = { /** * @function {Object, String, *} can-reflect.setKeyValue setKeyValue * @parent can-reflect/get-set * @description Set the value of a named property on a MapLike object. * * @signature `setKeyValue(obj, key, value)` * * Set the property on Map-like `obj`, identified by the String, Symbol or Object value `key`, to the value `value`. * The default behavior can be overridden on `obj` by implementing [can-symbol/symbols/setKeyValue @@@@can.setKeyValue], * otherwise native named property access is used for string keys, and `Object.defineProperty` is used to set symbols. * * ```js * var foo = new DefineMap({ bar: "baz" }); * * canReflect.setKeyValue(foo, "bar", "quux"); * foo[bar]; // -> "quux" * ``` * @param {Object} obj the object to set on * @param {String} key the key for the property to set * @param {*} value the value to set on the object */ setKeyValue: function(obj, key, value){ if( type.isSymbolLike(key) ) { if(typeof key === "symbol") { obj[key] = value; } else { Object.defineProperty(obj, key, { enumerable: false, configurable: true, value: value, writable: true }); } return; } var setKeyValue = obj[setKeyValueSymbol]; if(setKeyValue !== undefined) { return setKeyValue.call(obj, key, value); } else { obj[key] = value; } }, /** * @function {Object, String} can-reflect.getKeyValue getKeyValue * @parent can-reflect/get-set * @description Get the value of a named property on a MapLike object. * * @signature `getKeyValue(obj, key)` * * Retrieve the property on Map-like `obj` identified by the String or Symbol value `key`. The default behavior * can be overridden on `obj` by implementing [can-symbol/symbols/getKeyValue @@@@can.getKeyValue], * otherwise native named property access is used. * * ```js * var foo = new DefineMap({ bar: "baz" }); * * canReflect.getKeyValue(foo, "bar"); // -> "baz" * ``` * * @param {Object} obj the object to get from * @param {String} key the key of the property to get */ getKeyValue: function(obj, key) { var getKeyValue = obj[getKeyValueSymbol]; if(getKeyValue) { return getKeyValue.call(obj, key); } return obj[key]; }, /** * @function {Object, String} can-reflect.deleteKeyValue deleteKeyValue * @parent can-reflect/get-set * @description Delete a named property from a MapLike object. * * @signature `deleteKeyValue(obj, key)` * * Remove the property identified by the String or Symbol `key` from the Map-like object `obj`, if possible. * Property definitions may interfere with deleting key values; the behavior on `obj` if `obj[key]` cannot * be deleted is undefined. The default use of the native `delete` keyword can be overridden by `obj` if it * implements [can-symbol/symbols/deleteKeyValue @@@@can.deleteKeyValue]. * * ```js * var foo = new DefineMap({ bar: "baz" }); * var quux = new CanMap({ thud: "jeek" }); * * canReflect.deleteKeyValue(foo, "bar"); * canReflect.deleteKeyValue(quux, "thud"); * * "bar" in foo; // -> true -- DefineMaps use property defs which cannot be un-defined * foo.bar // -> undefined -- but set values to undefined when deleting * * "thud" in quux; // -> false * quux.thud; // -> undefined * ``` * * @param {Object} obj the object to delete on * @param {String} key the key for the property to delete */ deleteKeyValue: function(obj, key) { var deleteKeyValue = obj[canSymbol_1_7_0_canSymbol.for("can.deleteKeyValue")]; if(deleteKeyValue) { return deleteKeyValue.call(obj, key); } delete obj[key]; }, /** * @function {Object} can-reflect.getValue getValue * @parent can-reflect/get-set * @description Get the value of an object with a gettable value * * @signature `getValue(obj)` * * Return the value of the Value-like object `obj`. Unless `obj` implements * [can-symbol/symbols/getValue @@@@can.getValue], the result of `getValue` on * `obj` will always be `obj`. Observable Map-like objects may want to implement * `@@@@can.getValue` to return non-observable or plain representations of themselves. * * ```js * var compute = canCompute("foo"); * var primitive = "bar"; * * canReflect.getValue(compute); // -> "foo" * canReflect.getValue(primitive); // -> "bar" * ``` * * @param {Object} obj the object to get from * @return {*} the value of the object via `@@can.getValue`, or the value itself. */ getValue: function(value){ if(type.isPrimitive(value)) { return value; } var getValue = value[getValueSymbol]; if(getValue) { return getValue.call(value); } return value; }, /** * @function {Object, *} can-reflect.setValue setValue * @parent can-reflect/get-set * @description Set the value of a mutable object. * * @signature `setValue(obj, value)` * * Set the value of a Value-like object `obj` to the value `value`. `obj` *must* implement * [can-symbol/symbols/setValue @@@@can.setValue] to be used with `canReflect.setValue`. * Map-like objects may want to implement `@@@@can.setValue` to merge objects of properties * into themselves. * * ```js * var compute = canCompute("foo"); * var plain = {}; * * canReflect.setValue(compute, "bar"); * compute(); // -> bar * * canReflect.setValue(plain, { quux: "thud" }); // throws "can-reflect.setValue - Can not set value." * ``` * * @param {Object} obj the object to set on * @param {*} value the value to set for the object */ setValue: function(item, value){ var setValue = item && item[setValueSymbol]; if(setValue) { return setValue.call(item, value); } else { throw new Error("can-reflect.setValue - Can not set value."); } }, splice: function(obj, index, removing, adding){ var howMany; if(typeof removing !== "number") { var updateValues = obj[canSymbol_1_7_0_canSymbol.for("can.updateValues")]; if(updateValues) { return updateValues.call(obj, index, removing, adding); } howMany = removing.length; } else { howMany = removing; } if(arguments.length <= 3){ adding = []; } var splice = obj[canSymbol_1_7_0_canSymbol.for("can.splice")]; if(splice) { return splice.call(obj, index, howMany, adding); } return [].splice.apply(obj, [index, howMany].concat(adding) ); }, addValues: function(obj, adding, index) { var add = obj[canSymbol_1_7_0_canSymbol.for("can.addValues")]; if(add) { return add.call(obj, adding, index); } if(Array.isArray(obj) && index === undefined) { return obj.push.apply(obj, adding); } return reflections.splice(obj, index, [], adding); }, removeValues: function(obj, removing, index) { var removeValues = obj[canSymbol_1_7_0_canSymbol.for("can.removeValues")]; if(removeValues) { return removeValues.call(obj, removing, index); } if(Array.isArray(obj) && index === undefined) { removing.forEach(function(item){ var index = obj.indexOf(item); if(index >=0) { obj.splice(index, 1); } }); return; } return reflections.splice(obj, index, removing, []); } }; /** * @function {Object, String} can-reflect.get get * @hide * @description an alias for [can-reflect.getKeyValue getKeyValue] */ reflections.get = reflections.getKeyValue; /** * @function {Object, String} can-reflect.set set * @hide * @description an alias for [can-reflect.setKeyValue setKeyValue] */ reflections.set = reflections.setKeyValue; /** * @function {Object, String} can-reflect.delete delete * @hide * @description an alias for [can-reflect.deleteKeyValue deleteKeyValue] */ reflections["delete"] = reflections.deleteKeyValue; var getSet = reflections; var slice = [].slice; function makeFallback(symbolName, fallbackName) { return function(obj, event, handler, queueName){ var method = obj[canSymbol_1_7_0_canSymbol.for(symbolName)]; if(method !== undefined) { return method.call(obj, event, handler, queueName); } return this[fallbackName].apply(this, arguments); }; } function makeErrorIfMissing(symbolName, errorMessage){ return function(obj){ var method = obj[canSymbol_1_7_0_canSymbol.for(symbolName)]; if(method !== undefined) { var args = slice.call(arguments, 1); return method.apply(obj, args); } throw new Error(errorMessage); }; } var observe = { // KEY /** * @function {Object, String, function(*, *), String} can-reflect/observe.onKeyValue onKeyValue * @parent can-reflect/observe * @description Register an event handler on a MapLike object, based on a key change * * @signature `onKeyValue(obj, key, handler, [queueName])` * * Register a handler on the Map-like object `obj` to trigger when the property key `key` changes. * `obj` *must* implement [can-symbol/symbols/onKeyValue @@@@can.onKeyValue] to be compatible with * can-reflect.onKeyValue. The function passed as `handler` will receive the new value of the property * as the first argument, and the previous value of the property as the second argument. * * ```js * var obj = new DefineMap({ foo: "bar" }); * canReflect.onKeyValue(obj, "foo", function(newVal, oldVal) { * console.log("foo is now", newVal, ", was", oldVal); * }); * * obj.foo = "baz"; // -> logs "foo is now baz , was bar" * ``` * * @param {Object} obj an observable MapLike that can listen to changes in named properties. * @param {String} key the key to listen to * @param {function(*, *)} handler a callback function that recieves the new value * @param {String} [queueName] the queue to dispatch events to */ onKeyValue: makeFallback("can.onKeyValue", "onEvent"), /** * @function {Object, String, function(*), String} can-reflect/observe.offKeyValue offKeyValue * @parent can-reflect/observe * @description Unregister an event handler on a MapLike object, based on a key change * * @signature `offKeyValue(obj, key, handler, [queueName])` * * Unregister a handler from the Map-like object `obj` that had previously been registered with * [can-reflect/observe.onKeyValue onKeyValue]. The function passed as `handler` will no longer be called * when the value of `key` on `obj` changes. * * ```js * var obj = new DefineMap({ foo: "bar" }); * var handler = function(newVal, oldVal) { * console.log("foo is now", newVal, ", was", oldVal); * }; * * canReflect.onKeyValue(obj, "foo", handler); * canReflect.offKeyValue(obj, "foo", handler); * * obj.foo = "baz"; // -> nothing is logged * ``` * * @param {Object} obj an observable MapLike that can listen to changes in named properties. * @param {String} key the key to stop listening to * @param {function(*)} handler the callback function that should be removed from the event handlers for `key` * @param {String} [queueName] the queue that the handler was set to receive events from */ offKeyValue: makeFallback("can.offKeyValue","offEvent"), /** * @function {Object, function(Array)} can-reflect/observe.onKeys onKeys * @parent can-reflect/observe * @description Register an event handler on a MapLike object, triggered on the key set changing * * @signature `onKeys(obj, handler)` * * Register an event handler on the Map-like object `obj` to trigger when `obj`'s keyset changes. * `obj` *must* implement [can-symbol/symbols/onKeys @@@@can.onKeys] to be compatible with * can-reflect.onKeys. The function passed as `handler` will receive an Array of object diffs (see * [can-util/js/diff-object/diff-object diffObject] for the format) as its one argument. * * ```js * var obj = new DefineMap({ foo: "bar" }); * canReflect.onKeys(obj, function(diffs) { * console.log(diffs); * }); * * obj.set("baz", "quux"); // -> logs '[{"property": "baz", "type": "add", "value": "quux"}]' * ``` * * @param {Object} obj an observable MapLike that can listen to changes in named properties. * @param {function(Array)} handler the callback function to receive the diffs in the key set */ // any key change (diff would normally happen) onKeys: makeErrorIfMissing("can.onKeys","can-reflect: can not observe an onKeys event"), /** * @function {Object, function(Array)} can-reflect/observe.onKeysAdded onKeysAdded * @parent can-reflect/observe * @description Register an event handler on a MapLike object, triggered on new keys being added. * * @signature `onKeysAdded(obj, handler)` * * Register an event handler on the Map-like object `obj` to trigger when a new key or keys are set on * `obj`. `obj` *must* implement [can-symbol/symbols/onKeysAdded @@@@can.onKeysAdded] to be compatible with * can-reflect.onKeysAdded. The function passed as `handler` will receive an Array of Strings as its one * argument. * * ```js * var obj = new DefineMap({ foo: "bar" }); * canReflect.onKeysAded(obj, function(newKeys) { * console.log(newKeys); * }); * * foo.set("baz", "quux"); // -> logs '["baz"]' * ``` * * @param {Object} obj an observable MapLike that can listen to changes in named properties. * @param {function(Array)} handler the callback function to receive the array of added keys */ // keys added at a certain point {key: 1}, index onKeysAdded: makeErrorIfMissing("can.onKeysAdded","can-reflect: can not observe an onKeysAdded event"), /** * @function {Object, function(Array)} can-reflect/observe.onKeysRemoved onKeysRemoved * @parent can-reflect/observe * @description Register an event handler on a MapLike object, triggered on keys being deleted. * * @signature `onKeysRemoved(obj, handler)` * * Register an event handler on the Map-like object `obj` to trigger when a key or keys are removed from * `obj`'s keyset. `obj` *must* implement [can-symbol/symbols/onKeysRemoved @@@@can.onKeysRemoved] to be * compatible with can-reflect.onKeysAdded. The function passed as `handler` will receive an Array of * Strings as its one argument. * * ```js * var obj = new CanMap({ foo: "bar" }); * canReflect.onKeys(obj, function(diffs) { * console.log(JSON.stringify(diffs)); * }); * * foo.removeAttr("foo"); // -> logs '["foo"]' * ``` * * @param {Object} obj an observable MapLike that can listen to changes in named properties. * @param {function(Array)} handler the callback function to receive the array of removed keys */ onKeysRemoved: makeErrorIfMissing("can.onKeysRemoved","can-reflect: can not unobserve an onKeysRemoved event"), /** * @function {Object, String} can-reflect/observe.getKeyDependencies getKeyDependencies * @parent can-reflect/observe * @description Return the observable objects that compute to the value of a named property on an object * * @signature `getKeyDependencies(obj, key)` * * Return the observable objects that provide input values to generate the computed value of the * property `key` on Map-like object `obj`. If `key` does not have dependencies on `obj`, returns `undefined`. * Otherwise returns an object with up to two keys: `keyDependencies` is a [can-util/js/cid-map/cid-map CIDMap] that * maps each Map-like object providing keyed values to an Array of the relevant keys; `valueDependencies` is a * [can-util/js/cid-set/cid-set CIDSet] that contains all Value-like dependencies providing their own values. * * `obj` *must* implement [can-symbol/symbols/getKeyDependencies @@@@can.getKeyDependencies] to work with * `canReflect.getKeyDependencies`. * * * ```js * var foo = new DefineMap({ "bar": "baz" }) * var obj = new (DefineMap.extend({ * baz: { * get: function() { * return foo.bar; * } * } * }))(); * * canReflect.getKeyDependencies(obj, "baz"); // -> { valueDependencies: CIDSet } * ``` * * @param {Object} obj the object to check for key dependencies * @param {String} key the key on the object to check * @return {Object} the observable values that this keyed value depends on */ getKeyDependencies: makeErrorIfMissing("can.getKeyDependencies", "can-reflect: can not determine dependencies"), /** * @function {Object, String} can-reflect/observe.getWhatIChange getWhatIChange * @hide * @parent can-reflect/observe * @description Return the observable objects that derive their value from the * obj, passed in. * * @signature `getWhatIChange(obj, key)` * * `obj` *must* implement `@@@@can.getWhatIChange` to work with * `canReflect.getWhatIChange`. * * @param {Object} obj the object to check for what it changes * @param {String} [key] the key on the object to check * @return {Object} the observable values that derive their value from `obj` */ getWhatIChange: makeErrorIfMissing( "can.getWhatIChange", "can-reflect: can not determine dependencies" ), /** * @function {Function} can-reflect/observe.getChangesDependencyRecord getChangesDependencyRecord * @hide * @parent can-reflect/observe * @description Return the observable objects that are mutated by the handler * passed in as argument. * * @signature `getChangesDependencyRecord(handler)` * * `handler` *must* implement `@@@@can.getChangesDependencyRecord` to work with * `canReflect.getChangesDependencyRecord`. * * ```js * var one = new SimpleObservable("one"); * var two = new SimpleObservable("two"); * * var handler = function() { * two.set("2"); * }; * * canReflect.onValue(one, handler); * canReflect.getChangesDependencyRecord(handler); // -> { valueDependencies: new Set([two]) } * ``` * * @param {Function} handler the event handler to check for what it changes * @return {Object} the observable values that are mutated by the handler */ getChangesDependencyRecord: function getChangesDependencyRecord(handler) { var fn = handler[canSymbol_1_7_0_canSymbol.for("can.getChangesDependencyRecord")]; if (typeof fn === "function") { return fn(); } }, /** * @function {Object, String} can-reflect/observe.keyHasDependencies keyHasDependencies * @parent can-reflect/observe * @description Determine whether the value for a named property on an object is bound to other events * * @signature `keyHasDependencies(obj, key)` * * Returns `true` if the computed value of the property `key` on Map-like object `obj` derives from other values. * Returns `false` if `key` is computed on `obj` but does not have dependencies on other objects. If `key` is not * a computed value on `obj`, returns `undefined`. * * `obj` *must* implement [can-symbol/symbols/keyHasDependencies @@@@can.keyHasDependencies] to work with * `canReflect.keyHasDependencies`. * * ```js * var foo = new DefineMap({ "bar": "baz" }) * var obj = new (DefineMap.extend({ * baz: { * get: function() { * return foo.bar; * } * }, * quux: { * get: function() { * return "thud"; * } * } * }))(); * * canReflect.keyHasDependencies(obj, "baz"); // -> true * canReflect.keyHasDependencies(obj, "quux"); // -> false * canReflect.keyHasDependencies(foo, "bar"); // -> undefined * ``` * * @param {Object} obj the object to check for key dependencies * @param {String} key the key on the object to check * @return {Boolean} `true` if there are other objects that may update the keyed value; `false` otherwise * */ // TODO: use getKeyDeps once we know what that needs to look like keyHasDependencies: makeErrorIfMissing("can.keyHasDependencies","can-reflect: can not determine if this has key dependencies"), // VALUE /** * @function {Object, function(*)} can-reflect/observe.onValue onValue * @parent can-reflect/observe * @description Register an event handler on an observable ValueLike object, based on a change in its value * * @signature `onValue(handler, [queueName])` * * Register an event handler on the Value-like object `obj` to trigger when its value changes. * `obj` *must* implement [can-symbol/symbols/onValue @@@@can.onValue] to be compatible with * can-reflect.onKeyValue. The function passed as `handler` will receive the new value of `obj` * as the first argument, and the previous value of `obj` as the second argument. * * ```js * var obj = canCompute("foo"); * canReflect.onValue(obj, function(newVal, oldVal) { * console.log("compute is now", newVal, ", was", oldVal); * }); * * obj("bar"); // -> logs "compute is now bar , was foo" * ``` * * @param {*} obj any object implementing @@can.onValue * @param {function(*, *)} handler a callback function that receives the new and old values */ onValue: makeErrorIfMissing("can.onValue","can-reflect: can not observe value change"), /** * @function {Object, function(*)} can-reflect/observe.offValue offValue * @parent can-reflect/observe * @description Unregister an value change handler from an observable ValueLike object * * @signature `offValue(handler, [queueName])` * * Unregister an event handler from the Value-like object `obj` that had previously been registered with * [can-reflect/observe.onValue onValue]. The function passed as `handler` will no longer be called * when the value of `obj` changes. * * ```js * var obj = canCompute( "foo" ); * var handler = function(newVal, oldVal) { * console.log("compute is now", newVal, ", was", oldVal); * }; * * canReflect.onKeyValue(obj, handler); * canReflect.offKeyValue(obj, handler); * * obj("baz"); // -> nothing is logged * ``` * * @param {*} obj * @param {function(*)} handler */ offValue: makeErrorIfMissing("can.offValue","can-reflect: can not