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 * 1. Fixed IE8 bug where whitespace get removed - Had to change `abbr` tag to a block element `div` 7 */ 8 9;(function($){ 10 11 /** 12 * Defaults 13 */ 14 15 var pluginName = 'glossarizer', 16 defaults = { 17 sourceURL : '', /* URL of the JSON file with format {"term": "", "description": ""} */ 18 replaceTag : 'abbr', /* Matching words will be wrapped with abbr tags by default */ 19 lookupTagName : 'p, ul, li, ol, dd, dl, div', /* Lookup in either paragraphs or lists. Do not replace in headings */ 20 callback : null, /* Callback once all tags are replaced: Call or tooltip or anything you like */ 21 replaceOnce : true /* Replace only once in a TextNode */, 22 replaceClass: 'glossarizer_replaced' 23 } 24 25 /** 26 * Constructor 27 */ 28 29 function Glossarizer(el, options){ 30 31 var base = this 32 33 base.el = el; 34 35 /* Element */ 36 base.$el = $(el) 37 38 /* Extend options */ 39 40 base.options = $.extend({}, defaults, options) 41 42 /* Terms */ 43 44 base.terms = []; 45 46 /* Excludes array */ 47 48 base.excludes = []; 49 50 /* Replaced words array */ 51 52 base.replaced = []; 53 54 55 /* Regex Tags */ 56 57 base.regexOption = base.options.replaceOnce? 'i': 'ig'; 58 59 60 /* Fetch glossary JSON */ 61 62 $.getJSON(this.options.sourceURL).then(function(data){ 63 64 base.glossary = data; 65 66 if(!base.glossary.length || base.glossary.length == 0) return; 67 68 /** 69 * Get all terms 70 */ 71 72 for(var i =0; i< base.glossary.length; i++){ 73 74 var terms = base.glossary[i].term.split(','); 75 76 for(var j = 0; j < terms.length; j++){ 77 78 /* Trim */ 79 80 var trimmed = terms[j].replace(/^\s+|\s+$/g, ''), 81 isExclusion = trimmed.indexOf('!'); 82 83 if(isExclusion == -1 || isExclusion != 0){ 84 85 /* Glossary terms array */ 86 87 base.terms.push(trimmed) 88 89 }else{ 90 91 /* Excluded terms array */ 92 93 base.excludes.push(trimmed.substr(1)); 94 } 95 } 96 97 98 } 99 100 101 /** 102 * Wrap terms 103 */ 104 105 base.wrapTerms(); 106 107 108 }) 109 110 111 112 } 113 114 /** 115 * Prototypes 116 */ 117 Glossarizer.prototype = { 118 119 getDescription: function(term){ 120 121 var regex = new RegExp('(^\s*)'+this.clean(term)+'\\s*|\\,$', 'i'); 122 123 /** 124 * Matches 125 * 1. Starts with \s* (zero or more spaces) 126 * 2. Ends with zero or more spaces 127 * 3. Ends with comma 128 */ 129 130 for(var i =0; i< this.glossary.length; i++){ 131 132 if(this.glossary[i].term.match(regex)){ 133 return this.glossary[i].description.replace(/\"/gi, '"') 134 } 135 } 136 137 }, 138 clean: function(text){ 139 140 var reEscape = new RegExp('(\\' + ['/', '.', '*', '+', '?', '(', ')', '[', ']', '{', '}', '\\'].join('|\\') + ')', 'g') 141 142 return text.replace(reEscape, '\\$1') 143 144 }, 145 146 /** 147 * Wraps the matched terms by calling traverser 148 */ 149 wrapTerms: function(){ 150 151 this.cleanedTerms = this.clean(this.terms.join('|')) 152 this.excludedTerms = this.clean(this.excludes.join('|')) 153 154 var nodes = this.el.querySelectorAll(this.options.lookupTagName) 155 156 for(var i =0; i < nodes.length; i++){ 157 this.traverser(nodes[i]) 158 } 159 160 /** 161 * Callback 162 */ 163 164 if(this.options.callback) this.options.callback.call(this.$el) 165 166 }, 167 168 /** 169 * Traverses through nodes to find the matching terms in TEXTNODES 170 */ 171 172 traverser: function(node){ 173 174 var next, 175 base = this; 176 177 if (node.nodeType === 1) { 178 179 /* 180 Element Node 181 */ 182 183 if (node = node.firstChild) { 184 do { 185 // Recursively call traverseChildNodes 186 // on each child node 187 next = node.nextSibling 188 189 /** 190 * Check if the node is not glossarized 191 */ 192 193 if( node.className != this.options.replaceClass) 194 { 195 196 this.traverser(node) 197 198 } 199 200 } while(node = next) 201 } 202 203 } else if (node.nodeType === 3) { 204 205 /* 206 Text Node 207 */ 208 209 var temp = document.createElement('div'), 210 data = node.data; 211 212 var re = new RegExp('(?:^|\\b)('+this.cleanedTerms+ ')(?!\\w)', base.regexOption), 213 reEx = new RegExp('(?:^|\\b)('+this.excludedTerms+ ')(?!\\w)', base.regexOption); 214 215 216 if(re.test(data)){ 217 218 var excl = reEx.exec(data); 219 220 data = data.replace(re,function(match, item , offset, string){ 221 222 223 if(base.options.replaceOnce && inArrayIn(match, base.replaced) >= 0){ 224 225 return match; 226 } 227 228 base.replaced.push(match); 229 230 var ir = new RegExp('(?:^|\\b)'+base.clean(match)+'(?!\\w)'), 231 result = ir.exec(data) 232 233 234 if(result){ 235 236 if(excl && base.excludes.length){ 237 238 var id = offset, 239 exid = excl.index, 240 exl = excl.index + excl[0].length; 241 242 if(exid <= id && id <= exl){ 243 244 return match; 245 246 }else{ 247 248 return '<'+base.options.replaceTag+' class="'+base.options.replaceClass+'" title="'+base.getDescription(match)+'">'+ match + '</'+base.options.replaceTag+'>' 249 250 } 251 } 252 else{ 253 254 return '<'+base.options.replaceTag+' class="'+base.options.replaceClass+'" title="'+base.getDescription(match)+'">'+ match + '</'+base.options.replaceTag+'>' 255 } 256 } 257 258 259 }); 260 261 /** 262 * Only replace when a match is found 263 * Resorting to jQuery html() because of IE8 whitespace issue. 264 * IE 8 removes leading whitespace from Text Nodes. Hence innerhtml doesnt work. 265 * 266 */ 267 268 $(temp).html(data) 269 270 271 272 while (temp.firstChild) { 273 node.parentNode.insertBefore(temp.firstChild, node) 274 } 275 276 node.parentNode.removeChild(node) 277 278 } 279 280 } 281 282 }, 283 284 }; 285 286 287 /** 288 * Public Methods 289 */ 290 291 var methods = { 292 293 destroy: function(){ 294 295 this.$el.removeData('plugin_' + pluginName); 296 297 /* Remove abbr tag */ 298 this.$el.find('.' + this.options.replaceClass).each(function(){ 299 300 var $this = $(this), 301 text = $this.text(); 302 303 304 $this.replaceWith(text) 305 306 }) 307 308 } 309 } 310 311 312 /** 313 * Extend Prototype 314 */ 315 316 Glossarizer.prototype = $.extend({}, Glossarizer.prototype, methods) 317 318 /** 319 * Plugin 320 * @param {[type]} options 321 */ 322 $.fn[pluginName] =function(options){ 323 324 return this.each(function(){ 325 326 327 var $this = $(this), 328 glossarizer = $this.data('plugin_' + pluginName); 329 330 /* 331 Check if its a method 332 */ 333 334 if(typeof options == "string" && glossarizer && methods.hasOwnProperty(options) ){ 335 336 glossarizer[options].apply(glossarizer) 337 338 }else{ 339 340 /* Destroy if exists */ 341 342 if(glossarizer) glossarizer['destroy'].apply(glossarizer); 343 344 345 /* Initialize */ 346 347 $.data(this, 'plugin_' + pluginName, new Glossarizer(this, options)) 348 } 349 }); 350 351 } 352 353 354 /** 355 * In Array 356 */ 357 358 function inArrayIn(elem, arr, i){ 359 360 if (typeof elem !== 'string'){ 361 return $.inArray.apply(this, arguments); 362 } 363 364 if (arr){ 365 var len = arr.length; 366 i = i ? (i < 0 ? Math.max(0, len + i) : i) : 0; 367 elem = elem.toLowerCase(); 368 for (; i < len; i++){ 369 if (i in arr && arr[i].toLowerCase() == elem){ 370 return i; 371 } 372 } 373 } 374 return -1; 375 } 376 377 378})(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.