Fork me on GitHub

Toura Mulberry

Mulberry at RioJS

| Comments

We were all surprised to find that one of our Mulberry friends from Brazil, Nuba Princigalli, had presented at RioJS about Mulberry and Dojo (Mulberry uses Dojo as a foundation)!

His talk was recorded and uploaded to YouTube here: http://www.youtube.com/watch?v=jC_3G-m_hx8

Nuba is working on getting his slides translated into English and once he does, we’ll link to them from here. Mulberry’s march across the world continues!

0.3 Brings New Tools for Interactive Apps

| Comments

Dan Imal gave a good overview of the 0.3 release of Mulberry the other day, but I wanted to follow up by taking a deeper dive into some of the new features that landed that make it easier to use Mulberry to build all kinds of apps. I’ll be talking about all of this at some upcoming events, but if you can’t make it to any of them, hopefully this post will give you good insight into how these improvements can help you build apps faster using Mulberry.

Probably the easiest way to understand these features is to look at a simple Todos app. Our app will have a home page that lets you add todo items and view unfinished ones, and a page that shows completed items; those items, and their state, will persist on the device until the user deletes them. You can follow along with the completed app on GitHub.

A Mulberry Todos App

Once we’ve scaffolded our app by running mulberry scaffold todos, we know that we’ll need a few things:

  • A page definition for the Todos page
  • A page definition for the Completed page
  • A store for the todo items
  • Components for entering todos, listing todos, and working with todos en masse

We can add all of those things via the command line:

1
2
3
mulberry create page_def todos completed
mulberry create store todos
mulberry create component TodoForm TodoList TodoTools

We’ll also want to specify routes for our two pages. As of 0.3, we get a new API method, mulberry.page, that lets us define a data-driven route and skip the need to create a markdown file for the pgae. Here’s what our routes.js file will look like:

javascript/routes.js
1
2
3
4
5
6
7
8
9
10
11
dojo.provide('client.routes');

mulberry.page('/todos', {
  name : 'Todos',
  pageDef : 'todos'
}, true);

mulberry.page('/completed', {
  name : 'Completed',
  pageDef : 'completed'
});

Each of these calls to mulberry.page passes an object that has details about how to display the page associated with the route, and in each case that object has a pageDef property. Page definitions spell out how different components will be laid out for a given page, and what capabilities that page will have. Here’s the page definition for the todos page, located at page_defs/todos.yml:

page_defs/todos.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
todos:
  capabilities:
  - name: PageTodos
  screens:
    - name: index
      regions:
        - className: todo-form-container
          components:
          - custom.TodoForm
        - scrollable: true
          className: todo-list-container
          components:
          - custom.TodoList
        - className: todo-tools-container
          components:
          - custom.TodoTools

The 0.3 release also introduced the concept of “stores,” which help us persist and query data. The default behavior of a store is to persist the data on device using local storage, though you can override or extend that behavior if you’d like. Here’s our todos store, located at javascript/stores/todos.js:

javascript/stores/todos.js
1
2
3
4
5
6
7
8
9
10
11
12
13
dojo.provide('client.stores.todos');

mulberry.store('todos', {
  model : 'Todo',

  finish : function(id) {
    this.invoke(id, 'finish');
  },

  unfinish : function(id) {
    this.invoke(id, 'unfinish');
  }
});

This creates a store at client.stores.todos; that store will automatically get methods like add, query, remove, and put, plus the additional finish and unfinish methods that we’ve specified in the prototype we passed. Those last two methods are used to pass messages to individual models.

We also specify a model property, which tells the store to expect that we’ve defined a Todo model. We do so in javascript/models/Todo.js, and it looks like this:

javascript/models/Todo.js
1
2
3
4
5
6
7
8
9
10
11
12
13
dojo.provide('client.models.Todo');

mulberry.model('Todo', {
  complete : false,

  finish : function() {
    this.set('complete', true);
  },

  unfinish : function() {
    this.set('complete', false);
  }
});

Now, we’re ready to wire up our app’s todos page. When we generated the page definition file for the todos page, Mulberry automatically assigned it a capability called PageTodos; this capability will be responsible for getting the page set up, and then brokering communication between the components as the user works with their todo items.

Here’s what that capability, located at javascript/capabilities/PageTodos.js, looks like, with some inline comments to make it easier to follow:

javascript/capabilities/PageTodos.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
dojo.provide('client.capabilities.PageTodos');

mulberry.capability('PageTodos', {
  /*
   * The capability expects the following components to be present in order for
   * the capability to work.
   */
  requirements : {
    todoList : 'custom.TodoList',
    todoForm : 'custom.TodoForm',
    todoTools : 'custom.TodoTools'
  },

  /*
   * These "listeners" will be set up as part of setting up the page. So, for
   * example, when the TodoForm component instance announces that a user has
   * added a todo (by calling its `onAdd` method), then the capability's `_add`
   * method will be run.
   */
  connects : [
    [ 'todoForm', 'onAdd', '_add' ],
    [ 'todoList', 'onComplete', '_complete' ],
    [ 'todoList', 'onDelete', '_delete' ],
    [ 'todoTools', 'onCompleteAll', '_completeAll' ]
  ],

  /*
   * When the page is set up, we'll grab a reference to the todos store, and
   * then update the list of todos.
   */
  init : function() {
    this.todos = client.stores.todos;
    this._updateList();
  },

  _delete : function(id) {
    this.todos.remove(id);
    this._updateList();
  },

  _add : function(item) {
    this.todos.add(item);
    this._updateList();
  },

  _complete : function(id) {
    this.todos.finish(id);
    this._updateList();
  },

  _updateList : function() {
    var items = this.todos.query(function(item) {
      return !item.complete;
    });

    this.todoList.set('todos', items);
  },

  _completeAll : function() {
    this.todos.query(function(item) {
      return !item.complete;
    }).forEach(dojo.hitch(this, function(t) {
      t.finish();
      this.todos.put(t);
    }));

    this._updateList();
  }
});

All that’s left to do now is to actually make the components do what they need to do. Components are intentionally “dumb” – their job is to receive data from an outside source, render that data using a template, and announce user interaction by calling (often empty) methods. Here, for example, is the TodoForm component, located at javascript/components/TodoForm.js:

javascript/components/TodoForm.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
dojo.provide('client.components.TodoForm');

mulberry.component('TodoForm', {
  componentTemplate : dojo.cache('client.components', 'TodoForm/TodoForm.haml'),

  init : function() {
    this.connect(this.domNode, 'submit', function(e) {
      e.preventDefault();

      var description = dojo.trim(this.descriptionInput.value),
          item;

      if (!description) { return; }

      item = { description : description };
      this.domNode.reset();
      this.onAdd(item);
    });
  },

  onAdd : function(item) { }
});

It displays the form (using the template located at javascript/components/TodoForm/TodoForm.haml), listens for the user to submit the form, checks to make sure there was some input, and then calls its onAdd method, which the PageTodos capability has registered its interest in. The other components are similarly simple.

The Mulberry framework was born out of our experience with making content-rich apps, but we’re hard at work making it easy to use Mulberry to build all kinds of apps – pretty much anything that might manifest itself as a single-page JavaScript application. We hope you’ll download the 0.3 version, take the Todos demo for a spin, and let us know how we’re doing.

Announcing Mulberry 0.3: Even Easier Customization

| Comments

It’s a new year, and with that comes a new release of Mulberry! As with the previous release, our goal is to make it easier to get started with Mulberry, and once you’re up and running, we want to make it easier to customize your app. Some of the highlights:

  • Installer scripts for OSX and Windows (Ubuntu is on the way): Getting started with Mulberry used to require downloading and installing a lot of other dependencies. While we haven’t been able to completely automate this, we have made it a whole lot easier. Find the script for your OS in /install/<os>/, follow the instructions in the README, then run the installer script, and you’ll be up and running.

  • Customizable default theme: The “default” theme used to be difficult to customize. If you decided to use it you were basically stuck with a lot of gray. Now all the colors and fonts can now be changed from the themes/default/_settings.scss file, so you don’t have to dig through alot of code or create a completely new theme just to change some colors. The icons are now customizable as well. Simply replace any of the images in themes/default/icons with whatever you want.

  • Templates are now “page_defs”: In 0.2, we had “templates” which were yaml files where you’d define the layout for a page in columns and rows. But these templates also had a lot of non-presentational information, which tended to be confusing. So we changed things so that page layout is handled in CSS (as it should be), and page_defs got a little simpler. We added a few Sass mixins to help with page layout. Read more about page_defs.

  • New and Improved Code Generators: It was already possible to create new routes, but now you can run mulberry create route '/foo/:bar' and it will generate the necessary code and add it to routes.js, which you can then customize. Running mulberry create page_def will now generate a boilerplate scss file and add import directive.

In addition to all the new stuff, we’ve got a bug of bugfixes, improved development server stability, and more complete test coverage. For the full story, read the full release notes.

As always, follow us at @touradev to keep up to date on the latest developments, or get help in the Google Group.

Announcing Mulberry 0.2: More Features for Easy Customization

| Comments

Mulberry 0.2 is out today, with several new features to make it even easier to customize your apps:

  • In Mulberry 0.1, you had to be very verbose when overriding the default styles. As of 0.2, when an app is scaffolded, the default theme is copied into the app, where it can be modified directly. To pull any upstream changes to the built-in themes, we added mulberry update_themes to the CLI.
  • A new toura.app.Phonegap.registerAPI method lets you create your own wrappers that make it easier to do development in a browser. Read more about browser-based development with Mulberry.
  • We beefed up mulberry create component; it now automatically creates a stub .scss file for the component, and adds an @import statement to your theme’s base.scss file.
  • Now when you add pages to your sitemap, you can just run mulberry scaffold inside your Mulberry project to create them all at once, rather than having to remember which ones to create yourself. Read about all the command line options.
  • It’s often necessary to repopulate a component or a piece of a component when it gets new data or its state changes. Mulberry 0.2 introduces the populate and populateElement methods on components to make this easier – just pass it a template and the data for the template, and it handles the rest. Read more about custom components.
  • Lots of people were asking us about how to create custom routes; it was possible before, but it’s easier in Mulberry 0.2: we added mulberry.route and mulberry.routes for defining custom routes. Read more about defining routes and other aspects of the JavaScript API.

We also landed a few other features, several bug fixes, some improvements to the command line, and lots of docs. Read the full release notes here.

As always, you can find us on Twitter @touradev, visit us in the #touramulberry channel on irc.freenode.net, or drop us a note at mulberry@toura.com.

Browser-based Development: Fewer Builds, Better Tools

| Comments

I’m in London after attending the Full Frontal JavaScript Conference, put on by the fantastic Remy and Julieanne Sharp in lovely, foggy, chilly Brighton. It was a great chance to meet a ton of developers from whom I’m usually separated by an ocean, and of course I spent a lot of time talking to those developers about Mulberry.

One of the things that seemed to especially get their attention was how Mulberry lets you do day-to-day development in a browser, avoiding time-consuming compilations most of the time. Setting up browser-based development on a PhoneGap project can be tricky, and development on device or in a simulator harkens back to the bad old days of IE6.

If you want to develop content-rich native apps that include a non-trivial amount of JavaScript, you’re going to want the comforts of a modern browser. We’ve solved this in Mulberry in a few different ways.

Wrappers for PhoneGap APIs

PhoneGap and PhoneGap plugin APIs don’t work when you’re running your code in a desktop browser – indeed, if you try to call them, you’ll usually get an error that blocks execution of any code that comes next, which can make debugging a serious pain.

Mulberry solves this by providing an interface for creating wrappers for PhoneGap APIs and plugins; it also has a few built-in wrappers. For example, here’s a wrapper for the notification API:

toura.app.PhoneGap.notification = function(pg, device) {
  return {
    alert : function(msg) {
      if (pg) {
        navigator.notification.alert(msg);
      } else {
        alert(msg);
      }
    }
  }
};

We set up the built-in wrappers using toura.app.PhoneGap.registerAPI(apiName, module), which you can use to create your own wrappers, too. Each wrapper is simply a function that gets two arguments: a boolean indicating whether PhoneGap is present, and a device object with information about the device that’s being targeted. Wrappers return an object with methods that become available at toura.app.Phonegap[apiName].

In the example of the notification API above, once the API is registered we can call the function toura.app.PhoneGap.notification.alert from anywhere in our code, and it will “just work” whether we’re developing in a browser or on device. Mulberry’s wrapper around the ChildBrowser plugin is a more complex example – it simulates ChildBrowser’s behavior in a desktop browser, and handles differences in the Android and iOS implementations behind the scenes, so opening a new URL in your app requires the exact same code regardless of the environment.

Rapid Switching Between Device-Specific Code Paths

Even though most of your JavaScript can be the same from one device to the next, sometimes you’ll need to branch your code to deal with a quirk of a particular OS. Working on the different code paths can be a challenge when you’re developing in the browser, because you need a way to tell your code which environment you want to be simulating.

Mulberry comes with a built-in server, and the server is set up so that switching between configurations is as simple as changing the URL. You can work on the iPad version of your app at http://localhost:3001/ios/tablet/, and then switch to working on the Android phone version of your app at http://localhost:3001/android/phone/. All of your code will automatically get access to information about the “device” you’re working on by calling toura.app.Config.get('device'), and of course any changes you make to your code or content will be visible as soon as you reload the page.

Remote Debugging

While browser-based development is great, nothing compares to testing your app on a device, but when you do, you’re suddenly in a world where console.log and alert feel like your only friends.

Every development build you make with Mulberry has a button in the lower right-hand corner of the screen marked “weinre”. If you haven’t seen weinre before, prepare for your life to be changed, because it lets you debug an app that’s running on your device from the comfort of your computer, using the Webkit developer tools you know and love.

As of this week, Toura is hosting its own version of weinre for Mulberry developers, and it’s connected directly to the weinre button in Mulberry apps. Read more about it here.

More to Come

The Toura Dev team doesn’t just work on Mulberry – we’ve also shipped more than 100 apps that use the underlying Mulberry technology, which means we’re always trying to make our own experience of the toolkit a pleasant one. Pretty much every developer-friendly feature we’ve added so far was added because we’re developers, too, and we hate when our tools get in our way. That said, we know tools can always be better – if there’s something we could do to make your life easier when developing mobile apps, we hope you’ll let us know.

Weinre: Sane, Hosted Debugging Tools for Your Mulberry Apps

| Comments

At Toura, we perform the vast majority of our day-to-day development in our desktop WebKit browsers - Chrome and Safari. Although the desktop browsers are a very close approximation to the mobile browsers, there’s no substitute for actually performing final QA and fit & finish checks on the mobile browsers themselves followed by a final sanity check with a compiled application.

What happens, though, if you find a display bug on device browser that you don’t see on the desktop? What about a JavaScript error in the WebView? How would you go about debugging that? Enter: Weinre.

Weinre, written by Patrick Mueller, provides a client/server environment to present you with a familiar “web inspector”-like experience on apps running on mobile device browsers and webviews.

Installing and configuring Weinre is simple enough and you can accomplish this by following the instructions at the Weinre website. OR, instead of going through all of that and keeping Weinre running (it requires a local process) and poking holes in your firewall, you can use our free hosted Weinre service!

To use the hosted Weinre service:

  1. Boot any app in development mode via:

    a. mulberry serve and point your device browser to your local Mulberry server OR

    b. mulberry test and run the app in your device simulator/emulator

  2. In the lower right hand corner, tap the “Weinre” button weinre button

  3. Go to Toura Weinre and enter the code displayed at the top of the page
  4. Click the provided link and debug away; that’s it!

Now you can develop on your local browser and perform final checks on your iOS and Android devices. If you encounter a bug, you can quickly and easily hook a remote web inspector to your page. Access the Javascript console, edit HTML, and more - right on the device!

An Opinionated Framework, Focused on Reuse

| Comments

One of the questions that’s come up several times in talking to other developers about Mulberry is this: How is Mulberry any different from all of the other mobile app development frameworks that are out there?

At Toura, we’ve been developing and using the technology behind Mulberry for more than a year, creating apps for clients that range from museums to major media companies. Because our core business is centered on the rapid creation of content-rich apps, we focused our development efforts on reuse from the start. That focus led us to an opinionated framework, one that makes some assumptions about the kinds of applications developers will build with it and about how those applications will be built.

Being opinionated results in a framework that dramatically reduces the effort that’s required to develop an app, and subsequent similar apps are easier still, because it’s vastly more likely that functionality developed for one app can be reused down the road.

While many JavaScript developers have come to think of DOM nodes as their building blocks, in Mulberry, the fundamental building block of an app is a component. The functionality of an app is described by how components, arranged on a page, display the app’s content and interact with each other.

Mulberry comes with lots of components and page layouts that use those components — making it easy to create content-rich applications that incorporate images, videos, map locations, and more — and it also provides simple APIs for creating custom components and adding them to the underlying framework. Custom and built-in components can be arranged into pages, and those pages can be enabled with pre-defined or custom interactions between components. Components neatly encapsulate their DOM structure, behavior, and state, and expose predictable interfaces that make them easy to integrate into applications. Mulberry apps aren’t about wiring up buttons and lists and grids — they’re about defining the interactions between far richer components, and then customizing the presentation layer to create a unique experience.

For Toura, all of this means that creating a particular kind of app has become a reliably repeatable process; more importantly, adding custom functionality to individual apps is also a reliably repeatable process whose results can be readily reused across multiple applications and platforms. Units of functionality — a Twitter feed component, say, or a component that displays a location’s name, address, phone number, web site, and map — take the place of DOM nodes as the building blocks for applications, and they can be mixed and matched and connected in any combination that makes sense.

We’ve placed a lot of value on being able to define new functionality, creating APIs like mulberry.component and mulberry.capability that let us set up new components and interactions without impacting existing functionality. We’ve also been diligent about keeping our presentation layer very loosely coupled with the behavior and data layers. All of this means that we can dramatically alter the “look and feel” of an app while leaving the underlying functionality largely intact, and create new experiences with shockingly little code.

Mulberry embraces a view of how we should be building apps that moves away from some of the DOM-centric habits that JavaScript developers have learned over the last few years — habits that have presented challenges as those same developers transition from building websites to building full-fledged apps. By rethinking the nature of apps to emphasize patterns that facilitate smart reuse, Mulberry makes app development and customization a simple, repeatable, and scalable endeavor.

Creating Custom Functionality With Mulberry

| Comments

Out of the box, Mulberry’s great at making simple content-driven apps, but one of the things we heard loud and clear during the closed alpha was that developers want to use Mulberry to make more complex, data-driven apps as well. No worries – Mulberry has that covered too.

In this post, we’ll take a look at what’s involved in creating a Twitter app – Twitter is the new “hello world”, after all. In the process, we’ll learn about creating custom components, templates, interactions, and routes. You can follow along by downloading Mulberry and heading to the demos/twitter directory.

To start, we’ll create a new Mulberry project named “twitter”. Once you’ve downloaded Mulberry and added the location of the binary to your $PATH, you can run the following command from any location on your filesystem where you have write permission:

1
mulberry scaffold twitter

This will create a new directory named twitter; you’ll want to cd twitter so you’re inside the project as you follow along.

An Overview of the App

The app we’re building will have two pages:

  • A home page that shows a list of people, and allows users to choose from the list; choosing a person from the list will show a map of the city where that person lives, their latest tweet, and their Twitter bio with a link to see all of their tweets.
  • A secondary page that shows a person’s 10 latest tweets.

Creating the Data

Since this is a data-driven app, the first thing we need to do is provide it with some seed data to drive it. We’ll run this command from the root of our Mulberry project:

1
mulberry create_data users

This creates a data file named users.yml in the assets/data/ directory inside your project. It’s an empty file, so we’ll add some information about our users to the file, in the YAML format:

demos/twitter/assets/data/users.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
type: users
users:
  - name: Paul Irish
    twitter: paul_irish
    location:
      lat: 37.7749295
      lng: -122.4194155
    image: https://twimg0-a.akamaihd.net/profile_images/1326877605/greenavatar_crop_normal.jpg
  - name: Alex Sexton
    twitter: SlexAxton
    location:
      lat: 30.267153
      lng: -97.7430608
    image: https://twimg0-a.akamaihd.net/profile_images/1384837213/SlexAxtonAvatar_normal.jpg
  - name: Adam Sontag
    twitter: ajpiano
    location:
      lat: 40.7143528
      lng: -74.0059731
    image: https://twimg0-a.akamaihd.net/profile_images/1396366703/twitpic_pool_normal.png
  - name: Rebecca Murphey
    twitter: rmurphey
    location:
      lat: 35.9940329
      lng: -78.898619
    image: https://twimg0-a.akamaihd.net/profile_images/1447727594/IMG_8534_normal.jpg

Note that there are two pieces to this data: a type property, and a users array that contains the actual data. The type property will help us locate the data later; its presence means that a page can have easy access to many different kinds of data.

Now that we’ve added this data to our project, we can move on to setting up the home page.

Creating the Home Page

When you scaffold a Mulberry app, a home page is automatically created in your project’s pages/ directory. By default, this page uses the home-tablet and home-phone templates, but we’ll want to change that, as well as tell the page that it should have access to the data we created. Here’s what our home.md file looks like when we’re done:

demos/twitter/pages/home.md
1
2
3
4
5
6
---
title: Home
template: twitter
data:
  - users.yml
---

Next, let’s take a closer look at the mockup of the home page. There are four distinct pieces of functionality on the page, and in Mulberry we refer to these pieces of functionality as “components”:

Breaking the home page into components

Mulberry has a GoogleMap component built in, but we’ll need to create custom components for the rest:

1
mulberry create_component LatestTweet UserInfo UserList

Running this command creates skeleton files in the project’s javascript/components directory for each of the components, and adds dojo.require statements to the project’s javascript/base.js file so that the components will automatically be available to the rest of your code.

Writing Our Custom Components

Components in Mulberry have three jobs:

  • receiving data from an external source
  • rendering that data
  • announcing user interaction with the component, if applicable

Mulberry automatically provides every component on a page with the information associated with that page; it puts that information into a component’s baseObj property. This means that our components will automatically get access to the user data we associated with the home page.

Because we remembered to add a type property to that data, fetching the array of people in the users.yml file from inside a component is easy:

1
this.baseObj.getData('users').users

Let’s look at the UserList component as an example. Its job is to receive data (in this case, a list of people); render that data (in this case, as an unordered list), and then announce user interaction (in this case, when a user selects a person from the list).

Here’s what our UserList component ends up looking like:

demos/twitter/javascript/components/UserList.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
dojo.provide('client.components.UserList');

mulberry.component('UserList', {
  componentTemplate : dojo.cache('client.components', 'UserList/UserList.haml'),

  prep : function() {
    this.users = this.baseObj.getData('users').users;
  },

  init : function() {
    this.connect(this.domNode, 'click', '_handleClick');
  },

  _handleClick : function(e) {
    var target = e.target;

    while (target !== this.domNode && target.parentNode !== this.domNode) {
      target = target.parentNode;
    }

    if (target.nodeName.toLowerCase() !== 'li') { return; }
    if (dojo.hasClass(target, 'selected')) { return; }

    this.query('.selected').removeClass('selected');
    dojo.addClass(target, 'selected');

    this.onSelect(dojo.attr(target, 'data-twitter-username'));
  },

  onSelect : function(username) {
    // stub for connection
  }
});

The prep and init methods are called automatically; the prep method is called before the component’s DOM structure is created, and the init method is called after its DOM structure is created.

In the case of the UserList component, we use the prep method to prepare the data associated with the page. Then, in the init method, we tell the component to listen for user interaction – in this case, a click on the component’s root DOM node. A click on that node will result in the component’s _handleClick method being called; if _handleClick decides that the click was on a list item, then it will call the component’s onSelect method, passing it the username of the person who was selected.

Note that the onSelect method doesn’t do anything. Later in this post, we’ll see how we can “connect” to that method from another part of our code, but it’s important to understand this key concept in Mulberry apps: components should never directly affect other pieces of the application. Their job is to receive, render, and announce – nothing more.

Some of the components in our app will need to receive data once they’re already on the page. We can use “setter methods” to make this possible. For example, the UserInfo component should be able to display a different user without having to re-create the component from scratch. To allow this, we create a _setUserAttr method on the UserInfo component

demos/twitter/javascript/components/UserInfo.js
1
2
3
4
5
6
7
8
9
10
11
dojo.provide('client.components.UserInfo');

mulberry.component('UserInfo', {
  componentTemplate : dojo.cache('client.components', 'UserInfo/UserInfo.haml'),

  _setUserAttr : function(user) {
    this.nameNode.innerHTML = user.name;
    this.twitterLinkNode.href = '#/twitter/' + user.twitter;
    this.bioNode.innerHTML = user.bio || '';
  }
});

This means that any code that has access to an instance of the UserInfo component can do the following:

1
myUserInfoInstance.set('user', userObject);

Whenever the set method is called on a component instance, the component looks for a setter method that matches the property name passed as the first argument to set. If it finds a corresponding method, it calls it; if it does not find a corresponding method, then it simply sets the value of a property on the component instance.

(This functionality is based entirely on Dojo’s dijit._WidgetBasesee how it works here.)

In the UserInfo component, we refer to several properties that do not seem to be defined anywhere: this.nameNode, this.bioNode, etc. These properties get set automatically by the component’s template using “attach points.” Here’s the UserInfo template, located at javascript/components/UserInfo/UserInfo.haml:

demos/twitter/javascript/components/UserInfo/UserInfo.haml
1
2
3
4
5
.component.user-info
  %h1{ dojoAttachPoint : 'nameNode' }
  %p.bio{ dojoAttachPoint : 'bioNode' }
  %p
    %a{ dojoAttachPoint : 'twitterLinkNode' } View all tweets

Using attach points greatly reduces the need for querying the DOM; you can create an attach point on any node, and then refer to that node from inside your component by using the attach point’s name:

1
this.myAttachPointName

Creating the Page Template

Once we’ve created our components (see the demo in the repo for details on how the rest of the components are set up), it’s time to assemble them into a page template. Earlier, we told our home page to use the twitter template; now, we’ll ask Mulberry to create that template for us:

1
mulberry create_template twitter

This creates a file at templates/twitter.yml that contains the skeleton of a template. We’ll fill it out with the details about how we want our page to look:

demos/twitter/templates/twitter.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
twitter:
  screens:
    - name: index
      regions:
        - size: fixed
          components:
            - custom.LatestTweet
        - size: fixed
          components:
            - GoogleMap
        - containerType: column
          regions:
            - className: user-list
              size: fixed
              components:
              - custom.UserList
            - className: user-info
              size: flex
              components:
              - custom.UserInfo

Again, we use YAML here, this time to say that we want a page template named “twitter” that has one screen named “index” (page templates can have more than one screen, with the intention that only one screen is visible at any time, but the details of that are beyond the scope of this post). That screen is broken down into regions:

  • The first region will be a fixed height, and it will contain our custom LatestTweet component.
  • The second region will be a fixed height, and it will contain the built-in GoogleMap component.
  • The third region will be split into two sub-regions, which will be displayed as columns (that is, side-by-side). The first sub-region will contain the custom UserList component, and will get the class name user-list so that we can target it with CSS; the second sub-region will contain the custom UserInfo component, and will get the class name user-info. The sub-region that contains UserList will be fixed-width; the UserInfo sub-region will flex to fill the remaining size.

At this point, we should be able to serve our application using the Mulberry development server:

1
mulberry serve

If you navigate to the home page, you’ll see that all of the components display in the proper arrangement, but not much is happening yet. We need some data.

Loading the External Data

We want to create an interface to the Twitter data we’ll need in order to populate our pages. For our home page, we’ll need a user’s latest tweet, as well as their bio; for our secondary page, we’ll need a users 10 most recent tweets. We can start by asking Mulberry to create a skeleton file for our datasource:

1
mulberry create_datasource Twitter

This creates a file at javascript/data/Twitter.js, and adds a dojo.require statement to our project’s javascript/base.js file. There’s not much here (we’re working on a more elaborate API for remote data sources), so for now it’s up to us to fill in the details:

demos/twitter/javascript/data/Twitter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
dojo.provide('client.data.Twitter');

mulberry.datasource('Twitter', {
  getLatest : function(username) {
    return this._get(username, 1).then(dojo.hitch(this, '_getLatest'));
  },

  getAll : function(username) {
    return this._get(username, 10).then(dojo.hitch(this, '_getAll'));
  },

  _get : function(username, count) {
    var url = 'http://twitter.com/status/user_timeline/${username}.json?count=' + (count || 10);

    return dojo.io.script.get({
      url : toura.tmpl(url, { username : username }),
      callbackParamName : 'callback'
    });
  },

  _getLatest : function(data) {
    if (!data || !data.length) { return false; }
    return this._formatTweet(data[0]);
  },

  _getAll : function(data) {
    return dojo.map(data, this._formatTweet);
  },

  _formatTweet : function(tweet) {
    return {
      text : tweet.text,
      date : dojo.date.locale.format(new Date(tweet.created_at)),
      bio : tweet.user.description
    };
  }
});

There’s not much that’s interesting here, except that this code takes advantage of the fact that all async methods in Dojo – such as dojo.io.script.get – return a “promise.” Promises are incredibly useful structures that greatly facilitate the development of asynchronous processes. A promise is, quite literally, a promise: a promise object is a guarantee that when the async operation is completed, the promise will execute any function that was passed to it via the promise’s then method.

In this example, the getLatest method receives the promise returned by the _get method, then attaches a callback to it using the then method of the promise. The then method ultimately returns another promise, which is resolved with the return value of the _getLatest method. This means that another piece of code with access to an instance of the Twitter datasource can do this:

1
2
3
myTwitterInstance.get('rmurphey').then(function(tweets) {
  console.log('these are the tweets', tweets);
});

The code inside the function will run once the tweets have been fetched.

Connecting the Components

We have our page template, we have our components, and we have our data – now it’s time to glue it all together. Mulberry uses “capabilities” to broker communication between components and datasources. We’ll ask Mulberry to create a capability that we’ll use to encapsulate the functionality of our Twitter page:

1
mulberry create_capability Twitter

This creates a skeleton file at javascript/capabilities/Twitter.js, and adds a dojo.require statement to our project’s javascript/base.js file.

Capabilities have a requirements object that indicates the components that it expects to be present. It assigns those components names that will be used to reference the components inside the capability. Capabilities also have a connects array, which contains zero or more arrays that describe how the capability will react when a certain method on a component is called. Remember how we had an empty onSelect method in our UserList component? Our Twitter capability connects to it, and describes how the rest of the page should react. Finally, capabilities have an init method, where you can do initial setup of components that might be page-dependent.

Here’s the Twitter capability in its entirety; you can see how all of the pieces we’ve talked about above come together:

demos/twitter/javascript/capabilities/Twitter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
dojo.provide('client.capabilities.Twitter');

mulberry.capability('Twitter', {
  requirements : {
    latestTweet   : 'custom.LatestTweet',
    map           : 'GoogleMap',
    userList      : 'custom.UserList',
    userInfo      : 'custom.UserInfo'
  },

  connects : [
    [ 'userList', 'onSelect', '_onUserSelect' ],
    [ 'map', 'onMapBuilt', '_onMapBuilt' ]
  ],

  init : function() {
    this.users = this.baseObj.getData('users').users;
    this.twitter = new client.data.Twitter();

    var user = this.users[0];
    this.userInfo.set('user', user);
    this._loadUser(user);
  },

  _onMapBuilt : function() {
    this.map.set('center', this.users[0].location);
  },

  _onUserSelect : function(username) {
    var user = dojo.filter(this.users, function(u) {
      return u.twitter === username
    })[0];

    this._loadUser(user);
    this.map.set('center', user.location);
  },

  _loadUser : function(user) {
    var req = this.twitter.getLatest(user.twitter);
    req.then(dojo.hitch(this.latestTweet, 'set', 'tweet'));
    req.then(dojo.hitch(this, function(tweet) {
      user.bio = tweet.bio;
      this.userInfo.set('user', user);
    }));
  }
});

Adding a Custom Route for the User Tweets Page

Our last task before our Twitter app is complete is to create the secondary page, which shows a specific person’s recent tweets. We already know how to set up a page template and a new component, and how to fetch the Twitter data using the datasource we created, but how do we get the page to figure out which person’s Tweets to load?

For the sake of this discussion, let’s assume that we’ve created another page template named user, and that the page template includes a custom component named Tweets. (You can see this page template and the custom component in the demo in the repo.)

In the UserInfo component, we indicated that we want to access the page by visiting #/twitter/<username>. We need to define a custom route that will run when a user navigates to this kind of page; the route will need to capture the username from the URL’s hash, and then get the proper data to the components on the page. We can add the following to our project’s javascript/base.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
dojo.subscribe('/routes/loaded', function() {

  mulberry.app.Router.registerRoute('/twitter/:username', function(params) {
    var twitter = new client.data.Twitter(),
        page = mulberry.app.PageFactory.createPage({
          pageController : 'user',
          tweets : twitter.getAll(params.username),
          name : params.username
        });

    mulberry.app.UI.showPage(page);
  });

});

This code tells the Mulberry router to be on the lookout for a URL hash that looks like /twitter/:username. When the router sees this hash, it should run the provided function. The provided function receives a params object, which contains any parameters that were included in the hash. So, in this case, our params object would have a username property, containing whichever username was in the URL hash.

Inside the function, we create an instance of our Twitter datasource, and then we ask the Mulberry PageFactory to create a page for us by passing it an object. This object will be available to all components on the page; it also must have a pageController property, which the PageFactory will use to determine which page template to use in creating the page.

The object that we pass to the PageFactory’s createPage method also has a tweets property, and here we see the power of promises again. We use the tweets property to pass the promise that’s returned by the Twitter datasource; by doing this, we allow the Tweets component to receive data without interacting directly with the datasource. The Tweets component simply attaches a callback to the promise using the promise’s then method. When the promise resolves, it provides an array of tweets to any callbacks that were attached; the Tweets component uses that array to populate itself.

Conclusion

Mulberry’s built-in components and page templates are focused on facilitating the rapid creation of static content apps, but the underlying patterns, tools, and architecture provide powerful tools for building all kinds of apps. If you build something interesting, we hope you’ll let us know!