1/** 2 * Plugin Name: Glossarizer 3 * Author : Vinay @Pebbleroad 4 * Date: 02/04/2013 5 * Description: Takes glossary terms from a JSON object -> Searches for terms in your html -> Wraps a abbr tag around the matched word 6 */ 7 (function($){ 8 $.extend({ 9 // Case insensative $.inArray (http://api.jquery.com/jquery.inarray/) 10 // $.inArrayIn(value, array [, fromIndex]) 11 // value (type: String) 12 // The value to search for 13 // array (type: Array) 14 // An array through which to search. 15 // fromIndex (type: Number) 16 // The index of the array at which to begin the search. 17 // The default is 0, which will search the whole array. 18 inArrayIn: function(elem, arr, i){ 19 // not looking for a string anyways, use default method 20 if (typeof elem !== 'string'){ 21 return $.inArray.apply(this, arguments); 22 } 23 // confirm array is populated 24 if (arr){ 25 var len = arr.length; 26 i = i ? (i < 0 ? Math.max(0, len + i) : i) : 0; 27 elem = elem.toLowerCase(); 28 for (; i < len; i++){ 29 if (i in arr && arr[i].toLowerCase() == elem){ 30 return i; 31 } 32 } 33 } 34 // stick with inArray/indexOf and return -1 on no match 35 return -1; 36 } 37 }); 38})(jQuery); 39(function($){ 40 41 /** 42 * Defaults 43 */ 44 45 var pluginName = 'glossarizer', 46 defaults = { 47 sourceURL : '', /* URL of the JSON file with format {"term": "", "description": ""} */ 48 replaceTag : 'abbr', /* Matching words will be wrapped with abbr tags by default */ 49 lookupTagName : 'p, ul, a', /* Lookup in either paragraphs or lists. Do not replace in headings */ 50 callback : null, /* Callback once all tags are replaced: Call or tooltip or anything you like */ 51 replaceOnce : false /* Replace only once in a TextNode */, 52 replaceClass: 'glossarizer_replaced' 53 } 54 55 /** 56 * Constructor 57 */ 58 59 function Glossarizer(el, options){ 60 61 var base = this 62 63 base.el = el; 64 base.$el = $(el) 65 base.options = $.extend({}, defaults, options) 66 67 /** 68 * Terms 69 */ 70 71 base.terms = []; 72 73 base.excludes = []; 74 75 base.replaced = []; 76 77 78 /** 79 * Regex Tags 80 */ 81 82 base.regexOption = base.options.replaceOnce? '': 'g'; 83 84 85 /* 86 Fetch glossary JSON 87 */ 88 89 function requestJson(url){ 90 91 var def = $.Deferred(); 92 93 if(MOM.Storage.load('MOMGlossary')){ 94 95 def.resolve(MOM.Storage.load('MOMGlossary')) 96 97 }else{ 98 99 $.getJSON(url).then(function(data){ 100 101 if(Modernizr.localstorage){ 102 MOM.Storage.save('MOMGlossary', data, 0) // 0 day cache 103 } 104 105 def.resolve(data); 106 }) 107 } 108 109 return def.promise(); 110 } 111 112 113 /** 114 * Get JSON glossary from Localstorage or JSON file 115 */ 116 requestJson(this.options.sourceURL).then(function(data){ 117 118 base.glossary = data; 119 120 if(!base.glossary.length || base.glossary.length == 0) return; 121 122 /** 123 * Get all terms 124 */ 125 126 for(var i =0; i< base.glossary.length; i++){ 127 128 var terms = base.glossary[i].term.split(','); 129 130 for(var j = 0; j < terms.length; j++){ 131 132 /* Trim */ 133 134 var trimmed = terms[j].replace(/^\s+|\s+$/g, ''), 135 isExclusion = trimmed.indexOf('!'); 136 137 if(isExclusion == -1 || isExclusion != 0){ 138 139 /* Glossary terms array */ 140 141 base.terms.push(trimmed) 142 143 }else{ 144 145 /* Excluded terms array */ 146 147 base.excludes.push(trimmed.substr(1)); 148 } 149 } 150 151 152 } 153 /** 154 * Wrap terms 155 */ 156 157 base.wrapTerms(); 158 159 }) 160 161 162 163 } 164 165 /** 166 * Prototypes 167 */ 168 Glossarizer.prototype = { 169 170 getDescription: function(term){ 171 172 var regex = new RegExp('(^\s*)'+term+'\\s*|\\,$', 'i'); 173 174 /** 175 * Matches 176 * 1. Starts with \s* (zero or more spaces) 177 * 2. or does not contain !
178 * 3. Ends with zero or more spaces 179 * 4. Ends with comma 180 */ 181 182 for(var i =0; i< this.glossary.length; i++){ 183 184 if(this.glossary[i].term.match(regex)){ 185 return this.glossary[i].description 186 } 187 } 188 189 }, 190 191 /** 192 * Wraps the matched terms by calling traverser 193 */ 194 wrapTerms: function(){ 195 196 var nodes = this.el.querySelectorAll(this.options.lookupTagName) 197 198 for(var i =0; i < nodes.length; i++){ 199 this.traverser(nodes[i]) 200 } 201 202 /** 203 * Callback 204 */ 205 206 if(this.options.callback) this.options.callback.call(this.$el) 207 208 }, 209 210 /** 211 * Traverses through nodes to find the matching terms in TEXTNODES 212 */ 213 214 traverser: function(node){ 215 216 var next, 217 base = this; 218 219 if (node.nodeType === 1) { 220 221 /* 222 Element Node 223 */ 224 225 //console.log(node.nodeName) 226 if (node = node.firstChild) { 227 do { 228 // Recursively call traverseChildNodes 229 // on each child node 230 next = node.nextSibling 231 232 /** 233 * Check if the node is not glossarized 234 */ 235 236 if( node.nodeName != 'ABBR' && 237 node.className != this.options.replaceClass) 238 { 239 240 this.traverser(node) 241 242 } 243 244 } while(node = next) 245 } 246 247 } else if (node.nodeType === 3) { 248 249 /* 250 Text Node 251 */ 252 253 var temp = document.createElement('div'), 254 data = replaceCharacters(node.data); 255 var re = new RegExp('\\b(' + this.terms.join('|') + ')\\b', base.regexOption), 256 reEx = new RegExp('\\b(' + this.excludes.join('|') + ')\\b', base.regexOption); 257 258 259 if(re.test(data)){ 260 261 var excl = reEx.exec(data); 262 263 data = data.replace(re,function(match, item , offset, string){ 264 265 266 if($.inArrayIn(match, base.replaced) >= 0){ 267 return match; 268 } 269 270 base.replaced.push(match); 271 272 273 274 var ir = new RegExp('\\b'+match+'\\b'), 275 result = ir.exec(data) 276 277 278 if(result){ 279 280 if(excl && excl[0].length){ 281 282 var id = offset, 283 exid = excl.index, 284 exl = excl.index + excl[0].length; 285 286 if(exid <= id && id <= exl){ 287 288 return match; 289 290 }else{ 291 292 return '<'+base.options.replaceTag+' class="'+base.options.replaceClass+'" title="'+base.getDescription(match)+'">'+ match + '</'+base.options.replaceTag+'>' 293 294 } 295 } 296 else{ 297 298 return '<'+base.options.replaceTag+' class="'+base.options.replaceClass+'" title="'+base.getDescription(match)+'">'+ match + '</'+base.options.replaceTag+'>' 299 } 300 } 301 302 303 }); 304 305 } 306 307 308 $(temp).html(data); 309 310 311 while (temp.firstChild) { 312 node.parentNode.insertBefore(temp.firstChild, node) 313 } 314 315 node.parentNode.removeChild(node) 316 317 } 318 319 }, 320 321 }; 322 323 324 /** 325 * Public Methods 326 */ 327 328 var methods = { 329 330 destroy: function(){ 331 332 this.$el.removeData('plugin_' + pluginName); 333 334 /* Remove abbr tag */ 335 this.$el.find('.' + this.options.replaceClass).each(function(){ 336 337 var $this = $(this), 338 text = $this.text(); 339 340 341 $this.replaceWith(text) 342 343 }) 344 345 } 346 } 347 348 /** 349 * Replace special characters 350 * @param {[type]} str [description] 351 * @return {[type]} [description] 352 */ 353 function replaceCharacters(str) { 354 var re = new RegExp(String.fromCharCode(60), "g"); 355 var re2 = new RegExp(String.fromCharCode(62), "g"); 356 357 var s = str.replace(re, "<"); 358 359 return s.replace(re2, '>'); 360 } 361 362 363 /** 364 * Extend Prototype 365 */ 366 367 Glossarizer.prototype = $.extend({}, Glossarizer.prototype, methods) 368 369 /** 370 * Plugin 371 * @param {[type]} options 372 */ 373 $.fn[pluginName] =function(options){ 374 375 return this.each(function(){ 376 377 378 var $this = $(this), 379 glossarizer = $this.data('plugin_' + pluginName); 380 381 /* 382 Check if its a method 383 */ 384 385 if(typeof options == "string" && glossarizer && methods.hasOwnProperty(options) ){ 386 387 glossarizer[options].apply(glossarizer) 388 389 }else{ 390 391 /* Destroy if exists */ 392 393 if(glossarizer) glossarizer['destroy'].apply(glossarizer); 394 395 396 /* Initialize */ 397 398 $.data(this, 'plugin_' + pluginName, new Glossarizer(this, options)) 399 } 400 }); 401 402 } 403 404 405})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.