leonidas / transparency

Transparency is a semantic template engine for the browser. It maps JSON objects to DOM elements by id, class and data-bind attributes.
http://leonidas.github.com/transparency/
MIT License
969 stars 112 forks source link

New syntax for directives (API break: Attribute assignment) #26

Closed pyykkis closed 12 years ago

pyykkis commented 12 years ago

I'm planning to implement a new API for directives. Main motivation is to clarify and harmonize the functionality. This issue invalidates the issues https://github.com/leonidas/transparency/issues/21 https://github.com/leonidas/transparency/issues/22

New API extends functionality by providing append and prepend, while replace remains as a default. Additionally, index is added as a new parameter for directives, which makes adding line counts or even and odd classes a breeze.

Principles:

  1. Keys are used to select elements. No more weird @ symbols and semantic confusion due to mixing element selection and attribute assignment.
  2. If the return value of a directive is type of string, it get assigned to the matching elements as text content. This provides backward compatibility and simplicity for the most common scenario.
  3. Optionally, directive can return an object, i.e., key-value pairs. Keys can be either text, html or any attribute name, e.g., class, src or href. Values can be either type of string, or objects. If the value is an object, it's expected to be either {append: "value"} or {prepend: "value"}, providing the obvious functionality.

Examples:

data =
  firstname: "John"
  lastname: "Wayne"
  friends: [
    name: "Uncle Bob"
  , name: "Jeff Buckley"
  , name: "Matt Hughes"
  ]

directives =
  # Render text content
  name1: ->
    "#{@firstname} #{@lastname}"

  # Render html content
  name2: ->
    html: "<b>#{@firstname} #{@lastname}</b>" # Render html content

  # Render text content and append a new class
  name3: ->
    text: "#{@firstname}"
    class: append: " even"

  friends:
    name: (element, i) -> 
     text: @name
     class: append: if i % 2 then " odd" else " even"

$('#template').render data, directives
<!-- Before -->
<div id="template">
  <div id="name1"></div>
  <div id="name2"></div>
  <div id="name3"></div>
  <div id="friends">
    <div id="name"></div>
  </div>
</div>

<!-- After -->
<div id="template">
  <div id="name1">John Wayne</div>
  <div id="name2"><b>John Wayne</b></div>
  <div id="name3" class="even">John</div>
  <div id="friends">
    <div class="name even">Uncle Bob</div>
    <div class="name odd">Jeff Buckley</div>
    <div class="name even">Matt Hughes</div>
  </div>
</div>

Waiting for comments until Monday.

miohtama commented 12 years ago

I assume the goal is to have micro-string-templates as alternative to pure Javascript functions.

Darn.... this kind of kills one of main points of Transparency :)

Can we think of something "cleaner" solution here? I believe we could come up with a syntax which is not that closely bound to individual DOM elements (text, html, class, have special append) etc.

miohtama commented 12 years ago

Here is my proposal of alternative approach which would behave like above but little more "streamlined" - e.g. the attribute name resolution for directive object content would be little cleaner

Complex directives

pyykkis commented 12 years ago

First goal is to make directives more consistent with normal rendering, i.e., getting rid of @ syntax. Second goal is to provide powerful enough semantics, so that directives can be written as side-effect free functions.

Directives are always functions, never strings. At the moment, they can only return strings. I'm proposing they could return either string or object.

By all means, I'm not going to going to provide string-based micro templating. I agree append: and prepend: keywords might need more consideration. Otherwise, I'm feeling the proposed approach is a lot cleaner than the current implementation.

Please see the comparisons below.

<!-- template -->
<div id="container">
  <a class="name"></a>
</div>
data = [
  id: 1, name: "foo"
, id: 2, name: "bar"
]

# Current implementation. 
# Ugly, as selecting the element and assigning to attribute are mixed together.
directives =
  'name@href': () -> "www.example.com/#{@id}"

# New implementation.
directives =
  name: () -> href: "www.example.com/#{@id}"

With current implementation, there's another ugly thing, if one needs to set multiple attributes and/or value. Given the same data and template than above, here's an example underlining the issue.

# Current implementation. Ugly, as directive function is not pure, 
# but instead have side effects in addition to return value.
# Additionally, it's not obvious what the return value does 
# (it's set as a text value of the element).
directives =
  name: (e) -> 
    $(e).attr('href', "www.example.com/#{@id}")
    "Hello #{name}!"

# New implementation. 
# A pure function without side-effects and self-explanatory functionality
directives =
  name: () -> 
    href: "www.example.com/#{@id}"
    text: "Hello #{name}!"

# New implementation, rendering unescaped html content
directives =
  name: () -> 
    href: "www.example.com/#{@id}"
    html: "<b>Hello #{name}!</b>"
pyykkis commented 12 years ago

Thanks @miohtama for the review and feedback. I'm closing this and creating a new ticket for the functional approach on append/prepend you proposed, it's just awesome.