source: Dev/branches/rest-dojo-ui/client/util/less/browser.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

  • Property svn:executable set to *
File size: 11.9 KB
Line 
1//
2// browser.js - client-side engine
3//
4
5var isFileProtocol = (location.protocol === 'file:'    ||
6                      location.protocol === 'chrome:'  ||
7                      location.protocol === 'chrome-extension:'  ||
8                      location.protocol === 'resource:');
9
10less.env = less.env || (location.hostname == '127.0.0.1' ||
11                        location.hostname == '0.0.0.0'   ||
12                        location.hostname == 'localhost' ||
13                        location.port.length > 0         ||
14                        isFileProtocol                   ? 'development'
15                                                         : 'production');
16
17// Load styles asynchronously (default: false)
18//
19// This is set to `false` by default, so that the body
20// doesn't start loading before the stylesheets are parsed.
21// Setting this to `true` can result in flickering.
22//
23less.async = false;
24
25// Interval between watch polls
26less.poll = less.poll || (isFileProtocol ? 1000 : 1500);
27
28//
29// Watch mode
30//
31less.watch   = function () { return this.watchMode = true };
32less.unwatch = function () { return this.watchMode = false };
33
34if (less.env === 'development') {
35    less.optimization = 0;
36
37    if (/!watch/.test(location.hash)) {
38        less.watch();
39    }
40    less.watchTimer = setInterval(function () {
41        if (less.watchMode) {
42            loadStyleSheets(function (root, sheet, env) {
43                if (root) {
44                    createCSS(root.toCSS(), sheet, env.lastModified);
45                }
46            });
47        }
48    }, less.poll);
49} else {
50    less.optimization = 3;
51}
52
53var cache;
54
55try {
56    cache = (typeof(window.localStorage) === 'undefined') ? null : window.localStorage;
57} catch (_) {
58    cache = null;
59}
60
61//
62// Get all <link> tags with the 'rel' attribute set to "stylesheet/less"
63//
64var links = document.getElementsByTagName('link');
65var typePattern = /^text\/(x-)?less$/;
66
67less.sheets = [];
68
69for (var i = 0; i < links.length; i++) {
70    if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) &&
71       (links[i].type.match(typePattern)))) {
72        less.sheets.push(links[i]);
73    }
74}
75
76
77less.refresh = function (reload) {
78    var startTime, endTime;
79    startTime = endTime = new(Date);
80
81    loadStyleSheets(function (root, sheet, env) {
82        if (env.local) {
83            log("loading " + sheet.href + " from cache.");
84        } else {
85            log("parsed " + sheet.href + " successfully.");
86            createCSS(root.toCSS(), sheet, env.lastModified);
87        }
88        log("css for " + sheet.href + " generated in " + (new(Date) - endTime) + 'ms');
89        (env.remaining === 0) && log("css generated in " + (new(Date) - startTime) + 'ms');
90        endTime = new(Date);
91    }, reload);
92
93    loadStyles();
94};
95less.refreshStyles = loadStyles;
96
97less.refresh(less.env === 'development');
98
99function loadStyles() {
100    var styles = document.getElementsByTagName('style');
101    for (var i = 0; i < styles.length; i++) {
102        if (styles[i].type.match(typePattern)) {
103            new(less.Parser)().parse(styles[i].innerHTML || '', function (e, tree) {
104                styles[i].type      = 'text/css';
105                styles[i].innerHTML = tree.toCSS();
106            });
107        }
108    }
109}
110
111function loadStyleSheets(callback, reload) {
112    for (var i = 0; i < less.sheets.length; i++) {
113        loadStyleSheet(less.sheets[i], callback, reload, less.sheets.length - (i + 1));
114    }
115}
116
117function loadStyleSheet(sheet, callback, reload, remaining) {
118    var url       = window.location.href.replace(/[#?].*$/, '');
119    var href      = sheet.href.replace(/\?.*$/, '');
120    var css       = cache && cache.getItem(href);
121    var timestamp = cache && cache.getItem(href + ':timestamp');
122    var styles    = { css: css, timestamp: timestamp };
123
124    // Stylesheets in IE don't always return the full path
125    if (! /^(https?|file):/.test(href)) {
126        if (href.charAt(0) == "/") {
127            href = window.location.protocol + "//" + window.location.host + href;
128        } else {
129            href = url.slice(0, url.lastIndexOf('/') + 1) + href;
130        }
131    }
132
133    xhr(sheet.href, sheet.type, function (data, lastModified) {
134        if (!reload && styles && lastModified &&
135           (new(Date)(lastModified).valueOf() ===
136            new(Date)(styles.timestamp).valueOf())) {
137            // Use local copy
138            createCSS(styles.css, sheet);
139            callback(null, sheet, { local: true, remaining: remaining });
140        } else {
141            // Use remote copy (re-parse)
142            try {
143                new(less.Parser)({
144                    optimization: less.optimization,
145                    paths: [href.replace(/[\w\.-]+$/, '')],
146                    mime: sheet.type
147                }).parse(data, function (e, root) {
148                    if (e) { return error(e, href) }
149                    try {
150                        callback(root, sheet, { local: false, lastModified: lastModified, remaining: remaining });
151                        removeNode(document.getElementById('less-error-message:' + extractId(href)));
152                    } catch (e) {
153                        error(e, href);
154                    }
155                });
156            } catch (e) {
157                error(e, href);
158            }
159        }
160    }, function (status, url) {
161        throw new(Error)("Couldn't load " + url + " (" + status + ")");
162    });
163}
164
165function extractId(href) {
166    return href.replace(/^[a-z]+:\/\/?[^\/]+/, '' )  // Remove protocol & domain
167               .replace(/^\//,                 '' )  // Remove root /
168               .replace(/\?.*$/,               '' )  // Remove query
169               .replace(/\.[^\.\/]+$/,         '' )  // Remove file extension
170               .replace(/[^\.\w-]+/g,          '-')  // Replace illegal characters
171               .replace(/\./g,                 ':'); // Replace dots with colons(for valid id)
172}
173
174function createCSS(styles, sheet, lastModified) {
175    var css;
176
177    // Strip the query-string
178    var href = sheet.href ? sheet.href.replace(/\?.*$/, '') : '';
179
180    // If there is no title set, use the filename, minus the extension
181    var id = 'less:' + (sheet.title || extractId(href));
182
183    // If the stylesheet doesn't exist, create a new node
184    if ((css = document.getElementById(id)) === null) {
185        css = document.createElement('style');
186        css.type = 'text/css';
187        css.media = sheet.media || 'screen';
188        css.id = id;
189        document.getElementsByTagName('head')[0].appendChild(css);
190    }
191
192    if (css.styleSheet) { // IE
193        try {
194            css.styleSheet.cssText = styles;
195        } catch (e) {
196            throw new(Error)("Couldn't reassign styleSheet.cssText.");
197        }
198    } else {
199        (function (node) {
200            if (css.childNodes.length > 0) {
201                if (css.firstChild.nodeValue !== node.nodeValue) {
202                    css.replaceChild(node, css.firstChild);
203                }
204            } else {
205                css.appendChild(node);
206            }
207        })(document.createTextNode(styles));
208    }
209
210    // Don't update the local store if the file wasn't modified
211    if (lastModified && cache) {
212        log('saving ' + href + ' to cache.');
213        cache.setItem(href, styles);
214        cache.setItem(href + ':timestamp', lastModified);
215    }
216}
217
218function xhr(url, type, callback, errback) {
219    var xhr = getXMLHttpRequest();
220    var async = isFileProtocol ? false : less.async;
221
222    if (typeof(xhr.overrideMimeType) === 'function') {
223        xhr.overrideMimeType('text/css');
224    }
225    xhr.open('GET', url, async);
226    xhr.setRequestHeader('Accept', type || 'text/x-less, text/css; q=0.9, */*; q=0.5');
227    xhr.send(null);
228
229    if (isFileProtocol) {
230        if (xhr.status === 0) {
231            callback(xhr.responseText);
232        } else {
233            errback(xhr.status, url);
234        }
235    } else if (async) {
236        xhr.onreadystatechange = function () {
237            if (xhr.readyState == 4) {
238                handleResponse(xhr, callback, errback);
239            }
240        };
241    } else {
242        handleResponse(xhr, callback, errback);
243    }
244
245    function handleResponse(xhr, callback, errback) {
246        if (xhr.status >= 200 && xhr.status < 300) {
247            callback(xhr.responseText,
248                     xhr.getResponseHeader("Last-Modified"));
249        } else if (typeof(errback) === 'function') {
250            errback(xhr.status, url);
251        }
252    }
253}
254
255function getXMLHttpRequest() {
256    if (window.XMLHttpRequest) {
257        return new(XMLHttpRequest);
258    } else {
259        try {
260            return new(ActiveXObject)("MSXML2.XMLHTTP.3.0");
261        } catch (e) {
262            log("browser doesn't support AJAX.");
263            return null;
264        }
265    }
266}
267
268function removeNode(node) {
269    return node && node.parentNode.removeChild(node);
270}
271
272function log(str) {
273    if (less.env == 'development' && typeof(console) !== "undefined") { console.log('less: ' + str) }
274}
275
276function error(e, href) {
277    var id = 'less-error-message:' + extractId(href);
278
279    var template = ['<ul>',
280                        '<li><label>[-1]</label><pre class="ctx">{0}</pre></li>',
281                        '<li><label>[0]</label><pre>{current}</pre></li>',
282                        '<li><label>[1]</label><pre class="ctx">{2}</pre></li>',
283                    '</ul>'].join('\n');
284
285    var elem = document.createElement('div'), timer, content;
286
287    elem.id        = id;
288    elem.className = "less-error-message";
289
290    content = '<h3>'  + (e.message || 'There is an error in your .less file') +
291              '</h3>' + '<p><a href="' + href   + '">' + href + "</a> ";
292
293    if (e.extract) {
294        content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':</p>' +
295            template.replace(/\[(-?\d)\]/g, function (_, i) {
296                return (parseInt(e.line) + parseInt(i)) || '';
297            }).replace(/\{(\d)\}/g, function (_, i) {
298                return e.extract[parseInt(i)] || '';
299            }).replace(/\{current\}/, e.extract[1].slice(0, e.column) + '<span class="error">' +
300                                      e.extract[1].slice(e.column)    + '</span>');
301    }
302    elem.innerHTML = content;
303
304    // CSS for error messages
305    createCSS([
306        '.less-error-message ul, .less-error-message li {',
307            'list-style-type: none;',
308            'margin-right: 15px;',
309            'padding: 4px 0;',
310            'margin: 0;',
311        '}',
312        '.less-error-message label {',
313            'font-size: 12px;',
314            'margin-right: 15px;',
315            'padding: 4px 0;',
316            'color: #cc7777;',
317        '}',
318        '.less-error-message pre {',
319            'color: #ee4444;',
320            'padding: 4px 0;',
321            'margin: 0;',
322            'display: inline-block;',
323        '}',
324        '.less-error-message pre.ctx {',
325            'color: #dd4444;',
326        '}',
327        '.less-error-message h3 {',
328            'font-size: 20px;',
329            'font-weight: bold;',
330            'padding: 15px 0 5px 0;',
331            'margin: 0;',
332        '}',
333        '.less-error-message a {',
334            'color: #10a',
335        '}',
336        '.less-error-message .error {',
337            'color: red;',
338            'font-weight: bold;',
339            'padding-bottom: 2px;',
340            'border-bottom: 1px dashed red;',
341        '}'
342    ].join('\n'), { title: 'error-message' });
343
344    elem.style.cssText = [
345        "font-family: Arial, sans-serif",
346        "border: 1px solid #e00",
347        "background-color: #eee",
348        "border-radius: 5px",
349        "-webkit-border-radius: 5px",
350        "-moz-border-radius: 5px",
351        "color: #e00",
352        "padding: 15px",
353        "margin-bottom: 15px"
354    ].join(';');
355
356    if (less.env == 'development') {
357        timer = setInterval(function () {
358            if (document.body) {
359                if (document.getElementById(id)) {
360                    document.body.replaceChild(elem, document.getElementById(id));
361                } else {
362                    document.body.insertBefore(elem, document.body.firstChild);
363                }
364                clearInterval(timer);
365            }
366        }, 10);
367    }
368}
369
Note: See TracBrowser for help on using the repository browser.