DoneJS StealJS jQuery++ FuncUnit DocumentJS
3.14.1
5.0.0 4.3.0 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-compute
      • can-define
      • can-define/list/list
      • can-define/map/map
      • can-define-stream
      • can-define-stream-kefir
      • can-event
      • can-event/async/async
      • can-event/batch/batch
      • can-event/lifecycle/lifecycle
      • can-kefir
      • can-list
      • can-map
      • can-map-backup
      • can-map-define
      • can-observation
      • can-observe
      • can-simple-map
      • can-simple-observable
      • can-stream
      • can-stream-kefir
    • Data Modeling
      • can-connect
      • can-connect-cloneable
      • can-connect-feathers
      • can-connect-ndjson
      • can-connect-signalr
      • 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>
        • special events
          • beforeremove
      • can-ejs
      • can-element
      • can-react-component
      • can-stache
      • can-stache/helpers/route
      • can-stache-bindings
      • can-stache-converters
      • can-view-autorender
      • can-view-callbacks
      • can-view-href
      • 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-pushstate
    • JS Utilities
      • can-assign
      • can-define-lazy-value
      • can-globals
      • can-key-tree
      • can-make-map
      • can-parse-uri
      • can-string
      • can-string-to-any
      • can-util
      • can-zone
      • can-zone-storage
    • DOM Utilities
      • can-ajax
      • can-attribute-encoder
      • can-control
      • can-dom-events
      • can-event-dom-enter
      • can-event-dom-radiochange
      • can-jquery
    • 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-namespace
      • can-reflect
      • 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-component

  • npm package badge
  • Star
  • Edit on GitHub

Create a custom element that can be used to manage widgets or application logic.

<TAG BINDINGS...>[TEMPLATES][LIGHT_DOM]</TAG>

Create an instance of a component on a particular tag in a can-stache view. Use the bindings syntaxes to set up bindings.

The following creates a my-autocomplete element and passes the my-autocomplete's ViewModel the Search model as its source property and a <can-template> that is used to render the search results:

<my-autocomplete source:from="Search">
  <can-template name="search-results">
    <li>{{name}}</li>
  </can-template>
</my-autocomplete>

Parameters

  1. TAG {String}:

    An HTML tag name that matches the tag property of the component. Tag names should include a hypen (-) or a colon (:) like: acme-tabs or acme:tabs.

  2. BINDINGS {can-stache-bindings}:

    Use the following binding syntaxes to connect the component’s ViewModel to the view's scope:

    • toChild:from=expression — one-way data binding to child
    • toParent:to=expression — one-way data binding to parent
    • twoWay:bind=expression — two-way data binding child to parent
    • on:event=expression — event binding on the view model

    Note that because DOM attribute names are case-insensitive, use hypens (-) in the attribute name to setup for camelCase properties.

    Example:

    <my-tag getChild:from="expression"
            setParent:to="expression"
            twoWay:bind="expression"
            on:event="callExpression()"></my-tag>
    
  3. TEMPLATES {sectionRenderer(context, helpers)}:

    Between the starting and ending tag can exist one or many <can-template> elements. Use <can-template> elements to pass custom templates to child components. Each <can-template> is given a name attribute and can be rendered by a corresponding <can-slot> in the component's view.

    For example, the following passes how each search result should look and an error message if the source is unable to request data:

    <my-autocomplete source:from="Search">
      <can-template name="search-results">
        <li>{{name}}</li>
      </can-template>
      <can-template name="search-error">
        <div class='error'>{{message}}</div>
      </can-template>
    </my-autocomplete>
    
  4. LIGHT_DOM {sectionRenderer(context, helpers)}:

    The content between the starting and ending tag. For example, Hello <b>World</b> is the LIGHT_DOM in the following:

    <my-tag>Hello <b>World</b></my-tag>
    

    The LIGHT_DOM can be positioned with a component’s view with the element. The data accessible to the LIGHT_DOM can be controlled with leakScope.

Use

To create a Component, you must first extend Component with the methods and properties of how your component behaves:

var Component = require("can-component");
var DefineMap = require("can-define/map/map");
var stache = require("can-stache");

var HelloWorldVM = DefineMap.extend({
    visible: {value: false},
    message: {value: "Hello There!"}
});

Component.extend({
  tag: "hello-world",
  view: stache("{{#if visible}}{{message}}{{else}}Click me{{/if}}"),
  ViewModel: HelloWorldVM,
  events: {
    click: function(){
        this.viewModel.visible = !this.viewModel.visible;
    }
  }
});

This element says “Click me” until a user clicks it and then says “Hello There!”. To create an instance of this component on the page, add <hello-world/> to a can-stache view, render the view, and insert the result in the page like:

var renderer = stache("<hello-world/>");
document.body.appendChild(renderer({ }));

Check this out here:

Typically, you do not append a single component at a time. Instead, you'll render a view with many custom tags like:

<srchr-app>
  <srchr-search models:from="models">
    <input name="search"/>
  </srchr-search>
  <ui-panel>
    <srchr-history/>
    <srchr-results models:from="models"/>
  </ui-panel>
</srchr-app>

Defining a Component

Use extend to define a Component constructor function that automatically gets initialized whenever the component’s tag is found.

Note that inheriting from components works differently than other CanJS APIs. You can’t call .extend on a particular component to create a “subclass” of that component.

Instead, components work more like HTML elements. To reuse functionality from a base component, build on top of it with parent components that wrap other components in their view and pass any needed viewModel properties via attributes.

Tag

A component’s tag is the element node name that the component will be created on.

The following matches <hello-world> elements.

Component.extend({
  tag: "hello-world"
});

View

A component’s view is a template that is rendered as the element’s innerHTML.

The following component:

Component.extend({
  tag: "hello-world",
  view: stache("<h1>Hello World</h1>")
});

Changes <hello-world/> elements into:

<hello-world><h1>Hello World</h1></hello-world>

Use the tag to position the custom element’s source HTML.

The following component:

Component.extend({
  tag: "hello-world",
  view: stache("<h1><content/></h1>")
});

Changes <hello-world>Hi There</hello-world> into:

<hello-world><h1>Hi There</h1></hello-world>

ViewModel

A component’s ViewModel defines a constructor that creates instances used to render the component’s view. The instance’s properties are typically set by attribute data bindings on the custom element. By default, every data binding’s value is looked up in the parent can-view-scope of the custom element and added to the viewModel object.

The following component:

Component.extend({
  tag: "hello-world",
  view: stache("<h1>{{message}}</h1>")
});

Changes the following rendered view:

var renderer = stache("<hello-world message:from='greeting'/>");
renderer({
  greeting: "Salutations"
});

Into:

<hello-world><h1>Salutations</h1></hello-world>

Default values can be provided. The following component:

Component.extend({
  tag: "hello-world",
  view: stache("<h1>{{message}}</h1>"),
  viewModel: {
    message: "Hi"
  }
});

Changes the following rendered view:

var renderer = stache("<hello-world/>");
renderer({});

Into:

<hello-world><h1>Hi</h1></hello-world>

If you want to set the string value of the attribute on the ViewModel, set an attribute without any binding syntax.

The following view, with the previous hello-world component:

var renderer = stache("<hello-world message='Howdy'/>");
renderer({});

Renders:

<hello-world><h1>Howdy</h1></hello-world>

Events

A component’s events object is used to listen to events (that are not listened to with view bindings). The following component adds “!” to the message every time <hello-world> is clicked:

Component.extend({
  tag: "hello-world",
  view: stache("<h1>{{message}}</h1>"),
  events: {
    "click" : function(){
      var currentMessage = this.viewModel.message;
      this.viewModel.message = currentMessage+ "!";
    }
  }
});

Components have the ability to bind to special inserted, beforeremove and removed events that are called when a component’s tag has been inserted into, is about to removed, or was removed from the page.

Helpers

A component’s helpers object provides stache helper functions that are available within the component’s view. The following component only renders friendly messages:

Component.extend({
  tag: "hello-world",
  view: stache("{{#isFriendly message}}"+
              "<h1>{{message}}</h1>"+
            "{{/isFriendly}}"),
  helpers: {
    isFriendly: function(message, options){
      if( /hi|hello|howdy/.test(message) ) {
        return options.fn();
      } else {
        return options.inverse();
      }
    }
  }
});

Generally speaking, helpers should only be used for view related functionality, like formatting a date. Data related methods should be in the view model or models.

Examples

Check out the following examples built with Component.

Tabs

The following demos a tabs widget. Click “Add Vegetables” to add a new tab.

An instance of the tabs widget is created by creating <my-tabs> and <my-panel> elements like:

<my-tabs>
  {{#each(foodTypes)}}
    <my-panel title:from='title'>{{content}}</my-panel>
  {{/each}}
</my-tabs>

To add another panel, all we have to do is add data to foodTypes like:

foodTypes.push({
  title: "Vegetables",
  content: "Carrots, peas, kale"
});

The secret is that the <my-panel> element listens to when it is inserted and adds its data to the tabs' list of panels with:

var vm = this.parentViewModel = canViewModel(this.element.parentNode);
vm.addPanel(this.viewModel);

TreeCombo

The following tree combo lets people walk through a hierarchy and select locations.

The secret to this widget is the viewModel’s breadcrumb property, which is an array of items the user has navigated through, and selectableItems, which represents the children of the last item in the breadcrumb. These are defined on the viewModel like:

    breadcrumb: {
        Value: DefineList
    },
    selectableItems: {
        get: function(){
            var breadcrumb = this.breadcrumb;

            // if there's an item in the breadcrumb
            if(breadcrumb.length){
                // return the last item's children
                var i = breadcrumb.length - 1;
                return breadcrumb[i].children;
            } else{
                // return the top list of items
                return this.items;
            }
        }
    },

When the “+” icon is clicked next to each item, the viewModel’s showChildren method is called, which adds that item to the breadcrumb like:

    showChildren: function(item, ev) {
        ev.stopPropagation();
        this.breadcrumb.push(item);
    },

Paginate

The following example shows 3 widget-like components: a grid, next / prev buttons, and a page count indicator. And, it shows an application component that puts them all together.

This demo uses a Paginate can-define/map/map to assist with maintaining a paginated state:

var Paginate = DefineMap.extend({
  ...
});

The app component, using can-define/map/map, creates an instance of the Paginate model and a websitesPromise that represents a request for the Websites that should be displayed. Notice how the paginate’s count value is tied to the value of the websitesPromise’s resolved value’s count.

var AppViewModel = DefineMap.extend({
    paginate: {
        value: function() {
            return new Paginate({
                limit: 5,
                count: compute(this, "websitesPromise.value.count")
            });
        }
    },
    websitesPromise: {
        get: function() {
            return Website.getList({
                limit: this.paginate.limit,
                offset: this.paginate.offset
            });
        }
    }
});

The my-app component passes paginate, paginate’s values, and websitesPromise to its sub-components:

<my-app>
  <my-grid promiseData:from='websitesPromise'>
    {{#each(items)}}
      <tr>
        <td width='40%'>{{name}}</td>
        <td width='70%'>{{url}}</td>
      </tr>
    {{/each}}
  </my-grid>
  <next-prev paginate:from='paginate'></next-prev>
  <page-count page:from='paginate.page' count:from='paginate.pageCount'></page-count>
</my-app>

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

On this page

Get help

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