Durandal: one compositionComplete to rule them all - javascript

With Durandal we have the compositionComplete event for last minutes processing on the view.
Currently my view composition hierarchy is quite complex, and I need to do various UI processing at different point in the app.
To avoid calling multiple times the same UI related code, and to guarantee it will be called at least once when some view needs it, I need a finalCompositionComplete() hook somewhere.
I didn't find such an event in the current Durandal implementation so I was thinking of adding it to the composition.js file (I figured out the endComposition() function would be a good place to start...)
It's obviously a bad idea modifying original durandal files for maintenance reasons.
Is there a better solution? One that is more maintenance friendly..
What I've done so far (and seems to be working):
created a lifecycle plugin in the plugins directory of durandal (and injected it in the composition module)
modified the composition.js file's endComposition() function
Now I can register callbacks through my lifecycle plugin and it will be called once at the very end
The endComposition() function in the composition.js file:
function endComposition() {
compositionCount--;
if (compositionCount === 0) {
setTimeout(function(){
var i = compositionCompleteCallbacks.length;
while(i--) {
try{
compositionCompleteCallbacks[i]();
}catch(e){
system.error(e);
}
}
// my modification is right here:
lifecycle.finalCompositionComplete();
compositionCompleteCallbacks = [];
}, 1);
}
}
lifecycle plugin
define(['durandal/system', 'underscore'], function (system, _) {
var _nameCounter = 0;
var _processes = {};
return {
registerFinalProcess: register,
finalCompositionComplete: processFinal
};
function register(processName, callback) {
processName = processName || '_noname' + _nameCounter++;
_processes[processName] = callback;
}
function processFinal() {
_.each(_.pairs(_processes), function(pair) {
try {
if (typeof pair[1] === 'function') {
pair[1]();
}
delete _processes[pair[0]];
} catch(e) {
system.log('error:could not call ' + pair[0]);
}
});
_processes = {};
_nameCounter = 0;
}
});
Anywhere, anytime in my app:
lifecycle.registerFinalProcess('someCallbackId', someFunction);

Related

How to create and trigger events on objects rather than elements

I'm writing a small library that essentially polls a site for data, and is then supposed to notify a consumer when it matches. In C# I'd use events, which are actually multicast delegates. I've written my own multicast delegate in javascript before, but I figure there has to be a better way.
A consumer should register a callback which should be called when data is available. Something like this:
window.MyLibrary.dataAvailable(function(data) {
// do something with data
});
In the background MyLibrary is polling for data. When it finally has something that matches, it should execute the registered function(s). Multiple functions should be able to be registered and probably unregistered too.
CustomEvent is very very close to what I want. The problem with CustomEvent is that the event has to be raised on an element - it can't be raised on an object. That is, this wouldn't work:
var event = new CustomEvent('dataAvailable', { data: 'dynamic data' });
window.MyLibrary.addEventListener('dataAvailable', function (e) {
// do something with e.data
}, false);
// From somewhere within MyLibrary
this.dispatchEvent(event, data);
How do you register handlers on objects in javascript? I need to support the usual browsers and IE11+. Ideally I wouldn't be pulling in a library to do any of this. jQuery will be available on the page and can be used if that would make things easier.
For reference, this is the Multicast Delegate implementation I've used in the past:
function MulticastDelegate(context) {
var obj = context || window,
handlers = [];
this.event = {
subscribe: function (handler) {
if (typeof (handler) === 'function') {
handlers.push(handler);
}
},
unsubscribe: function (handler) {
if (typeof (handler) === 'function') {
handlers.splice(handlers.indexOf(handler), 1);
}
}
};
this.execute = function () {
var args = Array.prototype.slice.call(arguments);
for (var i = 0; i < handlers.length; i++) {
handlers[i].apply(obj, args);
}
};
}
var myEvent = new MulticastDelegate();
myEvent.event.subscribe(function(data) { ... }); // handler 1
myEvent.event.subscribe(function(data) { ... }); // handler 2
myEvent.execute(some_data);

PubSub pattern alternative to transfer data between two decoupled javascript modules

I have two javascript modules which act on different parts of the page. Now at moment as you can see I'm using the PubSubJS library to publish and subscribe and transfer data if need be from one module to another module in a decoupled way. But I was thinking whether I can altogether omit the PubSubJS library use JQuery promises(or any other native JQuery method) instead to achieve the same. I'm not so good with JQuery promises hence the need for this question. Can somebody provide me any better solution with JQuery.
var salesOrder = (function() {
"use strict";
var $root, $salesOrderNo, $closeButton;
var _init = function() {
$root = $("#sales-order")
$closeButton = $root.find("#close-button");
_attachEvents();
};
var _attachEvents = function() {
$closeButton.on("click", _closeSalesOrder);
};
var _closeSalesOrder = function() {
PubSub.publish("ui.unloadShell", "closed"); //Here I'm publishing
}
return {
init: _init
}
})();
$(document).ready(salesOrder.init);
And the second module as so
var erpTest = (function() {
"use strict";
var $root, $btnMenu, $shell;
var _init = function() {
$root = $("body")
$btnMenu = $root.find(".menu-button");
$shell = $root.find("#shell");
_attachEvents();
}
var _attachEvents = function() {
$btnMenu.on("click", _loadShell);
PubSub.subscribe('ui.unloadShell', _unloadShell); //Here I'm subscribing
}
var _loadShell = function(evt) {
var url = $(evt.target).data("url");
if (url && url.length) {
$shell.load(url, _loadCompleted);
}
};
var _unloadShell = function(evt, data) {
$shell.html(null); //Here is the subscribed handler
};
var _loadCompleted = function(evt) {
$.each([buttonModule.init, nameModule.init], function(index, func) {
func($shell);
});
};
return {
init: _init
}
})();
$(document).ready(erpTest.init);
I use the PubSub pattern extensively. Your questions are the ones I was looking into a while ago. Here are my comments:
jQuery Promises: Promises are by nature async; do you really want an async channel of communication between components? Using Promises, you'd expect that any subscribers respond properly as your publisher might take action back using .then. Things will become complex as soon as you expect subscribers to respond accordingly to events.
jQuery has .on, .off, .one to publish events; you simply need to pass {} as aggregator. See that topic for further details: Passing an empty object into jQuery function. However jQuery has some overhead compared to a simple pubSub/aggreagator mechanism.
I built several labs of incremental complexity focused on the PubSub pattern that you can consult below. LineApp is the entry point.
https://pubsub-message-component-1975.herokuapp.com

Sub-viewmodels in Knockoutjs

Well met!
I am playing around with Knockoutjs with the goal of having a single ViewModel, which controls multiple sub-viewmodels. This in order to have more control over the views itself and to prevent putting various parts of my view into their own little place. The code below should explain my idea:
ApplicationViewModel
ApplicationViewModel = function () {
var self = this;
// Context (for laziness' sake, no separate VM)
self.activeProject = ko.observable();
// States
self.projectsLoaded = ko.observable(false);
// State-change events
// Let application know that loading of projects has been called
self.projectsLoaded.subscribe(function (newValue) {
if (newValue === true) {
console.log('Projects have loaded');
} else {
console.log('Projects have not loaded');
}
});
// Let application know that selection of a project has happened
self.activeProject.subscribe(function (newValue) {
if (newValue != null) {
// Notify other viewmodels that a project has been (successfully loaded)
// Use hook-pattern to hook into this event
} else {
// Notify something went wrong- present user with a notification
// Application stops processes that are project-dependant
}
});
self.ProjectViewModel = new ProjectViewModel();
};
ProjectViewModel
ProjectViewModel = function () {
var self = this;
self.projects = ko.observableArray();
self.loadProjects = function () {
// Business logic to retrieve projects, think AJAX
var placeHolderProjects = [];
// Find projects somewhere and load them up!
// If something went wrong, notify parent
if (placeHolderProjects.length > 0) {
self.projects(placeHolderProjects);
$root.projectsLoaded(true);
} else {
$root.projectsLoaded(false);
}
};
self.selectProject = function (projectId) {
if (!projectId) {
$.parent.activeProject = null;
return;
}
// Fetch data for project, stuff like membershipId
var loadProjectResult = magicalLoadFunction(projectId);
if (loadProjectsResult === true) {
$root.activeProject(projectId);
} else {
$root.activeProject(projectId);
}
// Exit
return;
}
/********** Constructor logic
****************************/
self.loadProjects();
};
So basically, what I am looking for, is a way to:
- Control parent/child properties from their respective child/parent inside the viewmodels.
I am looking into AngularJS as well, but I'd really like to get this working in KnockoutJS first :) Immediate problem, is that I can't get $root/$parent to work. I bind the ApplicationViewModel in a $(document).ready() handler, unsure if I have to actually bind the sub-viewmodels to the view as well. I have bound ApplicationViewModel to the body element.
Thanks for reading and, possibly for answering/helping me get on my way :)
The answer provided by #jansommer proved successful.
I changed the following line (added this as a parameter):
self.ProjectViewModel = new ProjectViewModel(this);
And that was what was needed.
Thanks!

how to expand jquery.plugin over multiple files

my current plugin is getting really big (just over 8000 lines right now) and i would like to know if there is a way to sort it into different files.
Just liike the require function in node.js, but for jquery, so it would be sorted into more files and thus, be more clearly arranged.
As #jcubic mentioned you need to have your code separated into individual modules / functionality purposes.
Store all of your methods in a method object of some sort (this could also be within the plugins namespace of course). This can also easily be added to, or even extended from a different file.
var methods = (function ($) {
return {
init : function () { initMethod(); },
another : function () { anotherMethod(); },
thirdThing : function () { thirdThing(); },
etcEtc : function () { etcEtc(); }
};
}(jQuery));
I highly recommend the method calling way of creating jQuery plugins, which would utilize this object.
$.fn.pluginName = function (method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.tooltip');
}
};
Now everything is separated, and you call your modules by doing $('whatever').pluginName('methodhere'); etc

Dynamic loading of modules from array using RequireJS

I am working on an application using RequireJS AMD loading method.
I have my modules dynamical picked up from a configuration file into an array
var amd_modules = ["module1", "module2","module3"]
now I have my requireJS code
require(amd_modules, function(result) {
console.log("All modules loaded");
}
Now, the result variable shows the first module which is "module1". How can I get other modules also into variable inside the function() parenthesis.
For eg,
require(amd_modules, function(module1, module2, module3) { //
}
I can't write the above hardcoding because the number of dynamic variables are not known until run time. Do let me know how I can catch the objects dynamically inside the function.
Thx
Simply use arguments:
require(amd_modules, function() {
console.log("All modules loaded");
// arguments should now be an array of your required modules
// in the same order you required them
});
However, unless you have a good reason to do so, you probably want to rethink the way you are designing your application - even at the topmost level your modules should be simple and testable. Having a widely varying number of dependencies indicates that you are probably trying to do to much in your callback function. Break each code path out into its own module and then switch only on your top-level dependency instead. In code:
// Instead of this:
require(amd_modules, function() {
console.log("All modules loaded");
if (complex_condition_A) {
var x = arguments[0],
y = arguments[1],
z = arguments[2];
// Do things with x, y and z
}
else if (complex_condition_B) {
var a = arguments[0],
b = arguments[1];
// Do things with a and b
}
else {
// et cetera, et cetera, et cetera
}
});
// Do this instead
var rootModule;
if (complex_condition_A) rootModule = "A";
else if (complex_condition_B) rootModule = "B";
else rootModule = "C";
require(rootModule, function(root) {
// Root has the same API, regardless of which implementation it is
// This might be as simple as an `init` method that does everything
// or as complex as, say Facebook's API, but with different libraries
// loaded depending on the platform the page is loaded on
// (IE vs. Android for example).
});
Try something like this:
define(function () {
var ClassLoader = function () {
var _construct = function () {
return _public;
},
_loadClass = function (className, callback) {
//
require([className],
function (LoadedClass) {
callback(LoadedClass);
});
},
_public = {
loadClass: _loadClass
};
return _construct();
}
return ClassLoader;
});

Categories