UNPKG

can-define

Version:

Create observable objects with JS dot operator compatibility

1,325 lines (1,199 loc) 40.7 kB
"use strict"; "format cjs"; var ns = require("can-namespace"); var canSymbol = require("can-symbol"); var canReflect = require("can-reflect"); var Observation = require("can-observation"); var ObservationRecorder = require("can-observation-recorder"); var AsyncObservable = require("can-simple-observable/async/async"); var SettableObservable = require("can-simple-observable/settable/settable"); var ResolverObservable = require("can-simple-observable/resolver/resolver"); var eventQueue = require("can-event-queue/map/map"); var addTypeEvents = require("can-event-queue/type/type"); var queues = require("can-queues"); var assign = require("can-assign"); var canLogDev = require("can-log/dev/dev"); var stringToAny = require("can-string-to-any"); var defineLazyValue = require("can-define-lazy-value"); var MaybeBoolean = require("can-data-types/maybe-boolean/maybe-boolean"), MaybeDate = require("can-data-types/maybe-date/maybe-date"), MaybeNumber = require("can-data-types/maybe-number/maybe-number"), MaybeString = require("can-data-types/maybe-string/maybe-string"); var newSymbol = canSymbol.for("can.new"), serializeSymbol = canSymbol.for("can.serialize"), inSetupSymbol = canSymbol.for("can.initializing"); var eventsProto, define, make, makeDefinition, getDefinitionsAndMethods, getDefinitionOrMethod; // UTILITIES function isDefineType(func){ return func && (func.canDefineType === true || func[newSymbol] ); } var peek = ObservationRecorder.ignore(canReflect.getValue.bind(canReflect)); var Object_defineNamedPrototypeProperty = Object.defineProperty; //!steal-remove-start if(process.env.NODE_ENV !== 'production') { Object_defineNamedPrototypeProperty = function(obj, prop, definition) { if (definition.get) { Object.defineProperty(definition.get, "name", { value: "get "+canReflect.getName(obj) + "."+prop, writable: true, configurable: true }); } if (definition.set) { Object.defineProperty(definition.set, "name", { value: "set "+canReflect.getName(obj) + "."+prop, configurable: true }); } return Object.defineProperty(obj, prop, definition); }; } //!steal-remove-end function defineConfigurableAndNotEnumerable(obj, prop, value) { Object.defineProperty(obj, prop, { configurable: true, enumerable: false, writable: true, value: value }); } function eachPropertyDescriptor(map, cb){ for(var prop in map) { if(map.hasOwnProperty(prop)) { cb.call(map, prop, Object.getOwnPropertyDescriptor(map,prop)); } } } function getEveryPropertyAndSymbol(obj) { var props = Object.getOwnPropertyNames(obj); var symbols = ("getOwnPropertySymbols" in Object) ? Object.getOwnPropertySymbols(obj) : []; return props.concat(symbols); } function cleanUpDefinition(prop, definition, shouldWarn, typePrototype){ // cleanup `value` -> `default` if(definition.value !== undefined && ( typeof definition.value !== "function" || definition.value.length === 0) ){ //!steal-remove-start if(process.env.NODE_ENV !== 'production') { if(shouldWarn) { canLogDev.warn( "can-define: Change the 'value' definition for " + canReflect.getName(typePrototype)+"."+prop + " to 'default'." ); } } //!steal-remove-end definition.default = definition.value; delete definition.value; } // cleanup `Value` -> `DEFAULT` if(definition.Value !== undefined ){ //!steal-remove-start if(process.env.NODE_ENV !== 'production') { if(shouldWarn) { canLogDev.warn( "can-define: Change the 'Value' definition for " + canReflect.getName(typePrototype)+"."+prop + " to 'Default'." ); } } //!steal-remove-end definition.Default = definition.Value; delete definition.Value; } } function isValueResolver(definition) { // there's a function and it has one argument return typeof definition.value === "function" && definition.value.length; } module.exports = define = ns.define = function(typePrototype, defines, baseDefine) { // default property definitions on _data var prop, dataInitializers = Object.create(baseDefine ? baseDefine.dataInitializers : null), // computed property definitions on _computed computedInitializers = Object.create(baseDefine ? baseDefine.computedInitializers : null); var result = getDefinitionsAndMethods(defines, baseDefine, typePrototype); result.dataInitializers = dataInitializers; result.computedInitializers = computedInitializers; // Goes through each property definition and creates // a `getter` and `setter` function for `Object.defineProperty`. canReflect.eachKey(result.definitions, function(definition, property){ define.property(typePrototype, property, definition, dataInitializers, computedInitializers, result.defaultDefinition); }); // Places a `_data` on the prototype that when first called replaces itself // with a `_data` object local to the instance. It also defines getters // for any value that has a default value. if(typePrototype.hasOwnProperty("_data")) { for (prop in dataInitializers) { defineLazyValue(typePrototype._data, prop, dataInitializers[prop].bind(typePrototype), true); } } else { defineLazyValue(typePrototype, "_data", function() { var map = this; var data = {}; for (var prop in dataInitializers) { defineLazyValue(data, prop, dataInitializers[prop].bind(map), true); } return data; }); } // Places a `_computed` on the prototype that when first called replaces itself // with a `_computed` object local to the instance. It also defines getters // that will create the property's compute when read. if(typePrototype.hasOwnProperty("_computed")) { for (prop in computedInitializers) { defineLazyValue(typePrototype._computed, prop, computedInitializers[prop].bind(typePrototype)); } } else { defineLazyValue(typePrototype, "_computed", function() { var map = this; var data = Object.create(null); for (var prop in computedInitializers) { defineLazyValue(data, prop, computedInitializers[prop].bind(map)); } return data; }); } // Add necessary event methods to this object. getEveryPropertyAndSymbol(eventsProto).forEach(function(prop){ Object.defineProperty(typePrototype, prop, { enumerable: false, value: eventsProto[prop], configurable: true, writable: true }); }); // also add any symbols // add so instance defs can be dynamically added Object.defineProperty(typePrototype,"_define",{ enumerable: false, value: result, configurable: true, writable: true }); // Places Symbol.iterator or @@iterator on the prototype // so that this can be iterated with for/of and canReflect.eachIndex var iteratorSymbol = canSymbol.iterator || canSymbol.for("iterator"); if(!typePrototype[iteratorSymbol]) { defineConfigurableAndNotEnumerable(typePrototype, iteratorSymbol, function(){ return new define.Iterator(this); }); } return result; }; var onlyType = function(obj){ for(var prop in obj) { if(prop !== "type") { return false; } } return true; }; define.extensions = function () {}; // typePrototype - the prototype of the type we are defining `prop` on. // `definition` - the user provided definition define.property = function(typePrototype, prop, definition, dataInitializers, computedInitializers, defaultDefinition) { var propertyDefinition = define.extensions.apply(this, arguments); if (propertyDefinition) { definition = makeDefinition(prop, propertyDefinition, defaultDefinition || {}, typePrototype); } var type = definition.type; //!steal-remove-start if(process.env.NODE_ENV !== 'production') { var hasZeroArgGetter = definition.get && definition.get.length === 0; var noSetter = !definition.set; var defaultInDefinition = ( "default" in definition || "Default" in definition ); var typeInDefinition = (definition.type && defaultDefinition && definition.type !== defaultDefinition.type) || (definition.Type && defaultDefinition && definition.Type !== defaultDefinition.Type); if(hasZeroArgGetter && noSetter && defaultInDefinition) { var defaultOrDefault = "default" in definition ? "default" : "Default"; canLogDev.warn("can-define: " + defaultOrDefault + " value for property " + canReflect.getName(typePrototype)+"."+ prop + " ignored, as its definition has a zero-argument getter"); } if(hasZeroArgGetter && noSetter && typeInDefinition) { var typeOrType = definition.type ? "type" : "Type"; canLogDev.warn("can-define: " + typeOrType + " value for property " + canReflect.getName(typePrototype)+"."+ prop + " ignored, as its definition has a zero-argument getter"); } if (type && canReflect.isConstructorLike(type) && !isDefineType(type)) { canLogDev.warn( "can-define: the definition for " + canReflect.getName(typePrototype) + "."+ prop + " uses a constructor for \"type\". Did you mean \"Type\"?" ); } } //!steal-remove-end // Special case definitions that have only `type: "*"`. if (type && onlyType(definition) && type === define.types["*"]) { Object_defineNamedPrototypeProperty(typePrototype, prop, { get: make.get.data(prop), set: make.set.events(prop, make.get.data(prop), make.set.data(prop), make.eventType.data(prop)), enumerable: true, configurable: true }); return; } definition.type = type; // Where the value is stored. If there is a `get` the source of the value // will be a compute in `this._computed[prop]`. If not, the source of the // value will be in `this._data[prop]`. var dataProperty = definition.get || isValueResolver(definition) ? "computed" : "data", // simple functions that all read/get/set to the right place. // - reader - reads the value but does not observe. // - getter - reads the value and notifies observers. // - setter - sets the value. reader = make.read[dataProperty](prop), getter = make.get[dataProperty](prop), setter = make.set[dataProperty](prop), getInitialValue; //!steal-remove-start if(process.env.NODE_ENV !== 'production') { if (definition.get) { Object.defineProperty(definition.get, "name", { value: canReflect.getName(typePrototype) + "'s " + prop + " getter", configurable: true }); } if (definition.set) { Object.defineProperty(definition.set, "name", { value: canReflect.getName(typePrototype) + "'s " + prop + " setter", configurable: true }); } if(isValueResolver(definition)) { Object.defineProperty(definition.value, "name", { value: canReflect.getName(typePrototype) + "'s " + prop + " value", configurable: true }); } } //!steal-remove-end // Determine the type converter var typeConvert = function(val) { return val; }; if (definition.Type) { typeConvert = make.set.Type(prop, definition.Type, typeConvert); } if (type) { typeConvert = make.set.type(prop, type, typeConvert); } // make a setter that's going to fire of events var eventsSetter = make.set.events(prop, reader, setter, make.eventType[dataProperty](prop)); if(isValueResolver(definition)) { computedInitializers[prop] = make.valueResolver(prop, definition, typeConvert); } // Determine a function that will provide the initial property value. else if ((definition.default !== undefined || definition.Default !== undefined)) { //!steal-remove-start if (process.env.NODE_ENV !== 'production') { // If value is an object or array, give a warning if (definition.default !== null && typeof definition.default === 'object') { canLogDev.warn("can-define: The default value for " + canReflect.getName(typePrototype)+"."+prop + " is set to an object. This will be shared by all instances of the DefineMap. Use a function that returns the object instead."); } // If value is a constructor, give a warning if (definition.default && canReflect.isConstructorLike(definition.default)) { canLogDev.warn("can-define: The \"default\" for " + canReflect.getName(typePrototype)+"."+prop + " is set to a constructor. Did you mean \"Default\" instead?"); } } //!steal-remove-end getInitialValue = ObservationRecorder.ignore(make.get.defaultValue(prop, definition, typeConvert, eventsSetter)); } // If property has a getter, create the compute that stores its data. if (definition.get) { computedInitializers[prop] = make.compute(prop, definition.get, getInitialValue); } // If the property isn't a getter, but has an initial value, setup a // default value on `this._data[prop]`. else if (getInitialValue) { dataInitializers[prop] = getInitialValue; } // Define setter behavior. // If there's a `get` and `set`, make the setter get the `lastSetValue` on the // `get`'s compute. if (definition.get && definition.set) { // the compute will set off events, so we can use the basic setter setter = make.set.setter(prop, definition.set, make.read.lastSet(prop), setter, true); // If there's zero-arg `get`, warn on all sets in dev mode if (definition.get.length === 0 ) { //!steal-remove-start if(process.env.NODE_ENV !== 'production') { canLogDev.warn("can-define: Set value for property " + canReflect.getName(typePrototype)+"."+ prop + " ignored, as its definition has a zero-argument getter"); } //!steal-remove-end } } // If there's a `set` and no `get`, else if (definition.set) { // Add `set` functionality to the eventSetter. setter = make.set.setter(prop, definition.set, reader, eventsSetter, false); } // If there's neither `set` or `get` or `value` (resolver) else if (dataProperty === "data") { // make a set that produces events. setter = eventsSetter; } // If there's zero-arg `get` but not `set`, warn on all sets in dev mode else if (definition.get && definition.get.length < 1) { setter = function() { //!steal-remove-start if(process.env.NODE_ENV !== 'production') { canLogDev.warn("can-define: Set value for property " + canReflect.getName(typePrototype)+"."+ prop + " ignored, as its definition has a zero-argument getter and no setter"); } //!steal-remove-end }; } // Add type behavior to the setter. if (type) { setter = make.set.type(prop, type, setter); } if (definition.Type) { setter = make.set.Type(prop, definition.Type, setter); } // Define the property. Object_defineNamedPrototypeProperty(typePrototype, prop, { get: getter, set: setter, enumerable: "serialize" in definition ? !!definition.serialize : !definition.get, configurable: true }); }; define.makeDefineInstanceKey = function(constructor) { constructor[canSymbol.for("can.defineInstanceKey")] = function(property, value) { var defineResult = this.prototype._define; if(typeof value === "object") { // change `value` to default. cleanUpDefinition(property, value, false, this); } var definition = getDefinitionOrMethod(property, value, defineResult.defaultDefinition, this); if(definition && typeof definition === "object") { define.property(constructor.prototype, property, definition, defineResult.dataInitializers, defineResult.computedInitializers, defineResult.defaultDefinition); defineResult.definitions[property] = definition; } else { defineResult.methods[property] = definition; } this.prototype.dispatch({ action: "can.keys", type: "can.keys", // TODO: Remove in 6.0 target: this.prototype }); }; }; // Makes a simple constructor function. define.Constructor = function(defines, sealed) { var constructor = function DefineConstructor(props) { Object.defineProperty(this, inSetupSymbol, { configurable: true, enumerable: false, value: true, writable: true }); define.setup.call(this, props, sealed); this[inSetupSymbol] = false; }; var result = define(constructor.prototype, defines); addTypeEvents(constructor); define.makeDefineInstanceKey(constructor, result); return constructor; }; // A bunch of helper functions that are used to create various behaviors. make = { computeObj: function(map, prop, observable) { var computeObj = { oldValue: undefined, compute: observable, count: 0, handler: function(newVal) { var oldValue = computeObj.oldValue; computeObj.oldValue = newVal; map.dispatch({ action: "set", key: "prop", target: map, value: newVal, oldValue: oldValue, type: prop, // TODO: Remove in 6.0 }, [newVal, oldValue]); } }; return computeObj; }, valueResolver: function(prop, definition, typeConvert) { var getDefault = make.get.defaultValue(prop, definition, typeConvert); return function(){ var map = this; var defaultValue = getDefault.call(this); var computeObj = make.computeObj(map, prop, new ResolverObservable(definition.value, map, defaultValue)); //!steal-remove-start if(process.env.NODE_ENV !== 'production') { Object.defineProperty(computeObj.handler, "name", { value: canReflect.getName(definition.value).replace('value', 'event emitter') }); } //!steal-remove-end return computeObj; }; }, // Returns a function that creates the `_computed` prop. compute: function(prop, get, defaultValueFn) { return function() { var map = this, defaultValue = defaultValueFn && defaultValueFn.call(this), observable, computeObj; if(get.length === 0) { observable = new Observation(get, map); } else if(get.length === 1) { observable = new SettableObservable(get, map, defaultValue); } else { observable = new AsyncObservable(get, map, defaultValue); } computeObj = make.computeObj(map, prop, observable); //!steal-remove-start if(process.env.NODE_ENV !== 'production') { Object.defineProperty(computeObj.handler, "name", { value: canReflect.getName(get).replace('getter', 'event emitter') }); } //!steal-remove-end return computeObj; }; }, // Set related helpers. set: { data: function(prop) { return function(newVal) { this._data[prop] = newVal; }; }, computed: function(prop) { return function(val) { canReflect.setValue( this._computed[prop].compute, val ); }; }, events: function(prop, getCurrent, setData, eventType) { return function(newVal) { if (this[inSetupSymbol]) { setData.call(this, newVal); } else { var current = getCurrent.call(this); if (newVal === current) { return; } var dispatched; setData.call(this, newVal); dispatched = { patches: [{type: "set", key: prop, value: newVal}], target: this, action: "set", value: newVal, oldValue: current, key: prop, type: prop // TODO: Remove in 6.0 }; //!steal-remove-start if(process.env.NODE_ENV !== 'production') { var lastItem, lastFn; dispatched.reasonLog = [ canReflect.getName(this) + "'s", prop, "changed to", newVal, "from", current ]; // If there are observations currently recording, this isn't a good time to // mutate values: it's likely a cycle, and even if it doesn't cycle infinitely, // it will likely cause unnecessary recomputation of derived values. Warn the user. if(ObservationRecorder.isRecording() && queues.stack().length && !this[inSetupSymbol]) { lastItem = queues.stack()[queues.stack().length - 1]; lastFn = lastItem.context instanceof Observation ? lastItem.context.func : lastItem.fn; var mutationWarning = "can-define: The " + prop + " property on " + canReflect.getName(this) + " is being set in " + (canReflect.getName(lastFn) || canReflect.getName(lastItem.fn)) + ". This can cause infinite loops and performance issues. " + "Use the value() behavior for " + prop + " instead, and listen to other properties and observables with listenTo(). https://canjs.com/doc/can-define.types.value.html"; canLogDev.warn(mutationWarning); queues.logStack(); } } //!steal-remove-end this.dispatch(dispatched, [newVal, current]); } }; }, setter: function(prop, setter, getCurrent, setEvents, hasGetter) { return function(value) { //!steal-remove-start var asyncTimer; //!steal-remove-end var self = this; // call the setter, if returned value is undefined, // this means the setter is async so we // do not call update property and return right away queues.batch.start(); var setterCalled = false, current = getCurrent.call(this), setValue = setter.call(this, value, function(value) { setEvents.call(self, value); setterCalled = true; //!steal-remove-start if(process.env.NODE_ENV !== 'production') { clearTimeout(asyncTimer); } //!steal-remove-end }, current); if (setterCalled) { queues.batch.stop(); } else { if (hasGetter) { // we got a return value if (setValue !== undefined) { // if the current `set` value is returned, don't set // because current might be the `lastSetVal` of the internal compute. if (current !== setValue) { setEvents.call(this, setValue); } queues.batch.stop(); } // this is a side effect, it didn't take a value // so use the original set value else if (setter.length === 0) { setEvents.call(this, value); queues.batch.stop(); return; } // it took a value else if (setter.length === 1) { // if we have a getter, and undefined was returned, // we should assume this is setting the getters properties // and we shouldn't do anything. queues.batch.stop(); } // we are expecting something else { //!steal-remove-start if(process.env.NODE_ENV !== 'production') { asyncTimer = setTimeout(function() { canLogDev.warn('can-define: Setter "' + canReflect.getName(self)+"."+prop + '" did not return a value or call the setter callback.'); }, canLogDev.warnTimeout); } //!steal-remove-end queues.batch.stop(); return; } } else { // we got a return value if (setValue !== undefined) { // if the current `set` value is returned, don't set // because current might be the `lastSetVal` of the internal compute. setEvents.call(this, setValue); queues.batch.stop(); } // this is a side effect, it didn't take a value // so use the original set value else if (setter.length === 0) { setEvents.call(this, value); queues.batch.stop(); return; } // it took a value else if (setter.length === 1) { // if we don't have a getter, we should probably be setting the // value to undefined setEvents.call(this, undefined); queues.batch.stop(); } // we are expecting something else { //!steal-remove-start if(process.env.NODE_ENV !== 'production') { asyncTimer = setTimeout(function() { canLogDev.warn('can/map/setter.js: Setter "' + canReflect.getName(self)+"."+prop + '" did not return a value or call the setter callback.'); }, canLogDev.warnTimeout); } //!steal-remove-end queues.batch.stop(); return; } } } }; }, type: function(prop, type, set) { function setter(newValue) { return set.call(this, type.call(this, newValue, prop)); } if(isDefineType(type)) { // TODO: remove this `canDefineType` check in a future release. if(type.canDefineType) { return setter; } else { return function setter(newValue){ return set.call(this, canReflect.convert(newValue, type)); }; } } // If type is a nested object: `type: {foo: "string", bar: "number"}` if (typeof type === "object") { return make.set.Type(prop, type, set); } else { return setter; } }, Type: function(prop, Type, set) { // `type`: {foo: "string"} if(Array.isArray(Type) && define.DefineList) { Type = define.DefineList.extend({ "#": Type[0] }); } else if (typeof Type === "object") { if(define.DefineMap) { Type = define.DefineMap.extend(Type); } else { Type = define.Constructor(Type); } } return function(newValue) { if (newValue instanceof Type || newValue == null) { return set.call(this, newValue); } else { return set.call(this, new Type(newValue)); } }; } }, // Helpes that indicate what the event type should be. These probably aren't needed. eventType: { data: function(prop) { return function(newVal, oldVal) { return oldVal !== undefined || this._data.hasOwnProperty(prop) ? "set" : "add"; }; }, computed: function() { return function() { return "set"; }; } }, // Helpers that read the data in a non-observable way. read: { data: function(prop) { return function() { return this._data[prop]; }; }, computed: function(prop) { // might want to protect this return function() { return canReflect.getValue( this._computed[prop].compute ); }; }, lastSet: function(prop) { return function() { var observable = this._computed[prop].compute; if(observable.lastSetValue) { return canReflect.getValue(observable.lastSetValue); } }; } }, // Helpers that read the data in an observable way. get: { // uses the default value defaultValue: function(prop, definition, typeConvert, callSetter) { return function() { var value = definition.default; if (value !== undefined) { if (typeof value === "function") { value = value.call(this); } value = typeConvert.call(this, value); } else { var Default = definition.Default; if (Default) { value = typeConvert.call(this,new Default()); } } if(definition.set) { // TODO: there's almost certainly a faster way of making this happen // But this is maintainable. var VALUE; var sync = true; var setter = make.set.setter(prop, definition.set, function(){}, function(value){ if(sync) { VALUE = value; } else { callSetter.call(this, value); } }, definition.get); setter.call(this,value); sync= false; // VALUE will be undefined if the callback is never called. return VALUE; } return value; }; }, data: function(prop) { return function() { if (!this[inSetupSymbol]) { ObservationRecorder.add(this, prop); } return this._data[prop]; }; }, computed: function(prop) { return function(val) { var compute = this._computed[prop].compute; if (ObservationRecorder.isRecording()) { ObservationRecorder.add(this, prop); if (!canReflect.isBound(compute)) { Observation.temporarilyBind(compute); } } return peek(compute); }; } } }; define.behaviors = ["get", "set", "value", "Value", "type", "Type", "serialize"]; // This cleans up a particular behavior and adds it to the definition var addBehaviorToDefinition = function(definition, behavior, value) { if(behavior === "enumerable") { // treat enumerable like serialize definition.serialize = !!value; } else if(behavior === "type") { var behaviorDef = value; if(typeof behaviorDef === "string") { behaviorDef = define.types[behaviorDef]; if(typeof behaviorDef === "object" && !isDefineType(behaviorDef)) { assign(definition, behaviorDef); behaviorDef = behaviorDef[behavior]; } } if (typeof behaviorDef !== 'undefined') { definition[behavior] = behaviorDef; } } else { definition[behavior] = value; } }; // This is called by `define.property` AND `getDefinitionOrMethod` (which is called by `define`) // Currently, this is adding default behavior // copying `type` over, and even cleaning up the final definition object makeDefinition = function(prop, def, defaultDefinition, typePrototype) { var definition = {}; canReflect.eachKey(def, function(value, behavior) { addBehaviorToDefinition(definition, behavior, value); }); // only add default if it doesn't exist canReflect.eachKey(defaultDefinition, function(value, prop){ if(definition[prop] === undefined) { if(prop !== "type" && prop !== "Type") { definition[prop] = value; } } }); // normalize Type that implements can.new if(def.Type) { var value = def.Type; var serialize = value[serializeSymbol]; if(serialize) { definition.serialize = function(val){ return serialize.call(val); }; } if(value[newSymbol]) { definition.type = value; delete definition.Type; } } // We only want to add a defaultDefinition if def.type is not a string // if def.type is a string it is handled in addDefinition if(typeof def.type !== 'string') { // if there's no type definition, take it from the defaultDefinition if(!definition.type && !definition.Type) { var defaultsCopy = canReflect.assignMap({},defaultDefinition); definition = canReflect.assignMap(defaultsCopy, definition); } if( canReflect.size(definition) === 0 ) { definition.type = define.types["*"]; } } cleanUpDefinition(prop, definition, true, typePrototype); return definition; }; // called by `can.defineInstanceKey` and `getDefinitionsAndMethods` // returns the value or the definition object. // calls makeDefinition // This is dealing with a string value getDefinitionOrMethod = function(prop, value, defaultDefinition, typePrototype){ // Clean up the value to make it a definition-like object var definition; if(typeof value === "string") { definition = {type: value}; } // copies a `Type`'s methods over else if(value && (value[serializeSymbol] || value[newSymbol]) ) { definition = { Type: value }; } else if(typeof value === "function") { if(canReflect.isConstructorLike(value)) { definition = {Type: value}; } // or leaves as a function } else if( Array.isArray(value) ) { definition = {Type: value}; } else if( canReflect.isPlainObject(value) ){ definition = value; } if(definition) { return makeDefinition(prop, definition, defaultDefinition, typePrototype); } else { return value; } }; // called by can.define getDefinitionsAndMethods = function(defines, baseDefines, typePrototype) { // make it so the definitions include base definitions on the proto var definitions = Object.create(baseDefines ? baseDefines.definitions : null); var methods = {}; // first lets get a default if it exists var defaults = defines["*"], defaultDefinition; if(defaults) { delete defines["*"]; defaultDefinition = getDefinitionOrMethod("*", defaults, {}); } else { defaultDefinition = Object.create(null); } eachPropertyDescriptor(defines, function( prop, propertyDescriptor ) { var value; if(propertyDescriptor.get || propertyDescriptor.set) { value = {get: propertyDescriptor.get, set: propertyDescriptor.set}; } else { value = propertyDescriptor.value; } if(prop === "constructor") { methods[prop] = value; return; } else { var result = getDefinitionOrMethod(prop, value, defaultDefinition, typePrototype); if(result && typeof result === "object" && canReflect.size(result) > 0) { definitions[prop] = result; } else { // Removed adding raw values that are not functions if (typeof result === 'function') { methods[prop] = result; } //!steal-remove-start else if (typeof result !== 'undefined') { if(process.env.NODE_ENV !== 'production') { // Ex: {prop: 0} canLogDev.error(canReflect.getName(typePrototype)+"."+prop + " does not match a supported propDefinition. See: https://canjs.com/doc/can-define.types.propDefinition.html"); } } //!steal-remove-end } } }); if(defaults) { // we should move this property off the prototype. defineConfigurableAndNotEnumerable(defines,"*", defaults); } return {definitions: definitions, methods: methods, defaultDefinition: defaultDefinition}; }; eventsProto = eventQueue({}); function setupComputed(instance, eventName) { var computedBinding = instance._computed && instance._computed[eventName]; if (computedBinding && computedBinding.compute) { if (!computedBinding.count) { computedBinding.count = 1; canReflect.onValue(computedBinding.compute, computedBinding.handler, "notify"); computedBinding.oldValue = peek(computedBinding.compute); } else { computedBinding.count++; } } } function teardownComputed(instance, eventName){ var computedBinding = instance._computed && instance._computed[eventName]; if (computedBinding) { if (computedBinding.count === 1) { computedBinding.count = 0; canReflect.offValue(computedBinding.compute, computedBinding.handler,"notify"); } else { computedBinding.count--; } } } var canMetaSymbol = canSymbol.for("can.meta"); assign(eventsProto, { _eventSetup: function() {}, _eventTeardown: function() {}, addEventListener: function(eventName, handler, queue) { setupComputed(this, eventName); return eventQueue.addEventListener.apply(this, arguments); }, // ### unbind // Stops listening to an event. // If this is the last listener of a computed property, // stop forwarding events of the computed property to this map. removeEventListener: function(eventName, handler) { teardownComputed(this, eventName); return eventQueue.removeEventListener.apply(this, arguments); } }); eventsProto.on = eventsProto.bind = eventsProto.addEventListener; eventsProto.off = eventsProto.unbind = eventsProto.removeEventListener; var onKeyValueSymbol = canSymbol.for("can.onKeyValue"); var offKeyValueSymbol = canSymbol.for("can.offKeyValue"); canReflect.assignSymbols(eventsProto,{ "can.onKeyValue": function(key){ setupComputed(this, key); return eventQueue[onKeyValueSymbol].apply(this, arguments); }, "can.offKeyValue": function(key){ teardownComputed(this, key); return eventQueue[offKeyValueSymbol].apply(this, arguments); } }); delete eventsProto.one; define.setup = function(props, sealed) { Object.defineProperty(this,"constructor", {value: this.constructor, enumerable: false, writable: false}); Object.defineProperty(this,canMetaSymbol, {value: Object.create(null), enumerable: false, writable: false}); /* jshint -W030 */ var definitions = this._define.definitions; var instanceDefinitions = Object.create(null); var map = this; canReflect.eachKey(props, function(value, prop){ if(definitions[prop] !== undefined) { map[prop] = value; } else { define.expando(map, prop, value); } }); if(canReflect.size(instanceDefinitions) > 0) { defineConfigurableAndNotEnumerable(this, "_instanceDefinitions", instanceDefinitions); } // only seal in dev mode for performance reasons. //!steal-remove-start if(process.env.NODE_ENV !== 'production') { this._data; this._computed; if(sealed !== false) { Object.seal(this); } } //!steal-remove-end }; var returnFirstArg = function(arg){ return arg; }; define.expando = function(map, prop, value) { if(define._specialKeys[prop]) { // ignores _data and _computed return true; } // first check if it's already a constructor define var constructorDefines = map._define.definitions; if(constructorDefines && constructorDefines[prop]) { return; } // next if it's already on this instances var instanceDefines = map._instanceDefinitions; if(!instanceDefines) { if(Object.isSealed(map)) { return; } Object.defineProperty(map, "_instanceDefinitions", { configurable: true, enumerable: false, writable: true, value: {} }); instanceDefines = map._instanceDefinitions; } if(!instanceDefines[prop]) { var defaultDefinition = map._define.defaultDefinition || {type: define.types.observable}; define.property(map, prop, defaultDefinition, {},{}); // possibly convert value to List or DefineMap if(defaultDefinition.type) { map._data[prop] = define.make.set.type(prop, defaultDefinition.type, returnFirstArg).call(map, value); } else if (defaultDefinition.Type && canReflect.isConstructorLike(defaultDefinition.Type)) { map._data[prop] = define.make.set.Type(prop, defaultDefinition.Type, returnFirstArg).call(map, value); } else { map._data[prop] = define.types.observable(value); } instanceDefines[prop] = defaultDefinition; if(!map[inSetupSymbol]) { queues.batch.start(); map.dispatch({ action: "can.keys", target: map, type: "can.keys" // TODO: Remove in 6.0 }); if(Object.prototype.hasOwnProperty.call(map._data, prop)) { map.dispatch({ action: "add", target: map, value: map._data[prop], oldValue: undefined, key: prop, type: prop, // TODO: Remove in 6.0 patches: [{type: "add", key: prop, value: map._data[prop]}], },[map._data[prop], undefined]); } else { map.dispatch({ type: "set", target: map, patches: [{type: "add", key: prop, value: map._data[prop]}], },[map._data[prop], undefined]); } queues.batch.stop(); } return true; } }; define.replaceWith = defineLazyValue; define.eventsProto = eventsProto; define.defineConfigurableAndNotEnumerable = defineConfigurableAndNotEnumerable; define.make = make; define.getDefinitionOrMethod = getDefinitionOrMethod; define._specialKeys = {_data: true, _computed: true}; var simpleGetterSetters = {}; define.makeSimpleGetterSetter = function(prop){ if(simpleGetterSetters[prop] === undefined) { var setter = make.set.events(prop, make.get.data(prop), make.set.data(prop), make.eventType.data(prop) ); simpleGetterSetters[prop] = { get: make.get.data(prop), set: function(newVal){ return setter.call(this, define.types.observable(newVal)); }, enumerable: true, configurable: true }; } return simpleGetterSetters[prop]; }; define.Iterator = function(obj){ this.obj = obj; this.definitions = Object.keys(obj._define.definitions); this.instanceDefinitions = obj._instanceDefinitions ? Object.keys(obj._instanceDefinitions) : Object.keys(obj); this.hasGet = typeof obj.get === "function"; }; define.Iterator.prototype.next = function(){ var key; if(this.definitions.length) { key = this.definitions.shift(); // Getters should not be enumerable var def = this.obj._define.definitions[key]; if(def.get) { return this.next(); } } else if(this.instanceDefinitions.length) { key = this.instanceDefinitions.shift(); } else { return { value: undefined, done: true }; } return { value: [ key, this.hasGet ? this.obj.get(key) : this.obj[key] ], done: false }; }; function isObservableValue(obj){ return canReflect.isValueLike(obj) && canReflect.isObservableLike(obj); } define.types = { // To be made into a type ... this is both lazy {time: '123-456'} 'date': MaybeDate, 'number': MaybeNumber, 'boolean': MaybeBoolean, 'observable': function(newVal) { if(Array.isArray(newVal) && define.DefineList) { newVal = new define.DefineList(newVal); } else if(canReflect.isPlainObject(newVal) && define.DefineMap) { newVal = new define.DefineMap(newVal); } return newVal; }, 'stringOrObservable': function(newVal) { if(Array.isArray(newVal)) { return new define.DefaultList(newVal); } else if(canReflect.isPlainObject(newVal)) { return new define.DefaultMap(newVal); } else { return canReflect.convert( newVal, define.types.string); } }, /** * Implements HTML-style boolean logic for attribute strings, where * any string, including "", is truthy. */ 'htmlbool': function(val) { if (val === '') { return true; } return !!stringToAny(val); }, '*': function(val) { return val; }, 'any': function(val) { return val; }, 'string': MaybeString, 'compute': { set: function(newValue, setVal, setErr, oldValue) { if (isObservableValue(newValue) ) { return newValue; } if (isObservableValue(oldValue)) { canReflect.setValue(oldValue,newValue); return oldValue; } return newValue; }, get: function(value) { return isObservableValue(value) ? canReflect.getValue(value) : value; } } }; define.updateSchemaKeys = function(schema, definitions) { for(var prop in definitions) { var definition = definitions[prop]; if(definition.serialize !== false ) { if(definition.Type) { schema.keys[prop] = definition.Type; } else if(definition.type) { schema.keys[prop] = definition.type; } else { schema.keys[prop] = function(val){ return val; }; } // some unknown type if(definitions[prop].identity === true) { schema.identity.push(prop); } } } return schema; };