|
| 1 | +import core from 'core-js'; |
| 2 | +import * as TheLogManager from 'aurelia-logging'; |
| 3 | +import {Metadata} from 'aurelia-metadata'; |
| 4 | +import {Container} from 'aurelia-dependency-injection'; |
| 5 | +import {Loader} from 'aurelia-loader'; |
| 6 | +import {join,relativeToFile} from 'aurelia-path'; |
| 7 | +import {BindingLanguage,ViewEngine,ViewSlot,ResourceRegistry,CompositionEngine,Animator} from 'aurelia-templating'; |
| 8 | + |
| 9 | +var logger = TheLogManager.getLogger('aurelia'); |
| 10 | + |
| 11 | +function loadPlugin(aurelia, loader, info){ |
| 12 | + logger.debug(`Loading plugin ${info.moduleId}.`); |
| 13 | + aurelia.currentPluginId = info.moduleId; |
| 14 | + |
| 15 | + return loader.loadModule(info.moduleId).then(m => { |
| 16 | + if('configure' in m){ |
| 17 | + return Promise.resolve(m.configure(aurelia, info.config || {})).then(() => { |
| 18 | + aurelia.currentPluginId = null; |
| 19 | + logger.debug(`Configured plugin ${info.moduleId}.`); |
| 20 | + }); |
| 21 | + }else{ |
| 22 | + aurelia.currentPluginId = null; |
| 23 | + logger.debug(`Loaded plugin ${info.moduleId}.`); |
| 24 | + } |
| 25 | + }); |
| 26 | +} |
| 27 | + |
| 28 | +/** |
| 29 | + * Manages loading and configuring plugins. |
| 30 | + * |
| 31 | + * @class Plugins |
| 32 | + * @constructor |
| 33 | + * @param {Aurelia} aurelia An instance of Aurelia. |
| 34 | + */ |
| 35 | +export class Plugins { |
| 36 | + constructor(aurelia){ |
| 37 | + this.aurelia = aurelia; |
| 38 | + this.info = []; |
| 39 | + this.processed = false; |
| 40 | + } |
| 41 | + |
| 42 | + /** |
| 43 | + * Configures a plugin before Aurelia starts. |
| 44 | + * |
| 45 | + * @method plugin |
| 46 | + * @param {moduleId} moduleId The ID of the module to configure. |
| 47 | + * @param {config} config The configuration for the specified module. |
| 48 | + * @return {Plugins} Returns the current Plugins instance. |
| 49 | + */ |
| 50 | + plugin(moduleId, config){ |
| 51 | + var plugin = {moduleId:moduleId, config:config || {}}; |
| 52 | + |
| 53 | + if(this.processed){ |
| 54 | + loadPlugin(this.aurelia, this.aurelia.loader, plugin); |
| 55 | + }else{ |
| 56 | + this.info.push(plugin); |
| 57 | + } |
| 58 | + |
| 59 | + return this; |
| 60 | + } |
| 61 | + |
| 62 | + _process(){ |
| 63 | + var aurelia = this.aurelia, |
| 64 | + loader = aurelia.loader, |
| 65 | + info = this.info, |
| 66 | + current; |
| 67 | + |
| 68 | + if(this.processed){ |
| 69 | + return; |
| 70 | + } |
| 71 | + |
| 72 | + var next = () => { |
| 73 | + if(current = info.shift()){ |
| 74 | + return loadPlugin(aurelia, loader, current).then(next); |
| 75 | + } |
| 76 | + |
| 77 | + this.processed = true; |
| 78 | + return Promise.resolve(); |
| 79 | + }; |
| 80 | + |
| 81 | + return next(); |
| 82 | + } |
| 83 | +} |
| 84 | + |
| 85 | +var logger = TheLogManager.getLogger('aurelia'), |
| 86 | + slice = Array.prototype.slice; |
| 87 | + |
| 88 | +if (!window.CustomEvent || typeof window.CustomEvent !== 'function') { |
| 89 | + var CustomEvent = function(event, params) { |
| 90 | + var params = params || { |
| 91 | + bubbles: false, |
| 92 | + cancelable: false, |
| 93 | + detail: undefined |
| 94 | + }; |
| 95 | + |
| 96 | + var evt = document.createEvent("CustomEvent"); |
| 97 | + evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); |
| 98 | + return evt; |
| 99 | + }; |
| 100 | + |
| 101 | + CustomEvent.prototype = window.Event.prototype; |
| 102 | + window.CustomEvent = CustomEvent; |
| 103 | +} |
| 104 | + |
| 105 | +function preventActionlessFormSubmit() { |
| 106 | + document.body.addEventListener('submit', evt => { |
| 107 | + const target = evt.target; |
| 108 | + const action = target.action; |
| 109 | + |
| 110 | + if (target.tagName.toLowerCase() === 'form' && !action){ |
| 111 | + evt.preventDefault(); |
| 112 | + } |
| 113 | + }); |
| 114 | +} |
| 115 | + |
| 116 | +function loadResources(container, resourcesToLoad, appResources){ |
| 117 | + var viewEngine = container.get(ViewEngine), |
| 118 | + importIds = Object.keys(resourcesToLoad), |
| 119 | + names = new Array(importIds.length), |
| 120 | + i, ii; |
| 121 | + |
| 122 | + for(i = 0, ii = importIds.length; i < ii; ++i){ |
| 123 | + names[i] = resourcesToLoad[importIds[i]]; |
| 124 | + } |
| 125 | + |
| 126 | + return viewEngine.importViewResources(importIds, names, appResources); |
| 127 | +} |
| 128 | + |
| 129 | +/** |
| 130 | + * The framework core that provides the main Aurelia object. |
| 131 | + * |
| 132 | + * @class Aurelia |
| 133 | + * @constructor |
| 134 | + * @param {Loader} loader The loader for this Aurelia instance to use. If a loader is not specified, Aurelia will use a defaultLoader. |
| 135 | + * @param {Container} container The dependency injection container for this Aurelia instance to use. If a container is not specified, Aurelia will create an empty container. |
| 136 | + * @param {ResourceRegistry} resources The resource registry for this Aurelia instance to use. If a resource registry is not specified, Aurelia will create an empty registry. |
| 137 | + */ |
| 138 | +export class Aurelia { |
| 139 | + constructor(loader, container, resources){ |
| 140 | + this.loader = loader || new window.AureliaLoader(); |
| 141 | + this.container = container || new Container(); |
| 142 | + this.resources = resources || new ResourceRegistry(); |
| 143 | + this.use = new Plugins(this); |
| 144 | + this.resourcesToLoad = {}; |
| 145 | + |
| 146 | + this.withInstance(Aurelia, this); |
| 147 | + this.withInstance(Loader, this.loader); |
| 148 | + this.withInstance(ResourceRegistry, this.resources); |
| 149 | + |
| 150 | + this.container.makeGlobal(); |
| 151 | + } |
| 152 | + |
| 153 | + /** |
| 154 | + * Adds an existing object to the framework's dependency injection container. |
| 155 | + * |
| 156 | + * @method withInstance |
| 157 | + * @param {Class} type The object type of the dependency that the framework will inject. |
| 158 | + * @param {Object} instance The existing instance of the dependency that the framework will inject. |
| 159 | + * @return {Aurelia} Returns the current Aurelia instance. |
| 160 | + */ |
| 161 | + withInstance(type, instance){ |
| 162 | + this.container.registerInstance(type, instance); |
| 163 | + return this; |
| 164 | + } |
| 165 | + |
| 166 | + /** |
| 167 | + * Adds a singleton to the framework's dependency injection container. |
| 168 | + * |
| 169 | + * @method withSingleton |
| 170 | + * @param {Class} type The object type of the dependency that the framework will inject. |
| 171 | + * @param {Object} implementation The constructor function of the dependency that the framework will inject. |
| 172 | + * @return {Aurelia} Returns the current Aurelia instance. |
| 173 | + */ |
| 174 | + withSingleton(type, implementation){ |
| 175 | + this.container.registerSingleton(type, implementation); |
| 176 | + return this; |
| 177 | + } |
| 178 | + |
| 179 | + /** |
| 180 | + * Adds globally available view resources to be imported into the Aurelia framework. |
| 181 | + * |
| 182 | + * @method globalizeResources |
| 183 | + * @param {Object|Array} resources The relative module id to the resource. (Relative to the plugin's installer.) |
| 184 | + * @return {Aurelia} Returns the current Aurelia instance. |
| 185 | + */ |
| 186 | + globalizeResources(resources){ |
| 187 | + var toAdd = Array.isArray(resources) ? resources : arguments, |
| 188 | + i, ii, resource, pluginPath = this.currentPluginId || '', path, |
| 189 | + internalPlugin = pluginPath.startsWith('./'); |
| 190 | + |
| 191 | + for(i = 0, ii = toAdd.length; i < ii; ++i){ |
| 192 | + resource = toAdd[i]; |
| 193 | + if(typeof resource != 'string'){ |
| 194 | + throw new Error(`Invalid resource path [${resource}]. Resources must be specified as relative module IDs.`); |
| 195 | + } |
| 196 | + |
| 197 | + path = internalPlugin |
| 198 | + ? relativeToFile(resource, pluginPath) |
| 199 | + : join(pluginPath, resource); |
| 200 | + |
| 201 | + this.resourcesToLoad[path] = this.resourcesToLoad[path]; |
| 202 | + } |
| 203 | + |
| 204 | + return this; |
| 205 | + } |
| 206 | + |
| 207 | + /** |
| 208 | + * Renames a global resource that was imported. |
| 209 | + * |
| 210 | + * @method renameGlobalResource |
| 211 | + * @param {String} resourcePath The path to the resource. |
| 212 | + * @param {String} newName The new name. |
| 213 | + * @return {Aurelia} Returns the current Aurelia instance. |
| 214 | + */ |
| 215 | + renameGlobalResource(resourcePath, newName){ |
| 216 | + this.resourcesToLoad[resourcePath] = newName; |
| 217 | + return this; |
| 218 | + } |
| 219 | + |
| 220 | + /** |
| 221 | + * Loads plugins, then resources, and then starts the Aurelia instance. |
| 222 | + * |
| 223 | + * @method start |
| 224 | + * @return {Aurelia} Returns the started Aurelia instance. |
| 225 | + */ |
| 226 | + start(){ |
| 227 | + if(this.started){ |
| 228 | + return Promise.resolve(this); |
| 229 | + } |
| 230 | + |
| 231 | + this.started = true; |
| 232 | + logger.info('Aurelia Starting'); |
| 233 | + |
| 234 | + preventActionlessFormSubmit(); |
| 235 | + |
| 236 | + return this.use._process().then(() => { |
| 237 | + if(!this.container.hasHandler(BindingLanguage)){ |
| 238 | + var message = 'You must configure Aurelia with a BindingLanguage implementation.'; |
| 239 | + logger.error(message); |
| 240 | + throw new Error(message); |
| 241 | + } |
| 242 | + |
| 243 | + if(!this.container.hasHandler(Animator)){ |
| 244 | + Animator.configureDefault(this.container); |
| 245 | + } |
| 246 | + |
| 247 | + return loadResources(this.container, this.resourcesToLoad, this.resources).then(() => { |
| 248 | + logger.info('Aurelia Started'); |
| 249 | + var evt = new window.CustomEvent('aurelia-started', { bubbles: true, cancelable: true }); |
| 250 | + document.dispatchEvent(evt); |
| 251 | + return this; |
| 252 | + }); |
| 253 | + }); |
| 254 | + } |
| 255 | + |
| 256 | + /** |
| 257 | + * Instantiates the root view-model and view and add them to the DOM. |
| 258 | + * |
| 259 | + * @method withSingleton |
| 260 | + * @param {Object} root The root view-model to load upon bootstrap. |
| 261 | + * @param {string|Object} applicationHost The DOM object that Aurelia will attach to. |
| 262 | + * @return {Aurelia} Returns the current Aurelia instance. |
| 263 | + */ |
| 264 | + setRoot(root='app', applicationHost=null){ |
| 265 | + var compositionEngine, instruction = {}; |
| 266 | + |
| 267 | + applicationHost = applicationHost || this.host; |
| 268 | + |
| 269 | + if (!applicationHost || typeof applicationHost == 'string') { |
| 270 | + this.host = document.getElementById(applicationHost || 'applicationHost') || document.body; |
| 271 | + } else { |
| 272 | + this.host = applicationHost; |
| 273 | + } |
| 274 | + |
| 275 | + this.host.aurelia = this; |
| 276 | + |
| 277 | + compositionEngine = this.container.get(CompositionEngine); |
| 278 | + instruction.viewModel = root; |
| 279 | + instruction.container = instruction.childContainer = this.container; |
| 280 | + instruction.viewSlot = new ViewSlot(this.host, true); |
| 281 | + instruction.viewSlot.transformChildNodesIntoView(); |
| 282 | + instruction.host = this.host; |
| 283 | + |
| 284 | + return compositionEngine.compose(instruction).then(root => { |
| 285 | + this.root = root; |
| 286 | + instruction.viewSlot.attached(); |
| 287 | + var evt = new window.CustomEvent('aurelia-composed', { bubbles: true, cancelable: true }); |
| 288 | + setTimeout(() => document.dispatchEvent(evt), 1); |
| 289 | + return this; |
| 290 | + }); |
| 291 | + } |
| 292 | +} |
| 293 | + |
| 294 | +/** |
| 295 | + * The aurelia framework brings together all the required core aurelia libraries into a ready-to-go application-building platform. |
| 296 | + * |
| 297 | + * @module framework |
| 298 | + */ |
| 299 | + |
| 300 | +export * from 'aurelia-dependency-injection'; |
| 301 | +export * from 'aurelia-binding'; |
| 302 | +export * from 'aurelia-metadata'; |
| 303 | +export * from 'aurelia-templating'; |
| 304 | +export * from 'aurelia-loader'; |
| 305 | +export * from 'aurelia-task-queue'; |
| 306 | +export * from 'aurelia-path'; |
| 307 | + |
| 308 | +export var LogManager = TheLogManager; |
0 commit comments