UNPKG

mojito

Version:

Mojito provides an architecture, components and tools for developers to build complex web applications faster.

314 lines (256 loc) 10.1 kB
/* * Copyright (c) 2011-2013, Yahoo! Inc. All rights reserved. * Copyrights licensed under the New BSD License. * See the accompanying LICENSE file for terms. */ /*jslint anon:true, sloppy:true, nomen:true*/ /*global YUI,Image*/ /** * @module ActionContextAddon */ YUI.add('mojito-assets-addon', function(Y, NAME) { var isInline = function(id) { return YUI._mojito && YUI._mojito._cache && YUI._mojito._cache.compiled && YUI._mojito._cache.compiled.css && YUI._mojito._cache.compiled.css.inline && YUI._mojito._cache.compiled.css.inline[id]; }; /** * <strong>Access point:</strong> <em>ac.assets.*</em> * Provides methods for adding HTML assets to a page. * @class Assets.common */ function AssetsAcAddon(command, adapter, ac) { this.assetsRoot = command.instance.assetsRoot; this.assets = {}; this.added = {}; // content: boolean this.mojitType = command.instance.type; this.context = command.context; // Add "assets" if they are found in the config. if (command.instance && command.instance.config && command.instance.config.assets) { this.addAssets(command.instance.config.assets); } } AssetsAcAddon.prototype = { namespace: 'assets', /** * Method for adding a CSS file to the page. * @method addCss * @param {string} link A URL (./local.css converts to * /static/mojit_type/assets/local.css). * @param {string} location Either "top" or "bottom". */ addCss: function(link, location) { this.addAsset('css', (location || 'top'), link); }, /** * Method for adding a JS file to the page. * @method addJs * @param {string} link A URL (./local.css converts to * /static/mojit_type/assets/local.css). * @param {string} location Either "top" or "bottom". */ addJs: function(link, location) { this.addAsset('js', (location || 'bottom'), link); }, /** * Method for adding a Blob of data to the page. This can be used * for adding custom "script" or "style" blocks. * @method addBlob * @param {string} content A string of data. * @param {string} location Either "top" or "bottom". */ addBlob: function(content, location) { this.addAsset('blob', (location || 'bottom'), content); }, /** * @method addAsset * @param {string} type css|js|blob * @param {string} location Either "top" or "bottom". * @param {string} content A string of data. */ addAsset: function(type, location, content) { if (!content) { return; } if (content.indexOf('./') === 0) { content = this.getUrl(content.slice(2)); } if (this.added[content]) { return; } this.added[content] = true; // If we have not added the files for this mojit, we should add // them inline now. if (('css' === type) && isInline(content)) { // We can't do this on the server, because YUI._mojito._cache is // a server-lifetime global, so it "tunnels" between requests. if ('client' === this.context.runtime) { if (!YUI._mojito._cache.compiled.css.inline.added) { YUI._mojito._cache.compiled.css.inline.added = {}; } if (YUI._mojito._cache.compiled.css.inline.added[content]) { // Looks like we've already added this to the DOM return; } YUI._mojito._cache.compiled.css.inline.added[ content ] = true; } // Y.log('Inlining css for mojitType for the first time' + // content, 'debug' , NAME); type = 'blob'; content = '<style type="text/css">\n' + YUI._mojito._cache.compiled.css.inline[content] + '</style>\n'; // Beware! "content" changes here. This is the actual CSS and // not the URI of the CSS resource!!! if (this.added[content]) { return; } } if (!this.assets[location]) { this.assets[location] = {}; } if (!this.assets[location][type]) { this.assets[location][type] = []; } this.assets[location][type].push(content); }, /** * @method addAssets * @param {object} assets by location (top|bottom) & type (css|js|blob) */ addAssets: function(assets) { var location, type, content; for (location in assets) { if (assets.hasOwnProperty(location)) { for (type in assets[location]) { if (assets[location].hasOwnProperty(type)) { for (content in assets[location][type]) { if (assets[location][type]. hasOwnProperty(content)) { this.addAsset(type, location, assets[location][type][content]); } } } } } } }, /** * @method preLoadImage * @param {string} url * @deprecated */ preLoadImage: function(url) { var img; if (typeof document !== 'undefined') { img = new Image(); img.src = url; } }, /** * @method preLoadImages * @param {array.<string>} url * @deprecated */ preLoadImages: function(urls) { var i; for (i in urls) { if (urls.hasOwnProperty(i)) { this.preLoadImage(urls[i]); } } }, /** * @method getUrl * @param {string} path of asset, relative * @return {string} */ getUrl: function(path) { return this.assetsRoot + '/' + path; }, /** * @method mixAssets * @param {object} to * @param {object} from * @return {object} */ mixAssets: function(to, from) { return Y.mojito.util.metaMerge(to, from); }, /** * @method getAssets * @return {object} assets by location (top|bottom) & type (css|js|blob) */ getAssets: function() { return this.assets; }, /** * @method renderLocations * @return {object} hash table with location and the HTML fragments * for each location with all the assets rendered. */ renderLocations: function () { var fragments = {}; // Attach assets found in the "meta" to the page Y.Object.each(this.assets, function (types, location) { if (!fragments[location]) { fragments[location] = ''; } Y.Object.each(types, function (list, type) { var i, data = '', url; if ('js' === type) { for (i = 0; i < list.length; i += 1) { // TODO: Fuly escape any HTML chars in the URL to avoid trivial // attribute injection attacks. See owasp-esapi reference impl. url = encodeURI(list[i]); data += '<script type="text/javascript" src="' + url + '"></script>\n'; } } else if ('css' === type) { for (i = 0; i < list.length; i += 1) { // TODO: Escape any HTML chars in the URL to avoid trivial // attribute injection attacks. See owasp-esapi reference impl. url = encodeURI(list[i]); data += '<link rel="stylesheet" type="text/css" href="' + url + '"/>\n'; } } else if ('blob' === type) { for (i = 0; i < list.length; i += 1) { // NOTE: Giant security hole...but used by everyone who uses // Mojito so there's not much we can do except tell authors of // Mojito applications to _never_ use user input to generate // blob content or populate config data. Whatever goes in here // can't be easily encoded without the likelihood of corruption. data += list[i] + '\n'; } } else { Y.log('Unknown asset type "' + type + '". Skipped.', 'warn', NAME); } fragments[location] += data; }); }); return fragments; }, /** * @mergeMetaInto * @private */ mergeMetaInto: function(meta) { this.mixAssets(meta.assets, this.assets); } }; Y.namespace('mojito.addons.ac').assets = AssetsAcAddon; }, '0.1.0', {requires: [ 'mojito', 'mojito-util' ]});