comparison 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
comparison
equal deleted inserted replaced
639:2eaea1afd6b3 640:901803e1305f
1 /*
2 * jOWL_UI, User Interface Elements for jOWL, semantic javascript library
3 * Creator - David Decraene
4 * Version 1.0
5 * Website: http://Ontologyonline.org
6 * Licensed under the MIT license
7 * Verified with JSLint http://www.jslint.com/
8 */
9 (function($){
10
11 jOWL.UI = {
12 broadcaster : function(){
13 var listeners = [];
14 this.addListener = function(obj){
15 function add(obj){if(obj.propertyChange) { listeners.push(obj); } }
16 if(obj.constructor == Array){for(var i=0;i<obj.length;i++){ add(obj[i]); } }
17 else { add(obj); }
18 return this;
19 };
20 this.broadcast = function(item){ for(var i=0;i<listeners.length;i++){ listeners[i].propertyChange.call(item, item); } };
21 if(!this.propertyChange){ this.propertyChange = function(item){}; }
22 },
23 asBroadcaster : function(ui_elem){ ui_elem.broadcaster = jOWL.UI.broadcaster; ui_elem.broadcaster(); },
24 defaults : {
25 contentClass: "jowl-content",
26 focusClass: "ui-state-hover",
27 wrapperClass : "ui-widget-content"
28 }
29 };
30
31 /**
32 WIDGETS
33 all widgets:
34 addListener : add an object with a propertyChange function, will be triggered on select
35 propertyChange: update the widget with a new jowl object
36 Events:
37 onSelect: (look into propertylens click), return false to suppress the event, this = jquery element, first argument = jOWL object
38 CSS:
39 wrapperClass: css class(es) for main element, the el itself
40 contentClass: css class(es) for main content element, accessible by el.content
41 focusClass: css class(es) for element in focus,
42 */
43 $.fn.extend({
44 /*
45 owl_navbar
46 options:
47 onSelect : this element refers to jquery node, first argument = jOWL object
48 */
49 owl_navbar: function(options){
50 options = $.extend({
51 contentClass : jOWL.UI.defaults.contentClass,
52 focusClass : jOWL.UI.defaults.focusClass,
53 onSelect : function(item){},
54 onPropertyChange : function(item){}
55 }, options);
56 var self = this;
57 this.addClass("jowl-navbar");
58 this.content = $("."+options.contentClass, this).empty();
59 if(!this.content.length) { this.content = $("<div/>").addClass(options.contentClass).appendTo(this); }
60 this.parents = $('<div/>').appendTo(this.content);
61 this.focus = $('<div/>').addClass(options.focusClass).appendTo(this.content);
62 this.children = $('<div/>').appendTo(this.content);
63 var listnode = $('<span/>').click(function(){
64 var node = $(this);
65 var res = jOWL(node.attr('title'));
66 if(options.onSelect.call(node, res) === false) { return; }
67 if(res && res.isClass) { self.propertyChange.call(res, res); self.broadcast(res); }
68 });
69
70 jOWL.UI.asBroadcaster(this);
71
72 this.propertyChange = function(item){
73 if(options.onPropertyChange.call(this, item) === false) { return; }
74 if(item.isClass){
75 item.bind(self.focus);
76 if(jOWL.options.reason) { item.hierarchy();}
77 self.parents.empty().append(item.parents().bind(listnode));
78 self.children.empty().append(item.children().bind(listnode));
79 }
80 };
81 return this;
82 },
83 /**
84 autocomplete field.
85 */
86 owl_autocomplete : function(options){
87 options = $.extend({
88 time:500, //responsetime to check for new keystrokes, default 500
89 chars:3, //number of characters needed before autocomplete starts searching
90 focus:false, //put cursor on the input field when loading
91 limit:10, //limit size of result list to given amount
92 contentClass : "ui-widget-content",
93 focusClass : jOWL.UI.defaults.focusClass,
94 hintClass : "ui-autocomplete-hint",
95 hint: false, //Message (if any) to show when unfocused.
96 onSelect : function(item){}, //function that can be overridden
97 formatListItem : function(listitem, type, identifier, termarray){ //formatting of results, can be overridden
98 if(type){ listitem.append($('<div class="type"/>').text(type)); }
99 listitem.append($('<div class="name"/>').text(identifier));
100 if(termarray.length) { listitem.append($('<div class="terms"/>').text(termarray.join(', '))
101 .prepend($('<span/>').addClass('termlabel').text("Terms: ")));
102 }
103 }}, options);
104 jOWL.UI.asBroadcaster(this);
105
106 this.showHint = function(){
107 this.hinted = true;
108 if(options.hint){
109 this.addClass(options.hintClass).val(options.hint);
110 }
111 else {this.val(''); }
112 };
113 this.showHint();
114
115 var self = this; var old = ''; var open = false; self.val('');
116 var results = $('<ul/>').addClass(options.contentClass).addClass("jowl_autocomplete_results");
117 var div = $("<div/>").addClass(options.wrapperClass).append(results); this.after(div);
118 results.cache = {};
119 results.isEmpty = function(){ for(x in results.cache) { return false; } return true; };
120 results.close = function(){this.hide();};
121 results.open = function(q, cache){
122 this.show();
123 if(q){
124 if(!cache || results.isEmpty()) { results.cache = jOWL.query(q, options); }
125 else {
126 var newcache = {};
127 for(x in results.cache){
128 var entry = results.cache[x];
129 var found = false;
130 var newentries = [];
131 if(x.searchMatch(q) > -1) { found = true; }
132 for(var i = 0;i<entry.length;i++){
133 if(entry[i].term.searchMatch(q) > -1) { found = true; newentries.push(entry[i]); }
134 }
135 if(found) { newcache[x] = newentries; }
136 }
137 results.cache = newcache;
138 }
139 this.populate(results.cache);
140 }
141 };
142
143 results.populate = function(data){
144 var res = this; this.empty(); var count =0;
145 var clickFunction = function(){
146 var node = $(this), res = jOWL(node.data("jowltype"));
147 if(options.onSelect.call(node, res) === false) { return; }
148 self.broadcast(res);
149 };
150
151 var onHover = function(){ $(this).addClass(options.focusClass); };
152 var offHover = function(){$(this).removeClass(options.focusClass);};
153
154 for(x in data){
155 if(count < options.limit){
156 var item = data[x];
157 var v = jOWL.isExternal(x);
158 v = v ? v[1] : x;
159 var list = $('<li/>').data("jowltype", x)
160 .click(clickFunction).hover(onHover, offHover)
161 .appendTo(res);
162 var terms = [];
163 for(var l = 0;l<item.length;l++){
164 var found = false; var newterm = item[l].term;
165 for(var y=0; y < terms.length;y++){ if(terms[y].toLowerCase() == newterm.toLowerCase()) { found = true; } }
166 if(!found) { terms.push(newterm); }
167 }
168 options.formatListItem.call(list, list, item[0].type, v, terms);
169
170 }
171 count++;
172 }
173 };
174
175 setInterval(function(){
176 var newvalue = self.val();
177 var cache = true;
178 if(old != newvalue){
179 var longervalue = newvalue.length > old.length && newvalue.indexOf(old) === 0;
180 if(!old) { cache = false; }
181 old = newvalue;
182 if(newvalue.length < options.chars && open){ results.close();open = false;}
183 else if(newvalue.length >=options.chars && newvalue.length > 0){
184 if(cache) { cache = longervalue && newvalue.length > options.chars; }
185 results.open(newvalue, cache);
186 open = true;
187 }
188
189 }
190 }, options.time);
191
192 self.bind('keyup', function(){ if(!this.value.length){ results.close(); open = false; } });
193 self.bind('blur', function(){
194 if(open){setTimeout(function(){results.close();}, 200);open = false;}
195 if(!self.val().length){self.showHint();}
196 });
197 //timeout for registering clicks on results.
198 self.bind('focus', function(){
199 if(self.hinted){
200 self.hinted = false;
201 $(this).removeClass(options.hintClass).val('');
202 }
203 if(self.val().length && !open){results.open('', open);open = true;}});
204 //reopen, but do not get results
205 return this;
206 },
207 /**
208 Tree View
209 */
210 owl_treeview : function(options){
211 options = $.extend({
212 contentClass : jOWL.UI.defaults.contentClass,
213 focusClass: "focus",
214 nameClass: "name",
215 treeClass: "jowl-treeview",
216 rootClass: "root",
217 onSelect : function(item){}, //function that can be overwritten to specfy behavior when something is selected
218 rootThing : false, //if true then topnode is (owl) 'Thing'
219 isStatic : false, // if static then selections will refresh the entire tree
220 addChildren : false //add a given objects children to the treeview as well
221 }, options);
222
223 /** construct the hierarchy & make a tree of it */
224 function TreeModel(owlobject){
225
226 function clear(el){ //reset invParents, for later use.
227 if(el.parents) { el.parents().each(function(){
228 this.invParents = null; clear(this);
229 }); }
230 }
231
232 function leaf(node){
233 node.jnode.addClass(options.focusClass);
234 if(options.addChildren){
235 var entry = jOWL(node.$name.attr('title'));
236 if(entry && entry.children){ entry.children().each(function(){ node.add(this); }); } }
237 }
238
239 function traverse(itemarray, appendto){
240 if(!itemarray) { return; }
241 itemarray.each(function(){
242 var node = appendto.add(this);
243 if(this.invParents){ traverse(this.invParents, node); }
244 else { leaf(node); }
245 });
246
247 }
248
249 var h = owlobject.hierarchy(true);
250 if(options.rootThing) { traverse(h, tree.root(jOWL("Thing"))); }
251 else {
252 var root = tree.root(h);
253 for(var i=0;i<root.length;i++){
254 traverse(root[i].invParents, root[i]);
255 if(!root[i].invParents) { leaf(root[i]); }
256 }
257
258 }
259 clear(owlobject);
260
261 }
262
263 /**
264 var tree = $(selector).owl_treeview();
265 var root = tree.root("node");
266 root.add("node2").add("child");
267 */
268 function Tree(node, treemodel, options){
269 var rack = $('<ul/>').addClass(options.treeClass).appendTo(node);
270 var tree = this;
271 /**item can be text, a jOWL object, or a jOWL array */
272 this.root = function(item){
273 var rt = null; //root
274 rack.empty();
275 if(item && item.each) {
276 rt = [];
277 item.each(function(it){
278 var x = new fn.node(it, true);
279 x.wrapper.addClass("tv");
280 x.jnode.appendTo(rack);
281 x.invParents = it.invParents; it.invParents = null; //reset for later use
282 rt.push(x);
283 });
284 return rt;
285 }
286 rt = new fn.node(item, true);
287 rt.wrapper.addClass("tv");
288 rt.jnode.appendTo(rack);
289 return rt;
290 };
291
292 var fn = {};
293 fn.node = function(text, isRoot){ //creates a new node
294 this.jnode = isRoot ? $('<li/>').addClass(options.rootClass) : $('<li class="tvi"/>');
295 this.$name = null;
296 if(text){
297 this.$name = $('<span/>').addClass(options.nameClass);
298 if(typeof text == "string") { this.$name.html(text); }
299 else if(text.bind) { text.bind(this.$name); }
300 var n = this.$name;
301 this.$name.appendTo(this.jnode).click(function(){
302 var entry = jOWL(n.attr('title'));
303 if(entry && options.onSelect.call(n, entry) === false) { return; }
304 tree.broadcast(entry);
305 if(options.isStatic) { tree.propertyChange(entry); }
306 return false;});
307 }
308
309 this.wrapper = $('<ul/>').appendTo(this.jnode);
310 var self = this;
311 self.jnode.click(function(){toggle(); return false;});
312
313 this.add = function(text){
314 var nn = new fn.node(text);
315 if(!self.wrapper.children().length) { toNode(); }
316 else {
317 var lastchild = self.wrapper.children(':last');
318 lastchild.swapClass("tvilc", "tvic");
319 lastchild.swapClass("tvile", "tvie");
320 lastchild.swapClass("tvil", "tvi");
321
322 }//children - change end of list
323 self.wrapper.append(nn.jnode.swapClass('tvi', 'tvil'));
324 return nn;
325 };
326
327 function toggle(){
328 var t = self.jnode.hasClass("tvic") || self.jnode.hasClass("tvie") || self.jnode.hasClass("tvilc") || self.jnode.hasClass("tvile");
329 if(!t) { return; }
330 self.jnode.swapClass('tvic', 'tvie'); self.jnode.swapClass('tvilc', 'tvile');
331 self.wrapper.slideToggle();
332 }
333
334 function toNode(){
335 self.jnode.swapClass('tvil', 'tvilc');
336 self.jnode.swapClass('tvi', 'tvic');
337 }
338 };
339 return this;
340 }// end Tree
341
342 this.addClass("jowl-tree");
343 this.content = $("."+options.contentClass, this).empty();
344 if(!this.content.length){ this.content = $('<div/>').addClass(options.contentClass).appendTo(this); }
345 var tree = new Tree(this.content, null, options);
346 jOWL.UI.asBroadcaster(tree);
347 tree.propertyChange = function(item){ if(item.isClass) { var m = new TreeModel(item); } };
348 return tree;
349 },
350 /** Uses templating
351 options:
352 onChange: owl:Class, owl:Thing, etc..., tell the widget what to do with the different kinds of Ontology Objects.
353 "data-jowl" : {split: ", ", "somevariable" : function_triggered_for_each_result }
354 example: "rdfs:label" : {split: ", ", "rdfs:label" : function(){ //'this' keyword refers to HTML element}} )
355 example: "sparql-dl:PropertyValue(owl:Class, ?p, ?x)" : {"?p": function(){ //'this' keyword refers to HTML element }}
356 //prefil: for sparql-dl queries
357 //onComplete: function to trigger when the specific propertybox query is completed, this refers to the HTML element for propertybox
358 //sort: sort results on specified parameter, for sparql-dl results.
359 onUpdate: called when the widget updates itself
360 */
361 owl_propertyLens : function(options){
362 var self = this;
363 self.options = $.extend({
364 backlinkClass : "backlink",
365 split: {},
366 disable : {},
367 click : {}},
368 options);
369 self.resourcetype = this.attr('data-jowl') || "owl:Class";
370 var propertyboxes = [];
371 $('.propertybox', this).each(function(){
372 var node = new jOWL.UI.PropertyBox($(this), self);
373 propertyboxes.push(node);
374 node.el.hide();
375 });
376 var backlink = $('.backlink', this).hide();
377 if(!backlink.length) { backlink = $('<div class="jowl_link"/>').addClass(self.options.backlinkClass).text("Back").hide().appendTo(this); }
378 jOWL.UI.asBroadcaster(this);
379
380 /** fn: optional function to execute*/
381 this.link = function(source, target, htmlel, fn){
382 htmlel.addClass("jowl_link").click(function(){
383 if(fn) { fn(); }
384 self.broadcast(target);
385 self.propertyChange(target);
386 backlink.source = source.name;
387 backlink.show().unbind('click').click(function(){
388 self.broadcast(source); self.propertyChange(source); backlink.hide();
389 });
390
391 });
392
393 };
394
395 var action = {
396 "rdfs:label": function(item){ return [{"rdfs:label": item.label() }]; },
397 "rdf:ID" : function(item){ return [{"rdf:ID": [item.name, item] }]; },
398 "rdfs:comment" : function(item){
399 return $.map(item.description(), function(n){return {"rdfs:comment":n }; });
400 },
401 "rdf:type" : function(item){
402 if(item.owlClass) { return [{"rdf:type": item.owlClass() }]; }
403 return [{"rdf:type": item.type }];
404 },
405 "term" : function(item){
406 return $.map(item.terms(), function(n, i){ return { "term" : n[0] }; });
407 },
408 "rdfs:range": function(item){if(item.range) { return [{"rdfs:range": item.range }]; } },
409 "rdfs:domain": function(item){if(item.domain) { return [{"rdfs:domain": item.domain }]; } },
410 "permalink": function(item){
411 var href = jOWL.permalink(item);
412 return [{"permalink": "<a href='"+href+"'>Permalink</a>" }];
413 },
414 "owl:disjointWith": function(item){
415 if(!(item.isClass)) { return; }
416 return $.map(
417 jOWL.Xpath('*', item.jnode)
418 .filter(function(){return this.nodeName == "owl:disjointWith"; }),
419 function(n, i){ return {"owl:disjointWith": jOWL($(n).RDF_Resource())};
420 });
421 },
422 "default" : function(item){
423 var type = this.attr("data-jowl");
424 return $.map(
425 jOWL.Xpath('*', item.jnode).filter(function(){return this.nodeName == type; }),
426 function(n, i){ var x = {}; x[type] = $(n).text(); return x; }
427 );
428 }
429 };
430
431 this.propertyChange = function(item){
432 if(!item) { return; }
433 self.property = item;
434 if(backlink.source != item.name) { backlink.hide(); } else { backlink.source = false; }
435
436 if(item.type != self.resourcetype){
437 if(item.isDatatypeProperty && self.resourcetype == "rdf:Property") {}
438 else if(item.isObjectProperty && self.resourcetype == "rdf:Property"){}
439 else { return; }
440 }
441
442 for(var i = 0;i<propertyboxes.length;i++){
443 var pbox = propertyboxes[i];
444 pbox.clear();
445 if(!pbox.actiontype){return; }
446 var actiontype = pbox.actiontype;
447 if(self.options.disable[actiontype]) { return; }
448
449 if(!self.options[actiontype]) { self.options[actiontype] = {}; }
450
451 if(actiontype.indexOf("sparql-dl:") === 0){
452 var query = actiontype.split("sparql-dl:", 2)[1];
453 var fill = {}; fill[self.resourcetype] = item;
454 if(self.options[actiontype].prefill) { $.extend(fill, self.options[actiontype].prefill); }
455 var qr = new jOWL.SPARQL_DL(query, fill).execute({onComplete : function(r){
456 if(self.options[actiontype].sort) { r.sort(self.options[actiontype].sort); }
457 pbox.setResults(r.results, item);
458 }});
459 }
460 else {
461 var choice = (action[actiontype]) ? actiontype : "default";
462 var results = action[choice].call(pbox.valuebox, item);
463 pbox.setResults(results, item);
464 }
465 }
466
467 if(self.options.onUpdate) { self.options.onUpdate.call(this, item); }
468 }; //end property change
469
470 if(self.options.tooltip){
471 var lens = this.remove();
472 this.display = function(element, htmlel){
473 htmlel.tooltip({
474 title: element.label(),
475 html: function(){ lens.propertyChange(element); backlink.hide(); return lens.get(0); }
476 });
477 };
478 }
479 return this;
480 },
481
482 /**
483 Use propertyChange to set the class
484 Use addField to add property refinements
485 Use serialize to serialize input
486 */
487 owl_datafield: function(options){
488 options = $.extend({
489 selectorClass : "jowl-datafield-selector",
490 messageClass : "jowl-datafield-message",
491 labelClass : "jowl-datafield-property-label"
492 }, options);
493 var self = this;
494 var pArray = {}; //associative array for properties.
495 this.messages = {};
496 this.messages[jOWL.NS.xsd()+"positiveInteger"] = "Allowed values: positive numbers or comparisons like '>5 && <15' ";
497
498 this.addClass("owl_UI");
499 jOWL.UI.asBroadcaster(this);
500
501 this.property = null;
502
503 this.propertyChange = function(item){
504 if(item.isClass){
505 this.property = item;
506 for(x in pArray){//reset all properties
507 if(pArray[x].remove){ pArray[x].remove(); delete pArray[x]; }
508 }
509 }
510 };
511
512 /** Sets up a new field */
513 this.addField = function(property){
514 if(pArray[property.URI]){
515 //allow for multiple fields?
516 return;
517 }
518
519 var $content = $("<div/>");
520 pArray[property.URI] = $content;
521
522 var $title = property.bind($("<div/>")).addClass(options.labelClass).appendTo($content).click(function(){ $content.remove(); delete pArray[property.URI]; });
523
524 if(property.isObjectProperty){
525
526 var sp = new jOWL.SPARQL_DL("Type(?t, ?c),PropertyValue(concept, property, ?c)", {concept : self.property, property : property }).execute({
527 onComplete : function(obj){
528 if(!obj.results.length){ return; } //modify to deal with non value results
529 obj.sort("?t");
530
531 $select = $("<select class='"+options.selectorClass+"'/>").appendTo($content);
532
533 for(var i=0;i<obj.results.length;i++){
534 obj.results[i]['?t'].bind($("<option/>")).appendTo($select);
535 }
536
537 $content.appendTo(self);
538 }});
539
540 }
541 else if(property.isDatatypeProperty){
542 var msg ="";
543 if(self.messages[property.range]){ msg = self.messages[property.range]; }
544
545 var $input = $('<div/>').addClass(options.selectorClass).attr("title", property.range).append($('<input type="text" style="font-size:11px;width:100px;"/>'));
546 var $message = $('<div/>').addClass(options.messageClass).text(msg).appendTo($input);
547
548 $content.append($input).appendTo(self);
549 $('input', $content).focus(function(){
550 $message.animate({opacity: 1.0}, 1500).fadeOut();
551 });
552
553
554 }
555
556 };
557
558 this.serialize = function(){
559 var q = { "Type": self.property, "PropertyValue" : [] };
560
561 $('.'+options.selectorClass, self).each(function(){
562 var $this = $(this);
563 var $prop = $this.siblings('.'+options.labelClass);
564 var prop = $prop.attr('title');
565 if( $this.is("select")){
566 var s = $this.get(0);
567 var thing = $(s[s.selectedIndex]).attr('title');
568 q.PropertyValue.push([prop, thing]);
569 }
570 else {
571 var $input = $this.find("input");
572 var datatype = $this.attr('title');
573 var entry = $input.get(0).value;
574 if(entry) { q.PropertyValue.push([prop, '"'+entry+'"']); }
575 }
576 });
577 return q;
578 };
579
580 return this;
581 }
582 });
583
584 /** Used by owl_PropertyLens */
585 jOWL.UI.PropertyBox = function($el, resourcebox){
586 var v = $('[data-jowl]', $el);
587 if(v.length){ this.descendant = true;}
588
589 this.el = $el;
590 this.resourcebox = resourcebox;
591 this.valuebox = v.length ? v : $el;
592 this.actiontype = this.valuebox.attr('data-jowl');
593 };
594
595 jOWL.UI.PropertyBox.prototype = {
596 setResults : function(results, item){
597 var nodes = jOWL.UI.Template(results, this.valuebox, this.resourcebox.options[this.actiontype].split);
598 this.complete(nodes, item);
599 if(nodes && nodes.length && this.descendant) { this.el.show(); this.valuebox.hide(); }
600 if(this.resourcebox.options[this.actiontype].onComplete) { this.resourcebox.options[this.actiontype].onComplete.call(this.el.get(0)); }
601 },
602 complete : function(nodes, item){
603 var res = this.resourcebox;
604 if(!nodes || !nodes.length) { return; }
605 var v = $.data(nodes, "parameters");
606 for(x in v){
607 if(v[x].length && typeof res.options[this.actiontype][x] == "function") {
608 v[x].each(res.options[this.actiontype][x]);
609 }}
610 for(x in res.options.onChange){
611 var data = $('[typeof='+x+']', nodes).add(nodes.filter('[typeof='+x+']'));
612 if(x.charAt(0) == "." || x.charAt(0) == "#"){ data = data.add($(x, nodes));}
613 data.each(function(){
614 var node = $(this);
615 $.data(node, 'data-jowl', x);
616 var id = node.attr('title');
617 if(id != "anonymousOntologyObject") { res.options.onChange[$.data(node, 'data-jowl')].call(node, item, jOWL(id), res); }
618 });
619 }
620 },
621 clear : function(){
622 var prev = this.valuebox.prev('.jowl-template-result');
623 if(!prev.length){ prev = this.valuebox.prev('.jowl-template-splitter');}
624 if(prev.length) { prev.remove(); this.clear(this.valuebox); }
625 }
626 };
627
628 /**arr: associative array of variablrd, jqel: node for which variables need to be substituted, */
629 jOWL.UI.Template = function(arr, jqel, splitter){
630 var options = {
631 resultClass : "jowl-template-result",
632 splitterClass : "jowl-template-splitter"
633 };
634 if(!arr) { return; }
635
636 function bindObject(value, jnode){
637 var bound = false;
638 if(!value) { return false; }
639 if(typeof value == 'string') { jnode.html(value); bound = true;}
640 else if(value.constructor == Array){
641 if(value.length == 2) { value[1].bind(jnode).text(value[0]); bound = true; }
642 }
643 else if(value.bind){ value.bind(jnode); bound = true; }
644 return bound;
645 }
646 var count = 0, a = [], b = {};
647 var remnantFn = function(){
648 var txt = $(this).text();
649 if(txt.indexOf('${') === 0 && txt.lastIndexOf('}') == txt.length-1 ) { $(this).hide(); }
650 };
651 for(var i=0;i<arr.length;i++){
652 var x = jqel.clone(true).wrapInner("<"+jqel.get(0).nodeName+" class='"+options.resultClass+"'/>").children();
653 /** copy style settings */
654 x.addClass(jqel.attr('class')).removeClass('propertybox');
655 /** accepted obj types= string, array["string", "jowlobject"], jowlobject*/
656 for(obj in arr[i]){
657 if(!b[obj]) { b[obj] = []; }
658 var occurrences = $(':contains(${'+obj+'})', x);
659 if(!occurrences.length){
660 if(x.text() == "${"+obj+"}") { if(bindObject(arr[i][obj], x)) {
661 count++; b[obj].push(x.get(0));
662 }}
663 }
664 else {
665 occurrences.each(function(){
666 if(this.innerHTML == "${"+obj+"}") { var node = $(this); if(bindObject(arr[i][obj], node)) { count++; b[obj].push(this); } }
667 });
668 }
669 }
670 var remnants = $(':contains(${)', x); //hide parameters that weren't substituted
671 remnants.each(remnantFn);
672 if(count){
673 x.insertBefore(jqel);
674 a.push(x.get(0));
675 if(count > 1 && splitter) {
676 $splitter = (splitter.indexOf('<') === 0) ? $(splitter) : $("<span/>").text(splitter);
677 $splitter.addClass(options.splitterClass).insertBefore(x);
678 }
679 }
680 }
681 for(x in b){ if(b[x].length) { b[x] = $(b[x]); } }
682 var nodes = $(a);
683 $.data(nodes, "parameters", b);
684 return nodes;
685 };
686
687 /**
688 Supporting functionality
689 */
690
691 $.fn.swapClass = function(c1,c2) {
692 return this.each(function() {
693 if ($(this).hasClass(c1)) { $(this).removeClass(c1); $(this).addClass(c2);}
694 else if ($(this).hasClass(c2)) {$(this).removeClass(c2);$(this).addClass(c1);}
695 });
696 };
697
698 })(jQuery);