This is more of a general question about the structure of my JavaScript code and if I'm going in the right direction towards well structured code.
The current code I've got:
(function (myNamespace, $, undefined) {
myNamespace.className = {
init:function { } // do stuff
}
} (window.myNamespace= window.myNamespace|| {}, jQuery)));
(function (myNamespace, $, undefined) {
myNamespace.className2 = {
init:function { } // do stuff
}
} (window.myNamespace= window.myNamespace|| {}, jQuery)));
Obviously with the above code, I can use the same Namespace (as per page/site section) and call them via myNamespace.className.init() etc. I can also combine these if I want to, but I'm encapsulating classes for readability.
Now, I've been reading http://addyosmani.com/largescalejavascript/ about the concept of mediators. My secondary question is when (and if) I should be using these? From className2 obviously I can do:
myNamespace.className2 = {
init:function { myNamespace.className.init() } // do stuff
}
So why would this ever subscribe to className like mediator.subscribe("classNameInit") and publish that event in className?
I'm highly open to suggestions about the structure of my code as this is something I need to get right whilst I'm changing the way I write my JavaScript.
You would use it when you have multiple pieces which will work together in unlimited combinations where you don't know all combinations ahead of time or where it's more efficient to assume all combinations.
Let's say you were building a social media app and you wrote a class to encapsulate a list of users. On some screens, clicking on a user in the list opens their profile, on another screen perhaps clicking a user searches for every comment they left, and on a third screen something else happens.
If you were to write this not using mediator/pubsub, what you'd end up with is a bunch of if statements in the onclick event...
UserList.prototype.onUserClick = function(user) {
// Check if we're supposed to open a popup
if (this.mode === 'profile')
// Check for something else
else if (this.mode === 'something else')
// Check for another case
else if (this.mode === 'foo')
}
Mediator is a solution to this problem because it doesn't require that UserList have knowledge of every single situation it might end up in. Instead, the above code in UserList could simply be refined to broadcast when a user is clicked on...
UserList.prototype.onUserClick = function(user) {
this.publish('user-click', user);
}
Then each of your other screens or UI pieces can simply listen for the user-click message...
// On pages where there needs to be a popup profile
Mediator.onMessage('user-click', function(data) {
showProfilePopup(data);
});
// Or perhaps on a search page
SearchBox.onMessage('user-click', function(data) {
this.searchByUser(data);
});
Furthermore, where mediator begins to shine is because these other UI components, like SearchBox are not interested in specifically when UserList fires a user-click, they're interested only when a user-click is published, other UI controls on the page can fire user-click as well and these pieces can react to it.
On a side note, className = { } isn't creating a class. What you probably want is className = function() { }.
Related
I have a wrapper for the HighCharts lib which autogenerates some code based on their API. In order to autogenerate this code I must export the HTML of the API website with ALL (recursive) the links (from the left side menu) expanded. This must be done recursive as new expanded links may have more not-yet-expanded links.
Right now I must manually proceed this loop from the Browser's Javascript console:
$('div.collapsed').find($('a.plus')).click();
$('div.collapsed').find($('a.plus')).length. If zero, I am done. If none zero, then proceed again with 1).
I tried to automatize this like follows:
while ( $('div.collapsed').find($('a.plus')).length !== 0 ) {
console.log('Doing a pass');
$('div.collapsed').find($('a.plus')).click();
console.log('Pass finished');
}
But it doesn't work as it goes to an endless loop. I guess this is because of onClick fires some async code (maybe an Ajax call?). Any idea how can I make it work?
Thanks in advance,
$('div.collapsed').find($('a.plus')).length is not going to change the value so please use
$('div.collapsed').find('a.plus').each(function(){
//some code
})
for more information regarding each. please check here
I finally fixed it this way:
/* Technically we could simulate the click on the links, like this:
$('div.collapsed').find($('a.plus')).click();
But that won't work as the clicks fire an async AJAX call so we don't know
when to expand their children. To make the recursion to work, I found there are many useful functions
in http://api.highcharts.com/resources/js/api.js
The function toogleExpand() allow us to pass a callback. That way, on callback, we expand again with children
making the recursion to work. */
function expandLinks( items ) {
if (items.find('div[id$="-menu"]').length !== 0) {
items.find('div[id$="-menu"]').each(function(){
var link = $(this);
toggleExpand(link.closest('.collapsed'), function() {
/* note that in this case we are making the recursion but only with the children of items */
expandLinks( items.find('div.menuitem.collapsed') )
});
});
} else {
if( $('div.collapsed').find($('a.plus')).length == 0 ) {
/* If there are no more links to open it means everything is ready so lets download the file */
downloadDetailsDivContent();
}
}
}
I'm attempting to create a modular sign in script for some webpages I'm developing. In short, I load the script on the main page, fire the main signIn function from a button press, and an overlay div is created on the main page which is managed by the external signIn.js. The external js sets some sessionStorage variables that will be utilized in the main page.
The hope for modularity would be to have signIn.js handle the authentication from the database and have the main page do with the process of signing in as needed (in this specific instance, it gives users access to their projects). Ideally, the sign in will not force a refresh of the main page due to other project goals.
The problem I'm encountering, is how do I notify the main page that the user has signed in without destroying any sense of modularity?
On top of other efforts, the most hopeful was attempting to create a custom event on the main page's document using $(document).on('userSignedIn', function() {...}); but signIn.js apparently cannot trigger this event.
Any suggestions for how to accomplish this or am I just going about this entirely wrong?
EDIT:
So, this was definitely a scope related issue I was experiencing. To flesh out the process, if anyone finds it relevant, signIn.js adds an overlay div to mainPage.html. $("#signInContainerDiv").load("signIn.html") is used to load the sign in form into the page. It turns out, when I was trying to reference $(document), it was using signIn.html's document, and not mainPage.html's. Upon that realization, I just created a div (signInNotify) on the mainPage that I bind the event to ($("#signInNotify").on("userSignedIn", function() {...});) and trigger it in signIn.js.
My own inexperience has conquered me, yet again.
jQuery can help you out when it comes to this. Here's an example from the main page for trigger
$( "#foo" ).on( "custom", function( event, param1, param2 ) {
alert( param1 + "\n" + param2 );
});
$( "#foo").trigger( "custom", [ "Custom", "Event" ] );
jQuery Page Reference
Another solution is to use some library like amplify.js, it has publish/subscribe functionality which can be useful for implementing the "observer pattern". You could also implement your own library for that, the code could be something like this:
// the implementation
function Notify () {
this.listeners = {};
}
Notify.prototype.subscribe = function (event, callback, context) {
this.listeners[event] = this.listeners[event] || [];
this.listeners[event].push({ callback: callback, context: context || null});
};
Notify.prototype.publish = function (event/*, args...*/) {
var args = Array.prototype.slice.call(arguments, 1);
(this.listeners[event] || []).forEach(function (x) {
x.callback.apply(x.callback.context, args);
});
};
// usage:
// an instance, or can be implemented as a singleton
var global_events = new Notify();
// wherever you want to be notified of login events
global_events.subscribe('login_success', function () {
// do something with the arguments
}, myContext/*optional*/);
// after success login
global_events.publish('login_success', user_credentials, other_data);
// and all subscribers (listeners) will be called after this
I have used that code for similar purposes and also used amplifyjs a couple times, you can read more about Amplify Pub/Sub.
I hope I'm not breaking any guidelines or anything as this is mostly a showcase of my search setup. There are a few question at the bottom. I promise.
I've made a Sencha Touch 2 PhoneGap app (which is now live in the AppStore for iOS, Android will be ready soon).
The basic premise of the app is a (somewhat huge) searchable list (with a couple of additional filters). Thus search performance is essential.
A rundown of my search controller:
When looking at examples I found people using either a submit button or search on keyup, since the latter interested me the most this is where I started.
In the examples I found, only the keyup listener was activated, a quick fix:
'searchfield[itemId=searchBox]' : {
clearicontap : 'onClearSearch',
keyup: 'onSearchKeyUp',
submit: 'onSubmit',
action: 'onSubmit',
paste: 'onSubmit',
blur: 'onSubmit'
}
Now, why are keyup and submit different you might ask?
Well, the onSubmit function just runs the search function (more on that later).
The onClearSearch just clears the filters set on the store and then adds the non search based ones back in.
The onSearchKeyUp function is where it gets interesting. Since the list is huge, searching on every single letter equals poor performance. The search field stalls while searching (especially on older devices), so while the user keeps tapping letters on the keyboard nothing shows up, while at the same time queuing up javascript and (in some cases) leading to the user tapping letters again so that when all the search functions are actually done, the input might not even be what the user intended.
I remedied this by trying to deduce when the user has finished typing. I did this by having the keyup triggering a timer, in this instance 650 ms, if another keyup event is triggered within this period, the timer is cancelled, if it runs its course it triggers the search function. This timer can of course be set higher or lower. Lower means the user has type faster for it not to search while typing. Higher means that the user will have to wait longer from their last keyup event for the actual searching to begin (perceived as lag).
Of course, there is an exception, if the keyup event is from the enter key, it searches right away. Code:
onSearchKeyUp: function(searchField,e) {
if (e.event.keyCode == 13){
window.clearTimeout(t);
window.timer_is_on=0;
searchFunction();
} else {
doTimer();
}
function timedCount()
{
t=setTimeout(function(){timedSearch();},650);
}
function timedSearch()
{
console.log('b4 search');
searchFunction();
window.timer_is_on=0;
}
function doTimer()
{
if (window.timer_is_on === 0)
{
window.timer_is_on=1;
timedCount();
} else {
window.clearTimeout(t);
window.timer_is_on=0;
console.log('cleared timeout');
doTimer();
}
}
}
onSubmit: function(searchField,e) {
if (window.timer_is_on === 0)
{
console.log('timer was not on when done was pressed');
} else {
console.log('timer was on when done was pressed');
window.clearTimeout(t);
window.timer_is_on=0;
searchFunction();
}
} else {
searchFunction();
},
So for the search function. I think I might have some room for improvement here.
First off, I noticed that when I had scrolled down the list and searched, I could end up below the actual list content, unable to scroll up. So the first thing the search function does is scroll to top.
As I mentioned in the beginning of the post, I have some additional filters. These are toolbar buttons which basically set variables and then trigger the search function. Within the search function, filtering is done based on the variables. I combined the search and filter functions as they both needed to clear filters and add them back in.
You'll also notice a loading mask and a delay. The delay is strictly empirical, turned out that a delay of 25 ms was needed for the loading mask to actually show.
My search function code:
function searchFunction() {
Ext.Viewport.setMasked({
xtype: 'loadmask',
message: 'Searching'
});
setTimeout(function() {
Ext.getCmp('lablist').getScrollable().getScroller().scrollTo(0,0);
var searchField = Ext.getCmp('searchBox');
queryString = searchField.getValue();
console.log('Please search by: ' + queryString);
var store = Ext.getStore('LabListStore');
store.clearFilter();
genderFilterFuncForSearch();
ageFilterFuncForSearch();
if(queryString){
var thisRegEx = new RegExp(queryString, "i");
store.filterBy(function(record) {
if (thisRegEx.test(record.get('Analysis'))||thisRegEx.test(record.get('Groupname'))) {
return true;
}
return false;
});
}},25);
}
function ageFilterFuncForSearch() {
if (ageFilter === 'A') {
Ext.getStore('LabListStore').filter('adult', '1');
} else if (ageFilter === 'C') {
Ext.getStore('LabListStore').filter('child', '1');
}
Ext.Viewport.setMasked(false);
}
function genderFilterFuncForSearch() {
if (genderFilter === 'M') {
Ext.getStore('LabListStore').filter('Male', '1');
} else if (genderFilter === 'F') {
Ext.getStore('LabListStore').filter('Female', '1');
}
}
That's basically my search setup. As this is my first Sencha Touch project it's been sort of a trial and error based workflow but where it's at now seems pretty good.
Hopefully someone'll have a few pointers to share (and hopefully I had some that some of you hadn't thought of).
A big part of this was collected from loads of different SO posts, thanks!
I promised I would have some questions:
Can you clear individual filters?
If so, would it be better
for performance to check which filters are set and then just
clearing those insted of calling store.clearFilter?
As you can see my searchfunction uses regex, is there a way to achieve the same
kind of search without converting it to a regex object, would it be
better for performance?
PS. Regarding 1)- I've found one way, store.filter('field', '', true); would actually clear a filter that was set in the same way. I couldn't clear the search filter in the same way though.
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 1 year ago.
Improve this question
Suppose I have the following code, which listens to an input element, specifically executing a callback when the user hits Enter:
...
document.getElementById('human_input').onkeyup = (function (indicate) {
return function(e) {
var e = e || window.event; // for IE8
if (e.keyCode === 13) { // if user presses 'Enter' key*
indicate('User has submitted text');
}
}
})(indicate);
[*keyCode reference here.]
...
What indicate does isn't really the point here but some code is applied to the argument 'User has submitted text' to display it somewhere.
Now, what if I want some script to listen in the same way to an 'invisible input' which is to be filled out, not by a human but by a separate part of the code?
Say that a black box script function called fillHidden() fills out the hidden_input field at some specified point.
document.getElementById('hidden_input').value = 'All filled out.'; // for example
How would a very separate listener script module find out about the value. Would it be enough to say do this...
document.getElementById('hidden_input').onkeyup = (function (indicate) {
return function(e) {
var e = e || window.event; // for IE8
if (e.keyCode === 13) { // if user presses 'Enter' key
indicate('User has submitted text');
}
}
})(indicate);
And have the fillHidden() code do this:
document.getElementById('hidden_input').value = 'All filled out.'; // for example
document.getElementById('hidden_input').onkeyup({keyCode: 13}); // simulate keypress
Or is that bad practice? There have been questions about how to do this or even simulate keyboard shortcuts but I could find nothing on the actual merits or otherwise of simulating keypresses.
Is there a better way, a pattern perhaps, or an interlocking system of callbacks, that can enable these modules to interact whilst separate? I have seen videos of Nicholas Zakas talking about this kind of architecture as 'loose coupling' and a book I have called JavaScript Patterns (Stefanov, 2010) alludes to possibilities of a subscriber/publisher model. What would be the advantages of these compared with a simulated keypress, especially when the simulated keypress makes for a kind of symmetry with the user's side of events? And what kind of disadvantages/dangers might one expect when simulating keypresses?
This is all very... abstract.
Which is all well and good.
It's not necessarily the goal of SO to come up with solutions to abstractions like this, because there are a million ways of doing them.
However, a few key points:
Unless you're linking simulated code to user-generated events in some way, the browser is likely going to prevent your simulations from taking over for the user
(imagine browsers letting any JS on the page simulate any keypress or any mouse-click at any time).
This typically means that you're tightly-bound, using some library which isn't meant for consumer use, using browser-specific (ie: FireFox/Chrome/IE) plug-ins which users must install/run, or bust. Pick one (or more).
Custom-Events, with callbacks, as a rule are what will allow you to keep your programs separate, but have them function together.
Zakas does some great talks on Sandboxing, but those are very enterprise-level, end-game type things. They're fantastic, and library builders and system-engineers should totally consider them, but for making the average page, it'd be better to complete the 100 lines you need to write, rather than build out a full framework, with a library which both wraps every module in an enclosure, and injects itself into that module.
That's where Pub-Sub (and Observer)/Moderator(or Mediator) come into play.
Any of the above might also be called an "emitter" or a "generator", et cetera, depending on the library.
Each one of the above is created in basically the same way, and does the same sort of thing.
The goal is to relay results to an audience who wants to listen.
The speaker chooses when to notify the audience, and what to tell them.
The audience chooses to tune in at any time, to await the next broadcast (which might never come, but at least they were paying attention for it), or they can choose to tune out and stop listening for broadcasts.
The differences between them, then, is how the "speaker" knows about the thing that's happening.
Publisher-Subscriber
In publisher-subscriber, the speaker IS the object that makes things happen.
Look at Twitter.
You sign up for a Twitter account. Once you have it, you can follow anyone you want.
Any time they tweet, you get notified about it.
Anybody can follow you, so that any time you tweet, they are notified about it.
The person who is doing the action publishes that action to any subscribers who want to hear it. There might be 3000 subscribers and one publisher (a newsletter), or 3000 publishers and one subscriber...
There might be publishers who won't subscribe, or subscribers who won't publish... ...but that's the paradigm.
Observer
In observer, you're talking about an object which is coupled to the thing doing the work.
It might be tight. It might be loose. But there's a thing doing something, and there's a thing that knows exactly what it's doing. Then people bug the watcher for updates.
Think of the days of Baseball, where people listened to the games on radio.
The observer would be the radio-commentator.
He wasn't the one hitting the ball or stealing bases. He was the one in the booth, who saw everything going on, and knew what it all meant, and turned it into user-friendly information for all of the people listening at home.
These days, players can tweet about plays as they're making them, directly to all of their fans (pub-sub), and I'm sure that FourSquare will find a way to get their geo down to the per-base accuracy, for hands-off updating of who the king of third-base is (for once, it's not Jeff, in his cramped Z28).
Mediator/Moderator
In this case, we're talking about an object which everybody knows about, but nobody knows about one another.
Imagine a call-in talk-radio show.
Everybody knows the show. Everybody can call into the show, and talk with the host. But other than coincidence, nobody knows anything about the other listeners.
It's a little bit different than pub-sub, because everybody's a publisher, but you don't have to know somebody's Twitter handle to hear from them. You can say Hey Twitter, any time anybody in the world mentions #browns, let me know. I'm starving..
It's a little different from observer because while the moderator IS watching the person who does the work, anybody can be doing the work at any time.
Which one is the right one?
It all depends on what you need, and what you're actually intending to do with it.
Here's how we might make a Moderator:
var Moderator = function () {
var events = {},
notify = function (evtName, data) {
var evt = events[evtName];
if (!evt) { return; }
evt.forEach(function (func) { func(data); });
},
listen = function (evtName, callback) {
events[evtName] = events[evtName] || [];
events[evtName].push(callback);
},
ignore = function (evtName, callback) {
var evt = events[evtName];
if (!evt) { return; }
evt.forEach(function (func, i, arr) {
if (func === callback) { arr.splice(i, 1); }
});
};
return { ignore : ignore,
listen : listen,
notify : notify };
};
Pretty simple and straightforward, right?
Of course, this isn't particularly filled with bells and whistles, like subscribing to only the next time an event fires, or the next-3 times, or whatever...
We might use it like this:
var Game = function () {
var game_moderator = Moderator(),
scoreboard = Scoreboard(),
messages = MessageBoard(),
player_one = Player(),
player_two = Player();
function initialize () {
player_one.initialize(game_moderator);
player_two.initialize(game_moderator);
game_moderator.listen("player:death", scoreboard.update);
game_moderator.listen("player:death", messages.add_kill);
game_moderator.listen("chat:input", messages.add_msg );
}
function start() {}
/* update... draw... etc... */
return {
load : load,
initialize : initialize,
start : start
};
};
var game = Game(),
loading = game.load();
loading.done(function () {
var initializing = game.initialize();
initializing.done(game.start);
});
Meanwhile, Player might look like this:
var Player = function (name) {
var system,
health = 30,
damage = [],
attack = function () { /* ... */ },
hurt = function (amt, type, from) {
health -= amt;
damage.push({ amount : amt, type : type, from : from });
},
die = function () {
var killing_blow = damage[damage.length - 1];
killing_blow.player = name;
system.notify("player:death", killing_blow);
},
update = function () {
if (health <= 0) { die(); }
},
draw = function () {},
initialize = function (sys) { system = sys; };
return {
initialize : initialize,
update : update,
draw : draw,
hurt : hurt
/* ... */
};
};
So looking back into the Game.initialize function, we can see that we've got a scoreboard and a message panel which are both going to do things with "player:death" events.
Because of the way the players are called and defined, I'm injecting a reference to the moderator, during their initialization (so that I can keep everything separate: dependency-injection).
But player_one knows nothing about player_two, scoreboard knows nothing about anything, except that something occasionally calls its .update method and passes in kill information, and messages gets all kinds of love, but it's the kind where everybody's a stranger...
Going back to your original problem:
If your hidden-input is being filled in by spying on key-presses, why not build an Observer?
Build an observer which connects to a keyup event-listener and a keydown event-listener.
Have that observer turn those events into useful information (for instance: when you hold a key down, the keydown event fires dozens of times per second -- you probably don't want that... so alert when a new key is added, or alert when a pressed key is released).
Have the hidden-input subscribe to that.
When the hidden-input is full, or however your requirements are operating... ...and you want to fire an event off, have a global-moderator (or a moderator which is at the top level of the system that hidden-input is a part of).
From there, fire an event called "hidden-input-filled" or whatever is meaningful.
The people who care about that happening can subscribe to that event through the moderator.
That is, of course, if your program is built in such a way that nobody should know about the hidden-input, BUT there are people who should know about hidden-input's events.
If there are only a select group of things which should know about hidden-input, and those are the only things which should know about its events, and hidden-input should also be able to know something about them, then make them pub-sub.
Or mix and match your connections:
The idea is to build communication which makes sense and tells people what they need to know, and no more.
So if Twitter-users should be sub-pub, but different widgets on the page (timeline vs search vs recent-pictures, etc) shouldn't know much about one another (and certainly not make every picture able to share with every timeline update), then make a global moderator that the whole widget can communicate to other widgets through (like when timelines need to update based on search results), and inside of each widget, have a moderator and/or pub-sub for different connections, between components.
Hope that helps, and I hope that explains why it's easier to engineer a loosely-coupled, large program by doing this, rather than by hijacking real events, and then firing fake ones down the line, intended to target different areas of your program.
In all honesty, if your full site, with all of its programs comes down to: "I've got this input that does this thing, and another input that does another thing", the answer is that it really doesn't matter too much.
When you get to: "I've got a page with 8 spaces for different widgets, and there are 16 possible widgets which could be loaded in any of those 8 slots at any time, and there are some major actions in some widgets which should cause responses in other widgets, and there are lots of events that each widget needs to control internally, and we need to pipe in an AJAX library and a DOM/MVC(or MVVM) libarary to control all of the stuff that goes on inside of each widget, itself, and there's only one of me..."
That's when it's a great idea to hammer out this stuff and hammer out Promises/Deferreds/Futures, and break your big ideas out into smaller pieces, spread out over different points in the life of the running application.
I’m trying to figure out which pattern to follow in a certain situation. I have web app that consists of several main widgets that interact with each other somehow. The widgets follow the module pattern.
To let code speak:
MyApp.Widgets.MainLeftBox = (function(){
var self = {};
self.doSomething = function(){
var certainState = MyApp.Widgets.MainRightBox.getCertainState();
if (certainState === 1){
console.log(‘this action happens now’);
}
else {
console.log(‘this action can’t happen because of a certain state in My.App.Widgets.MainRightBox’);
}
}
return self;
})();
As you can see, I have tight coupling here. As we all know, tight coupling is evil. (Except when you have found the one and only! ;-))
I know a lot of decoupling can be achieved by following a pub-sub / custom event pattern. But that’s better suited for situations were A starts something and B can react upon. But I have a situation where A starts something independently but needs to check a certain state from B to proceed.
As I’m striving for maintainability, I’m looking for a way out of this hell.
What first came to my mind is the mediator pattern.
But still, my code would look like this:
MyApp.Widgets.MainLeftBox = (function(mediator){
var self = {};
self.doSomething = function(){
var certainState = mediator.getCertainState();
if (certainState === 1){
console.log(‘this action happens now’);
}
else {
console.log(‘this action can’t happen because of a certain state in mediator’);
}
}
return self;
})(MyApp.Mediator);
This is a little better, because Widgets don't communicate directly but indirectly through the mediator.
However, I still feel that I'm doing it wrong and there must be a better way to achieve decoupling the widgets from each other.
EDIT
Let me sum things up so far!
In general, I do like the MVC approach of separating the views! However, think of this example more like complex modules. Those doesn't really have to be "boxes" in a visual sense. It's just easier to describe this way.
Another given fact should be, that A starts an action independently and needs to check for some state then. It can't subscribe to B's state change and provide the action or doesn't. It has to be like A starts it independently and then needs to check a certain state. Think of this as some complex operation that B needs be asked for.
So I came up with a mixture of custom events/callback/mediator approach and there are some things that I really like about it.
1.) A module doesn't know about any other module
2.) A module doesn't know about a mediator neither
3.) A module that depends on some external state does only know that it depends on some external state - not more
4.) A module really doesn't care who will provide this certain state
5.) A module can determine if that certain state has been provided or not
6.) The request pipeline is straight. In other words the module is the starter of this operation. it doesn't just subscribe to a state change event (Remember A starts the action and then needs a state from B (or somewhere)
I posted some example code here and also provide a jsFiddle: http://jsfiddle.net/YnFqm/
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
</head>
<body>
<div id="widgetA"></div>
<div id="widgetB"></div>
<script type="text/javascript">
var MyApp = {};
(function (MyApp){
MyApp.WidgetA = function WidgetA(){
var self = {}, inner = {}, $self = $(self);
//init stuff
inner.$widget = $('#widgetA');
inner.$button = $('<button>Click Me!</button>')
.appendTo(inner.$widget)
.click(function(){self.doAction();});
self.doAction = function(){
//Needs state from WidgetB to proceed
/* Tight coupling
if (MyApp.WidgetB.getState() == 'State A'){
alert('all fine!');
}
else{
alert("can't proceed because of State in Widget B");
}
*/
var state;
$self.trigger('StateNeeded',function(s){state = s});
if (state == 'State A'){
alert('all fine!');
}
else{
alert("can't proceed because of State in Widget B");
}
};
return self;
};
MyApp.WidgetB = function WidgetB(){
var self = {}, inner = {};
//init stuff
inner.$widget = $('#widgetB');
inner.$button = $('<button>State A</button>')
.appendTo(inner.$widget)
.click(function(){
var buttonState = inner.$button.text();
if (buttonState == 'State A'){
inner.$button.text('State B');
}
else{
inner.$button.text('State A');
}
});
self.getState= function(){
return inner.$button.text();
};
return self;
};
MyApp.Mediator = (function(){
var self = {}, widgetA, widgetB;
widgetA = new MyApp.WidgetA();
widgetB = new MyApp.WidgetB();
$(widgetA).bind('StateNeeded', function(event, callback){
//Mediator askes Widget for state
var state = widgetB.getState();
callback(state);
});
return self;
})();
})(MyApp);
</script>
</body>
</html>
You should checkout a great article about large scale JS apps presented by Addy Osmani Patterns For Large-Scale JavaScript Application Architecture and here is a code sample Essential js design patterns
You can still go with the mediator, but implement your business logic inside it. So, instead of mediator.getCertainState(), have a method mediator.canTakeAction() which knows about the widget(s) to query, and determine if the action is allowed.
This will still end up with a mediator which knows the widgets to query, of course. But since we've offloaded the business logic inside the mediator, I think it is OK for it to know of such things. It may even be the entity that creates these widgets. Alternatively, you can use some sort of registration mechanism where you tell your mediator which widget is used for what role when you create them.
EDIT: Providing an example in the spirit of the given code samples.
MyApp.DeleteCommand=(function(itemsListBox, readOnlyCheckBox) {
var self = {};
self.canExecute = function() {
return (not readOnlyCheckBox.checked()) && (itemsListBox.itemCount() > 0);
}
return self;
})(MyApp.Widgets.ItemsList, MyApp.Widgets.ReadOnly);
You can take this two steps further:
Register to state changed events of the source widgets, and update a local cache of the canExecute every time a state change occurs on one of the source widgets.
Also take a reference to a third control (say, to the delete button), and enable or disable the button according to the state.
Assuming I'm understanding the nature of a "box" as a box that's visible on your page, then the box should render a view that represents a state of your application or some piece of it -- the underlying state itself should be maintained by an object that's separate from the view that represents that state in the UI.
So, for example, a box view might render a view of a Person, and the box would be black when the person was sleeping and white when the person was awake. If another box on your was responsible for showing what the Person was eating, then you might want that box to only function when the person was awake. (Good examples are hard and I just woke up. Sorry.)
The point here is that you don't want views interrogating each other -- you want them to care about the state of the underlying object (in this case, a Person). If two views care about the same Person, you can just pass the Person as an argument to both views.
Chances are good that your needs are a tad more complicated :) However, if you can think about the problem in terms of independent views of "stateful objects", rather than two views that need to care directly about each other, I think you'll be better off.
Why can't you use pub-sub model in the following way
LeftBox issues a getStateFromRightBox event.
RightBox has getStateFromRightBox subscriber, which, issues sendStateToLeftBoxAndExecute event with the stateData
LeftBox has a sendStateToLeftBoxAndExecute subscriber which extracts stateData and executes the action conditionally.
A Few Potential Options
I would still recommend using a Mediator -- however, if you're more of an Inheritance fan, you may want to play around with the Template Method, State or Strategy, and Decorator Patterns -- since JavaScript does not have interfaces, these might be useful.
The latter approach might allow you to categorize your procedures into more manageable strategies, however, I'll go on to cover the Mediator since it makes the most sense [to me] in this situation.
You can implement it as EDM (Event-Driven Mediation) or as a classic Mediator:
var iEventHub = function iEventHub() {
this.on;
this.fire;
return this;
};
var iMediator = function iMediator() {
this.widgetChanged;
return this;
};
The only thing I can really advise is to break down your procedures to give Mediator a chance to have a say during the process. The mediation could look more like this:
var Mediator = function Mediator() {
var widgetA = new WidgetA(this)
, widgetB = new WidgetB(this);
function widgetChanged(widget) {
identifyWidget(widget); // magical widget-identifier
if (widgetA.hasStarted) widgetB.isReady();
if (widgetB.isReady) widgetA.proceed("You're proceeding!");
}
return this;
};
var WidgetA = function WidgetA(director) {
function start() {
director.widgetChanged(this);
}
function proceed(message) {
alert(message);
}
this.start = start;
this.proceed = proceed;
return this;
};
var WidgetB = function WidgetB(director) {
function start() {
this.iDidMyThing = true;
director.widgetChanged(this);
}
function isReady() {
return iDidMyThing;
}
this.iDidMyThing = false;
this.start = start;
this.isReady = isReady;
return this;
};
Basically, WidgetA has to get permission from Mediator to proceed, as Mediator will have the high-level view on state.
With the Classic Mediator, you'll likely still need to call director.widgetChanged(this). However, the beauty of using EDM is that you don't necessarily couple to Mediator, itself, but all modules implement an iEventHub interface or couple to a common hub. Alternatively, you can modify the classic Mediator to aid in Module Authorization by refactoring the widgetChanged method:
// Mediator
function widgetChanged(ACTION, state) {
var action = actionMap[ACTION || 'NO_ACTION_SPECIFIED'];
action && action.call && action.call(this, state);
}
// WidgetX
const changes = this.toJSON();
director.widgetChanged('SOMETHING_SPECIFIC_HAPPENED', changes);
I think you're very close -- I hope this helps.