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] |
— |
-
-
Save subtleGradient/759332 to your computer and use it in GitHub Desktop.
<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> |
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.
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.
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>
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.
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 ;)
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.
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'
)
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
@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) }
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.
And then with a separate behavior sheet.
UPDATED: See https://gist.github.com/765730 for the thread about Behavior Sheets.
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).
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
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?
Thomas, I'd like to see more practical examples than smelling and singing cheese ;)
@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
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)
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.
@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})
See https://gist.github.com/765730 for the thread about Behavior Sheets.
I thought about param tags, but one thing I really don't want to do is prescribe something that doesn't validate.