can
Version:
MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.
1,502 lines (1,440 loc) • 1.83 MB
JavaScript
(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