CanJS is a collection of front-end libraries that make it easier to build complex and innovative web apps that are maintainable over a long period of time. It’s broken up into dozens of individual packages, so you can pick-and-choose what you’d like in your application without being bogged down by a huge 100kb+ dependency.
CanJS promotes the MVVM (Model-View-ViewModel) architecture with the following key packages:
- can-component for custom elements
- can-connect for communicating with APIs
- can-define for observables
- can-stache for Handlebars-like templating
In this tutorial, we’re going to make a to-do list app that uses a GitHub repository’s issue list as its source. Our app will update in real-time thanks to GitHub’s Webhook API and we’ll be able to reorder issues thanks to jQuery UI’s sortable interaction.
You can find the finished source code for this app on GitHub. Here’s what the final app will look like:
If you're interested in taking your JavaScript skills to the next level, sign up for SitePoint Premium and check out our latest book, Modern JavaScript
MVVM in CanJS
Before we start our project for this tutorial, let’s dive into what MVVM means within a CanJS application.
Data Models
The “Model” in MVVM is for your data model: a representation of the data within your application. Our app deals with individual issues and a list of issues, so these are the data types that we have in our model.
In CanJS, we use can-define/list/list and can-define/map/map to represent arrays and objects, respectively. These are observable types of data that will automatically update the View or ViewModel (in MVVM) when they change.
For example, our app will have an Issue
type like this:
import DefineMap from 'can-define/map/map';
const Issue = DefineMap.extend('Issue', {
id: 'number',
title: 'string',
sort_position: 'number',
body: 'string'
});
Each instance of Issue
will have four properties: id
, title
, sort_position
, and body
. When a value is set, can-define/map/map
will convert that value to the type specified above, unless the value is null
or undefined
. For example, setting the id
to the string "1"
will give the id
property the number value 1
, while setting it to null
will actually make it null
.
We’ll define a type for arrays of issues like this:
import DefineList from 'can-define/list/list';
Issue.List = DefineList.extend('IssueList', {
'#': Issue
});
The # property on a can-define/list/list
will convert any item in the list to the specified type, so any item in an Issue.List
will be an Issue
instance.
View Templates
The “view” in a web application is the HTML user interface with which users interact. CanJS can render HTML with a few different template syntaxes, including can-stache, which is similar to Mustache and Handlebars.
Here’s a simple example of a can-stache
template:
<ol>
<li>
</li>
</ol>
In the above example, we use to iterate through a list of issues
, then show the title
of each issue with . Any changes to the issues
list or the issue titles will cause the DOM to be updated (e.g. an li
will be added to the DOM if a new issue is added to the list).
View Models
The ViewModel in MVVM is the glue code between the Model and View. Any logic that can’t be contained within the model but is necessary for the view is provided by the ViewModel.
In CanJS, a can-stache
template is rendered with a ViewModel. Here’s a really simple example:
import stache from 'can-stache';
const renderer = stache(' world');
const viewModel = {greeting: 'Hello'};
const fragment = renderer(viewModel);
console.log(fragment.textContent);// Logs “Hello world”
Components
The concept that ties all of these things together is a component (or custom element). Components are useful for grouping functionality together and making things reusable across your entire app.
In CanJS, a can-component is made up of a view (can-stache
file), a view-model (can-define/map/map
), and (optionally) an object that can listen for JavaScript events.
import Component from 'can-component';
import DefineMap from 'can-define/map/map';
import stache from 'can-stache';
const HelloWorldViewModel = DefineMap.extend('HelloWorldVM', {
greeting: {value: 'Hello'},
showExclamation: {value: true}
});
Component.extend({
tag: 'hello-world',
view: stache(' world!'),
ViewModel: HelloWorldViewModel,
events: {
'{element} click': () => {
this.viewModel.showExclamation = !this.viewModel.showExclamation;
}
}
});
const template = stache('hello-world');
document.body.appendChild(template);
In the example above, our template will either show “Hello world!” or just “Hello world” (no exclamation mark), depending on whether the user has clicked our custom element.
These four concepts are all you need to know to build a CanJS app! Our example app will use these four ideas to build a full-fledged MVVM app.
Prerequisites for this tutorial
Before getting started, install a recent version of Node.js. We’ll use npm to install a backend server that will handle the communication with GitHub’s API.
Additionally, if you don’t already have a GitHub account, sign up for one.
Set up our local project
Let’s start by creating a new directory for our project and switching to that new directory:
mkdir canjs-github
cd canjs-github
Now let’s create the files we’ll need for our project:
touch app.css app.js index.html
We’ll use app.css
for our styles, app.js
for our JavaScript, and index.html
for the user interface (UI).
CanJS Hello World
Let’s get coding! First, we’re going to add this to our index.html
file:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CanJS GitHub Issues To-Do List</title>
<link rel="stylesheet" href="http://ift.tt/2apRjw3">
<link rel="stylesheet" href="app.css">
</head>
<body>
<script type="text/stache" id="app-template">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h1 class="page-header text-center">
</h1>
</div>
</div>
</div>
</script>
<script type="text/stache" id="github-issues-template">
</script>
<script src="http://ift.tt/2rOgv46"></script>
<script src="http://ift.tt/2dsPzTA"></script>
<script src="http://ift.tt/2rO6rby"></script>
<script src="/http://ift.tt/1aeIZU4"></script>
<script src="app.js"></script>
</body>
</html>
This has a bunch of different parts, so let’s break it down:
- The two
link
elements in thehead
are the stylesheets for our project. We’re using Bootstrap for some base styles and we’ll have some customizations inapp.css
- The first
script
element (withid="app-template"
) contains the root template for our app - The second
script
element (withid="github-issues-template"
) will contain the template for thegithub-issues
component we will create later in this tutorial - The
script
elements at the end of the page load our dependencies: jQuery, jQuery UI, CanJS, Socket.io, and our app code
In our app, we’ll use jQuery UI (which depends on jQuery) to sort the issues with drag and drop. We’ve included can.all.js
so we have access to every CanJS module; normally, you would want to use a module loader like StealJS or webpack, but that’s outside the scope of this article. We’ll use Socket.io to receive events from GitHub to update our app in real-time.
Next, let’s add some styles to our app.css
file:
form {
margin: 1em 0 2em 0;
}
.list-group .drag-background {
background-color: #dff0d8;
}
.text-overflow {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
Lastly, let’s add some code to our app.js
file:
var AppViewModel = can.DefineMap.extend('AppVM', {
pageTitle: {
type: "string",
value: "GitHub Issues",
}
});
var appVM = new AppViewModel();
var template = can.stache.from('app-template');
var appFragment = template(appVM);
document.body.appendChild(appFragment);
Let’s break the JavaScript down:
can.DefineMap
is used for declaring custom observable object typesAppViewModel
is the observable object type that will serve as the root view-model for our apppageTitle
is a property of allAppViewModel
instances that defaults to the valueGitHub Issues
appVM
is a new instance of our app’s view-modelcan.stache.from
converts the contents of ascript
tag into a function that renders the templateappFragment
is a document fragment of the rendered template with theappVM
datadocument.body.appendChild
takes a DOM node and appends it to the HTML body
Continue reading %How to Build a Real-Time GitHub Issue To-Do List with CanJS%
by Chasen Le Hara via SitePoint
No comments:
Post a Comment