diff examples/browser/web/js/jOWL_UI.js @ 640:901803e1305f

First instance of audioDB browser code.
author mas01mj
date Thu, 08 Oct 2009 11:19:11 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/browser/web/js/jOWL_UI.js	Thu Oct 08 11:19:11 2009 +0000
@@ -0,0 +1,698 @@
+/*
+ * jOWL_UI, User Interface Elements for jOWL, semantic javascript library
+ * Creator - David Decraene
+ * Version 1.0
+ * Website: http://Ontologyonline.org
+ * Licensed under the MIT license
+ * Verified with JSLint http://www.jslint.com/
+ */
+(function($){
+
+jOWL.UI = {
+	broadcaster : function(){
+		var listeners = [];
+		this.addListener = function(obj){
+			function add(obj){if(obj.propertyChange) { listeners.push(obj); } }
+			if(obj.constructor == Array){for(var i=0;i<obj.length;i++){ add(obj[i]); } }
+			else { add(obj); }
+			return this; 
+		};
+		this.broadcast = function(item){ for(var i=0;i<listeners.length;i++){ listeners[i].propertyChange.call(item, item); } };
+		if(!this.propertyChange){ this.propertyChange = function(item){}; }
+	},
+	asBroadcaster : function(ui_elem){ ui_elem.broadcaster = jOWL.UI.broadcaster; ui_elem.broadcaster(); },
+	defaults : {
+		contentClass: "jowl-content",
+		focusClass: "ui-state-hover",
+		wrapperClass : "ui-widget-content"
+	}
+};
+
+/** 
+WIDGETS 
+all widgets: 
+	addListener : add an object with a propertyChange function, will be triggered on select
+	propertyChange: update the widget with a new jowl object
+Events:
+	onSelect: (look into propertylens click), return false to suppress the event, this = jquery element, first argument = jOWL object
+CSS:
+	wrapperClass: css class(es) for main element, the el itself
+	contentClass: css class(es) for main content element, accessible by el.content
+	focusClass: css class(es) for element in focus,
+*/
+$.fn.extend({
+/* 
+owl_navbar
+options:
+	onSelect : this element refers to jquery node, first argument = jOWL object
+*/
+	owl_navbar: function(options){
+		options = $.extend({
+			contentClass : jOWL.UI.defaults.contentClass,
+			focusClass : jOWL.UI.defaults.focusClass,
+			onSelect : function(item){},
+			onPropertyChange : function(item){}
+		}, options);
+		var self = this;
+		this.addClass("jowl-navbar");
+		this.content = $("."+options.contentClass, this).empty();
+			if(!this.content.length) { this.content = $("<div/>").addClass(options.contentClass).appendTo(this); }
+		this.parents =  $('<div/>').appendTo(this.content);
+		this.focus = $('<div/>').addClass(options.focusClass).appendTo(this.content);
+		this.children = $('<div/>').appendTo(this.content);
+		var listnode = $('<span/>').click(function(){
+			var node = $(this);
+			var res = jOWL(node.attr('title'));
+			if(options.onSelect.call(node, res) === false) { return; }
+			if(res && res.isClass) { self.propertyChange.call(res, res); self.broadcast(res); }
+		});
+
+		jOWL.UI.asBroadcaster(this);
+
+		this.propertyChange = function(item){
+			if(options.onPropertyChange.call(this, item) === false) { return; }
+			if(item.isClass){
+				item.bind(self.focus);
+				if(jOWL.options.reason) { item.hierarchy();}
+				self.parents.empty().append(item.parents().bind(listnode));
+				self.children.empty().append(item.children().bind(listnode));
+			}
+		};
+		return this;
+	},
+/** 
+autocomplete field.
+*/
+	owl_autocomplete : function(options){
+		options = $.extend({
+			time:500, //responsetime to check for new keystrokes, default 500
+			chars:3, //number of characters needed before autocomplete starts searching
+			focus:false, //put cursor on the input field when loading
+			limit:10, //limit size of result list to given amount
+			contentClass : "ui-widget-content",
+			focusClass : jOWL.UI.defaults.focusClass,
+			hintClass : "ui-autocomplete-hint",
+			hint: false, //Message (if any) to show when unfocused.
+			onSelect : function(item){}, //function that can be overridden
+			formatListItem : function(listitem, type, identifier, termarray){ //formatting of results, can be overridden
+				if(type){ listitem.append($('<div class="type"/>').text(type)); }
+				listitem.append($('<div class="name"/>').text(identifier));
+				if(termarray.length) { listitem.append($('<div class="terms"/>').text(termarray.join(', '))
+					.prepend($('<span/>').addClass('termlabel').text("Terms: ")));
+			}
+		}}, options);
+		jOWL.UI.asBroadcaster(this);
+
+		this.showHint = function(){
+			this.hinted = true;
+			if(options.hint){
+				this.addClass(options.hintClass).val(options.hint);
+			}
+			else {this.val(''); }
+		};
+		this.showHint();
+
+		var self = this; var old = ''; var open = false; self.val('');
+		var results = $('<ul/>').addClass(options.contentClass).addClass("jowl_autocomplete_results");
+		var div = $("<div/>").addClass(options.wrapperClass).append(results); this.after(div);
+		results.cache = {};
+		results.isEmpty = function(){ for(x in results.cache) { return false; } return true; };
+		results.close = function(){this.hide();};
+		results.open = function(q, cache){
+			this.show(); 
+			if(q){
+				if(!cache || results.isEmpty()) { results.cache = jOWL.query(q, options); }
+				else { 
+					var newcache = {};
+					for(x in results.cache){
+						var entry = results.cache[x]; 
+						var found = false;
+						var newentries = [];
+						if(x.searchMatch(q) > -1) { found = true; }
+						for(var i = 0;i<entry.length;i++){
+							if(entry[i].term.searchMatch(q) > -1) { found = true; newentries.push(entry[i]); }
+						}
+						if(found) { newcache[x] = newentries; }
+						}
+					results.cache = newcache;
+					}
+				this.populate(results.cache);
+				}
+		};
+
+		results.populate = function(data){
+			var res = this; this.empty(); var count =0;
+			var clickFunction = function(){
+				var node = $(this), res = jOWL(node.data("jowltype"));
+				if(options.onSelect.call(node, res) === false) { return; }
+				self.broadcast(res);
+			};
+
+			var onHover = function(){ $(this).addClass(options.focusClass); };
+			var offHover = function(){$(this).removeClass(options.focusClass);};
+
+			for(x in data){
+				if(count < options.limit){
+					var item = data[x];
+					var v = jOWL.isExternal(x);
+					v = v ? v[1] : x;
+					var list = $('<li/>').data("jowltype", x)
+					.click(clickFunction).hover(onHover, offHover)
+					.appendTo(res);
+					var terms = [];
+					for(var l = 0;l<item.length;l++){ 
+						var found = false; var newterm = item[l].term;
+						for(var y=0; y < terms.length;y++){ if(terms[y].toLowerCase() == newterm.toLowerCase()) { found = true; } }
+						if(!found) { terms.push(newterm); }
+						}
+					options.formatListItem.call(list, list, item[0].type, v, terms);
+
+				}
+				count++;
+			}
+		};
+
+		setInterval(function(){
+			var newvalue = self.val();
+			var cache = true;
+			if(old != newvalue){
+				var longervalue = newvalue.length > old.length && newvalue.indexOf(old) === 0;
+				if(!old) { cache = false; }
+				old = newvalue; 
+				if(newvalue.length < options.chars && open){ results.close();open = false;}
+				else if(newvalue.length >=options.chars && newvalue.length > 0){
+					if(cache) { cache = longervalue && newvalue.length > options.chars; }
+					results.open(newvalue, cache);
+					open = true;
+					}
+				
+			}
+		}, options.time);
+
+		self.bind('keyup', function(){ if(!this.value.length){ results.close(); open = false; } });
+		self.bind('blur', function(){
+			if(open){setTimeout(function(){results.close();}, 200);open = false;}
+			if(!self.val().length){self.showHint();}
+			});
+		//timeout for registering clicks on results.
+		self.bind('focus', function(){
+			if(self.hinted){
+				self.hinted = false;
+				$(this).removeClass(options.hintClass).val('');
+			}
+			if(self.val().length && !open){results.open('', open);open = true;}});
+		//reopen, but do not get results
+		return this;
+	},
+/** 
+Tree View
+*/
+	owl_treeview : function(options){
+		options = $.extend({
+			contentClass : jOWL.UI.defaults.contentClass,
+			focusClass: "focus",
+			nameClass: "name",
+			treeClass: "jowl-treeview",
+			rootClass: "root",
+			onSelect : function(item){}, //function that can be overwritten to specfy behavior when something is selected
+			rootThing : false, //if true then topnode is (owl) 'Thing'
+			isStatic : false, // if static then selections will refresh the entire tree
+			addChildren : false //add a given objects children to the treeview as well
+		}, options);
+
+		/** construct the hierarchy & make a tree of it */
+		function TreeModel(owlobject){
+			
+			function clear(el){ //reset invParents, for later use.
+				if(el.parents) { el.parents().each(function(){
+					this.invParents = null; clear(this);
+				}); }
+			}
+
+			function leaf(node){
+				node.jnode.addClass(options.focusClass);
+				if(options.addChildren){
+					var entry = jOWL(node.$name.attr('title'));
+					if(entry && entry.children){ entry.children().each(function(){ node.add(this); }); } }
+			}
+
+			function traverse(itemarray, appendto){
+				if(!itemarray) { return; }
+				itemarray.each(function(){
+					var node = appendto.add(this);
+					if(this.invParents){ traverse(this.invParents, node); }
+					else { leaf(node); }
+				});
+
+			}	
+
+			var h = owlobject.hierarchy(true);
+			if(options.rootThing) { traverse(h, tree.root(jOWL("Thing"))); }
+			else { 
+				var root = tree.root(h); 
+				for(var i=0;i<root.length;i++){ 
+						traverse(root[i].invParents, root[i]);
+						if(!root[i].invParents) { leaf(root[i]); }
+					}
+
+				}
+			clear(owlobject);
+
+		}
+
+		/**
+		var tree = $(selector).owl_treeview();
+		var root = tree.root("node");
+		root.add("node2").add("child");
+		*/
+		function Tree(node, treemodel, options){
+			var rack = $('<ul/>').addClass(options.treeClass).appendTo(node);
+			var tree = this;
+			/**item can be text, a jOWL object, or a jOWL array */
+			this.root = function(item){
+				var rt = null; //root
+				rack.empty();  
+				if(item && item.each) {
+					rt = [];
+					item.each(function(it){
+						var x =  new fn.node(it, true); 
+						x.wrapper.addClass("tv");
+						x.jnode.appendTo(rack);
+						x.invParents = it.invParents; it.invParents = null;	//reset for later use
+						rt.push(x);
+					}); 
+					return rt;
+				}
+				rt = new fn.node(item, true);
+				rt.wrapper.addClass("tv"); 
+				rt.jnode.appendTo(rack);
+				return rt;
+			};
+
+			var fn = {};
+			fn.node = function(text, isRoot){ //creates a new node
+				this.jnode = isRoot ? $('<li/>').addClass(options.rootClass) : $('<li class="tvi"/>');
+				this.$name = null;
+				if(text){
+					this.$name = $('<span/>').addClass(options.nameClass);
+					if(typeof text == "string") { this.$name.html(text); }
+					else if(text.bind) { text.bind(this.$name); }
+					var n = this.$name; 
+					this.$name.appendTo(this.jnode).click(function(){
+						var entry = jOWL(n.attr('title'));
+						if(entry && options.onSelect.call(n, entry) === false) { return; }
+						tree.broadcast(entry); 
+						if(options.isStatic) { tree.propertyChange(entry); }
+						return false;});
+				}
+				
+				this.wrapper = $('<ul/>').appendTo(this.jnode);
+				var self = this;
+					self.jnode.click(function(){toggle(); return false;});
+
+				this.add = function(text){
+					var nn = new fn.node(text);
+					if(!self.wrapper.children().length) { toNode();	}
+					else { 
+						var lastchild = self.wrapper.children(':last'); 
+						lastchild.swapClass("tvilc", "tvic");
+						lastchild.swapClass("tvile", "tvie");
+						lastchild.swapClass("tvil", "tvi");
+						
+						}//children - change end of list
+					self.wrapper.append(nn.jnode.swapClass('tvi', 'tvil'));
+					return nn;
+					};
+
+				function toggle(){ 
+					var t = self.jnode.hasClass("tvic") || self.jnode.hasClass("tvie") || self.jnode.hasClass("tvilc") || self.jnode.hasClass("tvile");
+					if(!t) { return; }
+					self.jnode.swapClass('tvic', 'tvie'); self.jnode.swapClass('tvilc', 'tvile');
+					self.wrapper.slideToggle();
+					}
+
+				function toNode(){ 
+					self.jnode.swapClass('tvil', 'tvilc');
+					self.jnode.swapClass('tvi', 'tvic');
+					}
+				};
+				return this;
+		}// end Tree
+
+		this.addClass("jowl-tree");
+		this.content = $("."+options.contentClass, this).empty();
+		if(!this.content.length){ this.content = $('<div/>').addClass(options.contentClass).appendTo(this); }
+		var tree = new Tree(this.content, null, options);
+		jOWL.UI.asBroadcaster(tree);
+		tree.propertyChange = function(item){ if(item.isClass) { var m = new TreeModel(item); } };
+		return tree;
+	},
+/** Uses templating 	
+options: 
+onChange: owl:Class, owl:Thing, etc..., tell the widget what to do with the different kinds of Ontology Objects.
+"data-jowl" : {split: ",  ", "somevariable" : function_triggered_for_each_result } 
+   example: "rdfs:label" : {split: ",  ", "rdfs:label" : function(){ //'this' keyword refers to HTML element}} )
+   example: "sparql-dl:PropertyValue(owl:Class, ?p, ?x)" : {"?p": function(){ //'this' keyword refers to HTML element }}
+   //prefil: for sparql-dl queries
+   //onComplete: function to trigger when the specific propertybox query is completed, this refers to the HTML element for propertybox
+   //sort: sort results on specified parameter, for sparql-dl results.
+onUpdate: called when the widget updates itself
+*/
+		owl_propertyLens : function(options){ 
+			var self = this;
+			self.options = $.extend({
+				backlinkClass : "backlink",
+				split: {},
+				disable : {},
+				click : {}},
+				options);
+			self.resourcetype = this.attr('data-jowl') || "owl:Class";
+			var propertyboxes = [];
+			$('.propertybox', this).each(function(){
+				var node = new jOWL.UI.PropertyBox($(this), self);
+				propertyboxes.push(node);
+				node.el.hide();
+			});
+			var backlink = $('.backlink', this).hide();
+			if(!backlink.length) { backlink = $('<div class="jowl_link"/>').addClass(self.options.backlinkClass).text("Back").hide().appendTo(this); }
+			jOWL.UI.asBroadcaster(this);
+
+			/** fn: optional function to execute*/
+			this.link = function(source, target, htmlel, fn){
+				htmlel.addClass("jowl_link").click(function(){
+				if(fn) { fn(); }
+				self.broadcast(target);
+				self.propertyChange(target);
+				backlink.source = source.name;
+				backlink.show().unbind('click').click(function(){
+					self.broadcast(source); self.propertyChange(source); backlink.hide();
+				});
+
+				});
+
+			};
+
+			var action = {
+				"rdfs:label": function(item){ return [{"rdfs:label": item.label() }]; },
+				"rdf:ID" : function(item){ return [{"rdf:ID": [item.name, item] }]; },
+				"rdfs:comment" : function(item){
+					return $.map(item.description(), function(n){return {"rdfs:comment":n }; });
+					},
+				"rdf:type" : function(item){
+					if(item.owlClass) { return [{"rdf:type": item.owlClass() }]; }
+					return [{"rdf:type": item.type }];
+				},
+				"term" : function(item){
+					return $.map(item.terms(), function(n, i){ return { "term" : n[0] }; });
+				},
+				"rdfs:range": function(item){if(item.range) { return [{"rdfs:range": item.range }]; } },
+				"rdfs:domain": function(item){if(item.domain) { return [{"rdfs:domain": item.domain }]; } },
+				"permalink": function(item){
+					var href = jOWL.permalink(item);
+					return [{"permalink": "<a href='"+href+"'>Permalink</a>" }];
+				},
+				"owl:disjointWith": function(item){
+					if(!(item.isClass)) { return; }
+					return $.map(
+							jOWL.Xpath('*', item.jnode)
+								.filter(function(){return this.nodeName == "owl:disjointWith"; }), 
+							function(n, i){ return {"owl:disjointWith": jOWL($(n).RDF_Resource())};
+							});	
+				},
+				"default" : function(item){
+					var type = this.attr("data-jowl");
+					return $.map(
+								jOWL.Xpath('*', item.jnode).filter(function(){return this.nodeName == type; }),
+								function(n, i){ var x = {}; x[type] = $(n).text(); return x; }
+								);	
+				}
+			};
+
+			this.propertyChange = function(item){ 
+				if(!item) { return; }
+				self.property = item;
+				if(backlink.source != item.name) { backlink.hide(); } else { backlink.source = false; }
+				
+				if(item.type != self.resourcetype){
+					if(item.isDatatypeProperty && self.resourcetype == "rdf:Property") {}
+					else if(item.isObjectProperty && self.resourcetype == "rdf:Property"){}
+					else { return; }
+				}
+
+				for(var i = 0;i<propertyboxes.length;i++){
+					var pbox = propertyboxes[i];
+					pbox.clear();
+					if(!pbox.actiontype){return; }
+					var actiontype = pbox.actiontype;
+					if(self.options.disable[actiontype]) { return; }
+
+					if(!self.options[actiontype]) { self.options[actiontype] = {}; }
+
+					if(actiontype.indexOf("sparql-dl:") === 0){ 
+						var query = actiontype.split("sparql-dl:", 2)[1];
+						var fill = {}; fill[self.resourcetype] = item;
+						if(self.options[actiontype].prefill) { $.extend(fill, self.options[actiontype].prefill); }
+						var qr = new jOWL.SPARQL_DL(query, fill).execute({onComplete : function(r){
+							if(self.options[actiontype].sort) { r.sort(self.options[actiontype].sort); }
+							pbox.setResults(r.results, item);
+							}});
+					}
+					else {
+						var choice = (action[actiontype]) ? actiontype : "default";
+						var results = action[choice].call(pbox.valuebox, item);
+						pbox.setResults(results, item);
+					}
+				}
+					
+					if(self.options.onUpdate) { self.options.onUpdate.call(this, item); }
+			}; //end property change
+		
+		if(self.options.tooltip){
+			var lens = this.remove();
+			this.display = function(element, htmlel){
+				htmlel.tooltip({
+					title: element.label(), 
+					html: function(){	lens.propertyChange(element); backlink.hide(); return lens.get(0); }
+				}); 
+			};
+		}
+		return this;
+		},
+
+/**
+Use propertyChange to set the class
+Use addField to add property refinements
+Use serialize to serialize input
+*/
+	owl_datafield: function(options){
+		options = $.extend({
+			selectorClass : "jowl-datafield-selector",
+			messageClass : "jowl-datafield-message",
+			labelClass : "jowl-datafield-property-label"
+		}, options);
+		var self = this;
+		var pArray = {}; //associative array for properties.
+		this.messages = {};
+		this.messages[jOWL.NS.xsd()+"positiveInteger"] = "Allowed values: positive numbers or comparisons like  '>5 && <15' ";
+
+		this.addClass("owl_UI");
+		jOWL.UI.asBroadcaster(this);
+
+		this.property = null;
+
+		this.propertyChange = function(item){
+			if(item.isClass){
+				this.property = item;
+					for(x in pArray){//reset all properties
+						if(pArray[x].remove){ pArray[x].remove(); delete pArray[x]; }
+					}
+			}
+		};
+
+		/** Sets up a new field */
+        this.addField = function(property){
+            if(pArray[property.URI]){
+                //allow for multiple fields?
+				return;
+            }
+
+			var $content = $("<div/>");
+				 pArray[property.URI] = $content;
+
+			var $title = property.bind($("<div/>")).addClass(options.labelClass).appendTo($content).click(function(){ $content.remove(); delete pArray[property.URI]; });
+
+            if(property.isObjectProperty){
+
+				var sp = new jOWL.SPARQL_DL("Type(?t, ?c),PropertyValue(concept, property, ?c)", {concept : self.property, property : property }).execute({ 
+					onComplete : function(obj){
+						if(!obj.results.length){ return; } //modify to deal with non value results
+						obj.sort("?t");
+						
+						$select = $("<select class='"+options.selectorClass+"'/>").appendTo($content);
+
+						for(var i=0;i<obj.results.length;i++){
+							obj.results[i]['?t'].bind($("<option/>")).appendTo($select);
+						}
+
+						$content.appendTo(self);
+					}});
+
+            }
+            else if(property.isDatatypeProperty){
+				var msg ="";
+				if(self.messages[property.range]){ msg = self.messages[property.range];	}
+
+				var $input = $('<div/>').addClass(options.selectorClass).attr("title", property.range).append($('<input type="text" style="font-size:11px;width:100px;"/>'));
+				var $message = $('<div/>').addClass(options.messageClass).text(msg).appendTo($input);
+
+				$content.append($input).appendTo(self);
+				$('input', $content).focus(function(){
+					$message.animate({opacity: 1.0}, 1500).fadeOut();
+				});
+
+				
+			}
+
+		};
+
+		this.serialize = function(){
+			var q = { "Type": self.property, "PropertyValue" : [] };
+
+			$('.'+options.selectorClass, self).each(function(){
+				var $this = $(this);
+				var $prop = $this.siblings('.'+options.labelClass);
+				var prop = $prop.attr('title');
+				if( $this.is("select")){
+						var s = $this.get(0);
+						var thing = $(s[s.selectedIndex]).attr('title');
+						q.PropertyValue.push([prop, thing]);
+					}
+				else {
+					var $input = $this.find("input");
+					var datatype = $this.attr('title');
+					var entry = $input.get(0).value;
+					if(entry) { q.PropertyValue.push([prop, '"'+entry+'"']); }
+				}
+			});
+			return q;
+		};
+
+		return this;
+	}
+});
+
+/** Used by owl_PropertyLens */
+jOWL.UI.PropertyBox = function($el, resourcebox){
+	var v = $('[data-jowl]', $el);
+	if(v.length){	this.descendant = true;}
+
+	this.el = $el;
+	this.resourcebox = resourcebox;
+	this.valuebox = v.length ? v : $el;
+	this.actiontype = this.valuebox.attr('data-jowl'); 
+};
+
+jOWL.UI.PropertyBox.prototype = {
+	setResults : function(results, item){
+		var nodes = jOWL.UI.Template(results, this.valuebox, this.resourcebox.options[this.actiontype].split);
+		this.complete(nodes, item);
+		if(nodes && nodes.length && this.descendant) { this.el.show(); this.valuebox.hide(); } 
+		if(this.resourcebox.options[this.actiontype].onComplete) { this.resourcebox.options[this.actiontype].onComplete.call(this.el.get(0)); }	
+	},
+	complete : function(nodes, item){
+		var res = this.resourcebox;
+		if(!nodes || !nodes.length) { return; }
+		var v = $.data(nodes, "parameters"); 
+		for(x in v){ 
+			if(v[x].length && typeof res.options[this.actiontype][x] == "function") {
+				v[x].each(res.options[this.actiontype][x]);
+			}}
+		for(x in res.options.onChange){
+			var data = $('[typeof='+x+']', nodes).add(nodes.filter('[typeof='+x+']'));
+			if(x.charAt(0) == "." || x.charAt(0) == "#"){ data = data.add($(x, nodes));}
+			data.each(function(){
+				var node = $(this);
+				$.data(node, 'data-jowl', x);
+				var id = node.attr('title');
+				if(id != "anonymousOntologyObject") { res.options.onChange[$.data(node, 'data-jowl')].call(node, item, jOWL(id), res); }
+			});
+		}
+	},
+	clear : function(){
+		var prev = this.valuebox.prev('.jowl-template-result');
+		if(!prev.length){ prev = this.valuebox.prev('.jowl-template-splitter');}
+		if(prev.length) { prev.remove(); this.clear(this.valuebox); }
+	}
+};
+
+/**arr: associative array of variablrd, jqel: node for which variables need to be substituted,  */
+jOWL.UI.Template = function(arr, jqel, splitter){
+	var options = {
+		resultClass : "jowl-template-result",
+		splitterClass : "jowl-template-splitter"
+	};
+	if(!arr) { return; }
+
+	function bindObject(value, jnode){
+		var bound = false;
+		if(!value) { return false; }
+		if(typeof value == 'string') { jnode.html(value); bound = true;}
+		else if(value.constructor == Array){ 
+			if(value.length == 2) { value[1].bind(jnode).text(value[0]); bound = true;	} 
+			}
+		else if(value.bind){ value.bind(jnode); bound = true; }
+		return bound;
+	}
+	var count = 0, a = [], b = {};
+	var remnantFn = function(){
+		var txt = $(this).text(); 
+		if(txt.indexOf('${') === 0 && txt.lastIndexOf('}') == txt.length-1 ) { $(this).hide(); }
+	};
+	for(var i=0;i<arr.length;i++){ 
+		var x = jqel.clone(true).wrapInner("<"+jqel.get(0).nodeName+" class='"+options.resultClass+"'/>").children();
+		/** copy style settings */
+			x.addClass(jqel.attr('class')).removeClass('propertybox');
+		/** accepted obj types= string, array["string", "jowlobject"], jowlobject*/
+		for(obj in arr[i]){
+			if(!b[obj]) { b[obj] = []; }
+			var occurrences = $(':contains(${'+obj+'})', x);
+			if(!occurrences.length){
+				if(x.text() == "${"+obj+"}") { if(bindObject(arr[i][obj], x)) {
+					count++; b[obj].push(x.get(0));
+				}}
+			}
+			else { 
+				occurrences.each(function(){	
+					if(this.innerHTML == "${"+obj+"}") { var node = $(this); if(bindObject(arr[i][obj], node)) { count++;  b[obj].push(this); }	}
+				});
+			}
+		}
+		var remnants = $(':contains(${)', x); //hide parameters that weren't substituted
+			remnants.each(remnantFn);
+		if(count){
+			x.insertBefore(jqel);
+			a.push(x.get(0));
+			if(count > 1 && splitter) { 
+				$splitter = (splitter.indexOf('<') === 0) ? $(splitter) : $("<span/>").text(splitter);
+				$splitter.addClass(options.splitterClass).insertBefore(x);
+				}
+		}
+	}
+	for(x in b){ if(b[x].length) { b[x] = $(b[x]); } }
+	var nodes = $(a);
+	$.data(nodes, "parameters", b);
+	return nodes;
+};
+
+/** 
+Supporting functionality
+*/
+
+$.fn.swapClass = function(c1,c2) {
+	return this.each(function() {
+		if ($(this).hasClass(c1)) { $(this).removeClass(c1); $(this).addClass(c2);} 
+		else if ($(this).hasClass(c2)) {$(this).removeClass(c2);$(this).addClass(c1);}
+		});
+};
+
+})(jQuery);
\ No newline at end of file