Welcome to Enyo 2.0!
If you're reading this tutorial, you're probably wanting to see how Enyo can help you make web applications. We'll do that over a few steps by showing how to make a simple application that shows different Twitter searches.
To start, we'll need to make an application folder on your desktop. This will hold the HTML file that hosts your application, the JavaScript code for the app, along with any assets used by the app.
Into that folder, we'll put a copy of the enyo-2.0 folder from the ZIP file. We'll be referring to the minimized JS source and CSS files from this folder in our HTML file.
Let's create app.html and start with these contents
<!DOCTYPE html>
<html>
<head>
<title>My Twitter Search</title>
<link rel=stylesheet href="http://enyojs.com/enyo-2.0b/enyo.css">
<script src="http://enyojs.com/enyo-2.0b/enyo.js"></script>
</head>
<body>
</body>
</html>
This is a minimal Enyo app. It's an empty HTML document that pulls in the base Enyo JavaScript file and style sheet. There's a title, but no actual page content and no calls into the Enyo code. Later on, we'll fix that, but for now, let's make this blank page a playground. If you're following on at home, you can open the JavaScript console and enter much of the following code directly into the console window.
In Enyo, all APIs, both methods and objects, are stored in a master "enyo"
object that acts as a namespace for the framework. In general, you won't see
things like $() defined in enyo. This can make the code more verbose, but
it also limits the amount of interference between Enyo code and your own code
or that of other frameworks. Libraries may extend that enyo namespace,
especially ones from the enyojs.com site, will usually add into the enyo
object, sometimes with a sub-namespace, e.g. enyo.dom.
The core Enyo code consists of four major functional areas:
We'll look at each part as we build a simple application.
Enyo's core provides a kind called enyo.Control. This is the building block
used to make all the widgets and content that will be rendered into your
webpage.
enyo.Control is a kind defined in the enyo code using the enyo.kind() method, so to create a new control, you call it as a constructor with a object containing properties for the new instance. Let's start by making a control that just outputs a paragraph with "Hello, World" in blue text.
var control = new enyo.Control({
name: "helloworld",
tag: 'p',
content: 'Hello, World!',
style: 'color: black'
});
After running this code, we have an enyo.Control object in the control
variable, but nothing has been added to our HTML. Enyo has two ways to make a
control visible. The first is the .write() method. This causes the control
to render it's contents to a string which is then output into the DOM using
the document.write() call. This is useful if you're invoking controls at
page load time. The output of this one would be
<p style="color: blue;" id="helloworld">Hello, World!</p>
The other method that's often used is .renderInto(domNode). This replaces
an element in the existing DOM with the HTML output from the control. A
common pattern for Enyo applications is to define one large control that's the
application with a whole hierarchy of controls, then render that into
document.body, replacing all the HTML that loaded with the page.
Let's finish this example by rendering control into document.body.
control.renderInto(document.body);
Enyo special cases the act of rendering a control into document.body and automatically adds a 'enyo-fit' class to the new element.
You'll notice that the generated HTML included an id based on the name
property of the control. Enyo keeps track of the names that it generates so
even if you have two controls with the same name in different parts of your
application, they'll have unique IDs when rendered into the document. Because
of this, the way to get access to the DOM node that goes with a control is to
call its .hasNode() method instead of looking up by ID.
If we wanted to make lots of "Hello, World" controls, we might want to define our own kind. This can be done like this:
enyo.kind({
name: "HelloWorld",
kind: enyo.Control,
tag: 'p',
content: 'Hello, World!',
style: 'color: blue'
});
Once we've run this, we could then say
var control2 = new HelloWorld({});
If we wanted to override that control's content, we could either say
var control3 = new HelloWorld({
content: "Hello, Everyone!"
});
and render it
control3.renderInto(document.body);
or change it after creation by calling
control3.setContent("Goodbye, World!");
You'll notice that if you've already rendered the control into the page,
calling .setContent() will cause it to be rendered again with the change.
If you remember from enyo.Object, any published properties of an object have
getProperty and setProperty methods created automatically. Calling
.setContent() triggers a call to the control's .contentChanged() method,
and that code looks to see if the control has been rendered to the DOM, and if
so, it calls .render() to refresh everything.
This works for the other properties. You could call
control3.setStyle("color: red");
and the content would stay the same, but the style attribute would change.
For style changes, you'll more likely to want to use methods like
.addClass() and .applyStyle() which are smart about how they adjust the
properties to preserve changes made by other code. We could make the same
effect by writing
control3.applyStyle("color", "black");
We need to make two different kinds of objects for showing Twitter search results. First, we need a display widget that can take the raw data of a tweet and show it nicely on a web page. For this, we'll make a new object that's derived from enyo.Control.
Controls don't simply render their own content. They also have the ability to host other controls, creating a whole hierarchy of DOM elements. For example, we could make a control that hosted a header and unordered list with a few list elements by writing
var list = new enyo.Control({
tag: "div",
components: [
{ tag: "p", content: "The Additive Primary Colors" },
{ tag: "ul", components: [
{ tag: "li", name: "red", content: "red" },
{ tag: "li", name: "green", content: "green" },
{ tag: "li", name: "blue", content: "blue" } ] } ]
});
list.renderInto(document.body);
This works because a enyo.Control is derived from enyo.Component. Components
can act as hosts to a whole hierarchy of items. Any component that's listed
in the components property will be added to a special hash called $ based
on the name property. So, if we wanted to actually make the red list item
red, we could write
list.$.red.applyStyle("color", "red");
list.$.green.applyStyle("color", "green");
list.$.blue.applyStyle("color", "blue");
and it would change. In this code, red's parent is the unordered list, but it's owner is the div control at the top level.
Notice that if you try to modify the content property of list, nothing seems
to happen. For a control, having children takes precedence over having content,
so when the control is rendered, you only get the content of the children, not your own.
Going more specific, let's look at what data Twitter sends to you when you use it's APIs to get a list of tweets. When you use the JSON interface, the tweets you search for come back as an array of objects. We can ignore most of those properties, but we want to pay attention to a few: name, avatar, and text.
A simple way to render this in HTML would be to make a div with a border and some internal padding, put the avatar icon inside it floated to the right, then show the username in bold and the text in a normal font. For example:
<div style="border: 2px; padding: 10px; margin: 10px; min-height: 50px">
<img src="http://twitter.com/imgs/a.png"
style="width:50px; height:50px; float: left; padding-right: 10px">
<b>handle:</b> <span>tweet text</span>
</div>
which renders as
handle: tweet text
Let's make a new kind called Tweet that will render this. We'll expose those three data items as published properties. This will cause us to have automatically generated setter methods.
In the code below, you'll note that we used the same names for the properties
and for the components that those properties affect. This is OK, as the
property is directly on the created object, while the components live in the
$ hash.
In our components definition, we rely upon the default kind set on enyo.Control. If you don't specify a kind of a the component, it will assume enyo.Control. The default is a property you can set when you define a kind; it doesn't have to be the same as yourself. You could use this to make a list widget where all the children controls were a list element type, for example.
As part of this, we also override the .create() method and call the changed
methods of all the properties. This is a very common components with
properies. Since these all are being changed pre-rendering, the actual call
is pretty lightweight. If you changed all of those properties after the
control had been rendered, the control would be re-rendered after each call.
It's very important that the code for this override start with the
this.inherited(arguments) call, since that's what lets all of the inherited
creation code keep working.
enyo.kind({
name: "Tweet",
kind: enyo.Control,
tag: "div",
style: "border-style: solid; border-width: 2px; " +
"padding: 10px; margin: 10px; min-height: 50px",
published: {
icon: "",
handle: "",
text: ""
},
components: [
{ tag: "img", name: "icon",
style: "width: 50px; height: 50px; float: left; padding-right: 10px" },
{ tag: "b", name: "handle" },
{ tag: "span", name: "text" }
],
create: function() {
this.inherited(arguments);
this.iconChanged();
this.handleChanged();
this.textChanged();
},
iconChanged: function() {
this.$.icon.setAttribute("src", this.icon);
},
handleChanged: function() {
this.$.handle.setContent(this.handle + ": ");
},
textChanged: function() {
this.$.text.setContent(this.text);
}
});
If I created a tweet object using
var t = new Tweet({
icon: "touchhead_sq_normal.jpg",
handle: "unwiredben",
text: "This is my tweet"});
Then render it into document.body or a <div> on a blank page,
t.renderInto(document.body);
I get output that looks like

We could even make a whole column of these by making a Control object to hold the Tweets, then adding them as children using the `.createComponent()' call to create new Tweet objects with the control as it's owner. Try
var l = new enyo.Control;
l.createComponent({
kind: Tweet,
icon: "touchhead_sq_normal.jpg",
handle: "unwiredben",
text: "First tweet"});
l.createComponent({
kind: Tweet,
icon: "touchhead_sq_normal.jpg",
handle: "unwiredben",
text: "Second tweet"});
l.createComponent({
kind: Tweet,
icon: "touchhead_sq_normal.jpg",
handle: "unwiredben",
text: "Third tweet"});
l.renderInto(document.body);
We didn't include names for these, so enyo will generate names based on the name of the kind. If you look at the l.$ array, you'll see items named "tweet", "tweet2", and "tweet3". If you wanted to remove the second item, you could then write
l.$.tweet2.destroy();
and that would both destry the second tweet object and let the control that
owns it know to remove it from its list. If you then called
.createComponent() again to add a new tweet, it would be named "tweet4", as
names aren't reused.
Enyo doesn't provide APIs to reorder components; you always add to the end of the list. If you're making a control that needs an embedded list with UI above and below it, you'll need to embed another control in the middle to serve as the location to add and remove items.
So far, what we've seen is great for static content that doesn't interact with the user. However, that's not much of an application. In order to work with user input, we'll need to start handling events.
Enyo provides its own event abstraction on top of the standard HTML DOM event model. The main reasons for this are to support routing events between controls and components and to better handle synthetic events where the framework abstracts away things like mouse events versus touch events.
Let's start by making an application kind that's going to hold simple controls
for a button and a div. We'll make the button add a new Tweet to the
div container.
enyo.kind({
name: "TweetApp",
kind: enyo.Control,
components: [
{ tag: "button", content: "Add Tweet", ontap: "addTweet" },
{ tag: "div", name: "tweetList" }
],
nextTweetNumber: 1,
addTweet: function(inSource, inEvent) {
this.createComponent({
kind: Tweet,
container: this.$.tweetList,
icon: "touchhead_sq_normal.jpg",
handle: "unwiredben",
text: "A new tweet! #" + this.nextTweetNumber
});
++this.nextTweetNumber;
this.$.tweetList.render();
}
});
var tweetApp = new TweetApp();
tweetApp.renderInto(document.body)
Compared to older kind definitions, there are a two new things here. First,
before the definition of the addTweet method, we also defined an internal
property called nextTweetNumber. Since this isn't in the published array,
there's no set/get methods. Each instance of a TweetApp gets its own copy
of that value.
Second, in defining the button in components, we defined an ontap
property. This is how you hook up event handlers in enyo; you provide a
property named based on the event type and set its value to the name of the
method on the component's owner that's called. Enyo automatically hooks up
most of the common DOM events; we could have used "onclick" instead, but we
chose the "tap" event because the framework synthesizes it from either mouse
or touch events.
The addTweet handler follows a standard model. The first parameter to any
handler is the object that's the source of the event. The second parameter is
an event object, and depending on the event, there could be other parameters
with additional information. However, since we know there's only control
hooked up to addTweet and a tap is such a simple event, we ignore the
parameters and just do our action, adding a new Tweet to the list container.
Another thing to note is a new attribute in the .createComponent() call,
container. This lets us create the Tweet object with its owner being the
top-level application object, but it being added into the componnets that are
physically part of tweetList.
Unlike modifying attributes on a control, adding new components doesn't cause
the control to be re-rendered immediately. Instead, you have to call the
.render() method yourself to get the new internals to be output. This is an
optimization so you don't do too much work when adding mulitiple components;
instead of rendering things over-and-over, it's all delayed until the whole
set is ready.
Now that we've defined a few kinds, it might be useful to put them in source
files for application. Enyo apps are usually structured as a set of .js files
bundled together in a package. This means you've added a package.js file
into your application which lists all the JS and CSS source files usign a call
to the enyo.depends() method.
If we saved our Tweet kind definition into the file Tweet.js and our
TweetApp kind into TweetApp.js, then our pacakge.js file would look like
enyo.depends(
"Tweet.js",
"TweetApp.js"
);
You can mention multiple source files as arguments to enyo.depends. The order is important, as that determines the order in which the JS code is loaded and run. You need to list kinds that stand alone first, then list the kinds that depend on those.
You can also mention directory names; in that case, the Enyo loader will try to open a package.js in that folder and process its items. This can continue recursively for a while as shown by the Enyo core source code, where one top-level package.js file lists all the subdirectories where the framework code lives.
For an application in development, your HTML file will often have
two <script> tags, one to load the minimized enyo.js and one to
load your local package.js file which then references all your source,
for example:
<link rel=stylesheet href="http://enyojs.com/enyo-2.0b/enyo.css">
<script src="http://enyojs.com/enyo-2.0b/enyo.js"></script>
<script src="package.js"></script>
For deployment, the Enyo minimizer script knows how to look through the package.js files and use that to make minified JS and CSS files for your application.
Now that we've figured out how to display tweets and handle some user input, lets wrap this up by talking to a remote web server. Twitter is a very popular service, one that has several stable APIs, so let's try this out.
To do a Twitter search, we need a field to input a search term, a button that starts the search, and code to handle making the request, interpreting the results, and making a list of tweets to display.
For many web APIs, it wouldn't be possible to make the request directly to the
API. This is because of a security feature of browsers that limits the
servers with which a XMLHttpRequest can communicate to ones sharing the same
origin. However, as web APIs became more and more popular, a scheme was
devised to get around this limitation. Web pages have always been able to
load <script> tags from foreign servers, so some developers realized that
the script could be dynamically generated JavaScript code that just sets a
variable or calls a function. This scheme has been named JSONP, or JavaScript
Object Notation with Padding.
Enyo has a built-in method that wraps XmlHttpRequests (the enyo.Xhr and
enyo.Ajax code), but nothing for JSONP. To work around this, I wrote my own
kind that subclasses from enyo.Async to do JSONP requests. We'll use
the new enyo.JsonpRequest here. The code is included for reference, but
you don't need to understand it all.
/**
A specialized form of enyo.Async that is used for making JSONP requests to a
remote server. This differs from normal XmlHTTPRequest calls because the
external resource is loaded using a <script> tag. This allows bypassing same-
domain rules that normally apply to XHR since the browser will load scripts
from any address.
*/
enyo.kind({
name: "enyo.JsonpRequest",
kind: enyo.Async,
published: {
//* The URL for the service.
url: "",
/**
name of the argument that holds the callback name. For example, the
Twitter search API uses "callback" as the parameter to hold the
name of the called function. We will automatically add this to
the encoded arguments.
*/
callbackName: ""
},
statics: {
// counter to allow creating unique names for each JSONP request
nextCallbackID: 0,
// For the tested logic around adding a <script> tag at runtime, see the
// discussion at the URL below:
// http://www.jspatterns.com/the-ridiculous-case-of-adding-a-script-element/
addScriptElement: function(src) {
var script = document.createElement('script');
script.src = src;
var first = document.getElementsByTagName('script')[0];
first.parentNode.insertBefore(script, first);
return script;
},
removeElement: function(elem) {
elem.parentNode.removeChild(elem);
}
},
//* @protected
constructor: function(inParams) {
enyo.mixin(this, inParams);
this.inherited(arguments);
},
//* @public
//* starts the JSONP request
go: function(inParams) {
this.startTimer();
this.jsonp(inParams);
return this;
},
//* @protected
// for a string version of inParams, we follow the convention of
// replacing the string "=?" with the callback name. For the more
// common case of inParams being an object, we'll add a argument named
// using the callbackName published property.
jsonp: function(inParams) {
var callbackFunctionName = "ENYO_JSONP_CALLBACK_" +
(enyo.JsonpRequest.nextCallbackID++);
var parts = this.url.split("?");
var uri = parts.shift() || "";
var args = parts.join("?").split("&");
var body;
if (enyo.isString(inParams)) {
body = inParams.replace("=?", "=" + callbackFunctionName);
}
else {
var params = enyo.mixin({}, inParams);
params[this.callbackName] = callbackFunctionName;
body = enyo.Ajax.objectToQuery(params);
}
args.push(body);
var url = [uri, args.join("&")].join("?");
var script = enyo.JsonpRequest.addScriptElement(url);
window[callbackFunctionName] = enyo.bind(this, this.respond);
// setup cleanup handlers for JSONP completion and failure
var cleanup = function() {
enyo.JsonpRequest.removeElement(script);
window[callbackFunctionName] = null;
};
this.response(cleanup);
this.error(cleanup);
}
});
Here's an example of using the Twitter search API. If you fetch the URL
http://search.twitter.com/search.json?q=enyo&callback=cb, you'll get back
something that is structured like:
cb({
// header about request
"results": [
{
// tweet data
},
{
// tweet data
},
...
]
});
They call sends back the normal JSON results that their search API would
return without the "callback" parameter, but now it's wrapped with the padding
of cb( in the front and ); at the end. For more details on this
parameter and all the others supported, see the Twitter Search API docs.
(A quick tip: the website JSONLint is great for making a hard-to-read pile of JSON data readable. It complains about JSONP results, but still manages to reformat all of the internals for viewing. I used it with this output to determine what fields are returned.)
Out of the results header, there's not much we need to care about. There are fields indicating the current maximum tweet ID and how to request the next page of data, but for now, we just care about the tweets stored in "results".
Let's update our app to a new kind, TwitterSearchApp, which we'll define with an input field, a button, and our container.
enyo.kind({
name: "TwitterSearchApp",
kind: enyo.Control,
components: [
{ tag: "input", name: "searchTerm" },
{ tag: "button", content: "Search", ontap: "search" },
{ tag: "div", name: "tweetList" }
],
addTweet: function(inResult) {
this.createComponent({
kind: Tweet,
container: this.$.tweetList,
icon: inResult.profile_image_url,
handle: inResult.from_user,
text: inResult.text
});
},
search: function() {
var searchTerm = this.$.searchTerm.hasNode().value;
var request = new enyo.JsonpRequest({
url: "http://search.twitter.com/search.json",
callbackName: "callback"
});
request.response(enyo.bind(this, "processSearchResults"));
request.go({ q: searchTerm });
},
processSearchResults: function(inRequest, inResponse) {
if (!inResponse) return;
this.$.tweetList.destroyClientControls();
enyo.forEach(inResponse.results, this.addTweet, this);
this.$.tweetList.render();
}
});
var twitterSearchApp = new TwitterSearchApp();
twitterSearchApp.renderInto(document.body);
The search() method grabs the search term from the input field, creates a
JsonpRequest object, sets up a success callback, then starts the request
running. We use a new Enyo method here, enyo.bind(); this lets you run a
method in a bound "this" context and is similar to the ECMAScript 5 .bind()
method for functions. The Enyo version has the advantage of doing property
lookups on the provided this object and it works on older JavaScript engines
that don't directly support the new call.
We display the results when our .processSearchResults() method is called by
the JSONP code. This happens when we get a result back from Twitter. We first
destroy any existing tweets that are displayed from a previous search, then we
use the enyo.forEach() utility method to iterate through the array, calling
.addTweet() on each on.
In our new addTweet method, we map from fields in the Twitter API results to
our existing Tweet UI control. The from_user field maps to our handle,
the text field maps to our text, and the profile_image_url field is a
match for icon. Unlike the previous version, we don't immediately call
.render()` here, but instead delay that until we've added all of the results.
I've included a working version of this final app as http://enyojs.com/tutorial/search.html. However, there's plenty left to do. Input validation and error handling are both missing here. There's also nothing shown to the user in the time between the user hitting the search button and the results being displayed. If you're on a mobile device or some other slow internet link, it may be useful to show a spinner or some other indication that the request has started.
It also would be good to style the results a bit. We worked with inline
styles, but you can also use CSS classes and have an external stylesheet. Look
in the docs for enyo.Control() for the methods and attributes that control
adding and removing classes from controls.
For more help with Enyo, check out the samples we've got on the Enyo web site and ask questions in our developer forums. Thanks for sticking with this, and enjoy coding with Enyo!