1 /** 2 * The purpose of this templating engine is to allow for extremely easy display and 3 * management of data within the context of the JSWidget environment. By rendering 4 * individual tokens for your data, you can create very robust environments in which 5 * your widgets can reside. 6 * 7 * @fileoverview 8 * @author Garth Henson (<a href="http://www.guahanweb.com">Guahan Web</a>) 9 * @version 0.1 10 */ 11 12 JSWidgets.Template = Class.extend(/** @lends Template.prototype */{ 13 /** 14 * Basic front-end templating engine to assist in granular updates of widgets 15 * @constructs 16 * @class Basic front-end templating engine allowing for easy data manipulation and presentation. 17 * @author Garth Henson (<a href="http://www.guahanweb.com">Guahan Web</a>) 18 * @version 0.1 19 * @requires TemplateToken 20 * @requires ItemRegistry 21 * @param {Object} opts Options used to configure the template 22 */ 23 construct : function(opts) { 24 // Set defaults 25 this.null_highlight = (opts.null_highlight || false) ? opts.null_highlight : false; 26 this.null_highlight_color = (opts.null_highlight_color || false) ? opts.null_highlight_color : '#fff000'; 27 this.null_tooltip = (opts.null_tooltip || false) ? opts.null_tooltip : false; 28 if (this.null_tooltip) { 29 this.tooltip = $("<p id=\"jswidgets-tpl-null-tooltip\">" + this.null_tooltip.msg + "</p>\n").hide().appendTo('body').css({ 30 border : '1px solid #aaaaaa', 31 padding: '10px', 32 fontSize: '11px', 33 fontFamily: 'Verdana, Arial, sans-serif', 34 backgroundColor: '#ffffff', 35 position: 'absolute' 36 }); 37 } 38 39 this.tokens = new ItemRegistry({ 40 type : JSWidgets.TemplateToken 41 }); 42 this.pattern = /\$\{\{(.+?)\}\}/gm; 43 }, 44 45 /** 46 * Loads the template from preexisting markup within the DOM. The raw template is 47 * extracted from the innerHTML of the provided <code>id</code> element. 48 * @public 49 * @param {string} id The ID attribute of the container element 50 */ 51 loadTemplateFromId : function(id) { 52 this.tpl = $('#' + id); 53 this.raw = this.tpl.html(); 54 this.findTokens(); 55 }, 56 57 /** 58 * Loads the provided <code>txt</code> as the raw template content. 59 * @public 60 * @param {string} txt The template text to use 61 */ 62 loadTemplateText : function(txt) { 63 this.tpl = $(txt); 64 this.raw = this.tpl.html(); 65 this.findTokens(); 66 }, 67 68 /** 69 * Loads the provided <code>key => value</code> pairs as data to replace into the template. 70 * The provided keys are expected to match to the name of the {@link Token} which is to be replaced. 71 * @public 72 * @param {object} obj The <code>key => value</code> pairs to be assigned 73 */ 74 loadValues : function(obj) { 75 for (var k in obj) { 76 var tok = this.tokens.checkout(k); 77 tok.setValue(obj[k]); 78 this.tokens.checkin(tok); 79 } 80 }, 81 82 /** 83 * Looks in the template for anything formed as ${{xxx}} 84 * Default values can be defined as ${{xxx|default: value}} 85 * If no default is defined, token will be left blank when rendered 86 * @private 87 */ 88 findTokens : function() { 89 this.tokens.clear(); 90 var tokens = this.raw.match(this.pattern); 91 for (var i = 0; i < tokens.length; i++) { 92 this.tokens.checkin(new JSWidgets.TemplateToken(tokens[i])); 93 } 94 }, 95 96 /** 97 * Using the raw template text and the assigned token values that have been loaded, render 98 * the output text to be displayed to the user. If the <code>ret</code> parameter is passed 99 * as a boolean <i>true</i>, the text is returned to the caller. Otherwise, this method 100 * returns <i>true</i> upon completion. 101 * @public 102 * @param {boolean} ret Whether or not to return the rendered text | defaults to <i>false</i> 103 * @returns {boolean|string} Boolean by default or rendered output upon request 104 */ 105 render : function(ret) { 106 var txt = this.raw; 107 for (var i = 0; i < this.tokens.length; i++) { 108 if (this.tokens.get(i).hasValue()) { 109 txt = this.tokens.get(i).replaceIn(txt); 110 } else if (this.null_highlight) { 111 // Highlight token 112 var hl_txt = '<span style="background-color: ' + this.null_highlight_color + ';" class="tpl-null-token">' + this.tokens.get(i).raw + '</span>'; 113 txt = txt.replace(this.tokens.get(i).raw, hl_txt); 114 } 115 } 116 117 if (ret || false) { 118 return txt; 119 } 120 121 this.tpl.html(txt); 122 if (this.null_tooltip) { 123 var that = this; 124 $('span.tpl-null-token').hover(function(e) { 125 that.showTooltip(e); 126 }, function(e) { 127 that.hideTooltip(e); 128 }); 129 } 130 return true; 131 }, 132 133 /** 134 * Render the template output to the specified <code>id</code> element 135 * @public 136 * @param {string} id ID attribute of the DOM element into which the output should be rendered 137 */ 138 renderTo : function(id) { 139 $('#' + id).html(this.render(true)); 140 }, 141 142 /** 143 * Shows this object's tooltip in relation to the hovered element 144 * @private 145 * @param {event} e The <code>mouseover</code> event 146 */ 147 showTooltip : function(e) { 148 e.preventDefault(); 149 var offset = $(e.target).offset(); 150 var that = this; 151 this.tooltip.css({ 152 top : offset.top - that.tooltip.outerHeight() + 'px', 153 left : offset.left + 'px' 154 }).show(); 155 }, 156 157 /** 158 * Hides this object's tooltip display 159 * @private 160 * @param {event} e The <code>mouseout</code> event 161 */ 162 hideTooltip : function(e) { 163 e.preventDefault(); 164 this.tooltip.hide(); 165 } 166 }); 167 168 JSWidgets.TemplateToken = Class.extend(/** @lends TemplateToken.prototype */{ 169 /** 170 * Individual token objects used by {@link Template} objects to render their appropriate 171 * content. TemplateToken syntax is expected as: <code>${{<title>|<mod_name>="<mod_value>"}}</code> 172 * where: 173 * <table> 174 * <tr> 175 * <td><b>title</b>:</td> 176 * <td>The title of the current token (used to assign values)</td> 177 * </tr> 178 * <tr> 179 * <td><b>mod_name</b>:</td> 180 * <td>Name of the modifier(s). Valid modifiers are covered in {@link TemplateToken#parseToken}.</td> 181 * </tr> 182 * <tr> 183 * <td><b>mod_value</b>:</td> 184 * <td>Value of the current modifier (ie, <code>type="text"</code>)</td> 185 * </tr> 186 * </table> 187 * @constructs 188 * @class Token objects used by {@link Template} to apply and display data granularly. 189 * @author Garth Henson (<a href="http://www.guahanweb.com">Guahan Web</a>) 190 * @version 0.1 191 * @param {Object} opts Options used to configure the template 192 */ 193 construct : function(txt) { 194 // Set defaults 195 this.value = null; 196 this.type = 'text'; 197 this.decimals = 0; 198 this.parseToken(txt); 199 }, 200 201 /** 202 * Parses the provided <code>txt</code> as a token, retrieving the provided modifiers 203 * and title. Currently supported modifiers are as follows: 204 * <table> 205 * <tr> 206 * <td><b>default</b></td> 207 * <td>The default value to render for this token when none is provided</td> 208 * </tr> 209 * <tr> 210 * <td><b>type</b></td> 211 * <td>The type of value this token represents ("text" or "number")</td> 212 * </tr> 213 * <tr> 214 * <td><b>decimals</b></td> 215 * <td>The number of decimals to which to round (associated with "number" type)</td> 216 * </tr> 217 * </table> 218 * @private 219 * @param {string} txt The raw token text 220 */ 221 parseToken : function(txt) { 222 this.raw = txt; 223 var inner = txt.substring(0, txt.length - 2); 224 this.inner = inner.substring(3); 225 226 var parts = this.inner.split('|', 2); 227 this.name = parts[0]; 228 if (parts.length > 1) { 229 // Options were provided, check for them 230 // Valid options supported currently: default|type 231 var pattern = '(default|type|decimals)=\"(.*?)\"'; 232 var d = parts[1].match(new RegExp(pattern, 'g')); 233 if (null !== d) { 234 for (var i = 0; i < d.length; i++) { 235 var x = d[i].match(new RegExp(pattern)); 236 switch(x[1]) { 237 case 'type': 238 var valid = ['text', 'number']; 239 for (var j = 0; j < valid.length; j++) { 240 if (x[2] == valid[j]) { 241 this[x[1]] = x[2]; 242 break; 243 } 244 } 245 break; 246 247 case 'decimals': 248 this[x[1]] = parseInt(x[2]); 249 break; 250 251 default: 252 this[x[1]] = x[2]; 253 } 254 } 255 } 256 } 257 258 // Set modifier for rounding 259 this.modifier = 0; 260 if (this.decimals > 0) { 261 var mod = 10; 262 for (var i = 1; i < this.decimals; i++) { 263 mod *= 10; 264 } 265 this.modifier = mod; 266 } 267 }, 268 269 /** 270 * Sets the value of this token to the provided <code>val</code>, taking into account any 271 * modifiers in its calculation. 272 * @public 273 * @param {varied} val The value to be assigned 274 */ 275 setValue : function(val) { 276 switch (this.type) { 277 case 'number': 278 val = parseFloat(val); 279 this.value = Math.round(this.modifier * val) / this.modifier; 280 break; 281 282 default: 283 this.value = val.toString(); 284 } 285 }, 286 287 /** 288 * Gets the value of the token, taking into account any default values that have been defined. 289 * @public 290 * @returns {varied} The current value 291 */ 292 getValue : function() { 293 if (this.value !== null) { 294 return this.value; 295 } 296 297 if (this['default'] !== null) { 298 return this['default']; 299 } 300 301 return false; 302 }, 303 304 /** 305 * Returns a boolean <code>true|false</code> representing whether or not there is a returnable value. 306 * @public 307 * @returns {boolean} Whether or not there is a value assigned 308 */ 309 hasValue : function() { 310 return !!this.getValue(); 311 }, 312 313 /** 314 * Replaces the current value for any occurrences of the raw token string in the provided <code>txt</code> 315 * @public 316 * @param {string} txt The text on which to run the replacement 317 * @returns {string} The modified text string 318 */ 319 replaceIn : function(txt) { 320 return txt.replace(this.raw, this.getValue()); 321 } 322 });