Skip to content

Instantly share code, notes, and snippets.

@mstade
Last active January 4, 2016 23:12
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mstade/7948912 to your computer and use it in GitHub Desktop.
Save mstade/7948912 to your computer and use it in GitHub Desktop.
Some thoughts on application/view

Application views using HTTP trickery

If the client makes a request, let's say this is an initial request to a resource that the client knows nothing about. It goes along and does a little something like this:

GET /foo HTTP/1.1
Accept: text/html

When the server responds, we'd like it to respond with some text/html, because that's what we asked for. So this particular server is pretty cool and nice and decides to play a long, and responds with something like this:

HTTP/1.1 200 OK
Content-type: text/html
Link: </foo>; type=application/javascript; rel="http://d3js.org/component"

<div>
  <!-- This right here is the state of the world. -->
  <h1>Hello,</h1>
  <span>world.</span>
</div>

Oh, what's that Link? Let's see! Wait, before that, let's update the HTML we're already displaying to the user so they're not upset with us going out looking for stuff.

OK – DOM is updated (took like 4ms because we rollin') now let's check out that Link. First, we see that the rel attribute says http://d3js.org/component. This is something I made up, and since we're in the bidniz of making stuff up, I've decided that at the end of that URL there's a document describing the relation as such:

When used as a link relation, it the representation at the other end should adhere to this profile.

This representation is a javascript document that exports a function with the d3 component signature:

    exports = function(selection, data) { ... }
    
This component should be used to a selection representing the DOM of the html that linked to this javascript representation. Thus, the HTML representation that links to this component was actually generated using this component and should be possible to update using this component.
    
That is all.

(Maybe an unconventional use for the rel attribute, I don't know. Look at the representation later on for a profile link with the same URL. Maybe this is weird overloading or something. Maybe @mamund could lecture me on that.)

Now that we know that, let's GET the javascript!

GET /foo HTTP/1.1
Accept: application/javascript

And server might respond with:

HTTP/1.1 200 OK
Content-Type: application/javascript
Link: <http://d3js.org/component>; rel="profile"

exports = function(selection, data) {
  // Stuff
}

This is cool, because now, if we request text/html again, maybe we could use this component to do what the profile said and just update the DOM rather than doing a server round-trip. Maybe the component does all sorts of requests to make sure the data is up to date, maybe it doesn't – I don't know! Point is, the server can get really clever. Maybe it sends cached data along with the component, so that the HTML view can change and the component is using cached data to do it. Maybe the component is smart enough to know when to request more data and when to not do so? So many possibilities!

Anyway, this seems like a cool way to provide a sort of hybrid between fat clients that know how to bootstrap an app from scratch and a client that knows how to incrementally change its state.

What about selectors?!

So one cool thing about application views was that we could make choices based on whether the DOM we inserted a view into adhered to some query. That meant that we could serve up different logic and markup depending on where in the application we were. Here's an example:

<div class='person profile-small'>
  <!-- This div would only get the essentials of a person profile card; name, phone number maybe. -->
</div>

<div class='person'>
  <!-- This div would get the standard version of a person profile card; name, phone number, shoe size maybe. -->
</div>

<div class='person profile-all-the-things'>
  <!-- This div would get the NSA version of a person profile card; that is, everything definitely. -->
</div>

When we made our requests, we knew enough about the resource to pick a representation ahead-of-time, because of our resource description format thingamajiggy. However cool this was, I think it tied the client to the server too much because the client were told before hand that "these are the things I have" and that couldn't change. Now let's say we wanted to do this with the approach outlined above.

Well, one obvious way of doing it is that the component that the server gave to the client decides. This is easy to implement and would look something like this:

function(selection, data) {
  selection.is('profile-small')?
    renderSmall(selection, data)
  :
  selection.is('profile-all-the-things')?
    renderNSA(selection, data)
  :
    renderDefault(selection, data)
}

(Unconventional syntax level 1000.)

And for a lot of cases this probably makes tons of sense. But look at what's happening. We're giving out the capability to render really big representations just as really small ones. If we're going with sending data along with the component for performance reasons, we might actually be sending tons of it even though we're really only using a fraction (i.e. profile-small vs. profile-all-the-things.) As well, this kind of technique essentially encourages monolithic component design which is less than ideal. Remember that components in the chat app for instance were actually pretty significantly different from each other, even though the dealt with what seemed like the same things.

So what to do? Well, let's go back to our original state-of-the-world response:

HTTP/1.1 200 OK
Content-type: text/html
Link: </foo>; type=application/javascript; rel="http://d3js.org/component"

<div>
  <!-- This right here is the state of the world. -->
  <h1>Hello,</h1>
  <span>world.</span>
</div>

What if we changed the link, to be something like this:

HTTP/1.1 200 OK
Content-type: text/html
Link: </foo>; type=application/javascript; rel="http://d3js.org/component"
Link: </foo/small>; type=application/javascript; rel="alternate profile-small http://d3js.org/component"
Link: </foo/nsa>; type=application/javascript; rel="alternate profile-all-the-things http://d3js.org/component"

<div>
  <!-- This right here is the state of the world. -->
  <h1>Hello,</h1>
  <span>world.</span>
</div>

Awesome, now there's the default component, i.e. it doesn't have additional relation types, as well as two alternates. Ok, so maybe we're doing too much with the rel attribute here. After all, using it like this means we would have to define these rels somewhere (and probably use URLs as well!) and this gets expensive real quickly. So what else could we do? Well, maybe we can abuse the media attribute? RFC 5988 has this to say about it:

The "media" parameter, when present, is used to indicate intended destination medium or media for style information (see [W3C.REC-html401-19991224], Section 6.13). Note that this may be updated by W3C.CR-css3-mediaqueries-20090915). Its value MUST be quoted if it contains a semicolon (";") or comma (","), and there MUST NOT be more than one "media" parameter in a link-value.

It's like @mnot read my mind! (More likely, he's just really smart and noted that the same way this stuff is useful in HTML it'd be useful in HTTP. Damn smart I say!) Here's the above example changed a wee bit:

HTTP/1.1 200 OK
Content-type: text/html
Link: </foo>; type=application/javascript; rel="http://d3js.org/component"
Link: </foo/small>; type=application/javascript; rel="alternate http://d3js.org/component"; media=".profile-small"
Link: </foo/nsa>; type=application/javascript; rel="alternate http://d3js.org/component"; media=".profile-all-the-things"

<div>
  <!-- This right here is the state of the world. -->
  <h1>Hello,</h1>
  <span>world.</span>
</div>

Ok, so maybe I abuse the media attribute a bit, because the spec actually references CSS3 media queries which is not the same as CSS3 selectors. Even if you do stick to putting media queries in there I think it affords enough power to do really cool contextual switching automagically. In the above example, I conveniently ignore the spec because I'm a cowboy. But really, it's because I think media queries offers some pretty cool tools for figuring out metrics, but not really context. For instance, the .profile-small selector might mean we have a div with a small size, but it could also mean we really just care about a smaller portion of the data. Arguably, this is what the rel attribute is for, but I feel like there's some sort of gray area here. Maybe, maybe not.

Anyway, this outlines a different way we could do the whole application/view thing, without need to declare a whole new media type, which seems like overkill for html+javascript that just happens to follow some conventions.

@mstade
Copy link
Author

mstade commented Dec 13, 2013

Note, the .is function referenced above in the d3 component doesn't actually exist, but I'm sure there is a way to do this. If not, it shouldn't be rocket science to implement.

@steveklabnik
Copy link

Maybe an unconventional use for the rel attribute, I don't know.

Nope, this is the right way to do it.

You should check out web components and extend the web forward and the extensible web manifesto....

@inadarei
Copy link

"rel" is fine, but I am not sure about how you use "media" :) Interesting thinking, either way.

+1 on considering web components.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment