Docs

Everything You Need to Know

Components

Use

Setup

When initializing a component, you can make it empty, or provide a number of options to give it various types of functionality. If you want a component, but aren't ready to have it associate with someting in the DOM quite yet, you can make an empty one like so:

const myComponent = new Component(options)
          

To be useful a component needs at least an element to associate it with, otherwise you can't do anything with it. You basically anchor the component to that elemnet. That element becomes the component's container in which it renders itself. Each component needs an element in the document to attach itself to. If you create a component with only and element, you can always attach a template to a component later. Now, let's create a component with an associated DOM element:

const simpleComponent = new Component({
  element: '#special'
})

The above component presupposes the existence of an element called #special. To make the component complete we need to provide a template to render. The template is a function that returns an ES6 template literal.

Template for a Component

Usually you'll want to define a template function for a component. The template is defined as the return value of the component's render function. In the component's render function you provide a parameter with the name you want to use in the template. We recommend you use our tagged template literal — html`` — because it automatically escapes the data before rendering. This protects you from malicious script injection, etc.

// Markup:
<div id='someTag'></div>

...

// Script:
const stuff = 'This is my stuff.'
const simpleComponent = new Component({
  element: '#someTag',
  render: (data) => html`${ data }`
})
simpleComponent.render(stuff)

// Result:

<div id='someTag'>This is my stuff.</div>

The above component would render "This is my stuff." inside the div. We could just as easily have used a number instead.

Using JSX for Templates

The default format for component templates is ES6 template literals. However, as of version 5.2.0 you can create projects that use JSX. Many developers prefer to use JSX for templating. You can learn more about how to use JSX with ChocolateChip-UI in the documentation.

Templates with Complex Data

So far we've only rendered simple data. If you have an object and want to render it, you'll need to change how you use the data variable. Suppose we have an object like this:

// Object:
const person = {
  name: 'Joe',
  job: 'Mechanic'
}

Because of the nature of this object, simply using data in the template won't work. We need to tell the template which object properties it should render. We do that using normal object dot notation:

// Define component with string template:
const simpleComponent = new Component({
  element: '#special',
  render: (data) => html`
    <li>
      <h3>${ data.name }</h3>
      <h4>${ data.job }</h4>
    </li>`
})
// Render component with the person object:
simpleComponent.render(person)

To learn more about templates please visit the documentation for templates.

// Define component with string template:
const simpleComponent = new Component({
  element: '#special',
  variable: 'fruit',
  render: '<li>${ fruit }</li>'
})
// Render component:
simpleComponent.render()

Binding a Component to State

As we saw above, you can use plain JavaScript objects with ChocolateChip-UI's components. However, in many cases you might prefer to create an State object and use that with a component. State objects offer you functionality that plain JavaScript objects do not. However, the most important reason for binding your component to a State object is that whenever the state changes, the component will render automatically. No need for you to call the component's render function. For more information about State, read its documentation.

To use state with a component, assign the state to the component's state property property:

const boundComponent = new Component({
  element: '#peopleList',
  state: peopleState
})

Make a Component Interactive

You can make a component interactive by defining actions. For this you use the component's actions property, which takes an array of event objects. Actions are for capturing user interaction. They therefore are objects have three possible properties: the event you want to capture an element to that will capture the event, and a callback. Instead of an element you can use the string "self" to target the component's anchor element. For events you can use any of the ones that ChocolateChip-UI supports: mouse events, touch events or gestures. Remember that ChocolateChip-UI's gestures work the same on desktop and mobile, iOS and Android. In the callback, this will refer to the element the user is interacting with:

const interactiveComponent = new Component({
  element: '#items',
  state: itemsState,
  // Define an event delegated to the list items:
  actions: [{
    element: 'li',
    event: 'tap',
    callback: function() {
      console.log($(this).text());
    }
  }]
})

Callbacks with Arrow Functions

ES6 is all about arrow functions. We love using them. However, when you use an arrow function for an event callback, the "this" keyword will not refer to the event target. Instead, "this" will refer to the scope of the callback. To get the element that captures the event, use the event argument in the callback. Here is the previous component with an arrow function. Notice how we use e.target to get the elment that was tapped:

const interactiveComponent = new Component({
  element: '#items',
  state: itemsState,
  // Define an event delegated to the list items:
  actions: [{
    element: 'li',
    event: 'tap',
    // Get tapped element from event:
    callback: (e) => {
      console.log($(e.target).text());
    }
  }]
})

Here's an example of a component with events bound to its items. Click on an item and it will alert the content:

See the Pen Component Example V5 by Robert Biggs (@rbiggs) on CodePen.

Changing a Component's Template

As we mentioned previously, you can change the template that a component is using. If you wanted to render different data in the same component depending on what the user chooses, you can change the component's template. To do so, you assign the template using the setTemplate() method. After changing the template, you will need to render the template again, as illustrated below:

// Define new template:
const template2 = (data) => html`<li&ft;${data)</li>`
// Assign new template to component:
peopleComponent.setTemplate(template2)
// Rerender component with new template:
peopleComponent.render(data)

If you want to change a component's state and template, change the template first, then the state. That way, the new state will render the component with the new template. Please see templates for more details.

Sequential Numbering

Sometimes you want to output sequential numbering in your component's template. You do this by providing a second parameter to the component's render function. By default all sequential numbering starts from o, so you may want to add 1 to it to get normal numbers.

And here's a template using it:

// We'll use "idx" to capture
// the loop index:
const personComponent = new Component({
  element: '#list',
  render: (data, idx) => html`
    <ul class="list" id="arrayTemplate1">
      <li>
        <h3>
          ${ idx + 1 }: ${ data.firstName } ${ data.lastName }
        </h3>
      </li>
    </ul>`
})

Formatting Output

You may need to format the data you are rendering in a component's template. ChocolateChip-UI provides a number of formatters to help you. These go right in the template where you want the data formated. ChocolateChip-UI has the following formatters:

  • formatNumber
  • sum
  • currency
  • formatTime
  • sortDate
  • sortNumbers
  • sortNumbersDescending

Please see temlates for more information about data formatters.

Using Template Helpers

Depending on your data, you may have the need to do some specialized formating of the data before outputing it. You can do this by definine a function that accepts the data and returns it transformed. Say you wanted a helper to make a name uppercase, you could write a helper function:

const upperCase = (data) => data.lastName.toUpperCase()

You could then use it like so:

const myComp = new Component({
  element: '#name',
  render: (data) => html`<li>${ upperCase(data) }</li>`
})

The above example is very simplified, but it is the technique that is important. You could write any type of function to transform data as needed.

Nested Templates

Rendering a data object that has a child element that is a collection is tricky. ChocolateChip-UI allows you to cover this by running JavaScript directly in your template. In fact, when the component is processed, your template gets converted into a JavaScript function. This means that you can use any JavaScript inside a template. You do need to escape the JavaScript code from the template. This is done using doube curly braces.

Let's say we want to output a list like in our above examples. But each list item might have a sub list. Using JavaScript in our template, we can have our component render the sub items. So let's use this data and see how to render the sub-lists of data:

const people = [
  {
    name: 'Joe',
    friends: [
      {
        name: "Sam"
      },
      {
        name: "Jack"
      }
    ]
  },,
  {
    name: 'Brad'
  }
  {
    name: 'Ellen',
    friends: [
      {
        name: 'Sherry'
      },
      {
        name: 'Jill'
      },
      {
        name: 'Bill'
      }
    ]
  },
  {
    name: 'Tom',
    friends: [
      {
        name: 'Joe'
      },
      {
        name: 'Mary'
      },
      {
        name: 'Kevin'
      }
    ]
  }
]

In the above data set, Brad has no friends. Therefore we want to check if each person has friends before create the friends list. Below is the code to make that happen. Notice that we check the person object to see it if has friends:

const peopleComponent = new Component({
  element: '#peopleList',
  render (somebody) => html`
    <li>
      <aside>
        <img width='80' src='${ somebody.image }'>
      </aside>
      <div>
        <h3>${ somebody.name }</h3>
      </div>
    </li>
    !${somebody.friends && somebody.friends.length ? html`
      <li>
        <div class='no-flex' style='max-width: 100px;'>
          <h3>Friends:</h3>
        </div>
        <div>
          <ol>
            ${
              somebody.friends.map((friend) => `<li>${friend.name}: ${friend.job}</li>`
              })
            }
          </ol>
        </div>
      </li>`
    : ''}`
  })
  peopleComponent.render(people)
  

And here's a working example:

See the Pen Nested Template Example V5 by Robert Biggs (@rbiggs) on CodePen.

Styles in Components

Since version 4.1.0, you can use the styles property to define styles for a component. This creates a virtual stylesheet for the component. This makes the component's style portable so you can reuse the component in other projects. If you are following the default patterns provided by ChocolateChip-UI you may not need to provide styles in a component. However, if you are creating a unique layout or customizing an existing one, you can use the styles property to make the component's layout look the way you want.

ChocolateChip-UI uses a special style object notation to define the styles for a component. Selectors must be quoted. These recevie a style object consisting of a property and value. Values must be quoted. Simple properties do not need to be quoted. However, if they are hyphenated, then they need to be quoted, otherwise you can use the camel case version of the property without quotes. The base of the stylesheet is the selector provided as the anchor of the component. In the example below, that will be the element #list. You can nest child elements in their parent selectors, similar to SASS and LESS. Note: Because this is object notation, CSS definitions do not end with a semi-colon but with a comma. Putting a semi-colon after your CSS value will throw an error.

// Component with style object:
const myComponent = new Component({
  element: '#list',
  render: (person) => html`
    <li>
      <h3>${ person.name }</h3>
      <h4>${ person.age }</h4>
    <li>`,
  // Define a style object for the component:
  styles: {
    // Style the partent list:
    border: 'solid  2px green',
    margin: '10px 20px',
    // Style the list's child elements:
    '> li': {
      '> h3': {
        color: 'red'
      },
      '> h4': {
        color: 'blue'
      }

    }
  }
})

And here's the stylesheet that gets created:

#list: {
  border: solid  2px green;
  margin: 10px 20px;
}
#list > li > h3 {
  color: red;
}
#list > li > h4 {
  color: blue;
}

And here are two examples of styled components:

See the Pen Styled Component V5 by Robert Biggs (@rbiggs) on CodePen.

To learn more about using a component's style property, please go to the page dedicated to it.

"This" in Actions

When you define an action for a component, you need to bear in mind that this will refer to what it would in a normal DOM event: the element the user interacted with. In the example below, this will be the h3 that the user taps.

// Create component:
const list = new Component({
  element: '#list',
  render: (data) => html`
  <li>
    <h3>${ data }</h3>
  </li>`,
  actions: [{
    event: 'tap',
    element: 'h3',
    callback: function() {
      // "this" is the h3 that was tapped
      console.log(this.textContent)
      console.log(this) // outputs the h3 node
    }
  }]
})

However, if we change our callback to use an ES6 arrow function, this will instead be the global context of where the component is instantiated. In most cases this will be the window object, in some cases this may actually return undefined. In such a case, the only way to get the element that was tapped is to do so through the event object's target property:

// Create component:
const list = new Component({
  element: '#list',
  render: (data) => html`
  <li>
    <h3>${ data }</h3>
  </li>`,
  actions: [{
    event: 'tap',
    element: 'h3',
    callback: (e) => {
      // Get the h3 that was tapped 
      // through the event object:
      alert(e.target.textContent)
      console.log(this) // outputs window or undefined
    }
  }]
})

The fact that DOM events have a different context than the component that holds and uses them can be a problem if you want to use component methods in a callback. This is especially so if you've created a custom component by extending the Component class. The only way to get access to the component from inside an action callback is to use the component instance directly. The following example is trivial, but it show how to extend the Component class, add special methods, and access them from within a DOM event in a component's action callback:

// Extend Component class:
class List extends Component {
  constructor(options) {
    super(options)
    this.renderFnc = (data, idx) => html`
      <li>
        <h3>${ data.name }</h3>
      </li>`
  }
  // Define methods that will be accessed 
  // in the instance action below:
  getTextFromElement(el) {
    return el.textContent.trim()
  }
  makeUpperCase(el) {
    // We can access the other method thru "this":
    return this.getTextFromElement(el)
  }
}
const list = new List({
  element: '#list',
  actions: [{
    event: 'tap',
    element: 'h3',
    callback: (e) => {
      const text = e.target.textContent.trim()
      // Access the method defined on the class.
      // Since "this" is not the component,
      // we need to access the componet instance directly:
      alert(list.makeUpperCase(e.target))
    }
  }]
})

Here's a Codepen example:

See the Pen Component Methods & This V5 by Robert Biggs (@rbiggs) on CodePen.

Lifecycle Methods

As of version 5.2.0, components offer lifecyle methods to allow you to accomplish tasks at different times:

    Mounting:

  1. componentWillMount - Use this when importing and mounting a component with mount(). It will execute before the component is mounted.
  2. componentDidMount - use this when importing and mounting a component with mount(). It will execute after the component is mounted.
  3. Unmounting:

  4. componentWillUnmount - use this when running unmount() on a component. It will execute before the component is unmounted.
  5. componentDidUnmount - use this when running unmout() on a component. It will execute after the component is unmounted.
  6. Rendering:

  7. componentWillUpdate - use this when running render() on a component. It will execute before the component is rendered.
  8. componentDidUpdate - use this when running render() on a component. It will execute after the component is rendered.

To use these, assign a method to them when initalizing your component:

// file /components/list.js
export const list = new Component({
  element: '#list',
  render: (fruit) => html`
    <li>
      <h3>${fruit.name}</h3>
    </li>`,

  componentDidMount: () => console.log('The component was imported and mounted. That means you can render it.'),

  componentDidUpdate: () => {
    document.querySelector('#list').insertAdjacentHTML('afterend', '<p>This is a footer for a recently rendered list component.</p>')
  }
})