Jump to content

Invision Community Blog


Managing successful online communities

Rikki
Sign in to follow this  
 

4.0 - Javascript framework

Javascript is a key component of front-end web development - it's essential for a modern web app to provide a good user experience, and javascript is central to enabling that. Getting it right in 4.0 has been one of our goals from the start.

Problems

To begin, let's summarize some of the issues with javascript in the current 3.x series:

  • Lack of file organization (a single JS directory that contains dozens of unrelated script files)
  • Different script types are bundled together (interface widgets, routes and full applications all in one place)
  • Lack of modularity (each JS file is pretty much a floating island in how it might implement functionality, with no formalized structure)
  • Simple things requiring code (how many times have you had to write half a dozen lines of JS just to launch a popup?)
  • New dom nodes aren't initialized automatically (load some new HTML via ajax, and your popups won't work without manually hooking them up)


Resolving these problems informed the javascript framework that we've built for 4.0. It all ultimately comes down to organizing code better and making the lives of developers easier (ours and yours!).

The solution

Our solution has been to build a framework which is modularized and heavily event-driven. In most cases, modules never call each other directly, but instead communicate with events which may (or may not) be responded to by another module. More on this later.

The framework breaks down into four types of module:
  • Widgets - interface widgets like popups, tooltips and menus
  • Utility modules - things like cookie handling or URL manipulation
  • Controllers - modules which work on a particular dom node. More on these later.
  • Models - modules which handle data. In the vast majority of cases this is simply fetching data from the server.

There's also 'interfaces', which are third party scripts like CKEditor, jQuery plugins and so forth. They aren't part of the framework, so I won't discuss them here.


The groundwork

Before getting to specific types of modules, we needed to lay the groundwork. Javascript 4.0 is modularized, with only a single global variable (ips) being created in the page. All other scripts are defined as modules, whether they are interface widgets, utilities or anything else. A module is defined as a function which returns an object containing public methods (the revealing module pattern, if you're interested). Here's an example module:

;( function($, _, undefined){
	"use strict";
	
	ips.createModule('ips.myModule', function () {

		// Private methods
		var _privateMethod = function () {

		};

		// Functions that become public methods
		var init = function () {

		},

		publicMethod = function () {

		};

		// Expose public methods
		return {
			init: init,
			publicMethod: publicMethod
		}
	});

}(jQuery, _));


This pattern works well for our purpose, because it enables a module to contain private methods for doing internal work, while exposing only those methods which should be public to the outside world.

This example module could then be used like so:

ips.myModule.publicMethod();


So this keeps everything neatly organized, and ensures variables don't leak into the global scope (which we want to avoid at all costs). When the document is ready, the module is automatically initialized (though you can also have functions that execute before DOMReady if necessary).


Interface widgets

It's fair to say that interface widgets make up a large proportion of the JS used in our software - a web app such as ours has an intrinsic need for popups, menus, tooltips and so on. As mentioned above though, the big hinderance in 3.x is that these widgets have to be created manually (or, in a few simple cases, a special classname is added to an element to initialize it). This feels unnecessary when all the developer wants to do is show a simple widget that is otherwise standard.

To alleviate this hassle, interface widgets in 4.0 all support a data API. What this means is that any widget can be created simply by adding some parameters to an HTML element, and specifying some options. Need a dialog box that loads a remote page? Simply do:

<a href='...' data-ipsDialog data-ipsDialog-title='My dialog' data-ipsDialog-url='http://...'>Click me to open a dialog</a> 


Or if you need a hovercard, just do:

<a href='...' data-ipsHover>This will launch a hovercard</a> 


We already have around two dozen widgets built, covering everything from dialogs, menus and tooltips, to keyboard navigation, tab bars and autocomplete - all supporting initialization with data attributes.

Building your own widgets is easy - they are built as a module, and then simply define some settings to be accepted. The widget module does the rest. They can either be initialized when they're first seen in the dom (which you'd want for something like an image slider widget), or when an event occurs (such as hovering on a link, in the case of hovercards). Whenever new content is loaded into the page, widgets will be found and initialized automatically, too.

Most widgets emit events when certain things occur - when we get to Controllers, you'll see why that is useful.


Utilities

Utilities are simple modules that don't need much discussion. They simply provide methods which do something useful - for example, fetch/set a cookie, write to the user's local browser database, or handle timestamps.


Controllers

Controllers are the meat of the application. Whereas interface widgets are used (and reused) as dumb tools on a page, controllers provide the logic for particular elements, sections and pages. They would, for example, handle the interactions in the topic listing, or the interactions with a post. Notice the word interaction - controllers are specifically designed to deal with events on the page. In fact, that's almost all they do!

Controllers are initialized by specifying the controller name on an element, like so:

<div id='topic_list' data-controller='forums.topicList'> </div>


This div becomes the controller's scope. The controller can manipulate content inside the div, watch for events, and so on.

Controllers, in general, should be as specific and succinct - so simply specifying a page-wide controller then handling everything inside it is discouraged. If we take the topic list in forum view as an example:

<div id='topic_list' data-controller='forums.topicList'> 
	<ul>
		<li data-controller='forums.topicRow'>
			...
		</li>
		<li data-controller='forums.topicRow'>
			...
		</li>
		<li data-controller='forums.topicRow'>
			...
		</li>
	</ul>
</div>


Each topic row might specify the forums.topicRow controller which handles locking, pinning, or marking that topic. The topic list itself might specify the forums.topicList controller, which handles sorting and loading more topics. By doing it this way, controllers become responsible only for a specific portion of the functionality, which keeps them lean and simple.


Controllers are entirely decoupled and cannot reference each other - which is by design, given that a controller is only interested and responsible for its own scope. To communicate, events are used. A controller can trigger events on the page, which other controllers, widgets and models might respond to (and in turn emit their own events).

Continuing the example above, let's assume one of our topic rows is being deleted. The forums.topicRow controller handles removing the HTML from the DOM, but it doesn't care what happens after that - it's not its responsibility. However, it emits a deletedTopic event to let the page know. The forums.topicList controller sees this event, and because it does care, it loads a new topic entry into the list. By using events like this, we can build interfaces that respond fluidly to user interactions while still maintaining separation of concerns.

So, how does a controller deal with events? Because we're using jQuery, event handling in controllers piggy-backs with the on and trigger methods. In the controller's initialize method (which is specifically for setting up event handlers), you simply do:

this.on( 'menuItemSelected', '#menuid', this.handleMenuClick );


Usually when setting up events in an object using jQuery, you need to use $.proxy to properly control the scope of this, but in controllers, this is handled for you automatically - you just specify the method name.

Notice the event we're observing here - menuItemSelected. This is an event that the ui.menu widget emits, and it illustrates how widgets and controllers can interact. Controllers can watch for events from widgets, then do something with the information given, all without ever directly referring to each other.

Triggering an event is similar:

this.trigger( 'doSomething', {
    color: 'yellow',
    size: 'big'
});


This is the same syntax as jQuery's own trigger, except that the controller will ensure the parameters object is passed between different event handlers in the same chain. This will hopefully be clearer when you get your hands on it.


Models

Models are quite similar to controllers (they also use the special on and trigger methods), but their only purpose is to handle data. By decoupling data handling from controllers, we can centralize data getting/setting so that any controller can use it.

Let's say we have a user model, which handles data for the current user. It might have event handlers for adding a friend, for example, so when it sees the event addFriend, it handles it appropriately. Let's also assume we have a controller on each post in a topic, there's three posts, and that the controllers are observing the click event on the 'add friend' button. Here's the sequence of events:

(controller1) click 'add friend button'
(controller1) emit 'addFriend' event
(user model) adds a friend via ajax
(user model) emits 'addedFriend' event
(controller1) updates friend icon
(controller2) updates friend icon
(controller3) updates friend icon

Even though it was controller1 that requested that the model adds a friend, all controllers respond to the event the model emits and updates the friend icon in its own post. This again shows the power of using events as the primary communication system - anyone can respond, and the caller doesn't have to deal with maintaining associations.



Conclusion

So that's about it - the new JS framework in IPS4. Hopefully this in-depth post has covered everything you need to know at this stage. You'll be pleased to know that most of the framework and widgets are already documented, and that will be available when IPS4 hits beta.

Do note that everything covered here is subject to change or removal, as usual in our development blogs.

If you have any questions, feel free to ask!

Sign in to follow this  

Comments

Recommended Comments

This is indeed interesting, no more having to "hack around" or "overload" the JS functions to perform specific things when something happens. Our own controllers will be able to catch the event now.

Share this comment


Link to comment
Share on other sites

Can't wait till Prototype and Scriptaculous go away... They add a lot of weight/size to first page load.

Going to a clean Controller - Model structure should make things a lot better moving forward. :smile:

James

Share this comment


Link to comment
Share on other sites

Can't wait till Prototype and Scriptaculous go away... They add a lot of weight/size to first page load.

They're going away to make room for jQuery, so don't get too excited. Better library, but still big.

 

Any chance you can clue us in on what the minimum version of jQuery you are expecting to use in 4.0?

I would imagine we see whatever the latest version is at release time--1.10 most likely.

 

 

This framework looks neat I suppose, but I can't help but wonder whether all of this complexity is really necessary. jQuery's event and observer system is already fairly robust, is it not?

Share this comment


Link to comment
Share on other sites

 

This framework looks neat I suppose, but I can't help but wonder whether all of this complexity is really necessary. jQuery's event and observer system is already fairly robust, is it not?

 

Yes, we're not trying to replicate or replace it. What I've described is how we're organizing our code and using jQuery's event system within our software, as well as providing some shortcuts relevant to what we do. Controllers use jQuery events extensively, but they are primarily a way of building an automatically-initializing, reusable module scoped to a specific dom element - instead of a bunch of script files with haphazard event binding.

 

The jQuery version we'll be using will be whatever the most recent release of the 1.x line is when we hit final. We're not ready to move to 2.0 at this stage given that it drops support for older IE versions (which we won't officially support, but we still want to give it an acceptable experience).

Share this comment


Link to comment
Share on other sites

Which IE version is the last IPS supported? Because IE is the last version evaible for XP, so they shut be able to upgrade to it.

 

IE9 is our lowest officially-supported version, although we aim to make sure IE8 has an acceptable, if not 100%, experience.

Share this comment


Link to comment
Share on other sites

Kinda sounds like you guys made your own angular.js (which hey, I'm OK with). ;) Was looking into making updates to the JS the other night and got frustrated with the lack of being able to listen in on core code to fire my own events

 

The jQuery version we'll be using will be whatever the most recent release of the 1.x line is when we hit final. We're not ready to move to 2.0 at this stage given that it drops support for older IE versions (which we won't officially support, but we still want to give it an acceptable experience).

 

I was kinda hoping this wouldn't be the case. Why not just use 2.x and have a fallback of 1.1x if the browser is unsupported? You guys already do conditional comments for the CSS, might as well reward those using newer browsers with smaller file sizes since the API is the same. 

Share this comment


Link to comment
Share on other sites

Kinda sounds like you guys made your own angular.js (which hey, I'm OK with). ;) Was looking into making updates to the JS the other night and got frustrated with the lack of being able to listen in on core code to fire my own events

 

 

I was kinda hoping this wouldn't be the case. Why not just use 2.x and have a fallback of 1.1x if the browser is unsupported? You guys already do conditional comments for the CSS, might as well reward those using newer browsers with smaller file sizes since the API is the same. 

 

Though you're right they are compatible APIs, as of right now, jQuery 1 is only around 3KB bigger than 2 (32KB vs 29KB compressed). Honestly, for the tradeoff of us having to track two versions, having to make sure either version is used appropriately when websites try and integrate their existing site wrappers and so on, I'm not sure it's worth it right now.

Share this comment


Link to comment
Share on other sites

This sound similar to AngularJS. How did you make the decision to make your own, instead of using AngularJS so developers don't have to learn or adapt to yet another framework like this?

 

Was there anything you saw lacking or was it too extensive? 

Share this comment


Link to comment
Share on other sites

This sound similar to AngularJS. How did you make the decision to make your own, instead of using AngularJS so developers don't have to learn or adapt to yet another framework like this?

 

Was there anything you saw lacking or was it too extensive? 

 

Yeah, firstly you're correct in that Angular is very extensive. Many of the new JS frameworks (Angular, Backbone, etc.) are specifically designed for single-page apps, which we obviously aren't. That means they are designed mostly around the concept of abstracting the dom - since they are MVC, they have features like automatic data binding, to show data changes automatically in elements and so on. In a traditional multi-page app like ours, those features aren't really as important or useful (in our experience, at least). We also don't want to introduce a lot of new complexity (such as the special syntax Angular uses). One of our goals with 4.0 is to work properly even if JS is disabled, and the established frameworks generally don't make that easy (same applies to good SEO practices). Other than the simple tag attributes I outlined in the post above, our JS is not involved in the HTML at all.

 

Our framework isn't really like those ones. It's very hands-off - we're defining the structure of your modules, and providing a way of hooking them up to an element in the HTML, but what you actually do in a module is up to you. It is very lightweight.

 

So it's certainly not that I feel we can do something better than Angular, but rather I feel that we have different problems to solve.

Share this comment


Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

Important Information

We use technologies, such as cookies, to customise content and advertising, to provide social media features and to analyse traffic to the site. We also share information about your use of our site with our trusted social media, advertising and analytics partners. See more about cookies and our Privacy Policy