Creating and working with add-ons

Ember has a common way of sharing code using something called Ember Addons (also known as add-ons). Ember Addons make it easy to distribute reusable libraries with other applications. Anyone can create add-ons. You can publish them to NPM or to your own private Git repository.

Keep in mind that you can also use Bower to install frontend dependencies. This is done through the Bower package manager. Take a look at Chapter 1, Ember CLI Basics for more information on how to do this.

In this recipe, we'll take our chat program from the last section and make it an add-on.

How to do it...

  1. Create a new add-on called sockjs-chat. Generate these files:
    $ ember addon sockjs-chat
    $ cd sockjs-chat
    $ ember g component sockjs-chat
    $ ember g service sockjs
    $ ember g blueprint sockjs-chat
    $ npm install ember-cli-htmlbars --save
    

    The ember addon command generates the folder structure for the add-on. We'll discuss the folder structure in more detail later. The blueprint command creates a new blueprint called sockjs-chat. Blueprints are used to generate snippets of code. This is needed so that the SockJS library can be installed. If we're doing anything with templates, we'll need to add ember-cli-htmlbars.

  2. Create the sockjs-chat blueprint so that it installs the SockJS library:
    // blueprints/sockjs-chat/index.js
    /*jshint node:true*/
    module.exports = {
        normalizeEntityName() {
    },
    
        afterInstall() {
          return this.addBowerPackageToProject('sockjs-client', '~1.0.3');
        }
    };

    The afterInstall hook is used to add Bower packages. By default, the blueprint file will be run during the add-on installation. This guarantees that the sockjs-client library is installed via the Bower package manager.

  3. Update the root index.js file so that the SockJS library is imported:
    // index.js
    /* jshint node: true */
    'use strict';
    
    module.exports = {
        name: 'sockjs-chat',
          included(app) {
            this._super.included(app);
            app.import(app.bowerDirectory + '/sockjs-client/dist/sockjs.min.js');
          }
    };

    The JavaScript SockJS library is installed in the blueprint. However, we still need to import it to Ember. This can be done in the root folder's index.js file. This file is the entry point to the application. The included hook is used to import the Bower components to the application. Imports are added to the application in the order that they appear.

  4. Set the package.json file with the correct information for the project:
    // package.json
    {
        "name": "sockjs-chat",
        "version": "1.0.0",
        "description": "EmberJS Sockjs Chat Addon",
    …
    "repository": "https://github.com/ErikCH/sockjs-chat",
    …
    "author": "Erik Hanchett",
    …
        "keywords": [
          "ember-addon",
          "sockjs",
          "ember websockets"
    …

    It's important to have your package.json file updated with at least your name, description, repository, author, and keywords. This is extremely important if you plan on open sourcing your add-on and publishing it to NPM. Without this information, your add-on will be hard to find.

  5. In the generated service file, add a new setup and send method:
    // addon/services/sockjs.js
    /* global SockJS */
    import Ember from 'ember';
    var {run} = Ember;
    
    export default Ember.Service.extend(Ember.Evented,{
        socket: null,
        setupSockjs(url) {
          let socket = new SockJS(url);
          socket.addEventListener('message', run.bind(this, (event)=> {
            this.trigger('messageReceived', event.data);
            console.log(event.data);
          }));
          this.set('socket',socket);
        },
        sendInfo(message) {
          let socket= this.get('socket');
          if(socket != null){
            socket.send(message);
          }
        }
    
    });

    This may look familiar. This is almost the same service that we created in the last recipe. However, this time, we have a new setupSockjs method that takes url as a parameter. The url parameter is used to set the new socket listener:

          socket.addEventListener('message', run.bind(this,(event)=> {
            this.trigger('messageReceived', event.data);
            console.log(event.data);
          }));

    This event is triggered when a new message is received. After a new message arrives, a new trigger called messageReceived will be called:

        sendInfo(message) {
          let socket= this.get('socket');
          if(socket != null){
            socket.send(message);
          }

    As long as socket isn't null, message will be sent to the WebSocket server.

  6. Set up the sockjs-chat.js component:
    // addon/components/sockjs-chat.js
    import Ember from 'ember';
    import layout from '../templates/components/sockjs-chat';
    const {typeOf} = Ember;
    export default Ember.Component.extend({
    
        sockjs: Ember.inject.service('sockjs'),
        layout,
        message:'',
    
        init() {
          this._super(...arguments);
          this.get('sockjs').setupSockjs(this.attrs.url);
          this.get('sockjs').on('messageReceived',this,(message)=>{
            this.set('message',message);
            this._actionHandler('receiveAction',message);
          });
        },
        _actionHandler(actionName, ...args) {
    
          if(this.attrs && typeOf(this.attrs[actionName]) === 'function'){
            this.attrs[actionName](...args);
          } else {
            this.sendAction(actionName,...args);
          
          },
    
          actions: {
            enter(info,username) {
              this._actionHandler('sendAction',info,username);
    
            }
          }
    
    });

    The purpose of the component is to make it easy for someone to add a chat feature to their application without having to understand the internals of the service that we created earlier. To use this component, the template must be in block or non-block form with these properties:

    {{sockjs-chat
    url='http://localhost:7000'
    receiveAction=(action 'receiveMessage')
    sendAction=(action 'sendMessage') }}

    The url property is the location of the WebSocket. The receiveAction method is the parent component's action name. This will be triggered whenever a message is received. The sendAction method is the parent component's name for action that will be sending out messages.

    Let's take a look at the component in more detail:

        layout,
        message:'',
        init() {
          this._super(...arguments);
          this.get('sockjs').setupSockjs(this.attrs.url);
          this.get('sockjs').on('messageReceived',this,(message)=>{
            this.set('message',message);
            this._actionHandler('receiveAction',message);
          });
        },

    The layout property is the same as layout: layout. This is a part of ES6. The init hook is run when the component is initialized. Whenever you extend a built-in method, it's always a good idea to run this._super. This makes sure that the component is set up correctly. The …arguments array is a part of the new ES6 syntax. It's known as Rest parameters and represents an indefinite number of arguments in an array. We'll be using this several times in this component.

    After super is run, we pass the url property to the setupSockjs method in our service. The this.attrs.url retrieves the url property that was passed to the component.

    As we are using the Ember.Event mixin, we can subscribe to the service and watch for the messageReceived trigger. When messageReceived is triggered, we set the internal message, this.message property, to the message that was received. We then pass the message to a new method called _actionHandler:

        _actionHandler(actionName, ...args) {
    
          if(this.attrs && typeOf(this.attrs[actionName]) === 'function'){
            this.attrs[actionName](...args);
          } else {
            this.sendAction(actionName,...args);
          }
        },

    The purpose of actionHandler is to take an action passed by the receiveAction or sendAction property and invoke it. However, we need to make sure that we can handle actions passed via closure actions, as described in Chapter 6, Ember Components, or just a named action. If it's a closure action such as (action 'receiveMessage'), then we simply call it using this.attrs[actionname](…args). If not, then we use sendAction, which will send the action to the parent component:

        actions: {
          enter(info,username) {
            this._actionHandler('sendAction',info,username);
    
          }
        }

    The enter action calls the action handler and passes the info and username over. As we are using Rest parameters in _actionHandler, (…arguments), we can pass to it as many arguments as we need.

  7. Update the component template for sockjs-chat.hbs:
    // addon/templates/components/sockjs-chat.hbs
    {{#if hasBlock}}
        {{yield this}}
    {{else}}
    
        <textarea id="chat-content" style="width:500px;height:300px" ></textarea><br/>
        {{input type='text' placeholder='User Name' value=uname}}
        {{input type='text' placeholder='Chat Message' value=mess}}
        <button {{action 'enter' mess uname}}>Send</button><br>
    
    {{/if}}

    This gives the user a couple of choices when using this add-on. They can use the component in block form, which will look similar to the service we created in the last chapter, or they can design their own. The hasBlock helper returns true if the user adds the component in block form. If the component was not added in block form, then it displays the normal chat window.

    One important aspect in this template is {{yield this}}. When in block form, this will give the block access to the component itself. We'll have full access to the components, properties, and methods in the templates block. We'll take a look at this when we test the add-on.

Testing the sockjs-chat add-on

The /tests folder in the add-on is where all the test cases reside. This is very similar to any other Ember application. However, add-ons also include a dummy folder in the test folder. This folder is generally where add-on makers create their test applications. The program in this folder will have access to the add-on, although you'll need to install any Bower dependencies manually.

  1. Run this command to install sockjs-client for testing purposes in the add-on folder:
    $ bower install sockjs-client –-save-dev
    

    This will install sockjs-client in the bower.json devDependencies section. The bower.json file is used only for the application in the /tests/dummy/ folder.

  2. Update the ember-cli-build.js file with SockJS bower_component:
    // ember-cli-build.js
    /*jshint node:true*/
    /* global require, module */
    var EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
    
    module.exports = function(defaults) {
        var app = new EmberAddon(defaults, {
          // Add options here
        });
    
    /*
    This build file specifes the options for the dummy test app of this
    addon, located in `/tests/dummy`
    This build file does *not* influence how the addon or the app using it
    behave. You most likely want to be modifying `./index.js` or app's build file
    */
    
        app.import('bower_components/sockjs-client/dist/sockjs-0.3.4.js');
        return app.toTree();
    };

    This will add the sockjs-client library to our /tests/dummy app.

  3. In the /tests/dummy folder, add the component from the add-on in a non-block form:
    // tests/dummy/app/templates/application.hbs
    <h2 id="title">Welcome to Ember</h2>
    
    {{sockjs-chat
    url='http://localhost:7000'
    receiveAction=(action 'receiveMessage')
    sendAction=(action 'sendMessage') }}

    This will add our new component add-on to the application. The url property will be passed to the service so that it can connect to the WebSocket server at port 7000. The receiveAction and sendAction properties point to closure actions. This will trigger when we receive a message or want to send a message.

  4. Define the send and receive actions in the application controller:
    // tests/dummy/app/controllers/application.js
    
    import Ember from 'ember';
    const {$} = Ember;
    export default Ember.Controller.extend({
        sockjs: Ember.inject.service('sockjs'),
        actions:{
          receiveMessage(message){
            $('#chat-content').val((i, text)=>
            `${text}${message}
    `
            );
            this.set('message',message);
    
          },
          sendMessage(message, username){
            console.log(username);
            console.log(message);
            var send = this.get('sockjs');
            send.sendInfo(`${username}: ${message}`);
    
           }
        }
    });

    These actions handle the sending and receiving of messages. The receive method uses a little bit of jQuery to append the latest message to the chat window. The send method uses the service from the add-on to send a message.

  5. Run ember server command and test out the add-on:
    $ ember server
    

    You can run the server command directly in the add-on folder. This will serve up the files in the /tests/dummy/ folder. Make sure to also begin the WebSockets server as well. Check out the last recipe on how to create a WebSocket server in Node.js.

  6. Open a web browser and type in a message:
    Testing the sockjs-chat add-on

    This chat box is generated from the template in the add-on. The message typed here will be sent using the action created in the controller.

  7. Use the component in block form and create your own chat box:
    // tests/dummy/app/templates/application.hbs
    <h2 id="title">Welcome to Ember</h2>
    
    {{outlet}}
    
    <h2>Alternative</h2>
    {{#sockjs-chat
    url='http://localhost:7000'
    receiveAction=(action 'receiveMessage')
    sendAction=(action 'sendMessage') as |sockjs|}}
    
        <textarea id="chat-content" style="width:300px;height:300px"></textarea><br/>
        {{input type='text' placeholder='User Name' value=uname}}
        {{input type='text' placeholder='Chat Message' value=mess}}
        <button {{action 'enter' mess uname target=sockjs}}>Send</button><br>
        {{sockjs.message}}
    
    {{/sockjs-chat}}

    This template uses the add-on component in block form. This time, we create a smaller chat room instead of using the default one created by the add-on:

    {{#sockjs-chat
    url='http://localhost:7000'
    receiveAction=(action 'receiveMessage')
    sendAction=(action 'sendMessage') as |sockjs|}}

    When a component begins with hash #, it's considered to be in block form. To get access to the component itself, we add |sockjs| at the end. Now sockjs has access to all the properties in the component:

    <button {{action 'enter' mess uname target=sockjs}}>Send</button><br>

    As we have access to the component in the block, we can set target of this action to sockjs. We can also display the message anywhere we need to:

        {{sockjs.message}}

    This will display the message property in the component.

  8. Run ember server again in the add-on folder and open a web browser. Type in a message:
    Testing the sockjs-chat add-on

    As you can see, this new chat window looks a little different. However, it behaves in the same way and uses the same add-on as before.

  9. Create a new application and link it to the add-on to test:
    $ cd sockjs-chat
    $ npm link
    $ cd ..
    $ ember new chat 
    $ cd chat
    $ npm link sockjs-chat
    

    The first thing that we do is navigate to the sockjs-chat folder that has our new add-on in it. We then run the npm link command. This generates a symbolic link from the local NPM cache to the add-on project. To access the add-on, we must then run npm link sockjs-chat in our new application. This creates a link to the add-on.

  10. Add the add-on to the package.json file in the chat test application:
    // chat/package.json
    …
        "devDependencies": {
        "sockjs-chat": "*"
    …

    This is one of the last steps when linking an add-on to test. Ember must have this code in devDependencies for it to see the add-on.

  11. Run install and add the blueprint:
    $ npm install
    $ ember g sockjs-chat
    

    After updating the package.json file, we must install the new package using npm install. Finally, running ember g sockjs-chat runs the default blueprint that will install sockjs-client in the application. The blueprint is automatically run when a new Ember add-on is installed. However, we must run it manually if we use the npm link technique.

    We can now use the add-on in the application. Take note that we'll need to implement the same controller as we did in the dummy application to make this add-on work.

Publishing the sockjs-chat add-on

There are two ways to publish the new add-on. We can use either NPM or Git.

  1. Publish your add-on to a private Git repository:
    $ cd sockjs-chat
    $ git add .
    $ git commit –m "first commit"
    $ git remote add origin git@yourserver:username/sockjs-chat.git
    $ git push origin master
    

    To publish privately, you need to set up a private Git repository. Then push the add-on to this repository. In this case, replace yourserver:username with the server and username of your private Git repository.

  2. Install the add-on from the Git repository in a new application:
    $ cd my-app
    $ ember install git+ssh://git@yourserver:username/sockjs-chat.git 
    

    This will install the add-on in the application. Make sure that the name of the repository matches the name of the add-on, or you'll get a message that the add-on cannot be found.

  3. Publish your add-on to NPM:
    $ cd sockjs-chat
    $ npm adduser
    $ npm publish
    

    This will add you as a new user to the http://npm.org site. You can then publish the npm as long as the package.json file is set up correctly. Later, you can use the npm version to bump the add-on version if needed.

  4. Install your add-on in a new application:
    $ cd my-app2
    $ ember install sockjs-chat
    

    This will install the sockjs-chat application from npm in the my-app2 application.

How it works...

Ember uses an add-on system to share code between applications. Each add-on has its own package that can be added to any application. Unlike the Bower package manager, these libraries can be more complicated and can encapsulate Ember code.

Ember add-ons can be accessed via NPM or private Git server. This can be used to share information between applications.

See also

Using Ember Addons can really speed up the development process. There are thousands of add-ons available. Check out the following two websites:

Both websites list add-ons and rank them. Use them for your applications. You won't regret it.

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

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