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);