Condividi tramite


JavaScript Architecture

No, that isn’t meant to be an oxymoron. But something I’ve noticed recently is that people’s approach to JavaScript seems to be diverging down two common paths. This blog post is designed to encourage you to adopt the one you probably aren’t planning to adopt right now! The approaches I’m describing are depicted below;

Keep that in mind and we’ll look at each in turn…

Script-on-top

What I’ve dubbed “script-on-top” is what I believe most developers do right now. That is, they look to sit JavaScript on top of the HTML and CSS; the script uses the HTML and CSS and manipulates them as though they are components just waiting to be orchestrated. You’ll often see code that looks a bit like this (I’m using jQuery);

    1:  $(document).ready(function () {
    2:      $('#combo').change(function () {
    3:          var value = $(this).val();
    4:          var result = myframework.getDisplayMessageFromServer(value);
    5:          $('#messagediv').html(result);
    6:      });
    7:  });

* Note that none of the script in this post is real. I just hacked it into notepad – but it demonstrates the point sufficiently.

There are some good practices there – not least the fact that the call back to the server is encapsulated within a reusable framework. This is layering, effectively creating a data access layer in JavaScript, and hence is following patterns that have been established for a long time for server-side code. The configuration for how to communicate with the server and the location of the server endpoint is all encapsulated within this framework.

However, I have a few problems with this approach.

1. I don’t like specifying element identifiers in JavaScript. Why? Because it feels wrong. It just does. Stick with me and you might start to agree. Fundamentally this comes down to the separation of concerns between CSS, HTML, and JavaScript.

2. If I have lots of drop-down lists that have very similar behaviour, I still have to wire up every single one. A lot of developers really do wire up every single one manually in code like that above. There are ways to apply behaviour in a blanket manner but none quite so good as my preferred approach… so hold that thought.

3. I don’t like the idea of having to write an entirely new User Interface “manager” component of some description. What often happens with the above approach is that when a page gets more complex developers create a central class that wires up all these events, maybe caches some data, and more. Why? Surely we’re done all that server-side, must I really Repeat myself (R capitalised in homage to DRY) on the client?

4. The URLs to each endpoint are embedded in the framework. But my URLs are “designed” on the server (especially if you’re using Routing, perhaps with ASP.NET MVC)… surely I don’t want to hard-code them in the script too?

Script-as-a-platform

So what is the alternative? I believe we need to turn our way of thinking about JavaScript on it’s head. Instead, think of CSS and JavaScript as defining a platform that our HTML mark-up simply refers to in order to build up a user interface. Rather than our JavaScript taking ownership of HTML elements, our HTML makes use of predefined JavaScript “behaviours” (yes, I know that is an emotive term for JavaScript so forgive me!) and CSS styles.

Such a mind-set leads to HTML that looks like this (I’m using ASP.NET MVC 3 with a Razor view);

    1:  <select 
    2:      data-getmessage-output="#messagediv" 
    3:      data-getmessage-source="@Url.Action("Message", "Ajax", new { item = "{placeholder}" })">
    4:  </select>
    5:   
    6:  <div id="messagediv"></div>

… and matching JavaScript a bit like this;

    1:  $(document).ready(function () {
    2:      $('*[data-getmessage-source]').change(function () {
    3:          var value = $(this).val();
    4:          var url = $(this)
    5:              .data('getmessage-source')
    6:              .replace('{placeholder}', value); ;
    7:          var output = $(this).data('getmessage-output');
    8:          var result = myframework.getDataFromServer(url);
    9:          $(output).html(result);
   10:      });
   11:  });

So what is the difference? The difference is that my JavaScript is a reusable component, and my HTML has output instructions to the client to make use of that component through the use of an HTML 5 Custom Data Attribute. It could easily have used a CSS class instead if the parameters to my component were simpler. The script is slightly longer, but it isn’t complex – and none of it will be repeated in individual pages.

As a result, the URL to the endpoint is no longer hard-coded in a JavaScript library. And all SELECTs that should have this behaviour get it automatically just by referencing this script. The pairing of output DIV and drop-down list is clearly defined in HTML, not tucked away in JavaScript somewhere. There are, in fact, no IDs in my JavaScript, which means that my HTML is completely owning the document structure while JavaScript provides behaviour for that structure... without detailed knowledge of it.

I can still create reusable JavaScript – in fact, my sample code could be hugely refactored into something much more reusable.

But most of all, there is no longer a need or temptation for a central orchestrating JavaScript component – our orchestrating is done by the HTML, which is generated server-side, so our server code is really pulling the strings. We’ve moved our mind-set from writing JavaScript to “make the UI work” into writing HTML that composes reusable JavaScript components and behaviours. The highlight is because I believe that’s a good summary description of what I’d encourage you to do.

Does It Matter?

Yes, I believe it does. Follow this composition approach and your JavaScript code will be easier to thoroughly test (because you won’t have lots of separate wiring up code for each page), and the act of composing will often feel simpler to the developer than writing imperative code to “pull strings” for any particular page.

So, what do you think?

Comments

  • Anonymous
    February 28, 2011
    Nice decomposition of the problem and I agree.

  • Anonymous
    February 28, 2011
    Hi Simon - good post. I agree with a lot of the ideas presented here. I actually used a technique very similar in a post from a few days ago. The article was technically about progressive enhancement techniques (also with MVC 3 and Razor), but my conventions for creating hyperlinks which can open dialog windows and customize the title of the dialog all fit the conventions you describe here. www.matthidinger.com/.../Progressive-enhancement-tutorial-with-ASP-NET-MVC-3-and-jQuery.aspx Let me know what you think -Matt @matthidinger

  • Anonymous
    February 28, 2011
    Very interesting idea, which reflects some of the sentiments I have also had. I have go to great lengths to prevent redeclaring the URLs in my JavaScript. Definitely gonna try this out sometime. My only concern is that the HTML might become cluttered with a lot of attributes that distract from the semantics.

  • Anonymous
    February 28, 2011
    @ Matt; Nice link. I blogged on PE a while ago too; blogs.msdn.com/.../jquery-mvc-progressive-enhancement.aspx ... but as will be obvious from the sample my ideas have evolved even further since about how that PE should be implemented, hence this post. Great example in yours though, and very nicely walked through. @ Colby & Erik; Thanks! And interesting point about the cluttering of the HTML. I'll be keen to see if this does become a problem - my suspicion is that having strict rules about HTML formatting and indentation would negate that issue... in many ways, HTML is already very cluttered so I'd hope we could deal with it. Simon

  • Anonymous
    February 28, 2011
    This is interesting. It definitely gives me some ideas to think about and definitely has advantages in some scenarios. Will have to explore more, and I mean that, despite what I am about to type :) I have three concrete examples from my work where the script-as-a-platform-driven-by-attributes approach gives you less control than script-on-top.

  1. If the UI being developed is just plain ol' html pages that needs a re-usable javascript library to make server-side calls or encapsulate logic, putting things in attributes gets you into the same troubles. You now have to start embedding the urls manually in attributes in the page. The only other advantage that applies is that you don't need the id's, but this is not a big advantage. Server-side generated items, usually form elements, will have id's generally anyway.
  2. At work we have a mixed application. The UI guys are building on top of a really crapily done skinning implementation on top of webforms. Now, we could start tweaking the old webforms code to start putting in attributes but at that point we are no longer allowing our UI guys to work independently. Not a problem with the script-on-top approach.
  3. We do a lot of A/B testing of the UI layer. Having to have different server-side code to generate A and another to generate B would be a bad idea, and that would be necessary if you wanted them to behave differently. Another reason to consider the script-on-top approach rather than the script-as-a-platform is that it more closely parallels the relationship of css to the markup. The code may be generating the hooks and putting them in attributes, but you are still changing your markup more just to accomodate your script. Or, going further, you are actually changing your markup MORE to accomodate your script this way. That's not necessarily wrong but I like that so much about the css...I want it with my js too :) Okay, and now for two very short and admittedly pedantic statements :) First, "Script-on-top" is pretty descriptive but "Script-as-a-platform" is not because in my mind it equally applies to both. Of course the the first applies to the latter approach as well. Second, I'm not sure the images convey these concepts well. So there are a few cases where the approach you are not advocating wins, IMO. Oh, and there is no reason why you couldn't auto-generate some js that defines your routes and have your js reference that, so you can still get away with not hard-coding your routes in your js files. But interesting post. Like I said, will be experimenting some.
  • Anonymous
    February 28, 2011
    @ Eric; interesting points - I certainly think your point (2) is food for thought, in that non-greenfield development will be more difficult to port to my "platform" approach. As for (1), that is easily counteracted if necessary, perhaps by having a data-retrieve-data-type="person" attribute, which means the framework code knows how to go and get that data, what the URL is, etc... I quite like having the URLs in mark-up, but I see your point. As for (3) I don't know enough about this to comment :) I intentionally named it "Script-as-a-platform" because I wanted to evoke emotional feelings and mental pictures of using JavaScript functionality in the same way as you might a browser, or .NET, or similar - so I disagree that the Script-on-top approach shares those qualities. Note I'm referring to your own custom Script when I call it script-as-a-platform... obviously you could argue jQuery or JavaScript itself is a platform for your script, but that's not what I'm trying to get at. Wow, I just confused myself with that last sentence, so if you're still following well done :-o Simon

  • Anonymous
    February 28, 2011
    The comment has been removed

  • Anonymous
    February 28, 2011
    @ Eric, LOL about scripting-with-kittens. Why didn't I think of that? As for URLs, I agree 100% - there should be a single version of the truth, not multiple "embeddings", which is why I think that truth should be the routing table on the server, which should emit the relevant URL into the HTML to help the script along when needed. As for the layering, I'm not saying you shouldn't layer and componentise JavaScript - I'm just trying to switch the mind-set around of what that script and its layers do. Once we have that approach right, layering and reuse are hugely important. Simon

  • Anonymous
    March 02, 2011
    Have you looked at Knockout which is an MVVM pattern for javascript. http://knockoutjs.com/

  • Anonymous
    March 02, 2011
    @ CJ; I have, briefly, and other similar approaches, and they're very interesting. They certainly hugely improve the structure of a lot of JavaScript. But I feel like that is going even further down the "script-on-top" route, so isn't really compatible with wait I'm aiming at here... Simon