Chapter 2. Core Concepts

Introduction

In this chapter, we’ll cover the core concepts of Enyo that we only touched on in the last chapter. You will be able to write powerful apps after absorbing the information in just this chapter. We’ll go over the concepts one by one and illustrate each with code you can run in your browser.

One of the driving ideas behind Enyo is that combining simple pieces creates complex apps. Enyo introduces four concepts to assist you: kinds, encapsulation, components, and layout. We’ll cover components and layout more thoroughly in Chapter 3 and Chapter 4, respectively.

Kinds

Enyo is an object-oriented framework. It is true that every JavaScript application regardless of framework (or lack thereof) contains objects. However, Enyo’s core features provide a layer on top of JavaScript that makes it easier to express object-oriented concepts such as inheritance and encapsulation.

In Enyo, kinds are the building blocks that make up apps. The widgets that appear on screen are instances of kinds, as are the objects that perform Ajax requests. Kinds are not strictly for making visual components. Basically, kinds provide a template from which the actual objects that make up your app are generated.

Be Kind

One of the simplest possible declarations for a kind is:

enyo.kind({ name: "MyKind" });

enyo.kind is a “factory” for creating new kinds. In this case, we get a new object that inherits from the Enyo control kind, enyo.Control. Control is the base component for objects that will render when placed on a web page.

When creating kinds, you pass in an object that defines the starting state of the kind as well as any functions it will need. For example, control kinds have a content property:

enyo.kind({ name: "MyKind", content: "Hello World!" });

As you saw in Chapter 1, when rendered onto a page this code will create a div tag with the content placed in it. To render this into a body on a web page, you can use the renderInto() function.

We can add behaviors to our kind by adding functions (for example, the tap handling function we added to the Light kind). As you may recall, we referenced the function name in the handlers block using a string. We use strings so Enyo can bind our functions as kinds are created.

Encapsulation

Encapsulation is a fancy computer science term that refers to restricting outside objects’ access to an object’s internal features through providing an interface for interacting with the data contained in the object. JavaScript does not have very many ways to prohibit access to an object’s data and methods from outside, so Enyo promotes encapsulation by giving programmers various tools and conventions.

By convention, Enyo kinds should have no dependencies on their parent or sibling kinds and they should not rely on implementation details of their children. While it is certainly possible to create Enyo kinds that violate these rules, Enyo provides several mechanisms to make that unnecessary. Those mechanisms include published properties and events.

By being aware of encapsulation, Enyo programmers can tap in to the benefits of code reuse, easy testing, and drop-in components.

Published Properties

Kinds can declare published properties (for example, the color and powered properties from Chapter 1). These properties automatically get “getter” and “setter” functions as well as a mechanism for tracking changes to the properties. This is ideal for enforcing acceptable types and values. To declare published properties on a kind, include a section called published in the kind declaration:

enyo.kind({
    name: "MyKind",
    published: { myValue: 3 }
});

As you can see, you also specify a default value for published properties. Within MyKind you can access the property directly using this.myValue. When you are accessing myValue externally (e.g., from a parent control), you should call the getter function getMyValue() or the setter function setMyValue(). Whenever the value is modified using the setter, Enyo will automatically call a “changed” function. In this case, the changed function is myValueChanged(). When called, the changed function will be passed the previous value of the property as an argument.

Warning

The default setter function does not call the changed function if the value to be set is the same as the current value.

As mentioned, Enyo automatically implements the getter and setter functions for you. The default functions simply get or set the value of the property and the setter calls the changed function. You can override this behavior by implementing the functions yourself. In this way you can perform validation or calculations. If you override the setter, be sure to call your changed function, if appropriate.

If you look back to our earlier discussion on kinds you may have noticed that we passed in some values for properties when we were declaring our kinds. Many of those values are indeed properties. Enyo does not call the changed method during construction. If you have special processing that needs to occur, you should call the changed method directly within the constructor:

enyo.kind({
    name: "MyKind",
    published: { myValue: 3 },
    create: function() {
        this.inherited(arguments);
        this.myValueChanged();
    },
    myValueChanged: function(oldValue) {
        // Some processing
    }
});

Warning

You should only specify simple values (strings, numbers, booleans, etc.) for the default values of published properties and member variables. Using arrays and objects can lead to strange problems. See Instance Constructors for a method to initialize complex values.

Events

If properties provide a way for parent kinds to communicate with their children, then events provide a way for kinds to communicate with their parents. Enyo events give kinds a way to subscribe to events that they’re interested in and receive data. Events are declared like this:

enyo.kind({
    name: "MyKind",
    handlers: { ontap: "mytap" },
    events: { onMyEvent: "" },
    content: "Click for the answer",
    mytap: function() {
        this.doMyEvent({ answer: 42 });
    }
});

Event names are always prefixed with “on” and are always invoked by calling a function whose name is prefixed with “do”. Enyo creates the “do” helper function for us and it takes care of checking that the event has been subscribed to. The first parameter passed to the “do” function, if present, is passed to the subscriber. Any data to be passed with the event must be wrapped in an object.

Subscribing is easy:

enyo.kind({ name: "Subscriber",
    components: [{ kind: "MyKind", onMyEvent: "answered" }],
    answered: function(inSender, inEvent) {
        alert("The answer is: " + inEvent.answer);
        return(true);
    }
});

Tip

Try it out: jsFiddle.

The inSender parameter is the kind that last bubbled the event (which may be different from the kind that originated the event). The inEvent parameter contains the data that was sent from the event. In the case of DOM events, this object contains a dispatchTarget property that points to the Enyo control that started the event.

When responding to an event, you should return a truthy value to indicate that the event has been handled. Otherwise, Enyo will keep searching through the sender’s ancestors for other event handlers. If you need to prevent the default action for DOM events, use inEvent.preventDefault().

Note

Enyo kinds cannot subscribe to their own events, including DOM events, using the onXxx syntax. If you need to subscribe to an Event that originates on the kind, you can use the handlers block, as we did for the previous tap event.

Advanced Events

The standard events described previously are bubbling events, meaning that they only go up the app hierarchy from the object that originated them through the object’s parent. Sometimes it’s necessary to send events out to other objects, regardless of where they are located. While it might be possible to send an event up to a shared common parent and then call back down to the target, this is far from clean. Enyo provides a method called signals to handle this circumstance.

To send a signal, call the send() function on the enyo.Signals object. To subscribe to a signal, include a Signals kind in your components block and subscribe to the signal you want to listen to in the kind declaration. The following example shows how to use signals:

enyo.kind({
    name: "Signaller",
    components: [
        { kind: "Button", content: "Click", ontap: "sendit" }
    ],
    sendit: function() {
        enyo.Signals.send("onButtonSignal");
    }
});

enyo.kind({
    name: "Receiver",
    components: [
        { name: "display", content: "Waiting..." },
        { kind: "Signals", onButtonSignal: "update" }
    ],
    update: function(inSender, inEvent) {
        this.$.display.setContent("Got it!");
    }
});

enyo.kind({
    name: "App",
    components: [
        { kind: "Signaller" },
        { kind: "Receiver" }
    ]
});

new App().renderInto(document.body);

Tip

Try it out: jsFiddle.

Like regular events, signal names are prefixed with “on”. Unlike events, signals are broadcast to all subscribers. You cannot prevent other subscribers from receiving signals by passing back a truthy return from the signal handler. Signals should be used sparingly. If you begin to rely on signals for passing information back and forth between objects, you run the risk of breaking the encapsulation Enyo tries to help you reinforce.

Note

Enyo uses the signals mechanism for processing DOM events that do not target a specific control, such as onbeforeunload and onkeypress.

Final Thoughts on Encapsulation

While published properties and events go a long way towards helping you create robust applications, they are not always enough. Most kinds will have methods they need to expose (an API, if you will) and methods they wish to keep private. Enyo does not have any mechanisms to enforce that separation, however code comments and documentation can serve to help other users of your kinds understand what is and isn’t available to outside kinds. Enyo includes a documentation viewer that can process JavaScript files and pull out tagged documentation. For more information on this facility, read Documenting Code on the Enyo wiki.

Inheritance

Enyo provides an easy method for deriving new kinds from existing kinds. This process is called inheritance. When you derive a kind from an existing kind, it inherits the properties, events, and functions from that existing kind. All kinds inherit from at least one other kind. The ultimate ancestor for all Enyo kinds is enyo.Object. Usually, however, kinds derive from enyo.Component or enyo.Control.

To specify the parent kind, set the kind property during creation:

enyo.kind({
    name: "InheritedKind",
    kind: "Control"
});

As mentioned, if you don’t specify the kind, Enyo will automatically determine the kind for you. In most cases, this will be Control. An example of an instance where Enyo will pick a different kind is when creating menu items for an Onyx Menu kind. By default, components created within a Menu will be of kind MenuItem.

If you override a function on a derived kind and wish to call the same named method on the parent, use the inherited() function. You may recall that we did this for the create function in the Light kind. You must always pass arguments as the parameter to the inherited() function.

Advanced Kinds

Enyo provides two additional features for declaring kinds, which are most often used when creating reusable kinds: instance constructors and static functions.

Instance Constructors

For some kinds, initialization must take place when an instance of that kind is created. One particular use case is defining array properties. If you were to declare an array member in a kind definition then all instances would be initialized with the last value set to the array. This is unlikely to be the behavior you wanted. When declaring a constructor, be sure to call the inherited() method so that any parent objects can perform their initialization as well. The following is a sample constructor:

    constructor: function() {
        this.instanceArray = [];
        this.inherited(arguments);
    }

Tip

It’s worth noting that constructor() is available for all kinds. The create() function used in many examples is only available for descendants of enyo.Component.

Statics

Enyo supports declaring functions that are defined on the kind constructor. These functions are accessed by the kind name rather than from a particular instance of the kind. Statics are often used for utility functions that do not require an instance and for variables that should be shared among all instances, such as a count of the number of instances created. The following kind implements an instance counter and shows off both statics and constructors:

enyo.kind({
    name: "InstanceCounter",
    constructor: function() {
        InstanceCounter.count += 1;
        this.inherited(arguments);
    },
    statics: {
        count: 0,
        getCount: function() {
            return(this.count);
        }
    }
});

Tip

Try it out: jsFiddle.

Summary

We have now explored the core features of Enyo. You should now understand the object oriented features that allow for creating robust and reliable apps. We’ll build upon this knowledge in the next chapters by exploring the additional libraries and features that make up the Enyo framework.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.208.97