1 /* 2 * Copyright (C) 2012-2013 DFKI GmbH 3 * Deutsches Forschungszentrum fuer Kuenstliche Intelligenz 4 * German Research Center for Artificial Intelligence 5 * http://www.dfki.de 6 * 7 * Permission is hereby granted, free of charge, to any person obtaining a 8 * copy of this software and associated documentation files (the 9 * "Software"), to deal in the Software without restriction, including 10 * without limitation the rights to use, copy, modify, merge, publish, 11 * distribute, sublicense, and/or sell copies of the Software, and to 12 * permit persons to whom the Software is furnished to do so, subject to 13 * the following conditions: 14 * 15 * The above copyright notice and this permission notice shall be included 16 * in all copies or substantial portions of the Software. 17 * 18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 */ 26 27 28 define (['commonUtils', 'viewConstants', 'yield', 'storageUtils' ], 29 //this comment is needed by jsdoc2 [copy of comment for: function Layout(...] 30 /** 31 * The Layout class 32 * The constructor parses the layout and divides them into containers (headerContents, bodyContents, dialogsContents). 33 * 34 * @param {String} name 35 * Name of the Layout (usually the same name as the Layout's controller). 36 * @param {String} definition 37 * Layout description, i.e. the raw template code that will be processed. 38 * May be empty: in this case the processed contents must be 39 * added manually (cf. parser.StorageUtils) 40 * 41 * @requires if param definition is NOT empty: parser.RenderUtils (must be loaded beforehand via <code>require(["renderUtils"]...</code>) 42 * 43 * @requires if param definition is NOT empty: parser.ParseUtils (must be loaded beforehand via <code>require(["parseUtils"]...</code>) 44 * 45 * @name Layout 46 * @class 47 */ 48 function( 49 commonUtils, ViewConstants, YieldDeclaration, storageUtils 50 ) { 51 52 /** @scope Layout.prototype *///for jsdoc2 53 54 //set to @ignore in order to avoid doc-duplication in jsdoc3 55 /** 56 * @ignore 57 * The Layout class 58 * The constructor parses the layout and divides them into containers (headerContents, bodyContents, dialogsContents). 59 * 60 * @constructs Layout 61 * @param {String} name 62 * Name of the Layout (usually the same name as the Layout's controller). 63 * @param {String} definition 64 * Layout description, i.e. the raw template code that will be processed. 65 * May be empty: in this case the processed contents must be 66 * added manually (cf. parser.StorageUtils) 67 * 68 * @requires if param <code>definition</code> is NOT empty: parser.RenderUtils (must be loaded beforehand via <code>require(["renderUtils"]...</code>) 69 * 70 * @requires if param <code>definition</code> is NOT empty: parser.ParseUtils (must be loaded beforehand via <code>require(["parseUtils"]...</code>) 71 * 72 */ 73 function Layout(name, definition, remote, ignoreMissingBody){ 74 // console.log("[Layout] initialize '"+name+"'."); 75 76 //FIXME MODIFICATIONS for "remote content layout object": 77 this.remoteaccess = false; 78 if ((typeof remote !== "undefined") && (remote == true)){ 79 this.remoteaccess = true; 80 } 81 82 83 /** 84 * The definition string of the layout (ehtml-format, taken from assets/www/views/layout/*.ehtml) 85 * 86 * @type Object 87 * @public 88 */ 89 this.def = definition? definition.replace(commonUtils.regexHTMLComment, '') : definition;//remove HTML comments! 90 91 /** 92 * The name of the layout. 93 * 94 * @type String 95 * @public 96 */ 97 this.name = name; 98 99 /** 100 * This variable holds the contents of the header part of the layout. 101 * 102 * @type String 103 * @public 104 * @deprecated unused 105 */ 106 this.headerContents = ""; 107 108 /** 109 * List for extracted & parsed SCRIPT, LINK and STYLE tags 110 * 111 * @type Array<Layout.TagElement> 112 * @public 113 */ 114 this.headerElements = []; 115 116 /** 117 * The page / layout title 118 * 119 * Will be extracted from <em>definition</em>'s TITLE-tag, if present. 120 * 121 * @type String 122 * @public 123 */ 124 this.title = ''; 125 126 /** 127 * This variable holds the contents of the body part of the layout. 128 * 129 * @type String 130 * @public 131 * @deprecated unused 132 */ 133 this.bodyContents = ""; 134 135 /** 136 * This variable holds the contents of the dialogs part of the layout. 137 * 138 * @type String 139 * @public 140 */ 141 this.dialogsContents = ""; 142 143 /** 144 * An associative array holding the contents of the different containers: header, body, footer and dialogs 145 * 146 * @type Array 147 * @public 148 */ 149 this.yields = new Array(); 150 151 if(this.def){ 152 153 //console.debug('Layout<constructor>: start rendering layout for "'+this.name+'"'+(remote?' (REMOTE)':'')+', RAW: '+this.def); 154 var parser = require('parseUtils'); 155 var parseResult = parser.parse(this.def, this); 156 157 var renderer = require('renderUtils'); 158 var renderedLayout = renderer.renderLayout(parseResult, null/*FIXME?*/); 159 160 //DISABLED: parsing a string as HTML via jQuery etc. does not work (removes head, body,... tags): 161 // var doc = new DOMParser().parseFromString(renderedLayout, "text/html"); 162 // if(!doc){ 163 // doc = document.implementation.createHTMLDocument('LAYOUT'); 164 // } 165 // var $layout = $(doc);//$.parseHTML(renderedLayout, doc); 166 // 167 // var headerElements = $('head', $layout); 168 // for(var i=0, size = headerElements.length; i < size; ++i){ 169 // this.headerContents += headerElements[i].outerHTML; 170 // } 171 // 172 // var bodyElement = $('body', $layout); 173 // this.bodyContents = bodyElement.html(); 174 // var dialogElement = $('dialogs', $layout); 175 // this.dialogsContents = dialogElement.html(); 176 177 178 //TODO remove this (replace with real HTML parser?) 179 180 var self = this; 181 182 //TODO these should be constants (remove to constants.js?): 183 this.markerAttributeName = ViewConstants.REMOTE_RESOURCES_ATTR_NAME; 184 this.markerAttributeValue = ViewConstants.REMOTE_RESOURCES_ATTR_VALUE; 185 this.markerUseSingleQuotes = false;//<- set value enclosed in single-quotes (true) or double-quotes (false) 186 187 //appends the marker attribute for "signaling"/marking that a 188 // script/style/link-TAG was parsed&evaluated by this layout object 189 var addMarkerAttribute = function(strStartOfTag){ 190 return strStartOfTag + ' ' 191 + self.markerAttributeName + '=' 192 + (self.markerUseSingleQuotes? '\'': '"') 193 + self.markerAttributeValue 194 + (self.markerUseSingleQuotes? '\'': '"') 195 ; 196 }; 197 198 //pure HTML: 199 // (1) removed all HTML comments (via RegExpr) 200 // (2) removed template comments (via parser/renderer) 201 var pureHtml = renderedLayout; 202 203 var regExprTagContent = //match one of the following (as groups): 204 '(('+ //match as groups 205 '('+ //(1) CDATA: this may contain anything, even a closing script-statement 206 '<!\\[CDATA\\['+ //CDATA open: <![CDATA[ 207 '(.|[\\r\\n])*?'+ //allow anything within CDATA, but match non-greedily... 208 '\\]\\]>'+ //...for the CDATA-closing statement: ]]> 209 // (i.e. the first time we encounter this, we stop) 210 ')'+ //close group for CDATA-matching 211 212 '|[\\r\\n]'+ //(2) OR line breaks \r and \n (in any combination) 213 //DISABLED (using . instead!): '|[^<]'+ //(3) OR any symbol that is NOT < 214 '|.'+ //(4) OR any symbol (REQUIRED for allowing <-symbol within script! 215 ')'+ //close group 216 '*'+ //match this any number of times (or even none) 217 '?'+ //do matching non-greedy (i.e. until the next pattern in the RegExpr can be matched the first time) 218 219 ')'; //close outer group (i.e. one group for ALL content that is matched) 220 221 //_non-greedy_ RegExpr for 222 // * any line-break: \r or \n or a combination of them 223 // * any other character but < (less-symbol); note that this itself does not include line breaks 224 var regExprTagInternal = '('+ //open group: match as a group (i.e. give access to the matched content via an index in the RegExpr object) 225 '[\\r\\n]'+ //line breaks \r and \n 226 '|'+ //OR 227 '[^<]'+ //any symbol that is NOT < 228 ')'+ //close group 229 '*'+ //match this any number of times (or even none) 230 '?'; //do matching non-greedy (i.e. until the next pattern in the RegExpr can be matched the first time) 231 232 233 //matching: <script some attributes...> some content and '<!CDATA[' with any content allowed </script> 234 // or: <script some attributes... /> 235 // var regExpScriptTag = /((<script([\r\n]|[^<])*?>)(((<!\[CDATA\[(.|[\r\n])*?\]\]>)|[\r\n]|.)*?)(<\/script>))|(<script([\r\n]|[^<])*?\/>)/igm;// /((<script([\r\n]|[^<])*?>)((<!\[CDATA|[\r\n]|[^<])*?)(<\/script>))|(<script([\r\n]|[^<])*?\/>)/igm; 236 var strRegExpScriptTag = '((<script'+regExprTagInternal+'>)'+regExprTagContent+'(</script>))'+ //DETECT "normal" script-TAG with optional content 237 '|(<script'+regExprTagInternal+'/>)'; //OR DETECT "self-closing" script TAG 238 var regExpScriptTag = new RegExp(strRegExpScriptTag,'igm'); 239 240 //change to the following RegExpr (change the ones for link/style etc too) 241 // from 242 // /((<script([\r\n]|[^<])*?>)(([\r\n]|[^<])*?)(<\/script>))|(<script([\r\n]|[^<])*?\/>)/igm; 243 // to 244 // /((<script([\r\n]|[^<])*?>)(((<!\[CDATA\[(.|[\r\n])*?\]\]>)|[\r\n]|.)*?)(<\/script>))|(<script([\r\n]|[^<])*?\/>)/igm 245 // 246 // -> this RegExpr additionally 247 // * respects CDATA (<![CDTATA[ ... ]]>), with any content within its boundaries, even a "closing" </script>-TAG 248 // * allows opening < within the script-TAGs 249 // LIMITATIONS: both this and the current one do not allow a < within the script-TAGS attributes, e.g. 250 // NOT: <script data-condition=" i < 5"> 251 // WORKAROUND: encode the <-symbol, i.e. instead use <script data-condition=" i < 5"> 252 // ==> use "<" or "<" instead of "<" in TAG attributes! 253 254 // regExpScriptTag[0]: complete match 255 // regExpScriptTag[1]: script with start and end tag (if matched) 256 // regExpScriptTag[4]: TEXT content of script-tag (if present/matched) 257 // regExpScriptTag[9]: self-closing script (if matched) 258 var matchScriptTag = null; 259 260 self.headerContents = ''; 261 262 var removedScriptAndLinkHmtl = new Array(); 263 // var matchIndex; 264 while(matchScriptTag = regExpScriptTag.exec(pureHtml)){ 265 // matchIndex = matchScriptTag[1] ? 1 : (matchScriptTag[9]? 9 : -1); 266 267 if(matchScriptTag[0]){//matchIndex != -1){ 268 // console.warn("Remote: " + self.remoteaccess); 269 if (self.remoteaccess) { 270 // self.headerContents += matchScriptTag[0].replace("<script", "<script loc=\"remote\"");//[matchIndex]; 271 self.headerContents += addMarkerAttribute('<script') + matchScriptTag[0].substring('<script'.length); 272 } else { 273 self.headerContents += matchScriptTag[0]; 274 } 275 //remove script tag, and continue search 276 // pureHtml = pureHtml.substring(0,matchScriptTag.index) + pureHtml.substring(matchScriptTag.index + matchScriptTag[0].length);// pureHtml.replace(matchScriptTag[matchIndex], ''); 277 278 removedScriptAndLinkHmtl.push({start: matchScriptTag.index, end: matchScriptTag.index + matchScriptTag[0].length}); 279 280 self.headerElements.push(new Layout.TagElement(matchScriptTag[2] || matchScriptTag[9], matchScriptTag[4], 'SCRIPT')); 281 } 282 } 283 284 //matching: <link some attributes...> some content and '<!CDATA[' with any content allowed </link> 285 // or: <link some attributes... /> 286 // var regExpLinkTag = /((<link([\r\n]|[^<])*?>)(((<!\[CDATA\[(.|[\r\n])*?\]\]>)|[\r\n]|.)*?)(<\/link>))|(<link([\r\n]|[^<])*?\/>)/igm; 287 var strRegExpLinkTag = '((<link'+regExprTagInternal+'>)'+regExprTagContent+'(</link>))'+ //DETECT "normal" script-TAG with optional content 288 '|(<link'+regExprTagInternal+'/>)'; //OR DETECT "self-closing" script TAG 289 var regExpLinkTag = new RegExp(strRegExpLinkTag,'igm'); 290 // regExpLinkTag[0]: complete match 291 // regExpLinkTag[1]: link with start and end tag (if matched) 292 // regExpLinkTag[4]: TEXT content of link-tag (if present/matched) 293 // regExpLinkTag[9]: self-closing link (if matched) 294 var matchLinkTag = null; 295 296 while(matchLinkTag = regExpLinkTag.exec(pureHtml)){ 297 // console.warn("Matchlinktag: " + matchLinkTag[0]); 298 if(matchLinkTag[0]){ 299 // console.warn("Remote: " + self.remoteaccess); 300 if (self.remoteaccess) { 301 // self.headerContents += matchLinkTag[0].replace("<link", "<link loc=\"remote\""); 302 self.headerContents += addMarkerAttribute('<link') + matchLinkTag[0].substring('<link'.length); 303 } else { 304 self.headerContents += matchLinkTag[0]; 305 } 306 removedScriptAndLinkHmtl.push({start: matchLinkTag.index, end: matchLinkTag.index + matchLinkTag[0].length}); 307 308 self.headerElements.push(new Layout.TagElement(matchLinkTag[2] || matchLinkTag[9], matchLinkTag[4], 'LINK')); 309 } 310 } 311 312 313 //matching: <style type="text/css" some attributes...> some content and '<!CDATA[' with any content allowed </style> 314 // var regExpStyleTag = /((<style([\r\n]|[^<])*?type="text\/css"([\r\n]|[^<])*?>)(((<!\[CDATA\[(.|[\r\n])*?\]\]>)|[\r\n]|.)*?)(<\/style>))/igm; 315 var strRegExpStyleTag = '((<style'+regExprTagInternal+'>)'+regExprTagContent+'(</style>))'; //DETECT only "normal" style-TAG with content 316 var regExpStyleTag = new RegExp(strRegExpStyleTag,'igm'); 317 // regExpStyleTag[0]: complete match 318 // regExpStyleTag[1]: script with start and end tag (if matched) 319 // regExpStyleTag[4]: TEXT content of style-tag (if present/matched) 320 var matchStyleTag = null; 321 322 while(matchStyleTag = regExpStyleTag.exec(pureHtml)){ 323 // matchIndex = matchStyleTag[1] ? 1 : -1; 324 325 if(matchStyleTag[0]){//matchIndex != -1){ 326 // console.warn("Remote: " + self.remoteaccess); 327 if (self.remoteaccess) { 328 // self.headerContents += matchStyleTag[0].replace("<style", "<style loc=\"remote\"");//[matchIndex]; 329 self.headerContents += addMarkerAttribute('<style') + matchStyleTag[0].substring('<style'.length); 330 } else { 331 self.headerContents += matchStyleTag[0]; 332 } 333 //remove script tag, and continue search 334 // pureHtml = pureHtml.substring(0,matchStyleTag.index) + pureHtml.substring(matchStyleTag.index + matchStyleTag[0].length);// pureHtml.replace(matchStyleTag[matchIndex], ''); 335 336 removedScriptAndLinkHmtl.push({start: matchStyleTag.index, end: matchStyleTag.index + matchStyleTag[0].length}); 337 338 self.headerElements.push(new Layout.TagElement(matchStyleTag[2], matchStyleTag[4], 'STYLE')); 339 } 340 } 341 342 //only need to "process" removed script/link tags, if some were found: 343 if(removedScriptAndLinkHmtl.length > 0){ 344 345 removedScriptAndLinkHmtl.sort(function(a,b){ 346 return a.start - b.start; 347 }); 348 349 var cleanedHtml = new Array(); 350 var remPos = 0; 351 var removalElement = removedScriptAndLinkHmtl[0]; 352 353 for(var i=0, size = removedScriptAndLinkHmtl.length; i < size; ++i){ 354 removalElement = removedScriptAndLinkHmtl[i]; 355 356 var text = pureHtml.substring(remPos, removalElement.start); 357 cleanedHtml.push(text); 358 359 remPos = removalElement.end; 360 } 361 //add rest of the HTML if necessary 362 if(removalElement.end < pureHtml.length){ 363 cleanedHtml.push(pureHtml.substring(removalElement.end)); 364 } 365 //replace HTML with the removed/clean version: 366 pureHtml = cleanedHtml.join(''); 367 } 368 369 //TODO this is needed my be of interest for further processing 370 // (for processing partial HTML responses) 371 //-> should this be part of the framework? (i.e. "public" property for Layout; TODO add to stringify()?) 372 this._processedDef = pureHtml; 373 374 //FIXME reg-expr does not detect body-TAG, if body has no content (i.e. body="<body></body>") 375 var regExpBodyTag = /(<body([\r\n]|.)*?>)(([\r\n]|.)*?)(<\/body>)/igm; 376 // matchBodyTag[0]: complete match 377 // matchBodyTag[1]: body start tag 378 // matchBodyTag[2]: last CHAR within body start tag, before closing, e.g. "...lskdjf>" -> "f" 379 // matchBodyTag[3]: body text content 380 // matchBodyTag[4]: last CHAR within body text content, before closing, e.g. "...lsk</body>" -> "k" 381 // matchBodyTag[5]: body end tag 382 var matchBodyTag = regExpBodyTag.exec(pureHtml); 383 384 self.bodyContents = ''; 385 386 if(matchBodyTag && matchBodyTag[3]){ 387 self.bodyContents += matchBodyTag[3]; 388 } 389 else if(!ignoreMissingBody) { 390 //TODO throw error? 391 console.error('Layout.<constructor>: Layout template does not contain a <body> element!'); 392 } 393 394 395 //TEST: experimental -> "remember" attributes of body tag 396 //NOTE this assumes that matchBodyTag-RegExpr starts with: /(<body([\r\n]|.)*?>) ... 397 if(matchBodyTag && matchBodyTag[1] && matchBodyTag[1].length > '<body>'.length){ 398 399 // //NOTE: 1st case should really never occur. 400 // var bodyAttrEnd = matchBodyTag[1].endsWith('/>')? matchBodyTag[1].length-2 : matchBodyTag[1].length-1; 401 // var bodyAttr = '<div ' + matchBodyTag[1].substring('<body'.length, bodyAttrEnd) + '</div>'; 402 // bodyAttr = jQuery(bodyAttr); 403 404 self.bodyAttributes = Layout.getTagAttr(matchBodyTag[1]); 405 } 406 407 //Extract title-tag 408 var regExpTitleTag = /(<title([\r\n]|.)*?>)(([\r\n]|.)*?)(<\/title>)/igm; 409 // matchTitleTag[0]: complete match 410 // matchTitleTag[1]: title start tag 411 // matchTitleTag[2]: last CHAR within title start tag, before closing, e.g. "...lskdjf>" -> "f" 412 // matchTitleTag[3]: title text content 413 // matchTitleTag[4]: last CHAR within title text content, before closing, e.g. "...lsk</title>" -> "k" 414 // matchTitleTag[5]: title end tag 415 var matchTitleTag = regExpTitleTag.exec(pureHtml); 416 417 if(matchTitleTag && matchTitleTag[3]){ 418 self.title = matchTitleTag[3]; 419 } 420 421 var regExpDialogsTag = /(<dialogs([\r\n]|.)*?>)(([\r\n]|.)*?)(<\/dialogs>)/igm; 422 // matchDialogsTag[0]: complete match 423 // matchDialogsTag[1]: dialogs start tag 424 // matchDialogsTag[2]: last CHAR within dialogs start tag, before closing, e.g. "...lskdjf>" -> "f" 425 // matchDialogsTag[3]: dialogs text content 426 // matchDialogsTag[4]: last CHAR within dialogs text content, before closing, e.g. "...lsk</dialogs>" -> "k" 427 // matchDialogsTag[5]: dialogs end tag 428 var matchDialogsTag = regExpDialogsTag.exec(pureHtml); 429 430 self.dialogsContents = ''; 431 if(matchDialogsTag && matchDialogsTag[3]){ 432 self.dialogsContents += matchDialogsTag[3]; 433 } 434 435 var parseBodyResult = parser.parse(this.bodyContents, this); 436 for(var i=0, size = parseBodyResult.yields.length; i < size ; ++i){ 437 this.yields.push(new YieldDeclaration(parseBodyResult.yields[i], ViewConstants.CONTENT_AREA_BODY)); 438 } 439 440 var parseDialogResult = parser.parse(this.dialogsContents, this); 441 for(var i=0, size = parseDialogResult.yields.length; i < size ; ++i){ 442 this.yields.push(new YieldDeclaration(parseDialogResult.yields[i], ViewConstants.CONTENT_AREA_DIALOGS)); 443 } 444 445 }//END: if(this.def) 446 447 }//END: Layout() 448 449 450 /** 451 * HELPER: extracts TAG attributes into an JSON-object 452 * 453 * @memberOf Layout 454 * @private 455 * @static 456 * 457 * @param {String} str 458 * the start-TAG as String 459 * @param {Object} [target] OPTIONAL 460 * the target-object to which the extracted attributes will be attached 461 * if omitted, a new, empty object will be created 462 * 463 * @return {Object} the object with the extracted attributes as properties 464 * (if <em>target</em> was provided, then this is the <em>target</em> object) 465 * 466 * @example 467 * e.g. <body onload="on_load();" class = 'biggestFont'> 468 * --> 469 * {"onload": "on_load()", "class": "biggestFont"} 470 * 471 */ 472 Layout.getTagAttr = function(str, target){ 473 474 //RegExp for: 475 // name = "..." 476 //or 477 // name = '...' 478 // 479 //NOTE: the RegExp does not extract "single properties", as e.g. <tag required> 480 // ... instead, for extraction, they must be specified as follows: <tag required="..."> 481 //NOTE: values MUST be enclosed in double-quotes or quotes, "quote-less" attribute values cannot be extracted! 482 // ... e.g. NOT: <tag name=value>, instead: <tag name="value"> or <tag name='value'> 483 //NOTE: the RegExp also detects escaped double-quotes and quotes respectively 484 var regExpr = /\s+([^=]*?)\s*=\s*(("((\\"|[^"])*)")|('((\\'|[^'])*)'))/igm; 485 var result = target || {}; 486 var match; 487 488 while(match = regExpr.exec(str)){ 489 490 if(match[4]){ 491 result[match[1]] = match[4]; 492 } 493 else if(match[7]){ 494 result[match[1]] = match[7]; 495 } 496 } 497 return result; 498 }; 499 500 /** 501 * HELPER class: extract raw TAG Strings into a property-object 502 * 503 * @public 504 * @constructor 505 * @param {String} tag 506 * the start TAG 507 * @param {String} content 508 * the TEXT content of the TAG (may be empty) 509 * @param {String} tagType 510 * the TAG type, e.g. "SCRIPT" 511 * 512 * @returns {Layout.TagElement} 513 * 514 * prop {String} tagName: the TAG type, e.g. "SCRIPT" 515 * prop {String} textContent: the TEXT content of the TAG (may be an empty String) 516 * prop EXTRACTED ATTRIBUTES: the extracted attributes form the start-TAG 517 * 518 * func {String} attr(STRING name): returns the attribute-value for name (may be undefined) 519 * func {String} html(): returns the TEXT content of the TAG (may be an empty String) 520 * 521 * func {Boolean} isScript(): returns TRUE if tagType is SCRIPT 522 * func {Boolean} isStyle(): returns TRUE if tagType is STYLE 523 * func {Boolean} isLink(): returns TRUE if tagType is LINK 524 */ 525 Layout.TagElement = function TagElement(tag, content, tagType){ 526 527 this.tagName = tagType; 528 this.textContent = content || ''; 529 530 var tis = this; 531 tis.attr = function(name){ 532 return this[name]; 533 }; 534 tis.html = function(){ 535 return this.textContent; 536 }; 537 tis.isScript = function(){return this.tagName === 'SCRIPT';}; 538 tis.isStyle = function(){return this.tagName === 'STYLE'; }; 539 tis.isLink = function(){return this.tagName === 'LINK'; }; 540 541 //extract attributes as properties from TAG string: 542 Layout.getTagAttr(tag, tis); 543 return tis; 544 }; 545 546 /** 547 * This methods returns an associative array holding the contents of the different containers: header, body, footer and dialogs. 548 * 549 * @function 550 * @returns {Array} An associative array holding the contents of the different containers: header, body, footer and dialogs 551 * @public 552 */ 553 Layout.prototype.getYields = function(){ 554 return this.yields; 555 }; 556 557 /** 558 * This methods returns the contents of the header part of the layout. 559 * 560 * @function 561 * @returns {String} The contents of the header part of the layout 562 * @public 563 */ 564 Layout.prototype.getHeaderContents = function(){ 565 return this.headerContents; 566 }; 567 568 /** 569 * This methods returns the contents of the dialog part of the layout. 570 * 571 * @function 572 * @returns {String} The contents of the dialog part of the layout 573 * @public 574 */ 575 Layout.prototype.getDialogsContents = function(){ 576 return this.dialogsContents; 577 }; 578 579 /** 580 * This methods returns the contents of the body part of the layout. 581 * 582 * @function 583 * @returns {String} The contents of the body part of the layout 584 * @public 585 */ 586 Layout.prototype.getBodyContents = function(){ 587 return this.bodyContents; 588 }; 589 590 /** 591 * Gets the name of the layout. 592 * 593 * @function 594 * @returns {String} The name of the layout. 595 * @public 596 */ 597 Layout.prototype.getName = function(){ 598 return this.name; 599 }; 600 601 Layout.prototype.stringify = function(){ 602 603 // "plain properties" list 604 var propList = [ 605 'name', 606 'remoteaccess', 607 'def', 608 'headerElements', 609 'headerContents', 610 'title', 611 'bodyContents', 612 'dialogsContents', 613 'markerAttributeName', 614 'markerAttributeValue', 615 'markerUseSingleQuotes', 616 'bodyAttributes' 617 ]; 618 619 //complex Array-properties 620 var arrayPropList = [ 621 'yields' //element type: YieldDeclaration (stringify-able) 622 ]; 623 624 //function for iterating over the property-list and generating JSON-like entries in the string-buffer 625 var appendStringified = storageUtils.appendStringified; 626 627 var moduleNameString = '"'+this.name+'Layout"'; 628 629 //TODO use requirejs mechanism? (NOTE there may occur timing problems for loading/registering the JS file, and querying the PresentationManager for it ...) 630 // var sb = ['define('+moduleNameString+', ["storageUtils"], function(parser){ return parser.restoreObject({ classConstructor: "layout"', ',']; 631 632 var sb = ['require("storageUtils").restoreObject({ classConstructor: "layout"', ',']; 633 634 appendStringified(this, propList, sb); 635 636 sb.push( 'initPublish: function(){ require("presentationManager").addLayout(this); }'); 637 sb.push(','); 638 639 //non-primitives array-properties with stringify() function: 640 appendStringified(this, arrayPropList, sb, null, function arrayValueExtractor(name, arrayValue){ 641 642 var buf =['[']; 643 for(var i=0, size = arrayValue.length; i < size; ++i){ 644 buf.push(arrayValue[i].stringify()); 645 buf.push(','); 646 } 647 //remove last comma 648 if(arrayValue.length > 0){ 649 buf.splice( buf.length - 1, 1); 650 } 651 buf.push(']'); 652 653 return buf.join(''); 654 }); 655 656 657 //if last element is a comma, remove it 658 if(sb[sb.length - 1] === ','){ 659 sb.splice( sb.length - 1, 1); 660 } 661 662 //TODO use requirejs mechanism? (see remark above) 663 // sb.push(' }, true); });\n require(['//<- add require-call, so that this JS-file adds itself to the loaded dependencies in requirejs 664 // + moduleNameString + ']);'); 665 666 sb.push(' }, true, '+storageUtils.STORAGE_FILE_FORMAT_NUMBER+');'); 667 668 return sb.join(''); 669 }; 670 671 return Layout; 672 673 }); 674