ChocolateChip-UI is a JavaScript framework for creating hybrid mobile apps for Android and iOS. It provides you with layouts, widgets and themes to make your web app really feel like a native mobile one. We’re not talking Bootstrap here. The Android and iOS themes are spot on. Over the years the framework has gradually evolved. Currently it is based on the concept of components. You know, like React, Angular, Vue and everybody else.

I would say that the component implementation is going to feel closer to React. ChocolateChip-UI’s Component is a JavaScript class. To create a component, you need to use the new keyword while passing it some options to initialize it:

const myComponent = new Component(options)

To initialize a component you can use any of five properties: element, render, state, actions or styles. There are also lifecycle events that you can use.

Initializing a Component

1. element — the root element of the component. This will be the component’s container. The root element must be a physical element in the DOM at the time the component is initialized. The value of the element must be a valid selector, such as id, class or tag:

const list = new Component({

element: '#componentList'

})

2. render — a function that returns a template. The template is an ES6 template literal or tagged template literal. ChocolateChip-UI provides a custom tagged template literal: html . This sanitizes the data passed to it to prevent malicious script injection and makes using Array.map() easier by automatically escaping the return values. No need to use .join('') at the end. Below is a simple example of the default tagged template literal usage:

const list = new Component({

element: ‘#componentList’,

render: (data) => html`

<li>

<h3>Hello ${data}!</h3>

</li>`

})

// Pass data to component:

list.render('World')

To capture more complex data, such as an object, you use normal object dot notation:

const person = {

firstName: 'Joe',

lastName: 'Bodoni'

}

const list = new Component({

element: ‘#componentList’,

render: (person) => html`

<li>

<h3>${person.firstName} ${person.lastName}</h3>

</li>`

})

// Since component is defined,

// we can render it by passing in data:

list.render(person)

Handling Arrays

You did not need to use some special syntax to tell the component how to handle an array. Just define the template you want your component to use, then pass the data. If the data is a primitive type — string, number, boolean — it will print out. Now if later you pass the component an array of primitive types, it will automatically loop over the array and render the array data with the component’s template. Same thing when deal with objects and arrays of objects. If you define your component to render an object and then pass it an array, as long as the properties of that array match the properties you exposed in your template, it will render as expected. No need for ng-for , v-for , each= , or other types of constructs to handle arrays. ChocolateChip-UI examines the data a component is receiving before rendering it. If it’s an array, it loops over it automatically.

const fruits = [

'Apples', 'Oranges', 'Banans', 'Peaches', 'Straweberries'

] const list = new Component({

element: '#componentList',

// Capture loop index with second param "idx":

render: (fruit) => html`

<li>

<h3>${fruit}</h3>

</li>`

}) // Render component with simple string:

list.render('Pineapple')

// Render component with fruits array:

list.render(fruits)

Capturing the Loop index

You can capture the loop index when the render function is using an array as data. You do this by providing the render function a second parameter. You can use whatever term you want. I usually use idx:

const fruits = [

'Apples', 'Oranges', 'Banans', 'Peaches', 'Straweberries'

] const list = new Component({

element: '#componentList',

// Capture loop index with second param "idx":

render: (fruit, idx) => html`

<li>

<h3>${idx + 1}: ${fruit}</h3>

</li>`

}) // Render component with fruits:

list.render(fruits)

Using JSX

If you want, you can use JSX instead of template literals. The biggest advantage is that text editors understand it and can give you proper syntax highlighting, making it easier to read. In contrast, template literals are just strings, so the text editor can understand their content.

When you build your project, Babel transpiles the JSX into a format the ChocolateChip-UI Component render function can understand using a special h function:

import {h} from '../src/utils/h' const list = new Component({

element: '#componentList',

render: (data) => (

<li>

<h3>{data}</h3>

</li>

)

})

The ChocolateChip-UI integration of JSX has one nice feature — you can use class='my-class' instead of className='my-class' , etc. However, all the other aspects of JSX apply, such as not using quotes on computed attribute values, passing props to tags, etc. It’s the JSX you’ve grown to love or hate. If you love it, you’ll know how to use it, making custom tags for cleaner composition. And this is just an option. If you don’t like JSX, stick with the default template literals. Capturing the loop index is the same as for template literals:

import {h} from '../src/utils/h' const fruits = [

'Apples', 'Oranges', 'Banans', 'Peaches', 'Straweberries'

] const list = new Component({

element: '#componentList',

// Capture loop index with second param "idx":

render: (fruit, idx) => (

<li>

<h3>{idx + 1}: {fruit}</h3>

</li>

)

})

JSX Custom Tags

One nice feature of JSX is custom tags. This makes complex templates more readable by allowing you to break them up in to smaller custom tags. A custom tag is really just a simple function whose name starts with a capital letter and which returns some JSX:

import {h} from '../src/utils/h' // Some data to use:

const people = [

{

id: 101,

firstName: 'Joe',

lastName: 'Bodoni',

job: 'Mechanic'

}, {

id: 102,

firstName: 'Ellen',

lastName: 'Vanerbilt',

job: 'Lab Technician'

}, {

id: 103,

firstName: 'Sam',

lastName: 'Anderson',

job: 'Developer'

}

] // Define the component:

const list = new Component({

element: '#componentList',

// Capture loop index with second param "idx":

// Use custom tag in render:

render: (person, idx) => (

<ListItem {...{person}} />

)

}) // Define some custom tags:

const Image = ({person}) => (

<img src={`./images/${person.toLowerCase()}.jpg`} />

) const Disclosure = () => (

<aside>

<disclsoure></disclsoure>

</aside>

) // Assemble the previous two tags into this one:

const ListItem = ({person}) => (

<li>

<Image {...{person}} />

<h3>{person.lastName}, {person.firstName}</h3>

<Disclosure />

</li>

)

Do check out the document for JSX on the ChocolateChip-UI website for a in depth coverage.

3. state — By default components are stateless. You pass some data to the component instance render function and it will render it out. If the data is the same, it will always render the same. However, using the state property during initialization allows you to assign a State object. The component will then use the State object to render. Any change to the State object will automatically cause the component to update. If you’re thinking data binding, you’re right. Here’s an example of how to set up state for a component:

const person = {

firstName: 'Joe',

lastName: 'Bodoni'

} // Create State object:

const personState = new State(person) // Define stateful component:

const list = new Component({

element: '#componentList',

// Assign state to component:

state: personState,

render: (persons) => html`

<li>

<h3>${person.firstName} ${person.lastName}</h3>

</li>`

}) // Render component:

list.render()

Components with state implement one-way data binding. There is no solution for two-way data-binding. However, you can pull it off by defining actions on a component. You can learn more about State in the documentation.

4. actions — This property allows you to define user interactions for your component. You might want to capture a tap on a component’s button or list item, or user input in a text field. An action consists of three parts:

A. An event: You can use standard events like click, touchstart, but ChocolateChip-UI provides synthetic events that work identically on mobile and desktop, making your development and testing easier: tap , dbltap , longtap , swipeup , swipedown , swipeleft , swiperight .

B. An element: If the element is the component root, you use self . Otherwise, use the selector of any child element you want to target with the event. When target child elements, the event will be delegated to the component root element.

C. A callback: This is the event callback you’ve used since forever. It’s got the same shortcomings as well. this is the element the user interacted with, not the component itself :-(. Of course, you can use arrow functions, but then this will refer to the global scope where the component is defined. Yeah, I hear your groans. Thank Brendan Eich for that annoyance. Or Netscape for not giving him more time to work on the language before launch.

Here is an example of a component with actions:

const fruits = [

'Apples', 'Oranges', 'Banans', 'Peaches', 'Straweberries'

] const list = new Component({

element: '#componentList',

// Capture loop index with second param "idx":

render: (fruit, idx) => html`

<li>

<h3>${idx + 1}: ${fruit}</h3>

</li>`,

// Define actions for component:

actions: [

{

event: 'tap',

element: 'h3',

callback: (e) => alert(e.target.textContent)

}

]

}) // Render component with fruits:

list.render(fruits)

5. styles — This property lets you define a virtual stylesheet scoped to the component. This makes your component’s styles highly portable. They’re right there as part of the component. And because they’re scoped to the component, they’ll never leak out to affect other parts of your app. Because this is a property on the component, it must adhere to JavaScript object notation conventions. That means that simple CSS selectors can be unquoted, such as li , h3 , button , etc. But complex selectors need to be quoted: ":hover" , "> li" , "li:first-child" . Similarly, simple CSS properties can be used as they are: width , color , margin . But hyphenated properties must be either quoted or camel cased: "background-color" or backgroundColor , "margin-left" or marginLeft .

Below is an example of a styles definition. Notice how we can nest child elements to indicate their relationship to the component’s root element:

const person = {

firstName: 'Joe',

lastName: 'Bodoni'

}

const list = new Component({

element: ‘#componentList’,

render: (person) => html`

<li>

<h3>${person.firstName} ${person.lastName}</h3>

</li>`,

styles: {

margin: 20,

border: 'solid 1px #ccc',

h3: {

color: 'blue',

cursor: 'pointer',

':hover': {

color: 'orange'

}

}

}

})

Lifecycle Events

Additionally, you can capture a componet’s lifecycle events. If you’ve used React or a React-compatible library, these will look familiar:

Mounting:

componentWillMount — Use this when importing and mounting a component with mount() . It will execute before the component is mounted.

. It will execute before the component is mounted. componentDidMount — use this when importing and mounting a component with mount() . It will execute after the component is mounted.

Unmounting:

componentWillUnmount — use this when running unmount() on a component. It will execute before the component is unmounted.

on a component. It will execute before the component is unmounted. componentDidUnmount — use this when running unmout() on a component. It will execute after the component is unmounted.

Rendering:

componentWillUpdate — use this when running render() on a component. It will execute before the component is rendered.

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

You setup a lifecyle event by adding one of the above methods to your component initialization. You might use them to do something after an imported component is mounted, or before or after a component is rendered:

export const list = new Component({

element: ‘#list’,

render: (fruit) => html`

<li>

<h3>${fruit.name}</h3>

</li>`,

// Define lifecycle events:

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>’)

}

})

When the above component gets imported and mounted, the first lifecyle event will fire. When the component gets render, the second one will.

Examples

Here are some Codepen examples:

A Simple List using template literals

A component with State:

A Simle List with State

A component with nested templates:

A Component with Nested Templates

A component with scoped styles:

A Component with Scoped Styles

And JSX:

A Complex Template Using JSX and Custom Tags

Extending Component

Because Component is an ES6 class, you can also extend it. This allows you to create custom, reusable components. You would do this if you wanted to add component-specific methods or standardize templates for reuse by multiple components. Here’s an example of extending Component:

Extending Component

To learn more about components in ChocolateChip-UI, check out the documentation.