Code Monkey home page Code Monkey logo

behavior's Introduction

Behavior

Auto-instantiates widgets/classes based on parsed, declarative HTML.

Purpose

All well-written web sites / apps that are interactive have the same basic pattern:

Web app layers

Each page of the site or app is esoteric. It may have any combination of interactive elements, some of which interact with each other (for example, a form validation controller might interact with an ajax controller to prevent it sending a form that isn't valid). Typically this "glue" code exists in a domready statement. It says, get this form and instantiate that class with these options. This code is brittle; if you change either the DOM or the code the state breaks easily. It's not reusable, it only works for a specific page state. It can easily get out of hand.

Behavior attempts to abstract that domready code into something you only write once and use often. It's fast and easily customized and extended. Instead of having a domready block that, say, finds all the images on a page and turns them into a gallery, and another block that searches the page for all the links on the page and turns them into tool tips, Behavior does a single search for all the elements you've marked. Each element is passed through the filter it names, where a filter is a function (and perhaps some configuration) that you've named. Each of these functions takes that element, reads properties defined on it in a prescribed manner and invokes the appropriate UI component.

Documentation

See markdown files in the Docs directory.

Why?

The nutshell is that instead of having a domready function that finds the stuff in your DOM and sets up instances of classes and whatnot, you put the configuration in the HTML itself and write the code that calls "new Foo(...)" only once. Example:

Instead of this:

$$('form').each(function(form){
  new FormValidator(form, someOptions);
  new Form.Request(form, someOptions);
});
new Tips($$('.tip'));
$$('.accordion').each(function(container){
  new Accordion(container.getElements('.toggler'), container.getElements('.section'), someOptions);
});
etc

You do this:

<form data-behavior="FormValidator FormRequest" data-formvalidator-options="{someOptions}">...</form>
<a data-behavior="Tip" title="I'm a tip!">blah</a>
<div data-behavior="Accordion" data-accordion-options="{someOptions}">...</div>

Think of it as delegation (as in event delegation) for class invocation. If you use domready to do your setup and you want to swap out some HTML with XHR, you need to reapply that startup selectively to only your components that you're updating, which is often painful. Not with Behavior, you just apply the filters to the response and call it a day.

You do a lot less DOM selection; you only ever run $$('[data-behavior]') once (though some filters may run more selectors on themselves - like Accordion finding its togglers and sections).

Domready setup is always closely bound to the DOM anyway, but it's also separated from it. If you change the DOM, you might break the JS that sets it up and you always have to keep it in sync. You almost can't do that here because the DOM and its configuration is closely bound and in the same place.

Developers who maybe aren't interested in writing components don't need to wade into the JS to use it. This is a big deal if you're working with a team you must support.

Behavior is designed for apps that are constantly updating the UI with new data from the server. It's not an MVC replacement though. It's designed for web development that uses HTML fragments not JSON APIs (though it can play nicely with them). If you destroy a node that has a widget initialized it's easy to make sure that widget cleans itself up. The library also allows you to create enforcement to prevent misconfiguration and an API that makes it easy to read the values of the configuration.

There are some other nifty things you get out of it; you get essentially free specs tests and benchmarks because the code to create both of them is in the Behavior filter. Here's an example of what it takes to write a spec for a widget and ALSO the benchmark for it's instantiation (this uses Behavior.SpecsHelpers.js).

Behavior.addFilterTest({
  filterName: 'OverText',
  desc: 'Creates an instance of OverText',
  content:  '<input data-behavior="OverText" title="test"/>',
  returns: OverText
});

This code above can be used to validate that the HTML fragment passed in does, in fact, create an OverText instance and it can also be used with Benchmark.js to see which of your filters are the most expensive. More on this stuff in a minute.

Delegator

Included in the library is also a file called Delegator which is essentially the same thing except for events. For example, let's say you have a predictable UI pattern of having a link that, when clicked, it hides a parent element. Rather than writing that code each time:

document.body.addEvent("click:a.hideParent", function(e, link){
  e.preventDefault();
  link.getParent().hide();
});

You register this pattern with Delegator and now you just do:

<a data-trigger="hideParent" data-hideparent-options ="{'target': '.someSelector'}">Hide Me!</a>

It provides essentially the same value as Behavior, but at event time. The above example is pretty straight forward so, you know, why bother, right? But consider how many of these little things you write to make a web app function. If you can create them once and configure them inline, you save yourself a lot of code.

Stock Behaviors

Check out these resources of available Behavior Filters provided by the author:

Notes

Below are some notes regarding the implementation. The documentation should probably be read first as it gives usage examples.

  • Only one selector is ever run; adding 1,000 filters doesn't affect performance.
  • Nodes can have numerous filters.
  • Nodes can have an arbitrary number of supported options for each filter (data-behaviorname-foo="bar").
  • Nodes can define options as JSON (this is actually the preferred implementation - data-behaviorname-options="<your JSON>").
  • Elements can be retired w/ custom destruction; cleaning up an element also cleans up all the children of that element that have had behaviors applied.
  • Behaviors are only ever applied once to an element; if you call myBehavior.apply(document.body) a dozen times, the elements with filters will only have those filters applied once (can be forced to override and re-apply).
  • Filters are instances of classes that are applied to any number of elements. They are named uniquely.
  • Filters can be namespaced. Declare a filter called Foo.Bar and reference its options as data-foo-bar-options="...".
  • There are "global" filters that are registered for all instances of behavior.
  • Instance filters get precedence. This allows for libraries to provide filters (like http://github.com/anutron/more-behaviors) but for a specific instance to overwrite it without affecting the global state. (This pattern is in MooTools' FormValidator and works pretty well).
  • Filters have "plugins". A requirement for Filters is that they are unaware of each other. They have no guarantee that they will be invoked in any order (the developer writing the HTML expresses their order) or that they will be invoked with others or on their own. In addition to this ignorance, it's entirely likely that in specific environments a developer might want to augment a filter with additional functionality invoked whenever the filter is. This is what plugins are for. Plugins name the filter they augment but otherwise are just filters themselves. It's possible to have plugins for plugins. When you need to make two filters aware of each other (FilterInput + HtmlTable.Zebra). Note that plugins are always executed after all the filters are, so when writing a plugin that checks for a combination of two filters it is guaranteed that both filters have been applied.
  • Behavior defines a bottleneck for passing environment awareness to filters (passMethod / behaviorAPI). Filters should not know too explicitly about the environment they were invoked in. If the filters, for example, had to be able to call a method on some containing class - one that has created an instance of Behavior for example, the filter shouldn't have to have a pointer to that instance itself. It would make things brittle; a change in that class would break any number of unknown filters. By forcing the code that creates the Behavior instance to declare what methods filters can use it makes a more maintainable API.

Limitations:

  • Due to the DOM-searching for both creation and destruction, you can't have behavior instances inside each other.

behavior's People

Contributors

anutron avatar davywentworth avatar holyshared avatar kamicane avatar kassens avatar kmike avatar megawac avatar mtorromeo avatar rolfnl avatar sergiocrisostomo avatar subtlegradient avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

behavior's Issues

Broken links

Hello,

The three links in the readme.md are broken, leads to a Github 404 page.

Tibor

Error catcher supresses errors

Hey
I wondered if it would be possible to cancel the error catcher behavior applies. I'm having 2 major issues with it:

  1. It cancel the stack trace, thus I can't see where the error originated from
  2. It actually suppresses errors, making it impossible to know something is actually broken and where.

Behavior API _getOptions bug with IE

The BehaviorAPI._getOptions method uses:

if (options && options[0] != '{') options = '{' + options + '}';

In IE 8/9, 'myString'[0] returns undefined, instead of the expected 'm'.

I suggest not using IE of course, but for those who must, changing the subject line to:

if (options && options.substring(0,1) != '{') options = '{' + options + '}';

Conflict with addBehavior method name

I tried using Behaviors in our system, but ran into a conflict with out history manager:
http://plugins.jquery.com/project/history-js
history.js has in its amplify.store.js file a section that begins

if ( div.addBehavior ) {
div.addBehavior( "#default#userdata" );

It appears to be related to compatibility for non-standard IE--it seems that addBehavior was the name of a method in some older version of IE's js, or something, and that the compatibility code in history-js is causing a conflict. If that addBehavior method was ever widely used, you could run into other conflicts as well.

mootool keep event alive for behavior

Hi,

I have a span element on my page ... test,

i need to addEvent to pay attention this element, so in my mootool js is kind of like this
var mytest = $('test')
test.addEvent('click', function (e, el) {
.... do something
};

whenever i click the , it will do something ...
on my html ... sometimes i need to toggle to remove that and show that

As what i know ... once i remove that , the event tracking is gone ... i think that's the purpose to avoid memory leak, correct?

But in my case, i kind of need to keep that event tracking even though the is gone ... any suggestion? any options in the mootool addEvent to keep it alive?

hongclub

Allow filters dependencies

Hey
So, I've been using Behavior quite heavily for the last few months (and having great fun at it :) ), and I've come across one issue a few times -
There are cases where one filter needs to use some functionality that is created by another. So far I've used several ways to work around it. Usually all my Classes have a load event of some sort, so I can check for it and use it, but what I could really use is a way to specify filter order (right now filters are using for-in loops - which don't have a specific order on all browsers, and I've noticed some inconsistent behavior on FF as well).
I haven't created a pull request because I think this is something that would require some change to the inner architecture, but I have several ideas for this.
Is it something you might implement or should I create my own implementation for this?

MooTools 1.3 syntax

Hi Aaron,
will you accept a patch with MooTools 1.3-only compatible syntax?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.