DoneJS StealJS jQuery++ FuncUnit DocumentJS
5.33.3
6.0.0 4.3.0 3.14.1 2.3.35
  • About
  • Guides
  • API Docs
  • Community
  • Contributing
  • Bitovi
    • Bitovi.com
    • Blog
    • Design
    • Development
    • Training
    • Open Source
    • About
    • Contact Us
  • About
  • Guides
  • API Docs
    • Observables
      • can-bind
      • can-compute
      • can-debug
      • can-define
      • can-define/list/list
      • can-define/map/map
        • events
          • can.keys
          • propertyName
        • static
          • extend
          • seal
        • prototype
          • assign
          • assignDeep
          • deleteKey
          • forEach
          • get
          • serialize
          • set
          • update
          • updateDeep
          • *
      • can-define-backup
      • can-define-stream
      • can-define-stream-kefir
      • can-event-queue
      • can-kefir
      • can-list
      • can-map
      • can-map-compat
      • can-map-define
      • can-observable-array
      • can-observable-object
      • can-observation
      • can-observation-recorder
      • can-observe
      • can-simple-map
      • can-simple-observable
      • can-stream
      • can-stream-kefir
      • can-value
    • Views
      • can-attribute-observable
      • can-component
      • can-stache
      • can-stache-bindings
      • can-stache-converters
      • can-stache-element
      • can-stache-route-helpers
      • can-view-autorender
      • can-view-callbacks
      • can-view-import
      • can-view-live
      • can-view-model
      • can-view-nodelist
      • can-view-parser
      • can-view-scope
      • can-view-target
      • steal-stache
    • Data Modeling
      • can-connect
      • can-connect-feathers
      • can-connect-ndjson
      • can-connect-tag
      • can-fixture
      • can-fixture-socket
      • can-local-store
      • can-memory-store
      • can-ndjson-stream
      • can-query-logic
      • can-realtime-rest-model
      • can-rest-model
      • can-set-legacy
      • can-super-model
    • Routing
      • can-deparam
      • can-param
      • can-route
      • can-route-hash
      • can-route-mock
      • can-route-pushstate
    • JS Utilities
      • can-assign
      • can-define-lazy-value
      • can-diff
      • can-globals
      • can-join-uris
      • can-key
      • can-key-tree
      • can-make-map
      • can-parse-uri
      • can-queues
      • can-string
      • can-string-to-any
      • can-zone-storage
    • DOM Utilities
      • can-ajax
      • can-attribute-encoder
      • can-child-nodes
      • can-control
      • can-dom-data
      • can-dom-events
      • can-dom-mutate
      • can-event-dom-enter
      • can-event-dom-radiochange
      • can-fragment
    • Data Validation
      • can-define-validate-validatejs
      • can-type
      • can-validate
      • can-validate-interface
      • can-validate-legacy
      • can-validate-validatejs
    • Typed Data
      • can-cid
      • can-construct
      • can-construct-super
      • can-data-types
      • can-namespace
      • can-reflect
      • can-reflect-dependencies
      • can-reflect-promise
      • can-types
    • Polyfills
      • can-symbol
      • can-vdom
    • Core
    • Infrastructure
      • can-global
      • can-test-helpers
    • Ecosystem
    • Legacy
  • Community
  • Contributing
  • GitHub
  • Twitter
  • Chat
  • Forum
  • News
Bitovi

can-define/map/map

  • Edit on GitHub

Create observable objects.

new DefineMap([props])

The can-define/map/map module exports the DefineMap constructor function.

Calling new DefineMap(props) creates a new instance of DefineMap or an extended DefineMap. Then, new DefineMap(props) assigns every property on props to the new instance. If props are passed that are not defined already, those property definitions are created. If the instance should be sealed, it is sealed.

import {DefineMap} from "can";

const person = new DefineMap( {
      first: "Justin",
      last: "Meyer"
} );

console.log( person.serialize() ); //-> {first: "Justin", last: "Meyer"}

Custom DefineMap types, with special properties and behaviors, can be defined with extend.

Parameters

  1. props {Object}:

    Properties and values to seed the map with.

Returns

{can-define/map/map}:

An instance of DefineMap with the properties from props.

Mixed-in instance methods and properties

Instances of DefineMap have all methods and properties from can-event-queue/map/map:

  • addEventListener - Register an event handler to be called when an event is dispatched.

  • @can.getWhatIChange - Return observables whose values are affected by attached event handlers

  • @can.isBound - Return if the observable is bound to.

  • @can.offKeyValue - Unregister an event handler to be called when an event is dispatched.

  • @can.onKeyValue - Register an event handler to be called when a key value changes.

  • dispatch - Dispatch event and key binding handlers.

  • listenTo - Listen to an event and register the binding for simplified unbinding.

  • off - A shorthand method for unbinding an event.

  • on - A shorthand method for listening to event.

  • one - Register an event handler that gets called only once.

  • removeEventListener - Unregister an event handler to be called when an event is dispatched.

  • stopListening - Stops listening for registered event handlers.

Example:

import {DefineMap} from "can";

const MyType = DefineMap.extend( {prop: "string"} );

const myInstance = new MyType( {prop: "VALUE"} );

myInstance.on( "prop", ( event, newVal, oldVal ) => {
    console.log( newVal ); //-> "VALUE"
    console.log( oldVal ); //-> "NEW VALUE"
} );

myInstance.prop = "NEW VALUE";

Mixed-in type methods and properties

Extended DefineMap constructor functions have all methods and properties from can-event-queue/type/type:

  • @can.offInstanceBoundChange - Stop listening to when an instance's bound status changes.

  • @can.offInstancePatches - Stop listening to patch changes on any instance.

  • @can.onInstanceBoundChange - Listen to when any instance is bound for the first time or all handlers are removed.

  • @can.onInstancePatches - Listen to patch changes on any isntance.

Example:

import {DefineMap, Reflect as canReflect} from "can";
const MyType = DefineMap.extend( {
  prop: "string",
} );

canReflect.onInstancePatches( MyType, ( instance, patches ) => {
  console.log(patches) //-> {key:"prop", type:"set", value:"VALUE"}
} );

var instance = new MyType({prop: "value"});
instance.prop = "VALUE";

Use

can-define/map/map is used to create easily extensible observable types with well defined behavior.

For example, a Todo type, with a name property, completed property, and a toggle method, might be defined like:

import {DefineMap} from "can";

const Todo = DefineMap.extend( {
    name: "string",
    completed: { type: "boolean", default: false },
    toggle: function() {
        this.completed = !this.completed;
    }
} );

const myTodo = new Todo({name: "my first todo!"});
myTodo.toggle();
console.log( myTodo.serialize() ); //-> {name: "my first todo!", completed: true}

The Object passed to .extend defines the properties and methods that will be on instances of a Todo. There are a lot of ways to define properties. The PropDefinition type lists them all. Here, we define:

  • name as a property that will be type coerced into a String.
  • completed as a property that will be type coerced into a Boolean with an initial value of false.

This also defines a toggle method that will be available on instances of Todo.

Todo is a constructor function. This means instances of Todo can be be created by calling new Todo() as follows:

import {DefineMap} from "can";

const Todo = DefineMap.extend( {
    name: "string",
    completed: { type: "boolean", default: false },
    toggle: function() {
        this.completed = !this.completed;
    }
} );

const myTodo = new Todo();
myTodo.name = "Do the dishes";
console.log( myTodo.completed ); //-> false

myTodo.toggle();
console.log( myTodo.completed ); //-> true

You can also pass initial properties and their values when initializing a DefineMap:

import {Todo} from "//unpkg.com/can-demo-models@5";

const anotherTodo = new Todo( { name: "Mow lawn", completed: true } );

console.log( anotherTodo.name ); //-> "Mow lawn"

Declarative properties

Arguably can-define's most important ability is its support of declarative properties that functionally derive their value from other property values. This is done by defining getter properties like fullName as follows:

import {DefineMap} from "can";

const Person = DefineMap.extend( {
    first: "string",
    last: "string",
    fullName: {
        get: function() {
            return this.first + " " + this.last;
        }
    }
} );

const person = new Person({
    first: "Justin",
    last: "Meyer"
});

console.log(person.fullName); //-> "Justin Meyer"

fullName can also be defined with the ES5 shorthand getter syntax:

import {DefineMap} from "can";

const Person = DefineMap.extend( {
    first: "string",
    last: "string",
    get fullName() {
        return this.first + " " + this.last;
    }
} );

const person = new Person({
    first: "Justin",
    last: "Meyer"
});

console.log(person.fullName); //-> "Justin Meyer"

Now, when a person is created, there is a fullName property available like:

import {Person} from "//unpkg.com/can-demo-models@5";

const me = new Person( { first: "Harry", last: "Potter" } );
console.log( me.fullName ); //-> "Harry Potter"

This property can be bound to like any other property:

import {Person} from "//unpkg.com/can-demo-models@5";

const me = new Person({first: "Harry", last: "Potter"});

me.on( "fullName", ( ev, newValue, oldValue ) => {
    console.log( newValue ); //-> Harry Henderson
    console.log( oldValue ); //-> Harry Potter
} );

me.last = "Henderson";

getter properties use can-observation internally. This means that when bound, the value of the getter is cached and only updates when one of its source observables change. For example:

import {DefineMap} from "can";

const Person = DefineMap.extend( {
    first: "string",
    last: "string",
    get fullName() {
        console.log( "calculating fullName" );
        return this.first + " " + this.last;
    }
} );

const hero = new Person( { first: "Wonder", last: "Woman" } );

console.log( hero.fullName ); // logs Wonder Woman

console.log( hero.fullName ); // logs Wonder Woman

hero.on( "fullName", () => {} );

console.log( hero.fullName ); // logs "Wonder Woman"

hero.first = "Bionic";        // logs "calculating fullName"

hero.last = "Man";            // logs "calculating fullName"

console.log( hero.fullName ); // logs "Bionic Man"

If you want to prevent repeat updates, use batch.start:

import {queues} from "//unpkg.com/can@5/core.mjs"
import {Person} from "//unpkg.com/can-demo-models@5";

// Extending person to log repeat updates.
const CustomPerson = Person.extend( {
  get fullName() {
    console.log( "calculating fullName" );
    return this.first + " " + this.last;
  }
} );

const hero = new CustomPerson();

hero.on( "fullName", () => {} );

hero.first = "Bionic"; // logs "calculating fullName"

hero.last = "Man";     // logs "calculating fullName"

console.log( hero.fullName ); // logs "calculating fullName"
                              //-> "Bionic Man"

queues.batch.start();
hero.first = "Silk";
hero.last = "Spectre";
queues.batch.stop();          // logs "calculating fullName"

Asynchronous getters

getters can also be asynchronous. These are very useful when you have a type that requires data from the server. This is very common in can-component view-models. For example, a ViewModel might take a todoId value, and want to make a todo property available:

import {DefineMap, ajax} from "can";

const TodoViewModel = DefineMap.extend( {
    todoId: "number",
    todo: {
        get: function( lastSetValue, resolve ) {
            ajax( { url: "/todos/" + this.todoId } ).then( resolve );
        }
    }
} );

Asynchronous getters only are passed a resolve argument when bound. Typically in an application, your template will automatically bind on the todo property. But to use it in a test might look like:

import {DefineMap, ajax, fixture} from "can";

const TodoViewModel = DefineMap.extend( {
    todoId: "number",
    todo: {
        get: function( lastSetValue, resolve ) {
            ajax( { url: "/todos/" + this.todoId } ).then( resolve );
        }
    }
} );

fixture( "GET /todos/5", () => {
    return { id: 5, name: "take out trash" };
} );

const todoVM = new TodoViewModel( { todoId: 5 } );

todoVM.on( "todo", function( ev, newVal ) {

    console.log( newVal.name ) //-> "take out trash"
} );

console.log(todoVM.todo) //-> undefined

Getter limitations

There's some functionality that a getter or an async getter can not describe declaratively. For these situations, you can use set or even better, use value or the can-define-stream plugin.

For example, consider a state and city locator where you pick a United States state like Illinois and then a city like Chicago. In this example, we want to clear the choice of city whenever the state changes.

This can be implemented with set like:

import {DefineMap} from "can";

const Locator = DefineMap.extend( {
    state: {
        type: "string",
        set: function() {
            this.city = null;
        }
    },
    city: "string"
} );

const locator = new Locator( {
    state: "IL",
    city: "Chicago"
} );

locator.state = "CA";
console.log( locator.city ); //-> null;

The problem with this code is that it relies on side effects to manage the behavior of city. If someone wants to understand how city behaves, they might have search the entire map's code.

The value behavior and can-define-stream-kefir plugin allow you to consolidate the behavior of a property to a single place. For example, the following implements Locator with value:

import {DefineMap} from "can";

const Locator = DefineMap.extend( "Locator", {
    state: "string",
    city: {
        value: ( prop ) => {

            // When city is set, update `city` with the set value.
            prop.listenTo( prop.lastSet, prop.resolve );

            // When state is set, set `city` to null.
            prop.listenTo( "state", function() {
                prop.resolve( null );
            } );

            // Initialize the value to the `set` value.
            prop.resolve( prop.lastSet.get() );
        }
    }
} );

const locator = new Locator( {
    state: "IL",
    city: "Chicago",
} );

locator.state = "CA";
console.log( locator.city ); //-> null

While functional reactive programming (FRP) can take time to master at first, once you do, your code will be much easier to understand and debug. The value behavior supports the basics of FRP programming - the ability to listen events and changes in other properties and resolve the property to a new value. If you are looking for even more FRP capability, checkout can-define-stream-kefir, which supports a full streaming library with many event-stream transformations:

import {DefineMap} from "can";

const Locator = DefineMap.extend( {
    state: "string",
    city: {
        stream: function( setStream ) {
            return this.stream( ".state" )
                .map( () => null )
                .merge( setStream );
        }
    }
} );

Notice, in the can-define-stream example, city must be bound for it to work.

Sealed instances and strict mode

By default, DefineMap instances are sealed. This means that setting properties that are not defined when the constructor is defined will throw an error in files that are in strict mode. For example:

"use strict";
import DefineMap from "can";

const MyType = DefineMap.extend( {
    myProp: "string"
} );

const myType = new MyType();

myType.myProp = "value"; // no error thrown
myType.otherProp = "value"; // throws Error!

Read the seal documentation for more information on this behavior.

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

On this page

Get help

  • Chat with us
  • File an issue
  • Ask questions
  • Read latest news