Docs
Everything You Need to Know
Tutorials
Organizing Your Code
ES6 (ECMAScript 2015)
If you use the ChocolateChip-UI command line tool to create a start project, you can write your app using ES6. JSPM will transpile the code into ES5. You can use many of the features of ES6 now while still supporting browsers that don't support it. You can use classes, arrow functions, object comprehensions, rest parameters, let and const, promises, template literals and most importantly, modules.
We have four sample apples written with ES6 modules. You can learn more about them here: Demos.
ES6 Modules
When you use the ChocolateChip-UI commandline tool to create a project, it creates a project with a build script to process your JavaScript with Rollup, Babel and Uglify. So you can organize your app's code as ES6 modules inside your project's dev
foler.
Modules are a great way to add structure to your app and make it easy for yourself and others to extend the app over time. However, you need to understand how modules work and how to structure you app's processes so that they can work with the module format. As we already mentioned, modules are imported before the DOM is loaded. This means any code that is looking for elements will not work when separated out as a module. You can get around this by wrapping your DOM code and then mounting it after importing. In fact, we made it so that components have a special method - .mount()
- so you can mount them after importing. This tells the component to re-scan the DOM to find elements or attach events.
Export
Before you can import something, it needs to be importable. You do this using the export
keyword. There are a number of types of code patterns you can export from a module:
- functions
- object or arrays of data
- classes
- components
- routes
Export Functions
These are the easiest. Just place export
before the function defintion:
export function whatever() { // Do stuff: alert('I was imported :-)') }
For code that deal with DOM nodes, such as get elements or binding events, you can wrap it in a function to mount after importing:
export function getAnElement() { // Get an element: $('#itemList').addClass('we-are-ready') }
export function bindEvent() { // Bind an event: $('#someButton').on('tap', function() { launchSomeProcess() }) }
You can export objects and arrays of data as well. Here is an object:
export const person = { name: { first: 'John', last: 'Doe' }, age: 32, job: 'developer' }
And here is an array:
export const todosData = [ 'Take a nap.', 'Eat a snack.', 'Play a game.' ]
You can also put your app's components in files that you export:
export const totalPurchasedItems = new Component({ element: '#purchaseDetails', render: (item) => html` <li> <h3><span>${ item.genre }</span>: ${ item.product_title }</h3> </li>` });
And routers:
// Set up router: export const selectedCoffeeRoute = $.Router() selectedCoffeeRoute.addRoute([ { route: 'coffeeShopDetail', callback(id) { const selectedShop = coffeeshops.filter(shop => { return shop.id === id })[0] shopDetailComponent.render(selectedShop) } } ])
Import
Because ChocolateChip-UI is for building apps that run in the browser, many of its features depend on the DOM being loaded for them to work. This becomes a problem when using ES6 modules, as imports and exports occur before the DOM is loaded. This means that components and widgets will not be able to find their elements to initialize themselves.
Importing a module is very straightforward - you use the import
keyword and the path to the module you want to use. You imports must be at the very top of the document, before any other code or they will not be imported:
import {bindEvent} from './controllers/bindEvent' import {todosData} from './data/todosData' import {totalPurchasedItemsComponents} from './components/totalPurchasedItemsComponents' import {selectedCoffeeRoute} from './routes/selectedCoffeeRoute'
In the above imports, notice the paths to the modules. This is very important. You will learn to hate ENOENT
errors when you try to build your app. These are "file not found" errors because you have a path wrong. This can be because you copy and pasted from one file to another, and the paths are different. Think about where the file that is importing the module is located. If it is in a folder called "components" and it is trying to access a folder outside it called "controllers", you will need to use a path like this:
import {bindEvent} from '../controllers/bindEvent
In contrast, from you app.js
file, which should be at the same level as all of your module folders, you should be able to access modules like this:
import {bindEvent} from './controllers/bindEvent
or:
import {bindEvent} from 'controllers/bindEvent
Mounting your Imports
After importing your modules, depending on the type, you may need to mount them. If the module is a component or a route, you'll definitely need to mount it. If the module has to bind events or somehow access the DOM, you'll also need to mount it. Components and routes have a special method to mount them:
import {shopsComponent} from './components/shopsComponent' import {shopDetailComponent} from './components/shopDetailComponent' // Mount the imported components: shopsComponent.mount() shopDetailComponent.mount()
After mounting a component, you might want to bind it to a model or render it like so:
import {shopsComponent} from './components/shopsComponent' import {shopDetailComponent} from './components/shopDetailComponent' import {coffeshopsArray} from './data/coffeshopsArray' // Make a model with imported data: const shopsModel = $.Model(coffeshopsArray) // Mount the imported components: shopsComponent.mount() shopDetailComponent.mount() // Render coffeeshops list: //========================= shopsComponent.setModel(shopsModel) shopsComponent.render() // Or render component with raw data: shopsComponent.render(coffeshopsArray)
After importing a route, you mount it like this:
import {selectedCoffeeRoute} from './route/router' // Mount imported route: //====================== selectedCoffeeRoute.mount()
For functions that have DOM related code — accessing elements, binding events or setting up a widget's functionality — you import and mount them by invoking them immediately:
import {addToCart} from './controllers/addToCart' import {goToCart} from './controllers/goToCart' import {cancelOrder} from './controllers/cancelOrder' import {placeOrder} from './controllers/placeOrder' // Mount imported controllers: //================================= addToCart() goToCart() cancelOrder() placeOrder()
Functions that do not access the DOM can be important and then invoked whenever you need to, as usual with function calls.
Project Setup for Modules
Below is the file structure for a project:
+data - bestWines.js - californiaWines.js + dev - app.js + controllers + about - aboutSheet.js + heroImage - outputHeroImg.js + purchase - handlePurchase.js - handlePurchaseProgressBar.js + search - handleWineSearch.js - searchPanelControlsSetup.js - searchPanelInit.js - searchParameters.js - showChosenSearchParameters.js + routes - router.js + components - components.js
And here's the app.js
file for that:
import * as app from './components/components' import {aboutSheet} from './controllers/about/aboutSheet' import {outputHeroImg} from './controllers/heroImage/outputHeroImg' import {bestWines} from '../data/bestWines' import {wines} from '../data/californiaWines' import {wineRoute} from './routes/router' import {searchPanelInit} from './controllers/search/searchPanelInit' import {searchPanelControlsSetup} from './controllers/search/searchPanelControlsSetup' import {showChosenSearchParameters} from './controllers/search/showChosenSearchParameters' import {searchParameters} from './controllers/search/searchParameters' import {handleWineSearch} from './controllers/search/handleWineSearch' import {handlePurchaseProgressBar} from './controllers/purchase/handlePurchaseProgressBar' import {handlePurchase} from './controllers/purchase/handlePurchase' $(function() { // Mount imported components: //====================== app.specialRedsComponent.mount() app.specialWhitesComponent.mount() app.selectedWineComponent.mount() app.filteredWinesComponent.mount() app.wineryComponent.mount() // Mount imported router: //======================= wineRoute.mount() app.specialRedsComponent.render(bestWines[0].data) app.specialWhitesComponent.render(bestWines[1].data) // Mount imported controllers: //============================ aboutSheet() outputHeroImg() searchPanelInit() searchPanelControlsSetup() handleWineSearch() handlePurchaseProgressBar() handlePurchase() })
The Reference Apps
We have provided examples of four simple apps made with ChocolateChip-UI: Fragranž, SFCoffee, TodoMVX and Vino. The come in two versions: double-clickable launch from the desktop and run from with terminal with Gulp, Babel, JSPM and Browsersync. You want to take a could look at the later. When you down the source code from Github, you'll find two folder: simple and jspm. The simple folder holds the double-clickable versions. These are great if you're new to ChocolateChip-UI and you just want to see how to put things together to make something work. However, once you want to get serious about build a real world app, you'll want to look closely at the versions of these apps in the jspm folder.
The best way to get the lay of an app is to open the dev folder of each project. This is where you would actually work. When you use the ChocolateChip-UI command line tool to create a new project, you'll also find you have a dev folder with an app.js file inside it. The build script takes this, checks for imports, runs everything through Rollup to bundle it up into one file and then transpiles it with Babel and minifies it with Uglify, complete with sourcemaps. This means that anything you import into your app.js file will get bundled into a single app.js file after building.
To learn more about these reference apps, visit Demos