Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Concrete UI programming library for jQuery
JavaScript Shell
Tree: 679b3dd8bd

Fetching latest commit…

Cannot retrieve the latest commit at this time

Failed to load latest commit information.
src
vendor
LICENSE
README.textile

README.textile

Concrete – Support for ConcreteUI programming in jQuery

A basic desire for jQuery programming is some sort of OO or other organisational method for code. For your consideration,
we provide a library for ConcreteUI style programming. In ConcreteUI you attach behavioral code to DOM objects. Concrete extends this
concept beyond what is provided by other libraries to provide a very easy to use system with class like, ploymorphic, namespaced properties

Basic use

First intro

To attach methods to DOM nodes, call the `concrete` function on a jQuery selector object, passing a hash listing the method names and bodys


  $('div').concrete({
    foo: function(..){..},
    bar: function(..){..}
  });

You can then call those methods on any jQuery object.


  $('#a').foo();

Any elements in the jQuery selection that match the selector used during definition (‘div’ in this example) will have foo called with that element
set as this. Any other objects are skipped. The return value will be the return value of foo() for the last matched DOM object in the set

A proper example

Given this DOM structure:


  <body>
    <div class="internal_text">Internal text</div>
    <div class="attribute_text" rel="Attribute text"></div>
    <div>Nonsense</div>
  </body>

And this concrete definition


  $('.internal_text').concrete({
    foo: function(){ console.log(this.text()); }
  });
  $('.attribute_text').concrete({
    foo: function(){ console.log(this.attr('rel')); }
  });

Then this call


  $('div').foo();

Will log this to the console


  Internal text
  Attribute text  

Limitations

When defining methods, the jQuery object that concrete is called on must be a plain selector, without context. These examples will not work


  $('div', el).concrete(...)
  $([ela, elb, elc]).concrete(...)
  $('<div id="a"></div>').concrete(...)

Live

The definitions you provide are not bound to the elements that match at definition time. You can declare behaviour prior to the DOM existing in any
form (i.e. prior to DOMReady) and later calls will function correctly.

Selector specifity

When there are two definitions for a particular method on a particular DOM node, the function with the most specific selector is used.
Specifity is calculated as defined by the CSS 2/3 spec. This can be seen as subclassing applied to behaviour.

Another example. Given this DOM structure


  <body>
    <div>Internal text</div>
    <div class="attribute_text" rel="Attribute text"></div>
    <div>Nonsense</div>
  </body>

And this concrete definition


  $('div').concrete({
    foo: function(){ console.log(this.text()); }
  });
  $('.attribute_text').concrete({
    foo: function(){ console.log(this.attr('rel')); }
  });

Then this call


  $('div').foo();

Will log this to the console


  Internal text
  Attribute text
  Nonsense

Events

If you declare a function with a name starting with ‘on’, then instead of defining that function, it will be bound to an event of that
name. Just like other functions this binding will be live, and only the most specific definition will be used


  <head>
    <script type='text/javascript'>
      /* No need for onready wrapper. Events are bound as needed */
      $('div').concrete({
        onclick: function(){ this.css({backgroundColor: 'blue'}); }
      });
      $('.green').concrete({
        onclick: function(){ this.css({color: 'green'}); }
      });
    </script>
  <body>
    <div>Background will turn blue when clicked on</div>
    <div>Will also have blue background when clicked on</div>
    <div class='green'>Will have green text when clicked on. Background color will not change</div>
  </body>

Constructors / Destructors

Declaring a function with the name `onmatch` will create a behavior that is called on each object when it matches. Likewise, `onunmatch` will
be called when an object that did match this selector stops matching it (because it is removed, or because you’ve changed its properties).

Note that an onunmatch block must be paired with an onmatch block – an onunmatch without an onmatch in the same concrete definition block is illegal

Like other functions, only the most specific definition will be used. However, because property changes are not atomic, this may not work as you
expect.

Namespaces

To avoid name clashes, to allow multiple bindings to the same event, and to generally seperate a set of functions from other code you can use namespaces


  $('div').concrete('foo.bar', function($){ return {
      baz: function(){}
  }});

You can then call these functions like this:


  $('div').concrete('foo.bar').baz()

Namespaced functions work just like regular functions (`this` is still set to a matching DOM Node). However, specifity is calculated per namespace.
This is particularly useful for events, because given this:


  $('div').concrete({
    onclick: function(){ this.css({backgroundColor: 'blue'}); }
  });
  
  $('div').concrete('foo', function($){ return {
      onclick: function(){ this.css({color: 'green'}); }
  }});

Clicking on a div will change the background and foreground color.

This is particularly important when writing reusable code, since otherwise you can’t know before hand whether your event handler will be called or not

Although a namespace can be any string, best practise is to name them with dotted-identifier notation.

Namespaces and scope (or What the hell’s up with that ugly function closure)

Inside a namespace definition, functions remember the namespace they are in, and calls to other functions will be looked up inside that namespace first.
Where they don’t exist, they will be looked up in the base namespace


  $('div').concrete('foo', {
    bar: function() { this.baz(); this.qux(); }
    baz: function() { console.log('baz'); }
  })
  
  $('div').concrete({
    qux: function() { console.log('qux'); }
  })

Will print baz, qux to the console

Note that ‘exists’ means that a function is declared in this namespace for any selector, not just a matching one. Given the dom


  <div>Internal text</div>

And the concrete definitions


  $('div').concrete('foo', {
    bar: function() { this.baz(); }
  })
  
  $('span').concrete('foo' {
    baz: function() { console.log('a'); }
  
  $('div').concrete({
    baz: function() { console.log('b'); }
  })

Then doing $(‘div’).bar(); will not display b. Even though the span rule could never match a div, because baz is defined for some rule in the foo namespace, the base namespace will never be checked

Forcing base

In some situations (such as the last example) you may want to force using the base namespace. In this case you can call concrete without any arguments. For example, if the first definition in the previous example was


  $('div').concrete('foo', {
    bar: function() { this.concrete().baz(); }
  })

Then b would be output to the console. It is up to the developer to decide when they need to be explicit about using the base namespace

Using

Sometimes a block outside of a namespace will need to refer to that namespace repeatedly. By passing a function to the concrete function, you can change the looked-up namespace


  $('div').concrete('foo', {
    bar: function() { console.log('a'); }
  })
  
  $('div').concrete('foo', function(){
    this.bar();
  });

This equivilent to /with/ in javascript, and just like /with/, care should be taken to only use this construct in situations that merit it.

Something went wrong with that request. Please try again.