Docs

Everything You Need to Know

Tutorials

Components

Understanding ChocolateChip-UI

Components are the part of ChocolateChip-UI that you will use most often. Maybe it's the only part you might use in your app. Components are flexible. They can be bound to a State object, or consume raw JavaScript data. You can switch out what template a component is using at any time. You can do that with their State objects as well. Components can also have events that enable user interaction. You might use components with ChocolateChip-UI mediators or with ChocolateChip-UI's router, or you might just write your own code to handle everything.

Components remove the necessity of jQuery-style DOM manipulations. When you have DOM nodes that need to have their text or attributes changed dynamically, look at how to create a component to handle it. Once you define a component, you can render it with data and let the component take care of updating the DOM. No more mess of apppend(), html(), text(), etc. You could event define a base component anchored to the body tag that only serves to wire up delegated events.

Create a Component

When you create a component, you want to associate it with an element. This element will serve as the container of DOM elements rendered by the component's template, or hosting registered events. To create a component, just assign it to a variable, which will hold the component's instance. There are other properties you can set when your create a component, but at bare minimum we can just provide it with an element to anchor to. When you indicate the element for the component, you use standard CSS selector markup, or a DOM node or a ChocolateChipJS DOM object. Usually you'll just use the selector approach:

const titleComponent = new Component({
  element: '#appTitle'
});

The above code creates a component instance named "MyComponent". We can use this instance to render templates, bind to state, etc.

Delimiters

Component templates are functions that return template literals or a tagged template literals. As such, in order to interpolate data in a template, we use dolar sign curly braces (${}) as delimiters. These can also be used for processing simple JavaScript actions:

const titleComponent = new Component({
  element: '#appTitle'
  render: (data) => html`<h1 id="appTitle" >${ data }</h1>`
})

We used the default term data above to indicate that we want a value ouput when the template renders. Whatevever we pass to the component's render function will be interpreted as a value of data. You can use whaterever parameter name makes sense for the data your template is using.

With a component instance defined, we can render the component with a simple string:

titleComponent.render('My Great App');

This would produce:

<h1 id="appTitle" >My Great App</h1>

Using this technique we can render a component with a string or a number. However, we usually have more complex data to use with components. Continue reading to learn how to handle that.

Object Properties

The previous example was quite simple. In most cases you are not going to be rendering a component with such simple data. Instead you're going to be dealing with objects. You can expose and object's properties to the component using dot notation. Let's create a person object:

const person = {
  firstName: 'John',
  lastName: 'Doe',
  age: 32,
  job: 'developer'
};

To use this data, we'll create the following component:

const personComponent = new Component({
  element: '#person',
  render: (data) => html`
    <ul id='person'>
      <li>
        <div>
          <h3>${ data.firstName } ${ data.lastName }</h3>
          <h4>${ data.age }</h4>
          </p>${ data.job }</p>
        </div>
      </li>
    </ul>`
});
// Render the component with the person object:
PersonComponent.render(person);

The above will give us this:

<ul id='person'>
  <li>
    <div>
      <h3>John Doe</h3>
      <h4>32</h4>
      </p>developer</p>
    </div>
  </li>
</ul>

See the Pen Template with Object V5 by Robert Biggs (@rbiggs) on CodePen.

Custom Variable Names

As I mentioned earlier, you can provide custom variable names so that the templates make sense for the data they are consuming. Instead of data, use the term you want in the template. Then when you define the component, provide the name you want to use on the component's variable property:

const personComponent = new Component({
  element: '#person',
  render: (person) => html`
    <ul id='person'>
      <li>
        <div>
          <h3>${ person.firstName } ${ person.lastName }</h3>
          <h4>${ person.age }</h4>
          </p>${ person.job }</p>
        </div>
      </li>
    </ul>`
});

// Render with the person object:
personComponent.render(person);

See the Pen Template with Object V5 by Robert Biggs (@rbiggs) on CodePen.

We could do that with our title component as well:

// Define "title" as custom variable:
const titleComponent = new Component({
  element: '#appTitle',
  render: (title) => html`<h1 id="appTitle" >${ title }</h1>`
});

// Render the title:
titleComponent.render('My Great App');

Arrays of Objects

So far we saw how to render simple strings and number, as well as objects with properties. However, many times we need to render an array of objects to create a list. Although other frameworks have special mechanisms to handle this, ChocolateChip-UI does not. That's because ChocolateChip-UI doesn't care whether your data is an object or an array. As long as the properties on the simple object and the array of objects are the same, ChocolateChip-UI will render them as a single item or a list. This means that you can turn a component rendering a single object into a list by rendering the same component with an array of similar objects. Or rendering a list component with a single object. The only requirement for this to work is that the object properties and the template variables match.

So, in the case of our person object, we can change our object to an array like this:

const persons = [
  {
    firstName: 'John',
    lastName: 'Doe',
    age: 32,
    job: 'developer'
  },
  {
    firstName: 'Sam',
    lastName: 'Smith',
    age: 28,
    job: 'mechanic'
  },
  // etc.
];

As you can see in the Codepen example below, we have the same component, but we're passing in an array of person objects. Even though the data is now an array, the component successfully renders a list of persons.

See the Pen Template with Array of Objects V5 by Robert Biggs (@rbiggs) on CodePen.

JavaScript in Templates

So far we've looked at how to use data interpolation in component templates. We can also write executable JavaScript in our templates. This allows you to render data conditionally, or whatever pre-rendering task you might need to perform.

To do so, we put our JavaScript inside dolar sign curly braces, like we did for data interpolation:

// JavaScript in template:
const executableComponent = new Component({
  element: '#executable',
  render: (data) => html`
    <div id='executable'>
      ${ alert("I just ran!"); }
      <p>${ data }</p>
    </div>`
});
executableComponent.render('Executable template')

When the above component is loaded in the browser, you'll get an alert with "I just ran!" This is a trivial example.

You Can't Run Everything!

The title says it all. You can't run all types of JavaScript inside a template literal, and this is a good thing. It protects you from malicious script injection, etc. You can perform string and math operations, as well as array functions lke map. You cannot use for loops or conditional structures with if or switch. However you can do boolean operations using terciary operators or simple boolean checks.

// JavaScript in template:
const joe = new Component({
  element: '#joeList',
  render: (data) => html`
    <li id='personsList'>
      ${ data.name !== 'Joe' ? data.name.toUpperCase() : '' }
    </li>`
})

In the above template we're doing two things with JavaScript -- we're testing the value of the data.name to see if it equals Joe, if it does we uppercase it. Otherwise we don't output anything.

Do Computation Before Return

One option to do complex JavaScript computation for a template is to do it before returning the template. In fact, using this tactic you could create logic that would return a different rendered template based on certain criteria. This gives you the ability to use whatever JavaScript you might need. You will need to use curly braces:

// JavaScript before returning template:
const joe = new Component({
  element: '#joeList',
  render: (data) => {
    if (data.name !== 'Joe') {
      // Return first template:
      return html`
        <li id='personsList'>
          ${ data.name.toUpperCase() }
        </li>`
    } else {
      // Return second template:
      return html`
        <li id='personsList'>
         Cannot show that name!
        </li>`
    }
  }
})

Another option for JavaScript if to simply process your data before passing it to a component to render.

Data Binding

To learn how to bind a component to a State object, read the tutorial about state. You need to know how State objects work before using them with components. State objects are reactive. This means when state changes, the associated component updates automatically.

Actions

You can define actions for your component. Actions allow you to capture user interaction. To define actions, you use the component's actions property. If you have only one action, you can assign a single action object to it. If you need more than one action, you can assign an array of actions to it. An action object has the following properties:

  • element - the element that will capture the event
  • event - the type of event to listen for
  • callback - the callback to execute when the event fires
// Use ES6 template literal:
const personComponent = new Component({
  element: '#person',
  actions: {
    element: 'h3',
    event: 'tap',
    callback: () => console.log($(this).text())
  },
  render: (person) => html`
    <li id='person'>
      <div>
        <h3>${ person.firstName } ${ person.lastName }</h3>
        <h4>${ person.age }</h4>
        </p>${ person.job }</p>
      </div>
    </li>`
});

As you can see in the above example, including everything into the component makes it easy to reason about what it is doing. You have one place to look for everything related to the component. In fact, you can even use the component's styles property to define a virtual stylesheet scoped to that component. For more detail information about actions and styles, check out the documentation on components.