DoneJS StealJS jQuery++ FuncUnit DocumentJS
4.3.0
5.0.0 3.13.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
      • can-define-backup
      • can-define-stream
      • can-define-stream-kefir
      • can-event-queue
      • can-kefir
      • can-list
      • can-map
      • can-map-define
      • can-observation
      • can-observation-recorder
      • can-observe
      • can-simple-map
      • can-simple-observable
      • can-stream
      • can-stream-kefir
      • can-value
    • Data Modeling
      • can-connect
      • can-connect-feathers
      • can-fixture
      • can-fixture-socket
      • can-ndjson-stream
      • can-set
    • Views
      • can-component
        • static
          • extend
        • prototype
          • ViewModel
          • events
          • helpers
          • leakScope
          • tag
          • view
          • viewModel
        • elements
          • <can-slot>
          • <can-template>
          • <content>
        • lifecycle hooks
          • connectedCallback
        • special events
          • beforeremove
      • can-stache
      • can-stache-bindings
      • can-stache-converters
      • 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
      • react-view-model
      • react-view-model/component
      • steal-stache
    • 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-util
      • can-zone
      • 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-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

ViewModel

  • Edit on GitHub

Provides or describes a constructor function that provides values and methods to the component’s view. The constructor function is initialized with values specified by the component element’s data bindings.

Object

An object that will be passed to DefineMap.extend and used to create a new observable instance accessible by the component’s view.

For example, every time <my-tag> is found, a new DefineMap instance will be created:

import Component from "can-component";

Component.extend( {
    tag: "my-tag",
    ViewModel: {
        message: "string"
    },
    view: "<h1>{{message}}</h1>"
} );

function(properties)

A constructor function (usually defined by DefineMap.extend or Map.extend) that will be used to create a new observable instance accessible by the component’s view.

For example, every time <my-tag> is found, a new instance of MyTagViewModel will be created:

import Component from "can-component";
import DefineMap from "can-define/map/map";

const MyTagViewModel = DefineMap.extend( "MyTagViewModel", {
    message: "string"
} );

Component.extend( {
    tag: "my-tag",
    ViewModel: MyTagViewModel,
    view: "<h1>{{message}}</h1>"
} );

Use can-view-model to read a component’s view model instance.

Parameters

  1. properties {Object}:

    The initial properties that are passed by the data bindings.

    The view bindings on a tag control the properties and values used to instantiate the ViewModel. For example, calling <my-tag> as follows invokes MyTagViewModel as shown in the following example:

    <my-tag/> <!-- new MyTagViewModel({}) -->
    
    <my-tag
        message:from="'Hi There'"/> <!-- new MyTagViewModel({message: "Hi There"}) -->
    

Returns

{Object}:

A new instance of the corresponding constructor function. This instance is added to the top of the can-view-scope the component’s view is rendered with.

Use

can-component’s ViewModel property is used to create an object, typically an instance of a can-define/map/map, that will be used to render the component’s view. This is most easily understood with an example. The following component shows the current page number based off a limit and offset value:

const MyPaginateViewModel = DefineMap.extend( {
    offset: { default: 0 },
    limit: { default: 20 },
    get page() {
        return Math.floor( this.offset / this.limit ) + 1;
    }
} );

Component.extend( {
    tag: "my-paginate",
    ViewModel: MyPaginateViewModel,
    view: "Page {{page}}."
} );

If this component HTML was inserted into the page like:

const renderer = stache( "<my-paginate/>" );
const frag = renderer();
document.body.appendChild( frag );

It would result in:

<my-paginate>Page 1</my-paginate>

This is because the provided ViewModel object is used to create an instance of can-define/map/map like:

const viewModel = new MyPaginateViewModel();

The value property definition makes offset default to 0 and limit default to 20.

Next, the values are passed into viewModel from the data bindings within <my-paginate> (in this case there is none).

And finally, that data is used to render the component’s view and inserted into the element using can-view-scope and can-stache:

const newViewModel = new Scope( viewModel );
const result = stache( "Page {{page}}." )( newViewModel );
element.innerHTML = result;

There is a short-hand for the prototype methods and properties used to extend DefineMap by setting the Component’s ViewModel to an object and using that anonymous type as the view model.

The following does the same as above:

Component.extend( {
    tag: "my-paginate",
    ViewModel: {
        offset: { default: 0 },
        limit: { default: 20 },
        get page() {
            return Math.floor( this.offset / this.limit ) + 1;
        }
    },
    view: "Page {{page}}."
} );

Values passed from attributes

Values can be "passed" into the viewModel instance of a component, similar to passing arguments into a function. Using can-stache-bindings, the following binding types can be setup:

  • toChild:from — Update the component’s viewModel instance when the parent scope value changes.
  • toParent:to — Update the parent scope when the component’s viewModel instance changes.
  • twoWay:bind — Update the parent scope or the component’s viewModel instance when the other changes.

Using can-stache, values are passed into components like this:

<my-paginate offset:from='index' limit:from='size' />

The above creates an offset and limit property on the component that are initialized to whatever index and size are.

The following component requires an offset and limit:

Component.extend( {
    tag: "my-paginate",
    ViewModel: {
        offset: { default: 0 },
        limit: { default: 20 },
        get page() {
            return Math.floor( this.offset / this.limit ) + 1;
        }
    },
    view: "Page {{page}}."
} );

If <my-paginate> is used like:

const renderer = stache( "<my-paginate offset:from='index' limit:from='size' />" );

const pageInfo = new DefineMap( { index: 0, size: 20 } );

document.body.appendChild( renderer( pageInfo ) );

…pageInfo’s index and size are set as the component’s offset and limit attributes. If we were to change the value of pageInfo’s index like:

pageInfo.index = 20;

…the component’s offset value will change and its view will update to:

<my-paginate>Page 2</my-paginate>

Using attribute values

You can also pass a literal string value of the attribute. To do this in can-stache, simply pass a quoted value not wrapped in single brackets, and the viewModel instance property will be initialized to this string value:

<my-tag title:from="'hello'" />

The above will set the title property on the component’s viewModel instance to the string hello.

If the tag’s title attribute is changed, it does not update the viewModel instance property automatically. Instead, you can use can-view-model to get a reference to the viewModel instance and modify it. This can be seen in the following example:

Clicking the Change title button sets a <my-panel> element’s title attribute like:

import canViewModel from "can-view-model";

out.addEventListener( "click", function( ev ) {
    const el = ev.target;
    const parent = canViewModel( el.parentNode );
    if ( el.nodeName === "BUTTON" ) {
        parent.title = "Users";
    }
} );

Calling methods on ViewModel from events within the view

Using html attributes like can-EVENT-METHOD, you can directly call a ViewModel method from a view. For example, we can make <my-paginate> elements include a next button that calls the ViewModel’s next method like:

Component.extend( {
    tag: "my-paginate",
    ViewModel: {
        offset: { default: 0 },
        limit: { default: 20 },
        next: function() {
            this.offset = this.offset + this.limit;
        },
        get page() {
            return Math.floor( this.offset / this.limit ) + 1;
        }
    },
    view: "Page {{page}} <button on:click='next()'>Next</button>"
} );

ViewModel methods get called back with the current context, the element that you are listening to and the event that triggered the callback.

Publishing events on ViewModels

DefineMaps can publish events on themselves. For instance, the following <player-edit> component, dispatches a "close" event when its close method is called:

Component.extend( {
    tag: "player-edit",
    view: document.getElementById( "player-edit-stache" ).innerHTML,
    ViewModel: DefineMap.extend( {
        player: Player,
        close: function() {
            this.dispatch( "close" );
        }
    } )
} );

These can be listened to with on:event bindings like:

<player-edit
    on:close="removeEdit()"
    player:from="editingPlayer" />

The following demo uses this ability to create a close button that hides the player editor:

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

On this page

Get help

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