DoneJS StealJS jQuery ++ FuncUnit DocumentJS
3.0.0
2.3.27

 

  • Github
  • Twitter
  • Chat
  • Forum
  • Components
  • Application Design
  • Setup
  • Constructors
  • The Define Plugin
  • Stache Templates
  • App State and Routing
  • Observables
  • View Models
  • Data Models and Fixtures
  • Loading States
  • Event Handling
  • Web Service Communication
  • Recap
  • Bitovi
    • Bitovi.com
    • Blog
    • Consulting
    • Training
    • Open Source
  • Chat
  • Forum
  • Star
  • Follow @canjs
  • Place My Order Guide
  • /
  • The Define Plugin
  • / On this page
    • The Define Plugin

      page
      • source

      In this Chapter

      • The Define Plugin

      There is no code to download for this chapter


      The define plugin allows you to finely control the the behavior of the attributes on a can.Map. For any property you declare in the define plugin, you can control its:

      • set
      • get
      • type
      • value
      • remove
      • serialization

      Before we get into the properties of the define plugin, however, let’s look at how to set it up. Creating a define is as simple as adding a define property to the instance properties of the can.Map. This property is an object literal. Remember from our conversation on can.Construct that passing in one argument to a can.Construct will set its instance properties. This is important to know should you create a can.Map that has both instance and static properties, and you want to use the define plugin. Below are two examples:

      //can.Map with one argument
      var Person = can.Map.extend({
          define: {
              //define properties go here
              myProperty: {
                 //property attributes
              }
          }
      });
      
      //can.Map with two arguments
      var Person = can.Map.extend(
      {
         //static properties go here
      },
      {
          define: {
              //define properties go here
              myProperty: {
                 //property attributes
              }
          }
      });
      

      set

      A set function defines what happens when a value is set on a can.Map. It is typically used to update other attributes on the can.Map as a side effect, or coerce the set value into specific format.

      The setter function can take two optional arguments:

      • newVal: The type function coerced value the user intends to set on the can.Map
      • setVal: A callback that can set the value of the property asynchronously.

      When using a setter function, the final value of the attribute is determined by the value the setter function returns. If the function returns a value, that value is used as the value of the attribute. If undefined is returned, the behavior depends on the number of arguments the setter declares, as below:

      // If the setter does not specify the newValue argument,
      // the attribute value is set to whatever was passed to attr.
      set: function() { ... }
      
      // If the setter specifies the newValue argument only,
      // the attribute value will be removed
      set: function(newValue) { ... }
      
      // If the setter specifies both newValue and setValue, the value of
      // the property will not be updated until setValue is called
      set: function(newValue, setValue) { ... }
      

      get

      A get function defines what happens when a value is read on a can.Map. It is typically used to provide properties that derive their value from other properties of the map, as below:

      var Person = can.Map.extend({
          define: {
              fullName: {
                  get: function () {
                      return this.attr("first") + " " + this.attr("last");
                  }
              }
          }
      });
      

      get is passed two optional arguments: lastSetValue and resolve.

      lastSetValue is the last value the property was set to. This, among other uses, can be used to update a list in place instead of replacing it. The following keeps taskIds updated with all of tasks' ids:

      var Person = can.Map.extend({
          define: {
              taskIds: {
                  Value: can.List,
                  get: function(initialValue){
                      var ids = this.attr('tasks').map(function(task){
                          return task.attr("id");
                      });
                      return initialValue.replace(ids);
                  }
              }
          }
      });
      

      resolve can asynchronously set the retrived value of a "bound" property. The following makes person update when personId changes. person will be a Person instance retrieved from the server.

      var AppViewModel = can.Map.extend({
        define: {
          person: {
            get: function(lastSetValue, resolve){
              Person.findOne({id: this.attr("personId")})
                  .then(resolve);
            }
          }
        }
      });
      

      Asynchronous getters must be bound to to behave correctly. In most apps, this happens automatically because observables are "bound" by a template. However, when testing, you'll need to remember to bind on that property before reading it. Here's how one might test the previous AppViewModel:

      var appVM = new AppViewModel({
        personId: 5
      });
      
      appVM.bind("person", function(ev, newVal, oldVal){
        // `person` will change from undefined to a
        // Person instance.
        ok(newVal instanceof Person)
      });
      
      // appVM is `undefined` here because
      // `Person.findOne` hasn't returned yet
      appVM.attr("person") //-> undefined
      

      type

      The type property converts a value passed to an attr setter function into a specific value type. The type can be specified as either a type function, or one of the following strings:

      • string - Converts the value to a string.
      • date - Converts the value to a date or null if the date can not be converted.
      • number - Passes the value through parseFloat.
      • boolean - Converts falsey values (such as "" or 0) to false and everything else to true.
      • * - Prevents the default coercion of Objects to can.Maps and Arrays to can.Lists.

      There are two ways to define the type property:

      • Type
      • type

      Type, uppercase, makes sure that the set value is an instanceof the provided constructor function. If not, the constructor function will be called with new and passed the set value as the first argument.

      In contrast, type, lowercase, is called no matter what and expected to return the value that should be set on the map.

      define: {
        items: {
          type: function(newValue){
            return typeof newValue === "string" ?  newValue.split(",") : newValue;
          }
        }
      }
      

      value

      Sets the default value for instances of the can.Map. If the default value should be an object of some type, it should be specified as the return value of a function, so that all instances of the map don't point to the same object. This is because JavaScript passes primitives by value, and all other values (objects, arrays, etc.) by reference.

      define: {
        prop: {
          value: function(){ return []; }
        }
      }
      

      As with type, above, there are two ways to define the value property: Value, or value. Value, uppercase, provides a constructor function, ensuring that a new instance of Value is made for each map instance. If value is not an function, that value will be the default value of the attribute. If value is a function, that function's return value will be used as the default value of the attribute.

      remove

      The remove property is called when an attribute is removed. This is often used to remove other related properties.

      serialization

      The last property we’ll talk about is serialization. The serialize property defines how the attribute will behave when the map is serialized. Managing this property can be useful when serializing complex types like dates, arrays, or objects into strings. You can also control whether or not a given property can be serialized. Returning undefined from a serialization function for any property means this property will not be part of the serialized object. Managing serialization is an important consideration in routing. We’ll see how this works when we discuss routing in a later chapter.

      define: {
        locationIds: {
          serialize: false
        }
      }
      

      ‹ Observables Stache Templates ›

      CanJS is part of DoneJS. Created and maintained by the core DoneJS team and Bitovi. Currently 3.0.0.