Skip to content

luning/jquery.parcel

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

jQuery.parcel is a jQuery plugin designed to simplify working with complex forms. It provides an Object-Oriented encapsulation of forms and their field groups using a straighforward declarative syntax. It enables some very powerful ways for decomposing forms into conceptual parcels, and provides meaningful abstractions for these parcels.

  1. Two Minute Tour
  2. Learn More
  3. API Reference
  4. Q&A

Given a HTML snippet:

  <div id="person">
    <select name="title">
        <option>Mr</option>
        <option checked="checked">Mrs</option>
    </select>
    <input type="text" name="firstname" value="Jane"/>
    <input type="text" name="lastname" value="Doe"/>
  </div>

You create a parcel like this:

  var person = $("#person").parcel();

This builds a parcel representing person with all fields set up automatically from their name attribute of the input field.

(The fields are actually jQuery wrappers of each form field element in the specified parcel)

  person.title;
  person.firstname;
  person.lastname;

firstname is a person field and calling state() will simply return the field value, just like val() on a jQuery object.

  person.firstname.state();
  #=> "Jane"

To change firstname to new value, just pass the value into the state() method.

  person.firstname.state("John");

This is just like person.firstname.val("John") in jQuery, but with the appropriate events are fired as if a real user typed the value into the field.

Setting the values of the entire person parcel is just as easy!

  person.state({ title: "Mr", firstname: "John", lastname: "Smith"});

Remember that all the user events get fired (ie. change, blur, click, etc).

Calling state() on the person parcel simply returns the current state of the parcel as an object:

  person.state();
  #=> { title: "Mr", firstname: "John", lastname: "Smith"}

You can tell if the state has changed with isDirty():

  person.isDirty();
  #=> true

Person is flagged as dirty, since the state of person has been changed since initialization.

Set the original state to the parcel with resetState()

  person.resetState();
  #=> { title: "Mrs", firstname: "Jane", lastname: "Doe"}

Let’s say you want to have a label element update with changes in the value of a field. Use the bindState() method passing in the CSS selector:

  person.firstname.bindState("#first_name_label");

Now any change of firstname will update the value of the label you specified.

But what if you wanted a single label to reflect all of the fields? Simply pass a transformation function as the second argument to bindState() like so. The state will be passed in as a parameter to this function so you can easily extract what you need.

  person.bindState("#id_of_summary_span", function(s) {
    return s.title + " " + s.firstname + " " + s.lastname;
  });

Any changes to person fields will now update the summary span using the specified transformation function.

  person.firstname.stateChange(function(state) { /* custom logic */});
  
  person.stateChange(function(state) { /* custom logic */});

State change of the firstname field or person parcel will trigger the custom handlers and pass in the latest state object.

You can extend a parcel by mixing in new behaviour.

  function Person() {
    this.summary = function() {
      var s = this.state();
      return s.title + " " + s.firstname + " " + s.lastname;
    }
  }

  var person = $("#person").parcel(Person);
  person.summary();

Define behaviour Person as a function, build parcel with the behaviour, and the summary() method will be mixed into the parcel.

Behaviour mixins are the recommended way to include custom logic for a specific parcel.

You can also specify the behaviour function in the containing DOM element by supplying a parcel attribute:

  <div id="person" parcel="Person">
    ...
  </div>

Then:

  var person = $("#person").parcel();
  person.summary();

jQuery.parcel will mixin the Person behaviour for you automatically.

You can build a composite parcel with another parcel as field by setting up parcel property on any DOM element which can be used as a container (typically _div_s, _fieldset_s, etc).

With this HTML:

  <div id="person">
    ...
    <div parcel name="contact">
      <input type="text" name="number" value="+61 2 9555 0123" />
      <input type="text" name="type" value="work" />
    </div>
  </div>

Then:

  var person = $("#person").parcel();

  person.state();  #=> { title: "Mr", firstname: "John", lastname: "Smith", contact: { number: "+61 2 9555 0123", type: "work" } }

  person.contact.state();  #=> { number: "+61 2 9555 0123", type: "work" }

  person.contact.number.state();  #=> "+61 2 9555 0123"

Build a parcel with a field containing an array of sub-fields with the same name (e.g. person may have multiple emails) by setting parcel property to [sub_field_name] on the container DOM element.

Given this HTML:

  <div id="person">
    ...
    <div parcel="[email]">
      <input type="text" name="email" value="a@my.com" />
      <input type="text" name="email" value="b@my.com" />
    <div>
  </div>

Then:

  var person = $("#person").parcel();

  person.state();  #=> { title: "Mr", firstname: "John", lastname: "Smith", email: ["a@my.com", "b@my.com"] }

  person.email.state();  #=> ["a@my.com", "b@my.com"]

  person.email.items[0].state();  #=> "a@my.com"

Adding a new DOM element dynamically will add the corresponding field. Any event registered on a parcel works for the newly added field too. (Pretty much like jQuery live events).

Given two emails in the DOM already, then:

  var person = $("#person").parcel();

  var newEmail = $("<input type="text" name="email"></input>");
  person.container.append(newEmail);
  newEmail.sync();

  var emailCount = person.emails.length;

emailCount is 3, reflects the added email.

jQuery.parcel extends jQuery with the sync() method. Just call it after the new elements are added.

The same thing happens when removing DOM elements, the fields will be removed.

Say we have two emails in the DOM already, and we remove the last one:

  var person = $("#person").parcel();

  person.container.find("input[name=email]:last").remove();
  person.sync();

  var emailCount = person.emails.length;

emailCount is now 1, reflecting the removal.

Call sync() on a parcel after removing DOM elements.

A virtual field in jQuery.parcel is a jQuery object of a div, fieldset or other container like dom element, which contains fields in it’s scope.

It represents a virtual part of parcel. You can treat a virtual field as a normal Field, that means you can call state(), isDirty(), resetState(), and do event operations like bindState(), stateChange() on it.

Given this HTML:

  <div id="person">
    <div id="basicInfo">
      <input type="text" name="firstname" value="Jane"/>
      <input type="text" name="lastname" value="Doe"/>
      ...
    </div>
    ...
  </div>

Then:

  var person = $("#person").parcel();
  
  var basicInfo = $("#basicInfo");
  
  basicInfo.state();  #=> { firstname: "Jane", lastname: "Doe", ... }

  basicInfo.state({ firstname: "Mary" });  #=> { firstname: "Mary", lastname: "Doe", ... }
  
  basicInfo.resetState();  #=> { firstname: "Jane", lastname: "Doe", ... }

  basicInfo.bindState("#id_of_some_label", function(state){ /* compose a string from state */ });

  basicInfo.stateChange(function(state){ /* play with the new state */ });
  ...

The flexibility of virtual field makes the event wiring up much more expressive and easier.

parcel([behaviour], [state])

Returns: Parcel/array

For each element in jQuery, create a parcel and return the set of created parcels, return single parcel if only one element in jQuery object.

behaviour – optional, a function which will be mixed in after the parcel is created.

state – optional, the initial state of the parcel.

Common logic

Build with normal jQuery field

Build with array field

Build with Parcel as field

Build with virtual field

Build with behaviour

Build with initial state

Build multiple Parcels at once

sync([dom]) Returns: Parcel

create field(s) with the DOM element passed in or remove field(s) reflecting some DOM element is removed if DOM element is not provided.

dom – optional, newly created DOM element

captureState() Returns: Parcel

capture current state as initial state of the parcel.

orderFields(fieldName1, fieldName2, […]) Returns: Parcel

reorder fields of the parcel. call orderField(“name”, “title”) if you want to make sure that title is filled in after name.

Problem:

The order is determined by their occurence in DOM tree by default, and used as the sequence of setting each field’s state. You need to change the order if it’s different with the fill-in order in real user interaction.

renameFields(setting) Returns: Parcel

Rename fields with setting. Field name is determined by property of DOM element in this sequence: ‘fieldname’ → ‘name’ → ‘id’, and you can rename after parcel is created by this method.

setting – format: {newName1: “originalName1”, newName2: “originalName2”, …}

applyBahaviour(behaviour) Returns: Parcel

apply the passed in behaviour to this parcel.

behaviour – a function encaptured related logic. It may add new property to parcel, do event wiring up and more.

Field is the core concept in jQuery.parcel. A field is a jQuery object, and conceptually can be:

- a parcel, build from div or fieldset, constructed by parcel() function, and containing other fields with different name. - an array parcel, build from div or fieldset, constructed by parcel() function, and containing sub fields with same name. e.g. a div of a list of textboxs representing multiple emails. - a normal jQuery object, representing single input or input group in DOM. e.g. textbox or radio group. - a virtual field, jQuery object created from div or fieldset containing other fields. Normal jQuery object or virtual Field are not necessarily stored as a property in its parent parcel, they can be created on the fly like $("#id"). All field operations and events are availiable to them.

state() Returns: object/string/array

get state of field.

state(s, [optoin]) Returns: Field

set state of field.

s – state to set.

option – optional, fine control the setting with below options:

exist: true to ensure property in s has corresponding UI element.

editable: true to ensure the UI element is both visible and enabled, ready for user interaction.

sync: true to turn off animation and async of ajax call during setting process.

check: true to make sure the state is actually updated as expected after setting.

defaultState() Returns: object/string/array

get the default state of field, which is the state to reset to.

defaultState(s) Returns: Field

set the default state of field.

Intercept getting default state by providing $.config.elementStrategy.getDefault is another way of specify default state if the default state follows strong conventions, e.g. the first option in select is the default, empty string is the default for textbox.

resetState() Returns: Field

reset state of field to default state.

initialState() Returns: object

get the initial state of field, which is the state of field after construction of parcel or after calling captureState().

isDirty() Returns: boolean

return true if the current state is different with initial state.

revertState() Returns: Field

set the field state back to initial state.

closestParcel() Returns: Parcel

return the closest parent parcel of the field, including itself.

getParcel() Returns: Parcel

get parcel instance of this jQuery object. This is the recommanded way of referencing an existing parcel.

sync() Returns: array of added fields

build field(s) on the newly created DOM element in parent parcel.

removeMe() Returns: Field

remove DOM element(s) from tree and remove field(s) in parent parcel as well. It’s a short cut of calling $("#id").remove() then parcel.sync().

parcelIgnored() Returns: boolean

return true if the DOM element is ignored by parcel by specified ‘parcelignored’ property in DOM or under any DOM element which has ‘parcelignored’ property specified.

Change on parcel ignored DOM element will not fire stateChange event.

fieldDom() Returns: array of DOM elements

return a set of DOM elements managed by this field.

bindState(context, [converter]) Returns: Field

State change of this field will update the state of context with the state of this field or converted state by converter callback if provided.

converter – optional, converting callback, accepts the state of the caller field and return converted value.

stateChange(handler) Returns: Field

Any state change of this field or any field it contains will trigger handler.

handler – accept event as parameter, refer to jQuery event parameter for details, ‘field’ is a property added to that event, representing the field this event is registered to.

  function(event){
    event.field; // the field this event registered to, aka the caller of the original stateChange()
    this; // the target DOM element
  }

showHide(target, showUpState/showUpCallback, [resetTarget], [callback]) Returns: Field

State change of this field or any field it contains will try to show/hide target.

showUpState/showUpCallback – will show if match showUpState or the return value of showUpCallback

    
  function(state){ // state of this field
    return true; // will show target
    return false: // will hide target
  }

resetTarget – optional, true to reset state of target soon after it becomes hidden, also after the animation is completed if has.

callback – optional, called after the target is showed or hided, also after the animation is completed if has.

textbox

select

radio

checkbox

$.config

$.parcelFn

$.fn

$.stateEqual(s1, s2) Returns: boolean

$.stateContain(whole, partial) Returns: boolean

$.stateEmpty() Returns: boolean

$.cloneState(s) Returns: object/string/array

About

A jQuery plugin designed to simplify and guide the OO encapsulation of client side Javascript code and more...

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published