PageSourceSearch

https://www.mom.gov.sg/html/global/vendor/js/glossarizer.js

js mom.gov.sg collected 2026-09-24 08:26:47 UTC 8,575 bytes, 405 lines download raw bytes

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, "&#x3c;");
358	  
359	  return s.replace(re2, '&#x3e;');
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.