Eventbus/Aggregator for two Requirejs apps - javascript

I have two applications (more to be added that may or may not use backbone + requirejs), both developed using backbone + requirejs. I want to create an eventbus on which both applications can publish and subscribe to specific events. I have considered a two options, both of which have their own inefficiencies. These include:
Using the document object since this is common to all applications on the page regardless of framework/architecture e.g.:
// Aggregator
define([], function () {
var eventAgg = document.getElementsByTagName('body')[0],
slice = Array.prototype.slice;
function _on() {
var args = slice.call(arguments);
eventAgg.addEventListener(arg[0], arg[1]);
}
function _trigger() {
var args = slice.call(arguments);
eventAgg.dispatchEvent(arg[0]);
}
return {
on: _on,
trigger: _trigger
};
});
// Somewhere in one of app 1's modules
define(['jquery',
'underscore',
'backbone',
'dispatch',
'someView'], function ($, _, Backbone, Dispatch, SomeView) {
...
Dispatch.on('init', function (e) {...};);
...
});
// Somewhere in one of app 2's modules
...
var customEvent = new CustomEvent('init', {'status': 'All data loaded successfully'});
dispatcher.dispatchEvent(event);
...
Extending Backbone.Events and injecting the event aggregator into all requirejs modules (though this approach has been finicky at best). Same approach as above except I extend Backbone.Events instead of using the document object.
Neither of these methods seem 'correct' for providing an global event aggregator but I have not been able to come up with anything better. Any suggestions?

Backbone is itself, an event bus. Which is probably the most straight forward way of doing it.
Backbone.on('myevent:app1', function(){alert('sup');})
Backbone.trigger('myevent:app1');

Related

What JavaScript design pattern / common practice to use to organize multiple functions with page specific calls

I have a js file looking like this:
$(document).ready() {
// Mostly DOM manipulation operations
functionOne();
functionTwo();
functionThree();
...
}
What I want to achieve is to encapsulate and organize all functions in one object, and create logic so they get called only on specific pages. I started writing something like this:
(function( window, document, $, undefined) {
var MyNamespace = {};
// Cache these to local scope
MyNamespace.$window = $(window);
MyNamespace.$document = $(document);
// Functions
MyNamespace.functionOne = function() {
...
};
MyNamespace.functionTwo = function() {
...
};
})( window, window.document, window.jQuery );
I wonder if I am going in the right direction, and if there are any better ways of doing this with the page specific logic that I have not started implementing yet (I have a page identifier already available). I have looked at this book written by Addy Osmani and only thing that looked similar to what I want to achieve was the Command pattern, but I am still not convinced if it would be the right choice.
What you are looking for is kind of a class in the normal OOP sense. I personally use the following schema:
var OPTIONS = {
selectorA: '.js-xxx'
};
var MyClass = function(el) {
this.el = el;
this._init() // private function called
};
MyClass.prototype._init = function () {
// Do some stuff
};
MyClass.prototype.publicFunction = function() {
// Do exposable stuff here
}
window.instance = new MyClass(document.querySelect('#myElement'));
In order to load these modules you should need requireJS or another AMD loader.

Centralising the Backbone Event Aggregator in RequireJS - how does this work?

I am writing a Backbone application using RequireJS for defining modules. I have reached the age old problem of how to pass around the event aggregator without using globals or passing it as a parameter into every single View.
vent = _.extend({}, Backbone.Events);
I have taken the approach used here for Marionette, but since I'm not using Marionette (yet), I've adapted it to use the boring old vent object:
vent.js:
define(['underscore', 'backbone'], function (_, Backbone) {
return _.extend({}, Backbone.Events);
});
So now in my various modules, I can publish and subscribe, like:
enquiry-list-view.js:
define(['backbone', 'vent'], function (Backbone, vent) {
var EnquiryListView = Backbone.View.extend({
events: {
'click .enquiry-list-item-manage': 'manageClick'
},
manageClick: function () {
vent.trigger('navigate', 'enquiry/' + id);
}
});
});
app-router.js:
define(['backbone', 'vent'], function (Backbone, vent) {
var AppRouter = Backbone.Router.extend({
routes: {
'enquiry/:id': 'enquiryManage'
},
initialize: function () {
//listen out for navigate events
this.listenTo(vent, 'navigate', this.gotoRoute);
},
gotoRoute: function (route) {
this.navigate(route, { trigger: true });
}
});
This pattern seems to work okay, the router can listen to the vent object and the view can trigger an event on it.
But... how are the router and view both looking at the same object? Doesn't return _.extend({}, Backbone.Events); in vent.js always return a new object every time it is required by a different module?
Is there something about RequireJS that I'm not getting, or is _.extend doing something to Backbone.Events, or is there perhaps something (else) about JavaScript that I don't fully understand?
In AMD, define callback executes only ones. Its result is used in all dependent modules. Checkout this post. I think your question is related to it.

Passing data from one Backbone view to another

Lets say I have the following Backbone view which loads two links, one with the anchor text "test1" and the other with the anchor text "test2".
I bind a click event and I get the HTML of the link that was clicked and store it inside the clickedHtml variable.
Now, this view is loaded by a Backbone router.
When the user clicks either one of the two links (test1 or test2) another view called "main" will be loaded by the router.
Now, how can I pass the "clickedHtml" variable to that view?
Should I use LocalStorage?
Should I declare it globally like window.clickedHtml?
Is there a better way?
Ty!
// file: views/test.js
define([
'jquery',
'underscore',
'backbone'
], function($, _, Backbone) {
var Test = Backbone.View.extend({
el : '.test',
initialize : function () {
var that = this;
that.$el.html('test1<br />test2');
},
events : {
'click .test a' : 'click'
},
click : function (e) {
var clickedHtml = $(e.target).html();
}
return Test;
});
Here is my router:
// file: router.js
define([
'jquery',
'underscore',
'backbone',
'views/test',
'views/main'
], function ($, _, Backbone, Test, Main) {
var Router = Backbone.Router.extend({
routes: {
'' : 'home',
'test' : 'test'
}
});
var initialize = function () {
var router = new Router();
router.on('route:home', function () {
var main = new Main();
});
router.on('route:test', function () {
var test = new Test();
});
Backbone.history.start();
}
return {
initialize : initialize
}
});
Basicly you should use Backbone.Event:(Or it's equivalents in Marionette)
//Declaration
var notificationService = {};
_.extend(notificationService, Backbone.Events);
//Used by listener
notificationService.on("alert", function(o) {
alert(o);
});
//Used by publisher
notificationService.trigger("alert", {foo:"bar"});
The real question is how does it get passed from one view to another?
The way I see it, you have 2 options:
Bubble notificationService from one view to another in initialization
Wrap the notificationService with a requirejs model that returns it (creates a 'almost global' notificationService that can be passed by requirejs).
Although I don't like singletons a bit, this case a of a singleton notificationService object that can easily get injected by requirejs in every model will come in handy.
EDIT:
Another option, the quick and dirty one, just use jquery to trigger event on the DOM (specifically the body element) and listen to body in the other view
//on Listening view, after DOM is ready
$( "body" ).on( "alert", function( event, param1, param2 ) {
alert( param1 + "\n" + param2 );
});
//on Triggering view, after DOM is ready
$( "body").trigger( "alert", [ "Custom", "Event" ] );
NOTE:
notice that once a listening view is closed, it must removes itself from listening to events (unbind/off), so you wont have memory leak
Architecturally speaking, your aim should be to keep your code generic & reusable.
One of the main things you don't want to do in a situation like this is to pass direct references from one object to another - if you end up changing the setup of one of the objects, or you need to pass data from another object as well, this can get messy really fast.
One design pattern that's widely used in situations like this is a mediator. Also known as "pub/sub" you can have a centralized standalone object that mediates information between objects. Certain objects will publish information and other objects can subscribe to them. The mediator acts as an intermediary so that the objects never have to communicate directly with each other. This makes a much more generic, reusable and maintainable solution.
More info here:
http://addyosmani.com/largescalejavascript/#mediatorpattern,
Javascript Patterns
On the Backbone side of things... If you've used Marionette, you may have come across a complimentary mini-library (also implemented by Derick Bailey) called wreqr. You can use this to create a simple mediator with low-overhead in your Backbone applications.
https://github.com/marionettejs/backbone.wreqr
It basically allows you to use backbone style events across objects. Example below:
First, you need to create a globally accessible mediator object, or add it to your app namespace or use require.js:
var mediator = new Wreqr.EventAggregator();
inside View #1
events : {
'click .test a' : 'click'
},
click : function (e) {
var clickedHtml = $(e.target).html();
// trigger an 'element:click' event, which can be listened to from other
// places in your application. pass var clickedHtml with the event
// (passed to the arguments in your eventhandler function).
mediator.trigger('element:click', clickedHtml);
}
Inside View #2
initialize: function(){
//...
this.listenTo(mediator, 'element:click', this.myEventHandler, this);
}
myEventHandler: function(elem){
// elem = clickedHtml, passed through the event aggregator
// do something with elem...
}
Backbone events are the way to go here.
When you capture the event in the view, I would bubble it up using:
click : function (e) {
var clickedHtml = $(e.target).html();
Backbone.Events.trigger("eventname",clickedHtml);
}
Then, you should be able to capture this in your router initialise function, using:
Backbone.Events.on("eventname", responseFunction); // listen out for this event
And then in the router declare a separate function:
responseFunction : function(clickedHtml)
{
//Do whatever you want here
}
I'm writing this from memory, so hopefully it make sense. I've also not tested catching an event like this i the router, but it should work.
HTH.
In the exact case you outline I would create a temp storage object on your global namespace and use that to transfer the data between your views, its a bit "hacky" but its better than using local storage, or the window object directly, at least with a temp object on your own global namespace the intent of the objects usage is known.
I find it better to use the http://backbonejs.org/#Events for a similar purpose of passing data between two views, though it does depend on how you structure your pages, if you have two views on the page representing a "control" or "component" this approach works really well.
If you post a link to your site or something I can have a look and give you some more help.
Russ
You could perhaps store it as a property on the view:
click : function (e) {
this.clickedHtml = $(e.target).html();
}
If your router can access both views, it can then simply pass the firstView.clickedHtml property to a function in the secondView (or to the initializer)

Creating reusable components with KnockoutJS

I've been creating reusable components as jQuery plugins for projects for some time now. I like being able to abstract away the logic, and inject all of the context (selectors, options, etc) on a case-by-case basis.
Now, I'm starting to use KnockoutJS, and have written a nice little jQuery plugin that uses Knockout for its internal logic. It works quite well, but I'm wondering if there is a better way to do it? Does Knockout itself have a pattern/convention for creating reusable components, or is this pattern okay?
Here is an example, it should be enough to give you the idea of what I'm doing.
/*globals jQuery, knockout */
(function ($, ko) {
"use strict";
$.fn.itemManager = function (options) {
// set up the plugin options
var opts = $.extend({}, $.fn.itemManager.defaultOptions, options),
$wrap = $(this),
templateUrl = '/path/to/template/dir/' + opts.templateName + '.html';
// set up the KO viewmodel
function ItemManagerModel(items) {
var self = this;
self.items = ko.observableArray(items);
self.chosenItemId = ko.observable();
self.chosenItemData = ko.observable();
// generic method for navigating the Item hierarchy
self.find = function (array, id) {
/* ... */
};
/* bunch of other stuff... */
self.goToItem(items[0]);
}
// this is where the whole thing starts...
$(opts.openTriggerSelector).click(function (e) {
e.preventDefault();
// set the template html
$.get(templateUrl, function (data) {
$wrap.html(data);
});
// initial load and binding of the data, in reality I have some more conditional stuff around this...
// there's probably a better way to do this, but I'll ask in a separate question :)
$.get(opts.getItemsServiceUrl, function (result) {
ko.applyBindings(new ItemManagerModel(result), document.getElementById($wrap.attr('id')));
$wrap.data('bound', true);
});
// opens the template, which now is bound to the data in a dialog
$wrap.dialog({ /* ... */ });
// provide default plugin options
$.fn.itemManager.defaultOptions = {
getItemsServiceUrl: '/path/to/service',
openTriggerSelector: 'a',
templateName: 'Default'
};
} (jQuery, ko));
I run a github project for KO components. It's using an older version of KO and is due for a major revamp but you may be able to get some ideas. I basically do it all through custom bindings that take model objects as their configuration and data.
I am always on the lookout for a better way of doing this. Keep me posted if you come up with a better way.
https://github.com/madcapnmckay/Knockout-UI

jQuery Plugin - Providing an API

I am currently developing a rather complex jQuery plugin. One that I am designing to be extensible. The quandary I have is how to exactly provide my users with the APIs available to them.
There are two methods that I can come up with:
Provide the API via an object in the global scope
This is the method I am currently using. I do it similar to this:
(function ($, win, undefined) {
//main plugin functionality
function pluginStuff() { /*...including method calling logic...*/ }
//register function with jQuery
$.fn.extend({ Plugin: pluginStuff });
//register global API variable
win.PluginAPI = { extendMe: {}, getVar: function() {} };
})(jQuery, window);
Unfortunately since I impliment the standard $().plugin('method') architecture its a little strange to have to use the jQuery method for some things and the API variable for others.
Provide the API via an object placed in jQuery
I toyed with this method as well but its best practice to take up only a single slot in jQueries fn scope, as not to crowd the jQuery variable. In this method I would put my api variable in $.fn instead of the window:
//register function with jQuery
$.fn.extend({ Plugin: pluginStuff });
//register global API variable
$.fn.PluginAPI = { extendMe: {}, getVar: function() {} };
I would rather not break this convention and take up two places.
Now that I write this I can see a third option where I assign my plugins slot in jQuery's fn scope to be an object:
$.fn.Plugin = { plugin: pluginStuff, api: { extendMe: {}, getVar: function() {} } };
but how well received would this be if users had to do $('#elm').Plugin.plugin({ setting: 'value' }) to create a new instance of the plugin?
Any help or pointers would be greatly appreciated.
Please Note: I'm am not looking for a way to incorporate the API object into my plugin functionality. I am looking for a way to keep it separately modularized, but intuitively available for use/extension.
You could always do like
var plugin = function plugin() { /* do the main stuff */ };
// api stuff here
plugin.getVar = function() { };
plugin.extendMe = {};
$.fn.Plugin = plugin;
Or stick the extra stuff in an object that you assign to plugin.api.
Any way you do it, though, you're going to have to worry a bit about settings bleeding into each other. Since everything's going to be using the same function, regardless of how you choose to set it up, you'll need a way to keep invocations of the plugin separate from one another. Perhaps using something like, say, this.selector (in your plugin function) as a key into an associative array of properties, for example. I'd normally recommend .data() to attach settings to individual elements, but that doesn't help much if the same element gets the plugin called for it twice.
The method I eventually decided to use was registering the plugin under the fn namespace and the api variable under the jQuery $ namespace. Since methods and options set operate on an instance of the plugin $.fn is the best choice.
However, the API is global and does not link to a single instance. In this case $.fn doesn't quite fit. What I ended up using was something similar to this:
(function ($, win, undefined) {
//main plugin functionality
function pluginStuff() { /*...including method calling logic...*/ }
//register function with jQuery
$.fn.Plugin = pluginStuff;
//register global API variable
$.Plugin = { extendMe: {}, getVar: function() {} };
})(jQuery, window);
now you can create an use a plugin object as expected:
$('#elm').Plugin();
$('#elm').Plugin('option', 'something', 'value');
$('#elm').Plugin('method');
and you can easily extend and access the API:
$.extend($.Plugin.extendMe, {
moreStuff: {}
});
$.Plugin.getVar('var');
Thanks for the help everyone!

Categories

Resources