Observable properties
JavaScript HTML

README.md

Build Status

can-define

Add observable properties, type conversion, and getter/setter logic to your constructor prototypes.

API

can-define function

Exports the can.define method that defines observable properties and their behavior.

can.define(prototype, propDefinitions)

Define observable properties, type conversion, and getter/setter logic to your constructor prototypes.

var Person = function(first, last){
  this.first = first;
  this.last = last;
};
can.define(Person.prototype,{
  first: {
    type: "string"
  },
  last: {
    type: "string"
  },
  fullName: {
    get: function(){
      return this.first+" "+this.last;
    }
  }
});
  1. prototype {Object}: The prototype object of a constructor function.

  2. propDefinitions {Object<String,propDefinition>}: An object of properties and their definitions.

propDefinition {Object}

Defines the type, initial value, and get, set, and serialize behavior for an observable property.

Object
  • value {function|*}: Specifies the initial value of the property or a function that returns the initial value.

    // A default age of `0`:
    var Person = DefineMap.extend({
      age: {value: 0}    
    });
    
    new Person().age //-> 0

    Object types should not be specified directly on value because that same object will be shared on every instance of the Map. Instead, a function that returns a fresh copy should be provided:

    // A default address object:
    var Person = DefineMap.extend({
      address: {
        value: function(){
          return {city: "Chicago", state: "IL"};
        };
      }    
    });
    
    new Person().address //-> {city: "Chicago", state: "IL"};
  • Value {function}: Specifies a function that will be called with new whose result is set as the initial value of the attribute.

    // A default empty DefineList of hobbies:
    var Person = DefineMap.extend({
      hobbies: {Value: DefineList}
    });
    
    new Person().hobbies //-> []
  • type {function|String}: Specifies the type of the property. The type can be specified as either a function that returns the type coerced value or one of the types names.

    var Person = DefineMap.extend({
      age: {type: "number"},
      hobbies: {
        type: function(newValue){
          if(typeof newValue === "string") {
            return newValue.split(",")
          } else if( Array.isArray(newValue) ) {
            return newValue;
          }
        }
      }
    });
    
    var me = new Person({age: "33", hobbies: "bball,js"})
    me.age //-> 33
    me.hobbies //-> ["bball","js"]
  • Type {function}: A constructor function that takes the value passed to [can.Map::attr attr] as the first argument and called with new. For example, if you want whatever gets passed to go through new Array(newValue) you can do that like:

    define: {
      items: {
        Type: Array
      }
    }

    If the value passed to [can.Map::attr attr] is already an Array, it will be left as is.

  • set {can.Map.prototype.define.set}: A set function that specifies what should happen when an attribute is set on a [can.Map]. set is called with the result of type or Type. The following defines a page setter that updates the map's offset:

    define: {
      page: {
        set: function(newVal){
          this.attr('offset', (parseInt(newVal) - 1) *
                               this.attr('limit'));
        }
      }
    }
  • get {get(lastSetValue)}: A function that specifies how the value is retrieved. The get function is converted to an [can.compute.async async compute]. It should derive its value from other values on the map. The following defines a page getter that reads from a map's offset and limit:

    define: {
      page: {
        get: function (newVal) {
        return Math.floor(this.attr('offset') /
                          this.attr('limit')) + 1;
      }
      }
    }

    A get definition makes the property computed which means it will not be serialized by default.

  • serialize {can.Map.prototype.define.serialize|Boolean}: Specifies the behavior of the property when [can.Map::serialize serialize] is called.

    By default, serialize does not include computed values. Properties with a get definition are computed and therefore are not added to the result. Non-computed properties values are serialized if possible and added to the result.

    Paginate = can.Map.extend({
      define: {
        pageNum: {
          get: function(){ return this.offset() / 20 }
        }
      }
    });
    
    p = new Paginate({offset: 40});
    p.serialize() //-> {offset: 40}

    If true is specified, computed properties will be serialized and added to the result.

    Paginate = can.Map.extend({
      define: {
        pageNum: {
          get: function(){ return this.offset() / 20 },
          serialize: true
        }
      }
    });
    
    p = new Paginate({offset: 40});
    p.serialize() //-> {offset: 40, pageNum: 2}

    If false is specified, non-computed properties will not be added to the result.

    Paginate = can.Map.extend({
      define: {
        offset: {
          serialize: false
        }
      }
    });
    
    p = new Paginate({offset: 40});
    p.serialize() //-> {}

    If a [can.Map.prototype.define.serialize serialize function] is specified, the result of the function is added to the result.

    Paginate = can.Map.extend({
      define: {
        offset: {
          serialize: function(offset){
            return (offset / 20)+1
          }
        }
      }
    });
    
    p = new Paginate({offset: 40});
    p.serialize() //-> {offset: 3}

types {Object}

Defines the type, initial value, and get, set, and serialize behavior for an observable property. All type converters leave null and undefined as is except for the "boolean" type converter.

Object
  • observable {function}: The default type behavior. It converts plain Objects to DefineMaps and plain Arrays to DefineLists. Everything else is left as is.
  • any {function}: Leaves the set value as is, performs no type conversion. Aliased as *.
  • string {function}: Converts to a string with ""+val.
  • date {function}: Converts to a JavaScript date using Date.parse(val) if a string is given or new Date(val) if a number is passed.
  • number {function}: Converts to a number with +(val).
  • boolean {function}: Converts to false if val is falsey, "0", or "false"; otherwise, converts to true.
  • htmlbool {function}: Like boolean, but converts to true if empty string ("") is passed.
  • compute {function}: Allows computes to be passed and the property take on the value of the compute.
  • stringOrObservable {function}: Converts plain Objects to DefineMaps, plain Arrays to DefineLists and everything else to strings. This is useful for routing.

can-define/map/map function

Create observable objects.

new can.DefineMap([props])

Creates a new instance of DefineMap or an extended DefineMap.

var person = new can.DefineMap({
  first: "Justin",
  last: "Meyer"
})
  1. props {Object}: Properties and values to seed the map with.

  • returns {can-define/map/map}: An instance of can.DefineMap with the properties from props.

can.DefineMap.extend([name,] [static,] prototype)

Extends can.DefineMap, or constructor functions derived from can.DefineMap, to create a new constructor function.

var Person = can.DefineMap.extend(
  "Person",
  {seal: true},
  {
    first: "string",
    last: {type: "string"},
    fullName: {
      get: function(){
        return this.first+" "+this.last;
      }
    },
    age: {value: 0},
  });

var me = new Person({first: "Justin", last: "Meyer"})
me.fullName //-> "Justin Meyer"
me.age      //-> 0
  1. name {String}: Provides an optional name for this type that will show up nicely in debuggers.

  2. static {Object}: Static properties that are set directly on the constructor function.

  3. prototype {Object<String,function|String|types|propDefinition>}: A definition of the properties or methods on this type.

    If the property definition is a plain function, it's considered a method.

    var Person = DefineMap.extend({
      sayHi: function(){ console.log("hi"); }
    });
    
    var me = new Person();
    me.sayHi();

    If the property definition is a string, it's considered a type setting to be looked up in can.define.types.

    var Person = DefineMap.extend({
      age: 'number',
      isCool: 'boolean',
      hobbies: 'observable'
    });
    
    var me = new Person({age: '33', isCool: 'false', hobbies: ['js','bball']});
    me.age    //-> 33
    me.isCool //-> false
    me.hobbies instanceof DefineList //-> true

    If the property definition is a Constructor function, it's considered a Type setting.

    var Address = DefineMap.extend({
      zip: 'number'
    });
    var Person = DefineMap.extend({
      address: Address
    });
    
    var me = new Person({address: {zip: '60048'}});
    me.address.zip //-> 60048

    If the property is an object, it's considered to be a propDefinition.

    var Person = DefineMap.extend({
      fullName: {
        get: function() {
          return this.first+" "+this.last;
        },
        set: function(newVal) {
          var parts = newVal.split(" ");
          this.first = parts[0];
          this.last = parts[1];
        }
      },
      // slick way of creating an 'inline' type.
      address: {
        Type: {
          zip: "number"
        }
      }
    });
    
    var me = new Person({fullName: "Rami Myer", address: {zip: '60048'}});
    me.first       //-> "Rami"
    me.address.zip //-> 60048

seal {Boolean}

Defines if instances of the map should be sealed in development.

Boolean

If true, in development, instances of this object will be sealed. In strict mode errors will be thrown when undefined properties are set. This is the default behavior of extended DefineMaps:

"use strict";
var Person = can.DefineMap.extend({});
var me = new Person();
me.age = 33 //-> throws "TypeError: Can't add property age, object is not extensible"

If false, the object will not be sealed. This is the default behavior of unextended DefineMaps. Use get and set to get and set values:

var person = new can.DefineMap();
person.set("first","Justin");
person.set("last","Meyer");

person.get("first") //-> "Justin"
person.get("last") //-> "Meyer"

Set seal to false on objects that have an indeterminate number of properties:

var Style = can.DefineMap.extend({
  seal: false
},{
  cssText: {
    get: function(){
      return _.map(this.get(), function(val, prop){
        return prop+": "+val;
      }).join(";")
    }
  }
});
var style = new Style();
style.set("color","green");
style.set("font","awesome");
style.cssText //-> "color:green; font: awesome;"

map.each( callback(item, propName ) )

each iterates through the Map, calling a function for each property value and key.

  1. callback {function(item, propName)}: the function to call for each property The value and key of each property will be passed as the first and second arguments, respectively, to the callback. If the callback returns false, the loop will stop.

  • returns {can.Map}: this Map, for chaining

map.toObject()

map.serialize()

map.get(propName)

map.set(propName, value)

can-define/list/list function

Create observable list.

new can.DefineList([items])

map.item(index, [newVal])

map.items()

list.each( callback(item, index) )

each iterates through the Map, calling a function for each element.

  1. callback {function(*, Number)}: the function to call for each element The value and index of each element will be passed as the first and second arguments, respectively, to the callback. If the callback returns false, the loop will stop.

  • returns {can.DefineList}: this DefineList, for chaining

list.forEach(callback[, thisArg])

  1. callback {function(element, index, list)}: a function to call with each element of the DefineList The three parameters that callback gets passed are element, the element at index, index the current element of the list, and list the DefineList the elements are coming from.
  2. thisArg {Object}: the object to use as this inside the callback

list.splice(index[, howMany[, ...newElements]])

  1. index {Number}: where to start removing or inserting elements

  2. howMany {Number}: the number of elements to remove If howMany is not provided, splice will remove all elements from index to the end of the DefineList.

  3. newElements {*}: elements to insert into the DefineList

  • returns {Array}: the elements removed by splice

list.replace(collection)

  1. collection {Array|can.DefineList|can.Deferred}: the collection of new elements to use If a [can.Deferred] is passed, it must resolve to an Array or can.DefineList. The elements of the list are not actually removed until the Deferred resolves.

list.push(...elements)

push adds elements onto the end of a DefineList.

  1. elements {*}: the elements to add to the DefineList

  • returns {Number}: the new length of the DefineList

list.unshift(...elements)

unshift adds elements onto the beginning of a DefineList.

  1. elements {*}: the elements to add to the DefineList

  • returns {Number}: the new length of the DefineList

list.pop()

pop removes an element from the end of a DefineList.

  • returns {*}: the element just popped off the DefineList, or undefined if the DefineList was empty

list.shift()

shift removes an element from the beginning of a DefineList.

  • returns {*}: the element just shifted off the DefineList, or undefined if the DefineList is empty

list.indexOf(item)

indexOf finds the position of a given item in the DefineList.

  1. item {*}: the item to find

  • returns {Number}: the position of the item in the DefineList, or -1 if the item is not found.

list.join(separator)

join turns a DefineList into a string by inserting separator between the string representations of all the elements of the DefineList.

  1. separator {String}: the string to seperate elements with

  • returns {String}: the joined string

list.reverse()

reverse reverses the elements of the DefineList in place.

  • returns {can.DefineList}: the DefineList, for chaining

list.slice([start[, end]])

slice creates a copy of a portion of the DefineList.

  1. start {Number}: the index to start copying from

  2. end {Number}: the first index not to include in the copy If end is not supplied, slice will copy until the end of the list.

  • returns {can.DefineList}: a new can.DefineList with the extracted elements

list.concat(...args)

  1. args {Array|can.DefineList|*}: Any number of arrays, Lists, or values to add in For each parameter given, if it is an Array or a DefineList, each of its elements will be added to the end of the concatenated DefineList. Otherwise, the parameter itself will be added.