angularAMD.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. /*
  2. angularAMD v<%= cvars.proj_version %>
  3. (c) 2013-2014 Marcos Lin https://github.com/marcoslin/
  4. License: MIT
  5. */
  6. define(function () {
  7. 'use strict';
  8. var bootstrapped = false,
  9. // Used in .bootstrap
  10. app_name,
  11. orig_app,
  12. alt_app,
  13. run_injector,
  14. config_injector,
  15. app_cached_providers = {},
  16. // Used in setAlternateAngular(), alt_angular is set to become angular.module
  17. orig_angular,
  18. alt_angular,
  19. // Object that wrap the provider methods that enables lazy loading
  20. onDemandLoader = {},
  21. preBootstrapLoaderQueue = [],
  22. // Used in setAlternateAngular() and .processQueue
  23. alternate_modules = {},
  24. alternate_modules_tracker = {},
  25. alternate_queue = [];
  26. // Private method to check if angularAMD has been initialized
  27. function checkBootstrapped() {
  28. if ( !bootstrapped ) {
  29. throw new Error('angularAMD not initialized. Need to call angularAMD.bootstrap(app) first.');
  30. }
  31. }
  32. /**
  33. * Create an alternate angular so that subsequent call to angular.module will queue up
  34. * the module created for later processing via the .processQueue method.
  35. *
  36. * This delaying processing is needed as angular does not recognize any newly created
  37. * module after angular.bootstrap has ran. The only way to add new objects to angular
  38. * post bootstrap is using cached provider.
  39. *
  40. * Once the modules has been queued, processQueue would then use each module's _invokeQueue
  41. * and _runBlock to recreate object using cached $provider. In essence, creating a duplicate
  42. * object into the current ng-app. As result, if there are subsequent call to retrieve the
  43. * module post processQueue, it would retrieve a module that is not integrated into the ng-app.
  44. *
  45. * Therefore, any subsequent to call to angular.module after processQueue should return undefined
  46. * to prevent obtaining a duplicated object. However, it is critical that angular.module return
  47. * appropriate object *during* processQueue.
  48. */
  49. function setAlternateAngular() {
  50. // This method cannot be called more than once
  51. if (alt_angular) {
  52. throw new Error('setAlternateAngular can only be called once.');
  53. } else {
  54. alt_angular = {};
  55. }
  56. // Make sure that bootstrap has been called
  57. checkBootstrapped();
  58. // Create a a copy of orig_angular with on demand loading capability
  59. orig_angular.extend(alt_angular, orig_angular);
  60. // Custom version of angular.module used as cache
  61. alt_angular.module = function (name, requires) {
  62. if (typeof requires === 'undefined') {
  63. // Return module from alternate_modules if it was created using the alt_angular
  64. if (alternate_modules_tracker.hasOwnProperty(name)) {
  65. return alternate_modules[name];
  66. } else {
  67. return orig_angular.module(name);
  68. }
  69. } else {
  70. var orig_mod = orig_angular.module.apply(null, arguments),
  71. item = { name: name, module: orig_mod};
  72. alternate_queue.push(item);
  73. orig_angular.extend(orig_mod, onDemandLoader);
  74. /*
  75. Use `alternate_modules_tracker` to track which module has been created by alt_angular
  76. but use `alternate_modules` to cache the module created. This is to simplify the
  77. removal of cached modules after .processQueue.
  78. */
  79. alternate_modules_tracker[name] = true;
  80. alternate_modules[name] = orig_mod;
  81. // Return created module
  82. return orig_mod;
  83. }
  84. };
  85. window.angular = alt_angular;
  86. if (require.defined('angular')) {
  87. require.undef('angular');
  88. define('angular', [], alt_angular);
  89. }
  90. }
  91. // Constructor
  92. function AngularAMD() {}
  93. /**
  94. * Helper function to generate angular's $routeProvider.route. 'config' input param must be an object.
  95. *
  96. * Populate the resolve attribute using either 'controllerUrl' or 'controller'. If 'controllerUrl'
  97. * is passed, it will attempt to load the Url using requirejs and remove the attribute from the config
  98. * object. Otherwise, it will attempt to populate resolve by loading what's been passed in 'controller'.
  99. * If neither is passed, resolve is not populated.
  100. *
  101. * This function works as a pass-through, meaning what ever is passed in as 'config' will be returned,
  102. * except for 'controllerUrl' attribute.
  103. *
  104. */
  105. AngularAMD.prototype.route = function (config) {
  106. // Initialization not necessary to call this method.
  107. var load_controller;
  108. var lang;
  109. /*
  110. If `controllerUrl` is provided, load the provided Url using requirejs. If `controller` is not provided
  111. but `controllerUrl` is, assume that module to be loaded will return a function to act as controller.
  112. Otherwise, attempt to load the controller using the controller name. In the later case, controller name
  113. is expected to be defined as one of 'paths' in main.js.
  114. */
  115. if ( config.hasOwnProperty('controllerUrl') ) {
  116. load_controller = config.controllerUrl;
  117. delete config.controllerUrl;
  118. if (typeof config.controller === 'undefined') {
  119. // Only controllerUrl is defined. Attempt to set the controller to return value of package loaded.
  120. config.controller = [
  121. '$scope', '__AAMDCtrl', '$injector',
  122. function ($scope, __AAMDCtrl, $injector) {
  123. if (typeof __AAMDCtrl !== 'undefined' ) {
  124. $injector.invoke(__AAMDCtrl, this, { '$scope': $scope });
  125. }
  126. }
  127. ];
  128. }
  129. } else if (typeof config.controller === 'string') {
  130. load_controller = config.controller;
  131. }
  132. // If controller needs to be loaded, append to the resolve property
  133. if (load_controller) {
  134. var resolve = config.resolve || {};
  135. resolve['__AAMDCtrl'] = ['$q', '$rootScope', function ($q, $rootScope) { // jshint ignore:line
  136. var defer = $q.defer();
  137. if ( config.hasOwnProperty('languageUrl') ) {
  138. lang = config.languageUrl;
  139. require([load_controller,lang], function (ctrl) {
  140. defer.resolve(ctrl);
  141. $rootScope.$apply();
  142. });
  143. }else{
  144. require([load_controller], function (ctrl) {
  145. defer.resolve(ctrl);
  146. $rootScope.$apply();
  147. });
  148. }
  149. return defer.promise;
  150. }];
  151. config.resolve = resolve;
  152. }
  153. return config;
  154. };
  155. /**
  156. * Expose name of the app that has been bootstrapped
  157. */
  158. AngularAMD.prototype.appname = function () {
  159. checkBootstrapped();
  160. return app_name;
  161. };
  162. /**
  163. * Recreate the modules created by alternate angular in ng-app using cached $provider.
  164. * As AMD loader does not guarantee the order of dependency in a require([...],...)
  165. * clause, user must make sure that dependecies are clearly setup in shim in order
  166. * for this to work.
  167. *
  168. * HACK ALERT:
  169. * This method relay on inner working of angular.module code, and access _invokeQueue
  170. * and _runBlock private variable. Must test carefully with each release of angular.
  171. *
  172. * As of AngularJS 1.3.x, there is new _configBlocks that get populated with configuration
  173. * blocks, thus replacing the need for "provider === '$injector' && method === 'invoke'"
  174. * logic.
  175. */
  176. AngularAMD.prototype.processQueue = function () {
  177. checkBootstrapped();
  178. if (typeof alt_angular === 'undefined') {
  179. throw new Error('Alternate angular not set. Make sure that `enable_ngload` option has been set when calling angularAMD.bootstrap');
  180. }
  181. // Process alternate queue in FIFO fashion
  182. function processRunBlock(block) {
  183. //console.info('"' + item.name + '": executing run block: ', run_block);
  184. run_injector.invoke(block);
  185. }
  186. // Process the config blocks
  187. for (var i=0;i<alternate_queue.length;i++) {
  188. var item = alternate_queue[i],
  189. invokeQueue = item.module._invokeQueue,
  190. y;
  191. // Setup the providers define in the module
  192. // console.info('invokeQueue: ', invokeQueue);
  193. for (y = 0; y < invokeQueue.length; y += 1) {
  194. var q = invokeQueue[y],
  195. provider = q[0],
  196. method = q[1],
  197. args = q[2];
  198. // Make sure that provider exists.
  199. if (app_cached_providers.hasOwnProperty(provider)) {
  200. var cachedProvider;
  201. if (provider === '$injector' && method === 'invoke') {
  202. cachedProvider = config_injector;
  203. } else {
  204. cachedProvider = app_cached_providers[provider];
  205. }
  206. // console.info('"' + item.name + '": applying ' + provider + '.' + method + ' for args: ', args);
  207. cachedProvider[method].apply(null, args);
  208. } else {
  209. // Make sure that console exists before calling it
  210. if ( window.console ) {
  211. window.console.error('"' + provider + '" not found!!!');
  212. }
  213. }
  214. }
  215. /*
  216. As of AngularJS 1.3.x, the config block are now stored in a new _configBlocks private
  217. variable. Loop through the list and invoke the config block with config_injector
  218. */
  219. if (item.module._configBlocks) {
  220. var configBlocks = item.module._configBlocks;
  221. // console.info('configBlock: ', configBlocks);
  222. for (y = 0; y < configBlocks.length; y += 1) {
  223. var cf = configBlocks[y],
  224. cf_method = cf[1],
  225. cf_args = cf[2];
  226. config_injector[cf_method].apply(null, cf_args);
  227. }
  228. }
  229. }
  230. //after we have executed all config blocks, we finally execute the run blocks
  231. while (alternate_queue.length) {
  232. var item = alternate_queue.shift();
  233. if (item.module._runBlocks) {
  234. angular.forEach(item.module._runBlocks, processRunBlock);
  235. }
  236. }
  237. /*
  238. Clear the cached modules created by alt_angular so that subsequent call to
  239. angular.module will return undefined.
  240. */
  241. alternate_modules = {};
  242. };
  243. /**
  244. * Return cached app provider
  245. */
  246. AngularAMD.prototype.getCachedProvider = function (provider_name) {
  247. checkBootstrapped();
  248. // Hack used for unit testing that orig_angular has been captured
  249. var cachedProvider;
  250. switch(provider_name) {
  251. case '__orig_angular':
  252. cachedProvider = orig_angular;
  253. break;
  254. case '__alt_angular':
  255. cachedProvider = alt_angular;
  256. break;
  257. case '__orig_app':
  258. cachedProvider = orig_app;
  259. break;
  260. case '__alt_app':
  261. cachedProvider = alt_app;
  262. break;
  263. default:
  264. cachedProvider = app_cached_providers[provider_name];
  265. }
  266. return cachedProvider;
  267. };
  268. /**
  269. * Create inject function that uses cached $injector.
  270. * Designed primarly to be used during unit testing.
  271. */
  272. AngularAMD.prototype.inject = function () {
  273. checkBootstrapped();
  274. return run_injector.invoke.apply(null, arguments);
  275. };
  276. /**
  277. * Create config function that uses cached config_injector.
  278. * Designed to simulate app.config.
  279. */
  280. AngularAMD.prototype.config = function () {
  281. checkBootstrapped();
  282. return config_injector.invoke.apply(null, arguments);
  283. };
  284. /**
  285. * Reset angularAMD for resuse
  286. */
  287. AngularAMD.prototype.reset = function () {
  288. if (typeof orig_angular === 'undefined') {
  289. return;
  290. }
  291. // Restore original angular instance
  292. window.angular = orig_angular;
  293. if (require.defined('angular')) {
  294. require.undef('angular');
  295. define('angular', [], orig_angular);
  296. }
  297. // Clear stored app
  298. orig_app = undefined;
  299. alt_app = undefined;
  300. // Clear original angular
  301. alt_angular = undefined;
  302. orig_angular = undefined;
  303. onDemandLoader = {};
  304. preBootstrapLoaderQueue = [];
  305. // Clear private variables
  306. alternate_queue = [];
  307. app_name = undefined;
  308. run_injector = undefined;
  309. config_injector = undefined;
  310. app_cached_providers = {};
  311. // Clear bootstrap flag but there is no way to un-bootstrap AngularJS
  312. bootstrapped = false;
  313. };
  314. /**
  315. * Initialization of angularAMD that bootstraps AngularJS. The objective is to cache the
  316. * $provider and $injector from the app to be used later.
  317. *
  318. * enable_ngload:
  319. */
  320. AngularAMD.prototype.bootstrap = function (app, enable_ngload, elem) {
  321. // Prevent bootstrap from being called multiple times
  322. if (bootstrapped) {
  323. throw Error('bootstrap can only be called once.');
  324. }
  325. if (typeof enable_ngload === 'undefined') {
  326. enable_ngload = true;
  327. }
  328. // Store reference to original angular and app
  329. orig_angular = angular;
  330. // Create new version of app
  331. orig_app = app;
  332. alt_app = {};
  333. orig_angular.extend(alt_app, orig_app);
  334. // Determine element to bootstrap angular
  335. elem = elem || document.documentElement;
  336. // Cache provider needed
  337. app.config(
  338. ['$controllerProvider', '$compileProvider', '$filterProvider', '$animateProvider', '$provide', '$injector', function (controllerProvider, compileProvider, filterProvider, animateProvider, provide, injector) {
  339. // Cache Providers
  340. config_injector = injector;
  341. app_cached_providers = {
  342. $controllerProvider: controllerProvider,
  343. $compileProvider: compileProvider,
  344. $filterProvider: filterProvider,
  345. $animateProvider: animateProvider,
  346. $provide: provide
  347. };
  348. // Substitue provider methods from app call the cached provider
  349. angular.extend(onDemandLoader, {
  350. provider : function(name, constructor) {
  351. provide.provider(name, constructor);
  352. return this;
  353. },
  354. controller : function(name, constructor) {
  355. controllerProvider.register(name, constructor);
  356. return this;
  357. },
  358. directive : function(name, constructor) {
  359. compileProvider.directive(name, constructor);
  360. return this;
  361. },
  362. filter : function(name, constructor) {
  363. filterProvider.register(name, constructor);
  364. return this;
  365. },
  366. factory : function(name, constructor) {
  367. // console.log('onDemandLoader.factory called for ' + name);
  368. provide.factory(name, constructor);
  369. return this;
  370. },
  371. service : function(name, constructor) {
  372. provide.service(name, constructor);
  373. return this;
  374. },
  375. constant : function(name, constructor) {
  376. provide.constant(name, constructor);
  377. return this;
  378. },
  379. value : function(name, constructor) {
  380. provide.value(name, constructor);
  381. return this;
  382. },
  383. animation: angular.bind(animateProvider, animateProvider.register)
  384. });
  385. angular.extend(alt_app, onDemandLoader);
  386. }]
  387. );
  388. // Get the injector for the app
  389. app.run(['$injector', function ($injector) {
  390. // $injector must be obtained in .run instead of .config
  391. run_injector = $injector;
  392. app_cached_providers.$injector = run_injector;
  393. }]);
  394. // Store the app name needed by .bootstrap function.
  395. app_name = app.name;
  396. // If there are angular provider recipe queued up, process it
  397. if (preBootstrapLoaderQueue.length > 0) {
  398. for (var iq = 0; iq < preBootstrapLoaderQueue.length; iq += 1) {
  399. var item = preBootstrapLoaderQueue[iq];
  400. orig_app[item.recipe](item.name, item.constructor);
  401. }
  402. preBootstrapLoaderQueue = [];
  403. }
  404. // Create a app.register object to keep backward compatibility
  405. orig_app.register = onDemandLoader;
  406. // Bootstrap Angular
  407. orig_angular.element(document).ready(function () {
  408. orig_angular.bootstrap(elem, [app_name]);
  409. // Indicate bootstrap completed
  410. bootstrapped = true;
  411. // Replace angular.module
  412. if (enable_ngload) {
  413. //console.info('Setting alternate angular');
  414. setAlternateAngular();
  415. }
  416. });
  417. // Return app
  418. return alt_app;
  419. };
  420. // Define provider
  421. function executeProvider(providerRecipe) {
  422. return function (name, constructor) {
  423. if (bootstrapped) {
  424. onDemandLoader[providerRecipe](name, constructor);
  425. } else {
  426. // Queue up the request to be used during .bootstrap
  427. preBootstrapLoaderQueue.push({
  428. 'recipe': providerRecipe,
  429. 'name': name,
  430. 'constructor': constructor
  431. });
  432. }
  433. return this;
  434. };
  435. }
  436. // .provider
  437. AngularAMD.prototype.provider = executeProvider('provider');
  438. // .controller
  439. AngularAMD.prototype.controller = executeProvider('controller');
  440. // .directive
  441. AngularAMD.prototype.directive = executeProvider('directive');
  442. // .filter
  443. AngularAMD.prototype.filter = executeProvider('filter');
  444. // .factory
  445. AngularAMD.prototype.factory = executeProvider('factory');
  446. // .service
  447. AngularAMD.prototype.service = executeProvider('service');
  448. // .constant
  449. AngularAMD.prototype.constant = executeProvider('constant');
  450. // .value
  451. AngularAMD.prototype.value = executeProvider('value');
  452. // .animation
  453. AngularAMD.prototype.animation = executeProvider('animation');
  454. // Create a new instance and return
  455. return new AngularAMD();
  456. });