79ee55e
click here to add a description
click here to add a homepage
A data linking plugin for jQuery. — Read more
This URL has Read+Write access
Added contacts demo
Showing 5 changed files with 317 additions and 6 deletions.
@@ -0,0 +1,34 @@
+/*
+css adapted from:
+http://veerle.duoh.com/blog/comments/a_css_styled_table/
+*/
+ a {
+ color: #6D929B;
+ }
+ .contacts {
+ border: 1px solid #C1DAD7
+ .contacts td {
+ border-right: 1px solid #C1DAD7;
+ border-bottom: 1px solid #C1DAD7;
+ padding: 6px 6px 6px 12px;
+ vertical-align: top;
+ .contacts th {
+ sans-serif;
+ border-top: 1px solid #C1DAD7;
+ letter-spacing: 2px;
+ text-transform: uppercase;
+ text-align: left;
+ .phones td, .phones th {
+ border: none;
+ padding: 2px 2px 2px 4px;
+
\ No newline at end of file
@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>My Contacts - Linking Demo</title>
+ <link type="text/css" rel="Stylesheet" href="demo-contacts.css" />
+<script type="text/javascript" src="http://code.jquery.com/jquery.js"></script>
+<script type="text/javascript" src="jquery.livelink.js"></script>
+<script type="text/javascript" src="jquery.tmpl.js"></script>
+<script type="text/javascript">
+jQuery(function(){
+// define some basic default data to start with
+var contacts = [
+ { firstName: "Dave", lastName: "Reed", phones: [
+ { type: "Mobile", number: "(555) 121-2121" },
+ { type: "Home", number: "(555) 123-4567" } ] },
+ { firstName: "Joe", lastName: "Smith", phones: [
+ { type: "Mobile", number: "(555) 444-2222" },
+ { type: "Home", number: "(555) 999-1212" } ] }
+];
+// enable using 'contacts' as the name of the template
+$.templates.contacts = $.tmpl($("#contacttmpl").html());
+$.extend($.convertFn, {
+ // linking converter that normalizes phone numbers
+ phone: function(value) {
+ // turn a string phone number into a normalized one with dashes
+ // and parens
+ value = parseInt(value.replace(/[\(\)\- ]/g, ""), 10).toString();
+ value = ("0000000000" + value).substr(-10);
+ value = "(" + value.substr(0,3) + ") " + value.substr(3,3) + "-" + value.substr(6);
+ return value;
+ },
+ fullname: function(value, settings) {
+ return settings.source.firstName + " " + settings.source.lastName;
+});
+// show the results of the linking -- object graph is already full of the data
+$("#save").click(function() {
+ $("#results").html(JSON.stringify(contacts, null, 4));
+// add a new contact when clicking the insert button.
+// notice that no code here exists that explicitly redraws
+// the template.
+$("#insert").click(function() {
+ $.push(contacts, { firstName: "", lastName: "", phones: [] });
+// function that clears the current template and renders it with the
+// current state of the global contacts variable.
+function refresh() {
+ $(".contacts").empty().append("contacts", {contacts:contacts});
+ // bind inputs to the data items
+ $(".contact").each(function(i) {
+ var contact = contacts[i];
+ $(".firstname", this).linkTo("val", contact, "firstName");
+ $(".lastname", this).linkTo("val", contact, "lastName");
+ $(".contact-fullname", this).linkFrom("text", contact, null, "fullname");
+ $(".contact-remove", this).click(function() {
+ $.splice(contacts, i, 1);
+ });
+ $(".phone", this).each(function(i) {
+ var phone = contact.phones[i];
+ $(".phone-type", this).linkTo("val", phone, "type");
+ $(".phone-number", this).linkTo("val", phone, "number", "phone");
+ $(".phone-remove", this).click(function() {
+ // note: I'd like to only redraw the phones portion of the
+ // template, but jquery.tmpl.js does not support nested templates
+ // very easily. So here I am triggering an arrayChange event on
+ // the main contacts array to force the entire thing to refresh.
+ // Note that user input is not lost since the live linking has
+ // already stored the values in the object graph.
+ $.splice(contact.phones, i, 1);
+ $([contacts]).trigger("arrayChange");
+ $(".newphone", this).click(function() {
+ $.push(contact.phones, { type: "", number: "" });
+}
+// subscribe to changes to the contact array, automatically refreshing
+// the rendering of the template
+$([contacts]).arrayChange(refresh);
+// initial view on load
+refresh();
+</script>
+<script id="contacttmpl" type="text/html">
+ <tr>
+ <th> </th>
+ <th>First Name</th>
+ <th>Last Name</th>
+ <th>Phone Numbers</th>
+ </tr>
+ {{each(i,contact) contacts}}
+ <tr class="contact">
+ <td><a href="#" class="contact-remove">remove</a></td>
+ <td>
+ <input class="firstname" type="text" value="${firstName}" />
+ <div>
+ full name: <span class="contact-fullname"></span>
+ </div>
+ </td>
+ <td><input class="lastname" type="text" value="${lastName}" /></td>
+ <table class="phones">
+ <th>Type</th>
+ <th>Number</th>
+ {{each(i,city) phones}}
+ <tr class="phone">
+ <td><a href="#" class="phone-remove">remove</td>
+ <td><input class="phone-type" type="text" value="${type}" /></td>
+ <td><input class="phone-number" type="text" value="${number}" /></td>
+ {{/each}}
+ </table>
+ <a href="#" class="newphone">add new phone</a>
+</head>
+<body>
+<table class="contacts">
+</table>
+<input type="button" id="insert" value="Insert new contact" />
+<input type="button" id="save" value="Save contacts" />
+<pre id="results">
+</pre>
+</body>
+</html>
@@ -117,8 +117,8 @@ $.each( ["attrChanging", "attrChange", "arrayChanging", "arrayChange"], function
var old_handler = handleObj.handler;
handleObj.handler = function( event, change ) {
var data = handleObj.data,
- attrName = isattr ? change.attrName : change.change;
- if ( !data || data === attrName || $.inArray( attrName, data ) > -1 ) {
+ attrName = change ? (isattr ? change.attrName : change.change) : null;
+ if ( !change || !data || data === attrName || $.inArray( attrName, data ) > -1 ) {
$.extend( event, change );
// todo: support extra parameters passed to trigger as
// trigger('attrChange', [<change>, extra1, extra2]).
@@ -171,13 +171,12 @@ $.link = function( settings ) {
if ( ev ) {
newValue = ev.newValue;
}
- else if ( sourceAttr.indexOf( "data:" ) === 0 ) {
+ else if ( sourceAttr && sourceAttr.indexOf( "data:" ) === 0 ) {
newValue = source.data( sourceAttr.substr( 5 ) );
- else {
- newValue = source.attr( sourceAttr );
+ else if ( sourceAttr ) {
+ newValue = sourceAttr === "val" ? source.val() : source.attr( sourceAttr );
- var newValue = ev ? ev.newValue : ( isVal ? source.val() : source.attr( sourceAttr ) );
if ( convert ) {
newValue = convert( newValue, settings );
@@ -0,0 +1,131 @@
+ * jQuery Templating Plugin
+ * NOTE: Created for demonstration purposes.
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ */
+(function(jQuery){
+ // Override the DOM manipulation function
+ var oldManip = jQuery.fn.domManip;
+ jQuery.fn.extend({
+ render: function( data ) {
+ return this.map(function(i, tmpl){
+ return jQuery.render( tmpl, data );
+ // This will allow us to do: .append( "template", dataObject )
+ domManip: function( args ) {
+ // This appears to be a bug in the appendTo, etc. implementation
+ // it should be doing .call() instead of .apply(). See #6227
+ if ( args.length > 1 && args[0].nodeType ) {
+ arguments[0] = [ jQuery.makeArray(args) ];
+ if ( args.length === 2 && typeof args[0] === "string" && typeof args[1] !== "string" ) {
+ arguments[0] = [ jQuery.render( args[0], args[1] ) ];
+ return oldManip.apply( this, arguments );
+ jQuery.extend({
+ render: function( tmpl, data ) {
+ var fn;
+ // Use a pre-defined template, if available
+ if ( jQuery.templates[ tmpl ] ) {
+ fn = jQuery.templates[ tmpl ];
+ // We're pulling from a script node
+ } else if ( tmpl.nodeType ) {
+ var node = tmpl, elemData = jQuery.data( node );
+ fn = elemData.tmpl || jQuery.tmpl( node.innerHTML );
+ fn = fn || jQuery.tmpl( tmpl );
+ // We assume that if the template string is being passed directly
+ // in the user doesn't want it cached. They can stick it in
+ // jQuery.templates to cache it.
+ if ( jQuery.isArray( data ) ) {
+ return jQuery.map( data, function( data, i ) {
+ return fn.call( data, jQuery, data, i );
+ } else {
+ return fn.call( data, jQuery, data, 0 );
+ // You can stick pre-built template functions here
+ templates: {},
+ /*
+ * For example, someone could do:
+ * jQuery.templates.foo = jQuery.tmpl("some long templating string");
+ * $("#test").append("foo", data);
+ tmplcmd: {
+ each: {
+ _default: [ null, "$i" ],
+ prefix: "jQuery.each($1,function($2){with(this){",
+ suffix: "}});"
+ "if": {
+ prefix: "if($1){",
+ suffix: "}"
+ "else": {
+ prefix: "}else{"
+ html: {
+ prefix: "_.push(typeof $1==='function'?$1.call(this):$1);"
+ "=": {
+ _default: [ "this" ],
+ prefix: "_.push($.encode(typeof $1==='function'?$1.call(this):$1));"
+ encode: function( text ) {
+ return text != null ? document.createTextNode( text.toString() ).nodeValue : "";
+ tmpl: function(str, data, i) {
+ // Generate a reusable function that will serve as a template
+ // generator (and which will be cached).
+ var fn = new Function("jQuery","$data","$i",
+ "var $=jQuery,_=[];_.data=$data;_.index=$i;" +
+ // Introduce the data as local variables using with(){}
+ "with($data){_.push('" +
+ // Convert the template into pure JavaScript
+ str
+ .replace(/[\r\t\n]/g, " ")
+ .replace(/\${([^}]*)}/g, "{{= $1}}")
+ .replace(/{{(\/?)(\w+|.)(?:\((.*?)\))?(?: (.*?))?}}/g, function(all, slash, type, fnargs, args) {
+ var tmpl = jQuery.tmplcmd[ type ];
+ if ( !tmpl ) {
+ throw "Template not found: " + type;
+ var def = tmpl._default;
+ return "');" + tmpl[slash ? "suffix" : "prefix"]
+ .split("$1").join(args || def[0])
+ .split("$2").join(fnargs || def[1]) + "_.push('";
+ })
+ + "');}return $(_.join('')).get();");
+ // Provide some basic currying to the user
+ return data ? fn.call( this, jQuery, data, i ) : fn;
+})(jQuery);
79ee55e79ee55e79ee55e79ee55e79ee55e