Additional Project Ideas
- move form into a separate view class
- make subscribe not clobber a potential subscribers property on the observed object
Get Your Truth out of the DOM
The mantra of DRY code popularized by Rails is essentially a pithy way of stating that there should only ever be a single source of truth for any given item of information in your code.
I recently heard Jeremy Ashkenas refer to this idea when he said that client-side HTML application developers should try to moe their truth out f the DOM. For example, don't scrape your DOM elements to build up an idea of what the data representation of something should be.
MVC, aka MVP, MVVM, etc.
If we're moving truth out of the DOM, where do we store it? In a model, of course.
How do we display that model to a user? With a view, of course.
I'll really only be looking at the M and the V anyway.
There's a lot of fuzzy nomenclature around how to best describe what people are doing on the web. Maybe traditional MVC doesn't fit, maybe we're looking at more of a view/view controller type of situation. There are some blurred lines where web programming is concerned.
Regarding models, there's a lot of hooplah about how using Node on the server is awesome because you can use the same model code on the server as the client. I think this is wrong; you shouldn't worry about making them the same. A lot of the time, client-side models will be simplified versions of their server counterparts—because all they have to deal with is the presentation logic. They could even be amalgamations of two or more server models. The point is, don't worry about it.
aka Observer pattern
Really, a lot of what we're building today is going to depend on the idea of the Observer pattern. It's a relatively straightforward way to make some pretty clean, decoupled code.
Roll Our Own
We'll be kinda cheating; starting with a hard-coded form which should probably be its own view model.
What's wrong with this?
Stores data in the DOM. Let's say we're doing something to manipulate this sucker after the fact; votes, comments, or the like. When we update this stuff, we have to have a bunch of complex code that parses the DOM anew to get a grasp of what's going on with the link. Like good ol' PHP, we're mudding all sorts of concerns.
Not exactly awesome that I'm using hard-coded HTML right there in the JS too. Hmm.
What if we moved it into a model?
Okay, so this is showing some stuff that is only here for demonstration purposes, but if you haven't seen anything like this before, it's good to know. Anyway, I got sick of having to specify the precise URL while I was testing this stuff; I just wanted to type in a few characters and have it grab a close match.
So one of the big deals with functional languages is so-called first class functions. That means you can pass functions around, for example as parameters to other functions.
You can also compose functions, or write functions that return other functions. That's what I'm doing here with the link_info function. I've moved the original object literal inside of a self-executing function expression, and then I return a function that actually searches through the URL keys of the objet literal looking for a match. A closure at work. Note that the object literal is now inaccessible from the outside world.
See what I'm doing there with for/in? Why is that dangerous in some cases?
Also, see how I'm assigning the URL property to the retrieved link info? What happens the second time I grab the same link info?
Last point, since I mentioned self-executing function expressions. It's generally a good idea to wrap all of your code in one. Why? (Limited damage to the global scope, less chance of conflicts, etc.) I didn't do it in my main.js because, well, it's already wrapped in a function because of the call to jQuery.
Here's my implementation of the publish/subscribe pattern for use in this project.
Subscribe is relatively straightforward. It's just a function you call, passing the object you want to observe, the name of the event you want to observe, and the callback to trigger when that event happens.
The other thing to note with subscribe is that I'm not passing the object doing the observing. I'm not passing a string representing the name of some method to call. Nope, I'm just padding a plain old function. That's gonna give me the most flexibility, because it could be an object's method, but it doesn't have to be.
Maybe also note the way I'm wrapping each callback in a call to setTimeout, in an effort to keep things moving relatively fast. Probably not necessary in a demo app of this size, but hey, it's a useful trick to keep in mind for larger projects, an easy perceived performance win.
I'm throwing a bunch of stuff in here because we sort of need it all to get going.
Incidentally, calling a function designed to be a constructor without "new" has some weird side effects, since when "this" has no other meaning, it defaults to the global scope. So you get this strange side effect of setting up a lot of global variables. This is why some JS programmers advocate against using "new", and lobby for factory-style functions instead. There are some other benefits to that approach, like being able to set up fake "private" variables, but I'm going to be relatively straightforward here.
Incidentally, I pretty much lifted the idea of .get and .set from Backbone. I don't really like it, but JS doesn't really have its own answer to Ruby's method_missing—maybe someday, though—so this is really the only way to wrap supplemental functionality around get/set calls.
Note the call to publish whenever set changes the value of a property.
Another idea I stole from Backbone. It makes it handy to deal with collections of stuff, in my case, adding and maybe removing elements of the collection.
Note how it publishes an event whenever a link is added to the collection.
The names are getting rather unwieldy here. I feel like a Java or Objective-C programmer.
Anywhow, I've created a view object for dealing with the collection as a whole.
There's our first call to "subscribe": Whenever a link is added to the collection, we want to know about it so we can generate a new SharedLinkView object.
Note that not a whole lot else is going on here—actually dealing with individual links gets delegated to SharedLinkView objects.
As SharedLinkCollectionView is tied to a SharedLinkCollection, so SharedLinkView is tied to an individual link.
This is where all the logic for actually displaying an individual link takes place. Everything is very nicely decoupled. Even the communication is often indirect, via the publish/subscribe mechanism. Good times.
So the current answer is templates. There are hella templating libraries out there. Now, I know we're rolling our own here, but I figured I'd punt on this one and just grab a library, and I chose Mustache.
Anyhow, so we're able to move the template file into its own script tag, with a type of "text/x-mustache" so it doesn't get parsed as JS by the browser. Note that it's got an id, so it can be grabbed trivially by jQuery.
Cool. So, my current thinking about how this relates to a more traditional pattern like view/view-controller. The templates, and how they integrate with the DOM (e.g., event handlers and such) really make them the views. The user interacts with them more or less directly. The classes I've suffixed with View actually act more like view controllers. User interaction gets passed up to these guys, and we deal with it there.
Anyway, so now I have no HTML in my JS. Cool. Cool cool cool.
This is another big jump. Basically, in apparently one fell swoop, I've changed the app so that initially shared links are marked as pending first (simulating a remote call to the server or something) before being added to the shared-link list.
This purpose of this is to try and show the advantages of moving to this sort of model. It's all driven by changes to the underlying model. When the model is "saved", its id property gets updated, which the pending-link collection is waiting for, so that it can remove the link from its list. Meanwhile, the shared-link collection has been modified to watch the pending-link collection; when it sees that a link has been removed from the list, it knows that it can add it to its own list.
One problem that comes to mind is the case where the "save" call errors out—the shared-link collection adds links whenever they're rmoved from the pending link collection. What if they were removed because the request couldn't be completed? So obviously this demo app has some cracks around the seams.
Note also that there's a lot of very similar code here, with the pending-link collection and views looking quite close to their shared-link cousins. Some of this stuff is looking ripe for pulling out into their own classes. Maybe I'll add yet another client-side MVC framework into the pool. Maybe not.
So this adds the ability to "like" shared links. I added this to show what the bubble-up interaction between a view object and its associated model might look like. Obviously there's some more problems; being able to like the same link any number of times, for example. But it's still a good example of encapsulating business logic at the model level, and just handling display concerns at the view level.
There are two main functions in the AMD API: require and define. The former is pretty much a mechanism for loading script files; the latter is a variant that specifically defines modules that can be required by other scripts. Both take as their first argument an array of strings representing the file names of their dependencies. These are then injected into the functions' second argument, a function.
However, it seems like it would be beneficial for testing, since it seems like it'd be a good mechanism for injecting mock versions of dependencies.
All in all, I really like the idea of require.js. For best performance, especially in production, it generally needs something like a compile step that resolves all the dependencies and produces a single file, but hey, most web apps of any size are already doing something like it already.