DoneJS StealJS jQuery ++ FuncUnit DocumentJS
3.0.0
2.3.27

 

  • Github
  • Twitter
  • Chat
  • Forum
  • Guides
  • Core
    • can-component
      • static
        • extend
      • prototype
        • ViewModel
        • events
        • helpers
        • leakScope
        • tag
        • view
        • viewModel
      • elements
        • <content>
      • special events
        • beforeremove
    • can-compute
    • can-connect
    • can-define
    • can-define/list/list
    • can-define/map/map
    • can-route
    • can-route-pushstate
    • can-set
    • can-stache
    • can-stache/helpers/route
    • can-stache-bindings
  • Ecosystem
  • Infrastructure
  • Legacy
  • Bitovi
    • Bitovi.com
    • Blog
    • Consulting
    • Training
    • Open Source
  • Chat
  • Forum
  • Star
  • Follow @canjs
  • CanJS
  • /
  • Core
  • /
  • can-component
  • /
  • ViewModel
  • / On this page
    • ViewModel

      property

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

      • source

      function(properties)

      A constructor function usually defined by DefineMap.extend or Map.extend that will be used to create an 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:

      var MyTagViewModel = DefineMap.extend("MyTagViewModel",{
          message: "string"
      });
      
      Component.extend({
          tag: "my-tag",
          ViewModel: MyTagViewModel,
          view: stache("<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}="'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 template. This is most easily understood with an example. The following component shows the current page number based off a limit and offset value:

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

      If this component HTML was inserted into the page like:

      var template = stache("<my-paginate/>");
      var frag = template();
      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:

      var 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 template and inserted into the element using can-view-scope and can-stache:

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

      There is a short-hand for the prototype methods and properties used to extend the default Map type (typically can-define/map/map) 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: {value: 0},
              limit: {value: 20},
              get page() {
                  return Math.floor(this.offset / this.limit) + 1;
              }
          },
          view: stache("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:

      • {to-child} - Update the component's viewModel instance when the parent scope value changes.
      • {^to-parent} - Update the parent scope when the component's viewModel instance changes.
      • {(two-way)} - 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}='index' {limit}='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: {value: 0},
              limit: {value: 20},
              get page() {
                  return Math.floor(this.offset / this.limit) + 1;
              }
          },
          view: stache("Page {{page}}.")
      });
      

      If <my-paginate> is used like:

      var template = stache("<my-paginate {offset}='index' {limit}='size' />");
      
      var pageInfo = new DefineMap({index: 0, size: 20});
      
      document.body.appendChild(template(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 template 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 any value not wrapped in single brackets, and the viewModel instance property will be initialized to this string value:

      <my-tag title="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 updates the viewModel instance property automatically. This can be seen in the following example:

      Clicking the Change title button sets a <panel> element's title attribute like:

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

      Calling methods on ViewModel from events within the template

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

      var ViewModel = DefineMap.extend({
          offset: {value: 0},
          limit: {value: 20},
          next: function(){
              this.offset = this.offset + this.limit;
          },
          get page() {
              return Math.floor(this.offset / this.limit) + 1;
          }
      });
      
      Component.extend({
          tag: "my-paginate",
          ViewModel: ViewModel,
          view: stache("Page {{page}} <button ($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 it's close method is called:

      Component.extend({
          tag: "player-edit",
          view: stache.from('player-edit-stache'),
          ViewModel: DefineMap.extend({
              player: Player,
              close: function(){
                  this.dispatch("close");
              }
          }),
          leakScope: true
      });
      

      These can be listened to with (event) bindings like:

      <player-edit
          (close)="removeEdit()"
          {player}="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 3.0.0.