1 /*! 2 * CSS Template Layout 3 * Copyright (c) 2011 Pablo Escalada 4 * MIT Licensed 5 */ 6 (function (global) { 7 var log = wef.logger("templateLayout"), 8 templateLayout, 9 buffer = {}, 10 tom, 11 parser; 12 13 /** 14 * Create the prototype main class 15 * 16 * @param {string|strings[]}[templateSource] template source. 17 * Supports 0..* strings containing valid CSS text or URL. 18 * </p> 19 * Empty constructor searches parent HTML file for STYLE tags and uses its 20 * CSS content as template source. 21 * </p> 22 * String params are analyzed and loaded in this way: 23 * <ul> 24 * <li>"htttp[s]://..." entries are loaded as files and content extracted</li> 25 * <li>"file://..." entries are loaded as files and content extracted</li> 26 * <li>Unmatched entries are loaded as CSS text</li> 27 * </ul> 28 * Multiple strings are first analyzed and then concatenated 29 * 30 * @class TemplateLayout is a CSS Template Layout prototype that implements 31 * some basic features defined in W3C working draft "Template Layout Module". 32 * Features: 33 * <ul> 34 * <li>basic template definition: letters, . (dot) and @</li> 35 * <li>column width in pixels and %</li> 36 * <li>row height imn pixels and %</li> 37 * </ul> 38 */ 39 templateLayout = function (templateSource) { 40 log.info("create templateLayout..."); 41 return new templateLayout.prototype.init(arguments); 42 }; 43 44 45 templateLayout.prototype = { 46 constructor:templateLayout, 47 /** 48 * Version number 49 */ 50 version:"0.0.1", 51 /** 52 * Template sources store 53 */ 54 templateSources:[], 55 /** 56 * Constant object that stores CSS properties names used as triggers 57 * </p> 58 * Currently used: 59 * <ul> 60 * <li>constants.DISPLAY = "display"</li> 61 * <li>constants.POSITION = "position"</li> 62 * </ul> 63 */ 64 constants:{ 65 DISPLAY:"display", 66 POSITION:"position" 67 }, 68 /** 69 * Template compiler 70 */ 71 compiler:null, 72 /** 73 * Template output generator 74 */ 75 generator:null, 76 77 /** 78 * @ignore 79 * see templateLayout constructor 80 */ 81 init:function (templateSources) { 82 var args, firstSource, internalSources = []; 83 log.debug("sources:", templateSources); 84 85 log.debug("init subsystems..."); 86 parser = wef.cssParser(); 87 log.debug("subsystems... [OK]"); 88 89 args = Array.prototype.slice.call(templateSources); 90 firstSource = args[0]; 91 92 //templateLayout() 93 if (!firstSource) { 94 log.info("no external template loaded!!!"); 95 Array.prototype.forEach.call(document.styleSheets, function (sheet) { 96 if (sheet.href !== null) { 97 //load external CSS 98 log.info("load external CSS", sheet.href); 99 internalSources.push(sheet.href); 100 } 101 else { 102 var text = sheet.ownerNode.innerHTML; 103 log.info("load style tag", text); 104 internalSources.push(text); 105 } 106 }); 107 108 this.templateSources = internalSources.map(getContent); 109 log.info("templateLayout... [OK]"); 110 return this; 111 } 112 113 //templateLayout("aString") and templateLayout("aString", "anotherString", ...) 114 if (args.length >= 1 && args.every(function (element) { 115 return typeof element == "string"; 116 })) { 117 this.templateSources = args.map(getContent); 118 log.info("templateLayout... [OK]"); 119 return this; 120 } 121 122 log.error("Invalid argument"); 123 throw new Error("Invalid argument"); 124 }, 125 /** 126 * Reads, compiles and generates the template 127 * 128 * @param {string}[options=all] Only for testing purposes. 129 * Supported values [none|parse|compile] 130 * </p> 131 * Stops transform process at different points: 132 * <ul> 133 * <li>none: transform does nothing</li> 134 * <li>parse: transform only parses template source</li> 135 * <li>compile: transform parses source and compiles the template</li> 136 * </ul> 137 */ 138 transform:function () { 139 log.debug("transform..."); 140 var options = parseTransformOptions(arguments); 141 142 if (options.parse) { 143 log.info("Step 1: parse"); 144 log.group(); 145 parser.whenStart(this.parserStarts); 146 parser.whenProperty(this.propertyFound); 147 parser.whenStop(this.parserDone); 148 149 parser.parse(this.templateSources.reduce(function (previous, source) { 150 return previous + source.sourceText; 151 }, "")); 152 153 log.groupEnd(); 154 log.info("Step 1: parse... [OK]"); 155 } 156 if (options.compile) { 157 log.info("Step 2: compile"); 158 log.group(); 159 tom = this.compiler().compile(buffer); 160 // log.info("TOM: ", tom); 161 log.groupEnd(); 162 log.info("Step 2: compile... [OK]"); 163 } 164 if (options.generate) { 165 log.info("Step 3: generate"); 166 log.group(); 167 this.generator(tom).patchDOM(); 168 log.groupEnd(); 169 log.info("Step 3: generate... [OK]"); 170 } 171 172 log.info("transform... [OK]"); 173 return this; 174 }, 175 /** 176 * Returns the info from the parsing step 177 * @returns {ParserBufferEntry[]}buffer 178 */ 179 getBuffer:function () { 180 return buffer; 181 }, 182 /** 183 * Returns TOM (Template Object Model) 184 * @returns {rootTemplate}tom 185 */ 186 getTOM:function () { 187 return tom; 188 }, 189 /** 190 * "Parser start" callback. Prints start time and resets buffer 191 * 192 * @param o Information sent by parser 193 * @param o.time Start time in milliseconds 194 */ 195 parserStarts:function (o) { 196 log.info("start parsing at", new Date(o.time).toLocaleTimeString()); 197 buffer = {}; 198 }, 199 /** 200 * "Property has been found" callback. Stores in buffer valid properties 201 * 202 * @param {CSSParserProperty}property found property information 203 */ 204 propertyFound:function (property) { 205 log.info("templateLayout listens: property found"); 206 if (templateLayout.fn.isSupportedProperty(property)) { 207 store(property); 208 } 209 }, 210 /** 211 * "Parser stop" callback. Prints stop time 212 * @param {StopCallbackData}o Information sent by parser 213 */ 214 parserDone:function (o) { 215 log.info("parsing done at", new Date(o.time).toLocaleTimeString()); 216 }, 217 /** 218 * Checks if given property is a valid one. 219 * If property name exists in constants then is a valid one 220 * 221 * @param {CSSParserProperty}property the property 222 * @returns {boolean}true if exists constants[???] == property.declaration.property 223 */ 224 isSupportedProperty:function (property) { 225 var iterator; 226 for (iterator in templateLayout.fn.constants) { 227 if (templateLayout.fn.constants.hasOwnProperty(iterator)) { 228 if (templateLayout.fn.constants[iterator] == property.declaration.property) { 229 log.info("supported property found: ", property.declaration.property); 230 return true; 231 } 232 } 233 } 234 return false; 235 } 236 }; 237 238 templateLayout.fn = templateLayout.prototype; 239 240 templateLayout.fn.init.prototype = templateLayout.fn; 241 242 function getSourceType(templateSource) { 243 var rxHttp = /^http[s]?:\/\/.*\.css$/i, rxFile = /^file:\/\/.*\.css$/i, rxPath = /^(?!\s*.*(http[s]?|file))(\.){0,2}(\/.*)*.*\.css$/i; 244 if (rxHttp.exec(templateSource)) { 245 return "http"; 246 } 247 if (rxPath.exec(templateSource) || rxFile.exec(templateSource)) { 248 return "file"; 249 } 250 return "css"; 251 } 252 253 function getContent(templateSource) { 254 var type = getSourceType(templateSource); 255 if (type == "http" || type == "file") { 256 return { 257 type:type, 258 sourceText:readFile(templateSource) 259 }; 260 } 261 if (type == "css") { 262 return { 263 type:type, 264 sourceText:templateSource 265 }; 266 } else { 267 throw new Error("unknown sourceType"); 268 } 269 } 270 271 function parseTransformOptions(args) { 272 var options = {parse:true, compile:true, generate:true}; 273 if (args.length === 0) { 274 return options; 275 } 276 if (args[0].action == "none") { 277 options.parse = options.compile = options.generate = false; 278 } 279 if (args[0].action == "parse") { 280 options.compile = options.generate = false; 281 } 282 if (args[0].action == "compile") { 283 options.generate = false; 284 } 285 return options; 286 } 287 288 function readFile(url) { 289 var templateText=""; 290 291 try { 292 log.info("reading file..."); 293 wef.net.ajax(url, { 294 asynchronous:false, 295 success:function (request) { 296 templateText = request.responseText; 297 } 298 }); 299 log.info("template loaded... [OK]"); 300 return templateText; 301 } catch (e) { 302 log.error("Operation not supported", e); 303 throw new Error("Operation not supported", e); 304 } 305 } 306 307 function store(rule) { 308 if (!buffer[rule.selectorText]) { 309 buffer[rule.selectorText] = 310 /** 311 * @namespace Data format of parser buffer entry 312 * @name ParserBufferEntry 313 */ 314 /** 315 * @lends ParserBufferEntry# 316 */ 317 { 318 /** 319 * property selector text 320 * @type string 321 */ 322 selectorText:rule.selectorText, 323 /** 324 * array of declarations (property_name:property_value) 325 * @type string[] 326 */ 327 declaration:{} 328 }; 329 } 330 buffer[rule.selectorText].declaration[rule.declaration.property] = rule.declaration.valueText; 331 log.info("property stored: ", rule.declaration.property); 332 } 333 334 global.templateLayout = templateLayout; 335 })(window);