Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save subtleGradient/759332 to your computer and use it in GitHub Desktop.
Save subtleGradient/759332 to your computer and use it in GitHub Desktop.
What is the best syntax to use for declarative markup in HTML5?

Declarative Component Mapping Syntax Options

Language / Prior Art ‘Valid’ HTML HTML Mapping Example Example with Args
‘Valid’ CSS Selectable CSS Mapping CSS Select CSS Selector w Args
JavaScript MooTools Multiple instance Separate Args new Foo(element); new Bar(element) new Foo(element, {a:"a", bB:"b B", c:{c:1}}); new Bar(element, {d:"d"})
HTML attr Dojo <1.6 NO 1 Element per instance <div nsType=foo> <div nsType=foo a=a bB="b B" c.c=1>
CSS: A Single + Args [nsType] [nsType=Foo][a=a][bB~=b][bB~=B][c.c=1]
data-* Dojo 1.6+ YES 1 Element per instance <div data-ns="Foo"> <div data-ns="Foo" data-ns-a="a" data-ns-b-b="b B" data-ns-c.c="1">
CSS: A Single + Args [data-ns-type] [data-ns-type=Foo][data-ns-a=a][data-ns-bB~=b][data-ns-bB~=B][data-ns-c.c=1]
class & attr Apple iAd NO Multiple instances Combined Args <div class="ad-foo ad-bar"></div> <div class="ad-foo ad-bar" ad-title="MyBarButton"></div>
CSS: A+ Multi + Args .ad-foo, .ad-bar .ad-foo[ad-title], .ad-bar[ad-title]
data-* Hue Behavior YES Multiple instances Combined Args <div data-filters="ns.foo, ns.bar"> <div data-filters="ns.foo, ns.bar" data-a="a" data-b-b="b B" data-c='{"c":1}' data-d="d">
CSS: D All + Args [data-filters] [data-filters][data-a=a][data-bB~=b][data-bB~=B]
data-* & CSS YES Multiple instances Separate Args <div data-ns data-ns.foo data-ns.bar> <div data-ns data-ns.foo="a:a; b-b:b B; c{c:1}" data-ns.bar="d:d">
CSS: B Multi [data-ns] [data-ns.foo], [data-ns.bar]
attr & CSS NO Multiple instances Separate Args <div ns foo bar> <div ns foo="a:a; b-b:b B; c{c:1}" bar="d:d">
CSS: B Multi [ns] [ns][foo], [ns][bar]
data-* YES Multiple instances Separate Args <div data-ns="Foo Bar Baz"> <div data-ns="Foo Bar Baz" value=HTML data-foo.value=FOO data-bar.value=BAR>
CSS: A++ Multi + Multi Args [data-ns] [data-ns~=Foo][value=Frog]:not([data-foo.value]), [data-ns~=Foo][data-foo.value=Frog]
HTML attr NO Multiple instances Separate Args <div ns="Foo Bar Baz"> <div ns="Foo Bar Baz" value=HTML foo.value=FOO bar.value=BAR>
CSS: A++ Multi + Multi Args [ns] [ns~=Foo][value=Frog]:not([foo.value]), [ns~=Foo][foo.value=Frog]
data-* YES Multiple instances Separate Args <div data-ns="Foo, Bar, Baz"> <div data-ns="Foo:value(FOO), Bar:value(BAR), Baz:value(HTML)" value=HTML>
CSS: D- All [data-ns]
data-* YES Multiple instances Separate Args <div data-ns="Foo, Bar, Baz"> <div data-ns="Foo[value=FOO], Bar[value=BAR], Baz[value=HTML]" value=HTML>
CSS: D- All [data-ns]
<meta data-is=DataStore.JSON name=jsonStore content=dataItems.json>
<meta data-is=Model.Forest name=continentModel
content=jsonStore data-query=type:continent
data-root-id=continentRoot data-root-label=Continents data-children-attrs=children
>
<table data-is=DataGrid.Tree id=grid data-model=continentModel>
<thead>
<tr>
<th data-field=name style=width:auto>Name
<th data-field=population style=width:auto>Population
<th data-field=timezone style=width:auto>Timezone
</thead>
</table>
<span dojoType="dojo.data.ItemFileWriteStore"
jsId="jsonStore" data="dataItems"></span>
<div dojoType="dijit.tree.ForestStoreModel" jsId="continentModel"
store="jsonStore" query="{type:'continent'}"
rootId="continentRoot" rootLabel="Continents" childrenAttrs="children"></div>
<table jsid="grid" dojoType="dojox.grid.TreeGrid" class="grid" treeModel="continentModel">
<thead>
<tr>
<th field="name" width="auto">Name</th>
<th field="population" width="auto">Population</th>
<th field="timezone" width="auto">Timezone</th>
</tr>
</thead>
</table>
<a href="http://archive.dojotoolkit.org/nightly/dojotoolkit/dojox/grid/tests/test_treegrid_model.html" title="dojox.grid.TreeGrid Model-based test">Example from Dojo Toolkit dojox.grid.TreeGrid Model-based test</a>
@cpojer
Copy link

cpojer commented Jan 1, 2011

I started using JSON for this kind of stuff almost two years ago and I have grown to dislike it. It might be slightly fewer bytes but having data-* properties on an element is much more expressive. I prefer expressiveness but I can see the concerns regarding file size.

@ryanflorence
Copy link

Another reason I like JSON is that most element-based classes have two constructor arguments: an element, and an options object.

A conceivably useful script would be a Class Mutator, that automatically throws your class into this declarative HTML pattern. With JSON, your options API--whether in JavaScript or in a data-attribute--is coherent.

@svicalifornia
Copy link

Using JSON as attribute values is not declarative HTML, it's embedded JavaScript.


A couple more ideas:

More Human-Readable:

<div data-filter="video"
data-video="audio=muted, height=9, width=19, controls, loop, preload,
source=foo.ogg, source=foo.mp4"></div>

Pros:

  • HTML-like key=value format
  • No brackets => better-looking => easier to read
  • Easily to parse

Cons:

  • Not as flexible as JSON
  • Not directly parsable by Slick

More HTML-Like:

<div
  data-filter="video"
  data-video-audio="audio"
  data-video-muted
  data-video-height="9"
  data-video-width="9"
  data-video-controls
  data-video-loop
  data-video-preload
  data-video-source="foo.ogg,foo.mp4"
></div>

Pros:

  • Fits HTML attribute="value" pattern

Cons:

  • Too verbose and redundant (too many data-video- prefixes)
  • Not as human-readable as grouping the attributes in one data-video attribute
  • Not as performant in older browsers without HTMLElement.dataset support (must get several attribute values from the element itself -- expensive)

I prefer the simple, concise, Human-Readable format.

@svicalifornia
Copy link

More CSS-like:

<div data-filter="video"
data-video="
  audio: muted;
  height: 9;
  width: 19;
  controls: true;
  loop: true;
  preload: true;
  sources: foo.ogg, foo.mp4;
"></div>

Pros:

  • Existing precedent for using CSS in HTML attribute (style)
  • Human-readable

Cons:

  • Parsing CSS-like syntax into JS object is a bit of a challenge, but maybe Sheet.js can do it?

@svicalifornia
Copy link

Cascading Behavior Attributes:

Wacky idea: How about some way to define classes of commonly-used Behavior attribute sets, as in CSS?

@ryanflorence
Copy link

Somebody has to set precedent, embedded CSS for CSS features, embedded JSON for JavaScript features.

In all sane proposals we embed some sort of data interchange format.

@ryanflorence
Copy link

Also, in your last example, replace semi-colons for commas, dynamically add the brackets and you've got JSON. Might as well use an existing, flexible format that you don't have to parse, document, or maintain.

/shrug

@svicalifornia
Copy link

I thought about the CSS-to-JSON conversion (it led to propose the CSS-like option), but the conversion isn't so simple. Following the procedure you suggested on my example does not yield the intended result:

{
  audio: muted,
  height: 9,
  width: 19,
  controls: true,
  loop: true,
  preload: true,
  sources: foo.ogg,
  foo.mp4
}

That said, I would be more in favor of JSON as attribute value if the brackets could be omitted from HTML and added dynamically on interpretation.

@ibolmo
Copy link

ibolmo commented Jan 2, 2011

What about ..

<div class="video">
    <param name="is_muted" value="false"/>
    <param name="height" value="9px"/>
    <param name="width" value="19px"/>
    <param name="controls" value="true"/>
    <param name="loop" value="true"/>
    <param name="preload" value="true"/>
    <param name="sources" value="foo.ogg, foo.mp4"/>
</div>

@ryanflorence
Copy link

I thought about param tags, but one thing I really don't want to do is prescribe something that doesn't validate.

@subtleGradient
Copy link
Author

JSON makes even more sense when you consider serverside integration e.g.

<div data-ns="FooBar" data-foo-bar='#{ foo_bar.to_json }'>

I would personally MUCH prefer a CSS-like syntax, but using JSON is just such a huge win in so many pragmatic ways that I simply can't argue against it.
Maybe I could allow for CSS-like as the default but if the first character is a { then parse it as JSON, but that may be too much confusion in the declarative API.

@subtleGradient
Copy link
Author

When coding in HTML you shouldn't have to think of it as passing an options object to a new Constructor(el, options).
That kind of thinking leads to declarative APIs that are are far too low-level and implementation-specific.

My goal is to create declarative HTML APIs that feels like native HTML features and then most of the actual JS code will feel like polyfill.

For most things it makes more sense to have separate real attributes.
e.g.

<button
  title="Clicking this button will send the current email that you've been working on all night"
  data-alt-working="Sending…"
  data-alt-invalid="Cannot Send"
><b data-default>Send Email<b></button>

Cf. http://jsfiddle.net/SubtleGradient/DWcgY/embedded/result,html,css/

In this example I am declaring what other values this button will have in each possible state.
The HTML shouldn't have to know or care if it's CSS or JS or the browser itself that actually handles swapping these things out, if at all.

I chose to use attributes for these values instead of additional tags inside the button so that the Web 1.0 view still makes sense. You can't have a button look as if it's in 5 separate states and still make sense to anyone. It also seems to map into the existing HTML convention of the title and alt attributes.

@subtleGradient
Copy link
Author

Another option would be to have behaviors be more like CSS.
You can declare style or behavior on an element directly e.g.

<button style="color:blue;" behavior="click:winTheWorld;">Click for great win</button>

Or declare all your style and behavior in separate files. e.g.

<button class="winsTheWorld">Click for great win</button>
/* CSS */
.winsTheWorld {color:blue;}
/* BE */
.winsTheWorld:click {win: theWorld;}

Or declare all your style and behavior in the same file using STYLE and special SCRIPT tags. e.g.

<button class="winsTheWorld">Click for great win</button>
<style> .winsTheWorld {color:blue;} </style>
<script type="text/behavior"> .winsTheWorld:click {win: theWorld;} </script>

@subtleGradient
Copy link
Author

Taking a step back, there are multiple different concepts going on here.

Content-based Webpage. Markup your content with tags. Add meta data using attributes. Add style using CSS. Add behaviors using a blob of domready code or a behavior sheet. UPDATED: See https://gist.github.com/765730 for the thread about Behavior Sheets.

<button class="isFancy" data-tooltip="This will make winning happen for you!">Click for great win</button>
/* CSS */ .isFancy:hover {background:url(prancing-ponies.gif);}
/* BE */ .isFancy:hover {tooltip:attr(data-tooltip)}

Data-backed Webapp. Expose content via REST / JSON. Define your app layout and widgets using declarative syntax (HTML + special attributes) or function syntax (JS). e.g.

<span id="addressBookModel" myType="DataModel" type="text/json" source="addressbook.json"></span>
<input type=search store="addressBookModel" myType="Filter" placeholder="Filter this list!">
<table id="addressBookView" dataStore="addressBookModel">
    <thead>
        <tr field="firstName">First Name</tr>
        <tr field="lastName">Last Name</tr>
        <tr field="iq">Awesomeness Quotient</tr>
    </thead>
</table>
<button something-goes-here plural="Delete selected Emails">Delete this Email</button>
<button something-goes-here>New Email</button>

I think a lot of the differences in syntax options comes from the very different use cases that each of us are planning to use this stuff for.

It's important to recognize up front if you're building a website or a webapp and then choose the programming patterns that make the most sense for that specific project. Trying to mash a webapp into the programming model of a website will be very bad, and vice-versa. Also consider the implications of using data-backed widgets inside of page-based websites.

Don't try to make every project conform to the programming patterns that you're already comfortable with.
Just imagine trying to build a realtime system monitoring application using website-style programming patterns.

I'm not sure it even makes sense to program webapps and websites using the same exact programming patterns.

@svicalifornia
Copy link

It's not BSS, it's CBS (Cascading Behavior Sheets).

I really like the Data-backed Webapp concept and proposed syntax. That's what I intended to do in Maui. (And nice use of element IDs, by the way. /inside joke ;)

@cpojer
Copy link

cpojer commented Jan 2, 2011

Just one note, if we use another file format that externally defines the behavior it kind of defeats the purpose of having this kind of functionality. The options must be tied to the HTML it comes with. Otherwise for highly dynamic content it might create yet another request (if you have several files with behaviors defined) and it isn't much better than just going with the "large domready block".

I just wanted to highlight that any kind of implementation must also be able to work well with updated content, not just on domready. So if you have a selector to retrieve all elements that need a behavior, you need to filter out the elements that have a behavior applied already. Something like [data-behavior]:not([data-behavior-attached]). Just sayin' for the record.

@digitarald
Copy link

I'd allow 2 styles (<ul data-slideshow-theme="golden" data-slideshow='{"src":"/data/images.json","speed":"JSON"}'>]), for the sake of controllable CSS ([data-slideshow-theme="golden"]) but also to allow servers-side generation of interchangeable data. Both sources would be merged into one option object.

This caters to both concepts and can be applied on content and app-based structures.

I see some edge cases that don't work after the element-2-instance pattern, requiring event delegation or other pattern. That's why I like the onAttach, onDetach approach I saw. We can also make the callback very customizable, allowing simple concepts like events ('ajax': { delegate: true, click: function(event, options) { stuff(); } }) but also classes (slideshow: App.Slideshow or provided via dependency injection slideshow: 'App/Slideshow')

@rolfnl
Copy link

rolfnl commented Jan 3, 2011

Previously I was using json as the attribute value for settings of some behavior like an fx because I thought it was the most logical approach. Later I dropped it and started using lots of data- attributes describing each param separately. Reason I dropped the json was because I read somewhere in the mootools docs/blog that such an approach (dunno in what class, maybe some Fx or Validator class) was going to be dropped as it wasn't valid html..?!

UPDATE: ah yes, here it's in Aaron's Form.Validator: http://mootools.net/docs/more/Forms/Form.Validator
"[...] You can use a property called "validatorProps" and pass in Json values if you like, but this is not valid XHTML. This is deprecated but will continue to be supported."

However, I sometimes prefix the data-value attributes for a filter like:
data-filter="video" data-video-width="9" data-video-source="foo.mp4" ...
else you don't know which attribute applies to the correct filter (e.g. when you apply two). With json you can put one object into 1 data- attribute... so... too many thoughts

http://jsfiddle.net/SubtleGradient/DWcgY/embedded/result,html,css/
Been using things like that as well.. would you consider this hackish/ugly? I kind of like it, but it can't work without js for IE7 and such

@subtleGradient
Copy link
Author

@digitarald & @Rolf-nl

I believe that using both separate attributes as well as separate options attributes are the way to go, but strictly separated by what they're for.
Use attributes when you are adding meta data about the tag and its contents. Use an options attribute when you are passing specific options to some specific behavior.

e.g.

<img src="Cheddar.png" alt="Pungent Orange Cheddar Cheese"
    data-type="food-dairy-cheese-cheddar" data-smell="pungent" data-color="orange"
    data-behavior="Dance Sing"
    data-behavior-dance='{type:"waltz", speed:"slow"}'
    data-behavior-sing='{type:"show tune", speed:"fast"}'
/>

or with minimal HTML and CSS-style syntax…

<img src=Cheddar.png alt="Pungent Orange Cheddar Cheese"
    data-type=food-dairy-cheese-cheddar data-smell=pungent data-color=orange
    data-behavior="Dance Sing"
    data-behavior-dance="type:waltz; speed:slow"
    data-behavior-sing="type:show tune; speed:fast;"
>

Has many attributes.
Has many behaviors.
Each Behavior has up to one options attribute.

And for extra credit… CSS!

[data-type|=food-dairy] { background-image: url(cow.png) }
[data-type|=food-dairy][data-behavior~=Dance]{ background-image: url(dancing-cow.png) }

@subtleGradient
Copy link
Author

Maybe we need separate terminology for behaviors that effect how you interact with it vs behaviors that effect what it is.

e.g. Attributes on a DataGrid is really meta data about what it is, so even though that effects how you interact with it, it is more suitable as separate attributes since it is describing what that element is.

@subtleGradient
Copy link
Author

And then with a separate behavior sheet.

UPDATED: See https://gist.github.com/765730 for the thread about Behavior Sheets.

@ryanflorence
Copy link

Not sure what the point of the behavior sheet is, while interesting, seems no different than just cranking out some slick selectors and calling element methods on the results (like we sort of did on goconnect).

@digitarald
Copy link

Maybe I am not thinking abstract enough, but I saw the mapping for data-[class]="[options-table]" and data-[class]-[options-key]="[options-value]" … which than maps to our classy new Class(element, options).

class could be either mapped in a sheet, auto-detected from the name or, just a thought, referenced in the HTML (<div data-require="table=App.Core.Table; zebra=App.More.Zebra" data-table="{…}" data-zebra-selectable="true">). The latter provides a nice way to avoid all kind of sheets, since all references are kept in one place.

I don't feel comfortable with the sheets, they need to be generic enough to be external and than would need to define resources shared within the whole application. Having them inline as attributes

@rolfnl
Copy link

rolfnl commented Jan 4, 2011

Yes, my thoughts too. Or is w3c coming with something like a behavior sheet recommendation (I don't keep up with the specs/updates)? It's a nice idea, but I wouldn't use it. JSON for options is probably the easiest way to go as it already works and it's relatively easy to implement. I like Ryan's idea in his Element.Filters that the choice of parser is optional.
I'd like to hear cpojer's thoughts as well.

@ryan, goconnect is goconnect.org?

@digitarald
Copy link

Thomas, I'd like to see more practical examples than smelling and singing cheese ;)

@rolfnl
Copy link

rolfnl commented Jan 4, 2011

@digitarald++ for the mapping, which feels like it should be. Though I also like it when you can describe your custom behavior/filter that might instantiate more than one classes or a singleton that does a bunch of stuff. Kind of like Ryan's Element.Filters

@rolfnl
Copy link

rolfnl commented Jan 4, 2011

Thomas, how can you sing a fast show tune and dance a slow waltz at the same time? or did you forgot the chain option? Plus, I wouldn't mix low culture hamburger cheddar with high culture waltz stuff.. (naah, on 2nd thought, waltz is simple == low culture.. mix is good)

@subtleGradient
Copy link
Author

Dancing and Singing have nothing to do with what that element IS but is more about how that thing behaves.

e.g.

Bob is a Person and acts like a Clown

<div name=bob is=Person age=37 height="5feet 3in" acts-like="Clown" clown='{"nose-color":"Red", "nose-sound":"honk"}'>

May map to something like…

new Person(element, { name:"bob", age:37, height:"5feet 3in" });
new Clown(element, { name:"bob", age:37, height:"5feet 3in", "nose-color":"Red", "nose-sound":"honk" });

Edgar is a Cow and acts like a Person

<div name=edgar is=Cow age=37 acts-like="Person" clown='{"height":"5feet 3in"}'>

May map to something like…

new Cow(element, { name:"edgar", age:37 });
new Person(element, { name:"edgar", age:37, height:"5feet 3in" });

Ralph is a Person and acts like a Ninja

etc…

So all the attributes of an element could merge in all the specific attributes of that 'acts-like' thing and give you a single object. But all that is kindof OT for this thread. I think I have solved the original question that this thread posed, that is, what is the best syntax to use for declarative markup.

@subtleGradient
Copy link
Author

@digitarald Mapping of alias to specific JS namespace should happen in JS, not HTML. e.g. SubtleBehavior.defineAlias({zebra: My.NameSpace.Of.Doom.Zembra, foo:Foo, bar:MySuperBar})

@subtleGradient
Copy link
Author

See https://gist.github.com/765730 for the thread about Behavior Sheets.

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