source: main/trunk/greenstone3/web/test.js@ 30695

Last change on this file since 30695 was 25701, checked in by kjdon, 12 years ago

default interface renamed to basic

File size: 17.9 KB
Line 
1/*
2
3Greenstone 3 'Client-side transformer'
4Performs client-side transformations of XSLT, using HTML5 local storage (simulated if the browser doesn't support it).
5
6Currently only supports Firefox 3 and greater, since it's the only browser (at the time of writing) that can do this properly.
7
8@author Steven McTainsh
9@date 14/02/2011
10
11*/
12
13/* These URLs and file paths are fetched dynamically */
14var gsweb = ""; // The file path to the Greenstone 3 web directory
15var gsurl = ""; // The root URL for this Greenstone 3 installation
16
17/* Misc. switches and paths */
18var keyUrl = ''; // Used across methods to build up query string for text retrieval (client-side transformed version)
19var on = true; // Set to false to disable operation
20
21var index = 0; // Used for query array (to keep track of number of elements)
22var deferredEls = new Array(); // Elements to defer text retrieval for until later
23var queryArr = new Array(); // Text to query for (the text for the corresponding deferred element)
24
25/* Methods */
26// This is generally only called from in-line JS in the head (i.e. var text = getText(...);)
27function getText(key, el) {
28 if(localStorage.getItem(key) != null)
29 return localStorage.getItem(key);
30 else {
31 push(deferredEls, el);
32 queryArr[el] = key; // Have it queried for too
33 return "";
34 }
35}
36
37function processEl(el, attr, key, append, isText, theText) {
38 // Assumes all necessary text has been loaded from the servlet
39 var displayText = (isText) ? theText : localStorage.getItem(key);
40
41 if(el == document && attr == 'title') {
42 // Handle document titles
43 if(append)
44 document.title += displayText;
45 else
46 document.title = displayText;
47 } else if(attr == 'innerText') {
48 // Handle element 'inner text'
49 if(append)
50 $(el).html($(el).html() + displayText);
51 else
52 $(el).html(displayText);
53 } else if(attr == 'value') {
54 // Handle value attribute
55 if(append)
56 $(el).val($el.val() + displayText);
57 else
58 $(el).val(displayText);
59 } else {
60 // All other cases (generic attributes)
61 if(append)
62 $(el).attr(attr, $(el).attr(attr) + displayText);
63 else
64 $(el).attr(attr, displayText);
65 }
66}
67
68function notSupported() {
69 // Set not supported cookie here
70 document.cookie = 'supportsXSLT=false; expires=0; path=/';
71 // Fall back to server version
72 var location = window.location.search.substring(1);
73 if(location == '') {
74 // Start with a question mark
75 location = window.location + "?o=server";
76 } else {
77 // Start with an ampersand
78 location = window.location + "&o=server";
79 }
80 window.location = location;
81}
82
83$(document).ready(function() {
84 if(on && isSupported()) {
85 if(placeholder) {
86 // Need to prepare page placeholders
87 transform(false);
88 }
89 else {
90 // Need to place text into the page (placeholders ready)
91 applyText(false);
92 }
93 }
94});
95
96function applyText(trial) {
97
98 try {
99 // Wait for document to be ready before propogating this URL (otherwise undefined)
100 keyUrl = 'grabtext?i='+$('#interface').html()+'&l='+$('#language').html()+'&k='
101
102 // First, see if the local storage should be emptied for a new language...
103 if(localStorage.getItem("_activeLanguage") != $('#language').html()) {
104 // Languages are different, clear existing strings
105 localStorage.clear();
106 localStorage.setItem("_activeLanguage", $('#language').html());
107 }
108
109 $('#loading').show();
110
111 var query = '';
112
113 $('.getTextFor').each(function() {
114
115 // Build up query
116 // Get the three parts to the class - 'getTextFor' 'element inner text to affect' 'attributes to affect'
117 // Proper splitting on spaces
118 var parts = parseAction($(this).attr('class'), ' ')
119 // parts[1] is this element's innerText key, parts[2] may or may not contain keys
120 // Can add null to the array
121 if(!contains(queryArr, parts[1]) && localStorage.getItem(parts[1]) == null)
122 // If the key has not already been queried for and is not in local storage...
123 // NO duplicates!
124 queryArr[index++] = parts[1];
125
126 if(parts.length == 3 && parts[2] != '') {
127 var affected = parts[2].split(',');
128
129 for(var j = 0; j < affected.length; j++) {
130 var els = parseAction(affected[j], ".");
131 // els[2] will contain key
132 // Ignore text, and those elements with text already in local storage and that will already be queried for
133 if(els[2].indexOf('text:') != 0 && !contains(queryArr, els[2]) && localStorage.getItem(els[2]) == null)
134 queryArr[index++] = els[2].replace("[a]", "");
135 }
136 }
137 });
138
139 query = queryArr.join(",");
140
141 // Fetch!
142 if(query != '') { // There is something to query for
143
144 $.get(keyUrl + query, function(data) {
145
146 // Process dictionary, load into local storage
147 $(data).find("item").each(function() {
148 localStorage.setItem($(this).attr('key'), $(this).attr('value'));
149 });
150
151 assignText();
152
153 }, 'xml');
154
155 } else { assignText(trial); }
156 } catch (e) { if(trial) notSupportedCookie(); else notSupported(); }
157}
158
159function assignText(trial) {
160
161try {
162$('.getTextFor').each(function() {
163
164 var parts = parseAction($(this).attr('class'), " ");
165 var me = parts[1];
166
167 // Process it's own inner text first
168 processEl($(this), 'innerText', me);
169
170 if(parts.length >= 3 && parts[2] != '') { // Otherwise 'x y ' mucks it up
171
172 // Affecting elements are listed
173 var affectees = parts[2];
174
175 var all = affectees.split(',');
176
177 for(var i = 0; i < all.length; i++) {
178 var parts = parseAction(all[i], ".");
179
180 var append = false;
181 var isText = false;
182 var theText = '';
183
184 if(parts[2].indexOf("[a]") != -1) {
185 parts[2] = parts[2].replace("[a]", "");
186 append = true;
187 }
188
189 if(parts[2].indexOf("text:'") != -1) {
190 // This action is plain text
191 isText = true;
192 theText = parts[2].substring(6, parts[2].length - 1);
193 }
194
195 if(parts[0] == 'parent') {
196 // Affects the parent
197 processEl($(this).parent(), parts[1], parts[2], append, isText, theText);
198 } else if(parts[0] == 'this') {
199 // Affects this element
200 processEl($(this), parts[1], parts[2], append, isText, theText);
201 } else if(parts[0] == 'document') {
202 // Affects document itself
203 processEl(document, parts[1], parts[2], append, isText, theText);
204 }
205
206
207 }
208 }
209
210 // Process deferred elements now
211 for(var def in deferredEls) {
212 $(def).html(localStorage[deferredEls[def]]); //dict[deferredEls[def]]);
213 }
214
215});
216
217var xsltClientCapable = document.cookie.indexOf('supportsXSLT') != -1;
218
219// At this point, the browser has proven itself capable of XSL transformations
220// So, modify all document links into JavaScript links that use AJAX to get content
221if(xsltClientCapable) {
222 // Could set cookie here too?
223 $('.clientDocView').each(function() {
224 var link = $(this).attr('href');
225 var startIndex = link.indexOf("&amp;d=");
226 var endIndex = link.indexOf('&amp;', startIndex + 7);
227 // If the endIndex == -1, end not found; just use end index
228 endIndex = (endIndex == -1) ? link.length : endIndex;
229 var docID = link.substring(startIndex, endIndex);
230 docID = docID.replace("&amp;d=", "");
231 $(this).attr('id', docID);
232 $(this).attr('onclick', "getNodeContent(this); return false;"); // .bind doesn't work for dynamically added HTML!
233
234 if(typeof initialHash != 'undefined') // Prevent errors when searching
235 if(docID == initialHash) {
236 // Load it!
237 getNodeContent($(this).get()[0]); // Talk about round the houses! This was the only way that produced something!
238 }
239 });
240
241 document.cookie = 'supportsXSLT=true; expires=0; path=/';
242}
243
244// Also undo escaping of necessary entities
245$('body').each(function() {
246
247 var text = $(this).html();
248 text = text.replace(/&lt;/g, "<");
249 text = text.replace(/&gt;/g, ">");
250 text = text.replace(/&amp;/g, "&");
251 $(this).html(text);
252
253});
254
255// Remove dead elements
256$('[xmlns]').each(function() {
257 if($(this).get(0).tagName.toLowerCase() != "html") {
258 // use toLower() in case of browser inconsistencies
259 $(this).hide();
260 }
261});
262
263//alert('Language is: ' + $('#language').html() + ', interface is: ' + $('#interface').html());
264
265$('#loading').fadeOut();
266
267} catch (e) { if(trial) notSupportedCookie(); else notSupported(); }
268
269}
270
271function transform(trial) {
272
273 try {
274 var queryStr = window.location.search.substring(1);
275 var rooturl = window.location;
276 //var absolute = gsurl;
277 var lead = queryStr == '' ? '?' : '&';
278 var skinurl = rooturl+lead+"o=skinandlibdoc";
279 var xmlurl = rooturl+lead+"o=xml";
280
281 var skindoc = "";
282 var xmldoc = "";
283
284 $.get(rooturl + lead + "o=clientside", function(data) {
285
286 data = data.replace(/gs3:id/g, "gs3id");
287 data = parseFromString(data, "text/xml");
288
289 // data contains the XML and the stylesheet too!
290 var skindoc = $(data).find("xslt\\:stylesheet")[0];
291 var xmldoc = $(data).find("page")[0];
292
293 gsurl = $($(xmldoc).find("metadata[name=siteURL]")[0]).text();
294 gsweb = new RegExp($($(xmldoc).find("param[name=filepath]")[0]).text().replace(/\\/g, "\\\\"), "g");
295
296 // Find xsl:include elements and update hrefs accordingly
297 $(skindoc).find("xsl\\:include").each(function() {
298 $(this).attr('href', $(this).attr('href').replace(gsweb, gsurl).replace(/\\/g, "/"));
299 });
300
301 $(xmldoc).find("xsl\\:include").each(function() {
302 $(this).attr('href', $(this).attr('href').replace(gsweb, gsurl).replace(/\\/g, "/"));
303 });
304
305 // Convert temporarily to text here
306 skindoc = convertToString(skindoc);
307 xmldoc = convertToString(xmldoc);
308
309 // This could just be done on the server (later)
310 //data = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" /*+ "<?xml-stylesheet type=\"text/xsl\" href=\"" + skinurl + "\"?>\r\n"*/ + data;
311 // replace all!
312 // Be careful with regex syntax and the use of special characters!
313 skindoc = skindoc.replace(/util\:exists\(\$meta, ''\)/g, "$meta!=''"); // For now - use regex instead
314 skindoc = skindoc.replace(/%3A/g, ":"); // undo colon escaping
315
316 // Convert to XML
317 xmldoc = parseFromString(xmldoc, "text/xml");
318 skindoc = parseFromString(skindoc, "text/xml");
319
320 var output = '';
321 var library_name = $('/page/xsltparams/param[name=library_name]', xmldoc).text();
322 var interface_name = $('/page/xsltparams/param[name=interface_name]', xmldoc).text();
323
324 // And post-process...
325 if(window.ActiveXObject) {
326 // IE
327 var procFactory = new ActiveXObject("MSXML2.XSLTemplate");
328 procFactory.stylesheet = skindoc;
329 var proc = procFactory.createProcessor();
330 proc.input = xmldoc;
331
332 proc.addParameter('library_name', library_name);
333 proc.addParameter('interface_name', interface_name);
334
335 proc.transform();
336 output = proc.output;
337 } else {
338 // Fx
339 xsltProc = new XSLTProcessor();
340 // The leading slash is oh-so important here
341 xsltProc.setParameter(null, 'library_name', library_name);
342 xsltProc.setParameter(null, 'interface_name', interface_name);
343 xsltProc.importStylesheet(skindoc);
344 result = xsltProc.transformToDocument(xmldoc);
345 output = (new XMLSerializer()).serializeToString(result);
346 }
347
348 //output = output.replace('var placeholder = false;', 'var placeholder = false; var xsltClientCapable = true;');
349
350 var doc = document.open();
351 doc.write(output);
352 doc.close();
353
354 document.cookie = 'supportsXSLT=true; expires=0; path=/';
355
356 }, 'html');
357 } catch (e) { if(trial) notSupportedCookie(); else notSupported(); }
358}
359
360function notSupportedCookie() {
361 document.cookie = 'supportsXSLT=false; expires=0; path=/';
362}
363
364function getNodeContent(node) {
365 var hash = $(node).attr('id');
366
367 // Some document titles appear within spans, some do not, so these adjustments are necessary
368 $('.clientDocView').next().css('font-weight', 'normal');
369 $('.clientDocView').parent().css('font-weight', 'normal');
370 $(node).parent().css('font-weight', 'bold');
371 $(node).next().css('font-weight', 'bold');
372
373 var loadingimg = '<img src="interfaces/default/images/loading.gif" alt="Loading" />';
374
375 // Fetch me some fresh XML
376 $('.documenttext').html(loadingimg);
377 var url = $(node).attr('href') + "&o=xml";
378 url = url.replace('&amp;', '&');
379 $.get(url, function(data) {
380
381 gsurl = $($(data).find("metadata[name=siteURL]")[0]).text();
382
383 $.get(gsurl + '/interfaces/basic/transform/document_text.xsl', function(text) {
384
385 var output = '';
386 var result = '';
387
388 try {
389 // FF only
390 xsltProc = new XSLTProcessor();
391 // The leading slash is oh-so important here
392 xsltProc.setParameter(null, 'library_name', $('/page/xsltparams/param[name=library_name]', data).text());
393 xsltProc.setParameter(null, 'interface_name', $('/page/xsltparams/param[name=interface_name]', data).text());
394 xsltProc.importStylesheet(text);
395 result = xsltProc.transformToDocument(data);
396 output = (new XMLSerializer()).serializeToString(result);
397 } catch(e) { window.location = $(node).attr('href'); } // will naturally cascade to client-side, then if necessary, server-side
398
399 // Get the book name
400 var book = $('#documentheading').text();
401 var title_arr = new Array();
402 var temp_arr = new Array();
403
404 title_arr.push(book);
405
406 // Go up the tree, until you reach the 'Table of Contents' node
407 $(node).parentsUntil('#tocstart').each(function() {
408 if($(this).get()[0].tagName == 'LI') {
409 // Only list elements...
410 // next child = a, next sibling = text node
411 temp_arr.push($(this).children('span').text());
412 }
413 });
414
415 // Store these temporarily to reverse them and get it right
416 temp_arr = temp_arr.reverse();
417 title_arr = title_arr.concat(temp_arr);
418 document.title = title_arr.join('::');
419
420 // Fix up entity encoding first
421 output = output.replace(/&amp;/g, '&');
422 output = output.replace(/&lt;/g, '<');
423 output = output.replace(/&gt;/g, '>');
424 $('.documenttext').html(output);
425
426 }, 'xml');
427
428 }, 'xml');
429}
430
431function parseAction(str, chr) {
432
433 // Problem with split - seems to discard the rest of string if it encounters splitable characters but doesn't split on them
434 // But, then all the split characters are missing from the array! So, need to slice the substring out.
435 // Get additional text
436 var parts = str.split(chr, 3);
437
438 var trimIndex = parts[0].length + parts[1].length + 2;
439 parts[2] = str.substring(trimIndex);
440
441 return parts;
442
443}
444
445// Method equivalent to PHP's in_array method
446function contains(array, value) {
447
448 for(var val in array)
449 if(array[val] == value)
450 return true;
451
452 return false;
453}
454
455// Method equivalent to PHP's own
456function print_r(arr) {
457
458 var result = "";
459
460 for(var a in arr) {
461 result += a + " => " + arr[a] + "\r\n";
462 }
463
464 return result;
465}
466
467function convertToString(content) {
468 try {
469 // If this fails, it's another indication that the browser doesn't have the support we need
470 if(typeof XMLSerializer != 'undefined') {
471 return (new XMLSerializer()).serializeToString(content);
472 } else {
473 return content.xml;
474 }
475 } catch (e) { notSupported(); }
476}
477
478function parseFromString(content, contentType) {
479 try {
480 var retobj;
481
482 if(typeof window.DOMParser != 'undefined') {
483 // Fx
484 retobj = (new DOMParser()).parseFromString(content, contentType);
485 } else {
486 // IE
487 var retobj = new ActiveXObject("Microsoft.XMLDOM");
488 retobj.async = "false";
489 retobj.loadXML(content);
490 }
491
492 return retobj;
493 } catch(e) { var obj = new ActiveXObject('MSXML.DomDocument'); obj.async = false; obj.loadXML(content); return obj; }
494}
495
496function isSupported() {
497 // Are cookies enabled?
498 if(navigator.cookieEnabled && typeof navigator.cookieEnabled != 'undefined') {
499 // Is there a cookie?
500 if(document.cookie.indexOf('supportsXSLT=') > -1) {
501 // Cookie exists - don't try the transformation, as the server will
502 // read the cookie and determine which version to serve up.
503 // If it happens to be client-side, allow transformation to proceed
504 return (document.cookie.indexOf('supportsXSLT=true') > -1);
505 } else {
506 // Cookie doesn't exist - test!
507 transform(true);
508 applyText(true);
509 return (document.cookie.indexOf('supportsXSLT=true') > -1);
510 }
511 } else {
512 return false;
513 }
514}
515
516/* Simulating web storage for browsers that don't support it */
517/* Credit: http://www.thewojogroup.com/2010/01/simulating-html5-local-storage/ */
518(function(){var k=this;if(!k.localStorage&&navigator.cookieEnabled){var x="storageData_",y="++",z="::",l=function(a,c,b){var e=new Date;e.setTime(e.getTime()+b);b="; expires="+e.toGMTString();document.cookie=a+"="+c+b+"; path=/"},h=function(a){a=a+"=";for(var c=document.cookie.split(";"),b=0,e=c.length;b<e;b++){for(var d=c[b];d.charAt(0)==" ";)d=d.substring(1,d.length);if(d.indexOf(a)===0)return d.substring(a.length,d.length)}return null},m=function(a){l(a,"",-1)},i=function(){for(var a="",c=0;h(y+c)!==null;)a+=h(y+
519c++);return a==""?[]:a.split(y)},n=function(a){for(var c=Math.ceil(a.length/4E3),b=0;b<c||h(y+b)!==null;){b<c?l(y+b,a.substr(b*4E3,(b+1)*4E3>a.length?a.length-b*4E3:4E3),2592E3):m(y+b);b++}},f=k.localStorage={length:0,setItem:function(a,c){var b=i(),e=0,d=b.length,g=false;for(e=0;e<d;e++){var j=b[e].split(z);if(j[0]==a){j[1]=c;g=true;b[e]=j.join(z);e=d}}if(!g){b.push(a+z+c.replace(/::/g,": :").replace(/\+\+/g,"+ +"));this.a.push(a);this.length++}n(b.join(y))},
520getItem:function(a){var c=i(),b=0,e=c.length;for(b=0;b<e;b++){var d=c[b].split(z);if(d[0]==a&&d[1])return d[1]}return null},removeItem:function(a){var c=i(),b=0,e=c.length,d=false,g=[];for(b=0;b<e;b++)if(c[b].split(z)[0]!=a)g.push(c[b]);else d=true;if(d){n(g.join(y));o()}},clear:function(){for(var a=0;h(y+a)!==null;)m(y+a++);this.a=[];this.length=0},key:function(a){return a<this.length?this.a[a]:null},a:[]},o=function(){f.a=i();for(var a=0;f.a[a];)f.a[a]=f.a[a++].split(z)[0];
521f.length=f.a.length};o()}})();
Note: See TracBrowser for help on using the repository browser.