0

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?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
guypursey
  • 3,114
  • 3
  • 24
  • 42
  • the question is, what is 'User submitted text' actually? Imho this answer would open up a lot of possibilities for solving your problem ;) – Robin Drexler Jan 17 '13 at 20:37
  • At the moment it's just a message, perhaps destined for the console, perhaps somewhere on the interface, which indicates the user has written something and pressed the Enter key. That's all the `indicate()` method does (hence my not covering specifics). I've changed the string to `'User has submitted text'` now as I can see how that might be confusing. Thanks for pointing it out :-) – guypursey Jan 17 '13 at 20:41

1 Answers1

2

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:

  1. 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).

  2. 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.

  3. 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.

Norguard
  • 26,167
  • 5
  • 41
  • 49
  • Just wanted to say many thanks for your thoroughly detailed answer; this was much more than I ever expected to get back! I also appreciate your point about considering the scale of the programme. I'm quite a keen coder, always itching to learn more about different types of solutions, but I think you're right that in practical terms one should consider the merits of a huge framework for a simple problem. I'll play around with some of the patterns you include here, in particular dependency-injection, as I ponder the scalability question further. Thanks again :-) – guypursey Jan 19 '13 at 12:56