ocLazyLoad.js 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004
  1. /**
  2. * oclazyload - Load modules on demand (lazy load) with angularJS
  3. * @version v0.5.0
  4. * @link https://github.com/ocombe/ocLazyLoad
  5. * @license MIT
  6. * @author Olivier Combe <olivier.combe@gmail.com>
  7. */
  8. (function() {
  9. 'use strict';
  10. var regModules = ['ng'],
  11. regInvokes = {},
  12. regConfigs = [],
  13. justLoaded = [],
  14. runBlocks = {},
  15. ocLazyLoad = angular.module('oc.lazyLoad', ['ng']),
  16. broadcast = angular.noop;
  17. ocLazyLoad.provider('$ocLazyLoad', ['$controllerProvider', '$provide', '$compileProvider', '$filterProvider', '$injector', '$animateProvider',
  18. function($controllerProvider, $provide, $compileProvider, $filterProvider, $injector, $animateProvider) {
  19. var modules = {},
  20. providers = {
  21. $controllerProvider: $controllerProvider,
  22. $compileProvider: $compileProvider,
  23. $filterProvider: $filterProvider,
  24. $provide: $provide, // other things
  25. $injector: $injector,
  26. $animateProvider: $animateProvider
  27. },
  28. anchor = document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0],
  29. jsLoader, cssLoader, templatesLoader,
  30. debug = false,
  31. events = false;
  32. // Let's get the list of loaded modules & components
  33. init(angular.element(window.document));
  34. this.$get = ['$timeout', '$log', '$q', '$templateCache', '$http', '$rootElement', '$rootScope', '$cacheFactory', '$interval', function($timeout, $log, $q, $templateCache, $http, $rootElement, $rootScope, $cacheFactory, $interval) {
  35. var instanceInjector,
  36. filesCache = $cacheFactory('ocLazyLoad'),
  37. uaCssChecked = false,
  38. useCssLoadPatch = false;
  39. if(!debug) {
  40. $log = {};
  41. $log['error'] = angular.noop;
  42. $log['warn'] = angular.noop;
  43. $log['info'] = angular.noop;
  44. }
  45. // Make this lazy because at the moment that $get() is called the instance injector hasn't been assigned to the rootElement yet
  46. providers.getInstanceInjector = function() {
  47. return (instanceInjector) ? instanceInjector : (instanceInjector = $rootElement.data('$injector'));
  48. };
  49. broadcast = function broadcast(eventName, params) {
  50. if(events) {
  51. $rootScope.$broadcast(eventName, params);
  52. }
  53. if(debug) {
  54. $log.info(eventName, params);
  55. }
  56. }
  57. /**
  58. * Load a js/css file
  59. * @param type
  60. * @param path
  61. * @returns promise
  62. */
  63. var buildElement = function buildElement(type, path, params) {
  64. var deferred = $q.defer(),
  65. el, loaded,
  66. cacheBuster = function cacheBuster(url) {
  67. var dc = new Date().getTime();
  68. if(url.indexOf('?') >= 0) {
  69. if(url.substring(0, url.length - 1) === '&') {
  70. return url + '_dc=' + dc;
  71. }
  72. return url + '&_dc=' + dc;
  73. } else {
  74. return url + '?_dc=' + dc;
  75. }
  76. };
  77. // Store the promise early so the file load can be detected by other parallel lazy loads
  78. // (ie: multiple routes on one page) a 'true' value isn't sufficient
  79. // as it causes false positive load results.
  80. if(angular.isUndefined(filesCache.get(path))) {
  81. filesCache.put(path, deferred.promise);
  82. }
  83. // Switch in case more content types are added later
  84. switch(type) {
  85. case 'css':
  86. el = document.createElement('link');
  87. el.type = 'text/css';
  88. el.rel = 'stylesheet';
  89. el.href = params.cache === false ? cacheBuster(path) : path;
  90. break;
  91. case 'js':
  92. el = document.createElement('script');
  93. el.src = params.cache === false ? cacheBuster(path) : path;
  94. break;
  95. default:
  96. deferred.reject(new Error('Requested type "' + type + '" is not known. Could not inject "' + path + '"'));
  97. break;
  98. }
  99. el.onload = el['onreadystatechange'] = function(e) {
  100. if((el['readyState'] && !(/^c|loade/.test(el['readyState']))) || loaded) return;
  101. el.onload = el['onreadystatechange'] = null
  102. loaded = 1;
  103. broadcast('ocLazyLoad.fileLoaded', path);
  104. deferred.resolve();
  105. }
  106. el.onerror = function(e) {
  107. deferred.reject(new Error('Unable to load ' + path));
  108. }
  109. el.async = 1;
  110. var insertBeforeElem = anchor.lastChild;
  111. if(params.insertBefore) {
  112. var element = angular.element(params.insertBefore);
  113. if(element && element.length > 0) {
  114. insertBeforeElem = element[0];
  115. }
  116. }
  117. anchor.insertBefore(el, insertBeforeElem);
  118. /*
  119. The event load or readystatechange doesn't fire in:
  120. - iOS < 6 (default mobile browser)
  121. - Android < 4.4 (default mobile browser)
  122. - Safari < 6 (desktop browser)
  123. */
  124. if(type == 'css') {
  125. if(!uaCssChecked) {
  126. var ua = navigator.userAgent.toLowerCase();
  127. // iOS < 6
  128. if(/iP(hone|od|ad)/.test(navigator.platform)) {
  129. var v = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/);
  130. var iOSVersion = parseFloat([parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3] || 0, 10)].join('.'));
  131. useCssLoadPatch = iOSVersion < 6;
  132. } else if(ua.indexOf("android") > -1) { // Android < 4.4
  133. var androidVersion = parseFloat(ua.slice(ua.indexOf("android") + 8));
  134. useCssLoadPatch = androidVersion < 4.4;
  135. } else if(ua.indexOf('safari') > -1 && ua.indexOf('chrome') == -1) {
  136. var safariVersion = parseFloat(ua.match(/version\/([\.\d]+)/i)[1]);
  137. useCssLoadPatch = safariVersion < 6;
  138. }
  139. }
  140. if(useCssLoadPatch) {
  141. var tries = 1000; // * 20 = 20000 miliseconds
  142. var interval = $interval(function() {
  143. try {
  144. el.sheet.cssRules;
  145. $interval.cancel(interval);
  146. el.onload();
  147. } catch(e) {
  148. if(--tries <= 0) {
  149. el.onerror();
  150. }
  151. }
  152. }, 20);
  153. }
  154. }
  155. return deferred.promise;
  156. }
  157. if(angular.isUndefined(jsLoader)) {
  158. /**
  159. * jsLoader function
  160. * @type Function
  161. * @param paths array list of js files to load
  162. * @param callback to call when everything is loaded. We use a callback and not a promise
  163. * @param params object config parameters
  164. * because the user can overwrite jsLoader and it will probably not use promises :(
  165. */
  166. jsLoader = function(paths, callback, params) {
  167. var promises = [];
  168. angular.forEach(paths, function loading(path) {
  169. promises.push(buildElement('js', path, params));
  170. });
  171. $q.all(promises).then(function success() {
  172. callback();
  173. }, function error(err) {
  174. callback(err);
  175. });
  176. }
  177. jsLoader.ocLazyLoadLoader = true;
  178. }
  179. if(angular.isUndefined(cssLoader)) {
  180. /**
  181. * cssLoader function
  182. * @type Function
  183. * @param paths array list of css files to load
  184. * @param callback to call when everything is loaded. We use a callback and not a promise
  185. * @param params object config parameters
  186. * because the user can overwrite cssLoader and it will probably not use promises :(
  187. */
  188. cssLoader = function(paths, callback, params) {
  189. var promises = [];
  190. angular.forEach(paths, function loading(path) {
  191. promises.push(buildElement('css', path, params));
  192. });
  193. $q.all(promises).then(function success() {
  194. callback();
  195. }, function error(err) {
  196. callback(err);
  197. });
  198. }
  199. cssLoader.ocLazyLoadLoader = true;
  200. }
  201. if(angular.isUndefined(templatesLoader)) {
  202. /**
  203. * templatesLoader function
  204. * @type Function
  205. * @param paths array list of css files to load
  206. * @param callback to call when everything is loaded. We use a callback and not a promise
  207. * @param params object config parameters for $http
  208. * because the user can overwrite templatesLoader and it will probably not use promises :(
  209. */
  210. templatesLoader = function(paths, callback, params) {
  211. if(angular.isString(paths)) {
  212. paths = [paths];
  213. }
  214. var promises = [];
  215. angular.forEach(paths, function(url) {
  216. var deferred = $q.defer();
  217. promises.push(deferred.promise);
  218. $http.get(url, params).success(function(data) {
  219. angular.forEach(angular.element(data), function(node) {
  220. if(node.nodeName === 'SCRIPT' && node.type === 'text/ng-template') {
  221. $templateCache.put(node.id, node.innerHTML);
  222. }
  223. });
  224. if(angular.isUndefined(filesCache.get(url))) {
  225. filesCache.put(url, true);
  226. }
  227. deferred.resolve();
  228. }).error(function(data) {
  229. var err = 'Error load template "' + url + '": ' + data;
  230. $log.error(err);
  231. deferred.reject(new Error(err));
  232. });
  233. });
  234. return $q.all(promises).then(function success() {
  235. callback();
  236. }, function error(err) {
  237. callback(err);
  238. });
  239. }
  240. templatesLoader.ocLazyLoadLoader = true;
  241. }
  242. var filesLoader = function(config, params) {
  243. var cssFiles = [],
  244. templatesFiles = [],
  245. jsFiles = [],
  246. promises = [],
  247. cachePromise = null;
  248. angular.extend(params || {}, config);
  249. var pushFile = function(path) {
  250. cachePromise = filesCache.get(path);
  251. if(angular.isUndefined(cachePromise) || params.cache === false) {
  252. if(/\.css[^\.]*$/.test(path) && cssFiles.indexOf(path) === -1) {
  253. cssFiles.push(path);
  254. } else if(/\.(htm|html)[^\.]*$/.test(path) && templatesFiles.indexOf(path) === -1) {
  255. templatesFiles.push(path);
  256. } else if(jsFiles.indexOf(path) === -1) {
  257. jsFiles.push(path);
  258. }
  259. } else if(cachePromise) {
  260. promises.push(cachePromise);
  261. }
  262. }
  263. if(params.serie) {
  264. pushFile(params.files.shift());
  265. } else {
  266. angular.forEach(params.files, function(path) {
  267. pushFile(path);
  268. });
  269. }
  270. if(cssFiles.length > 0) {
  271. var cssDeferred = $q.defer();
  272. cssLoader(cssFiles, function(err) {
  273. if(angular.isDefined(err) && cssLoader.hasOwnProperty('ocLazyLoadLoader')) {
  274. $log.error(err);
  275. cssDeferred.reject(err);
  276. } else {
  277. cssDeferred.resolve();
  278. }
  279. }, params);
  280. promises.push(cssDeferred.promise);
  281. }
  282. if(templatesFiles.length > 0) {
  283. var templatesDeferred = $q.defer();
  284. templatesLoader(templatesFiles, function(err) {
  285. if(angular.isDefined(err) && templatesLoader.hasOwnProperty('ocLazyLoadLoader')) {
  286. $log.error(err);
  287. templatesDeferred.reject(err);
  288. } else {
  289. templatesDeferred.resolve();
  290. }
  291. }, params);
  292. promises.push(templatesDeferred.promise);
  293. }
  294. if(jsFiles.length > 0) {
  295. var jsDeferred = $q.defer();
  296. jsLoader(jsFiles, function(err) {
  297. if(angular.isDefined(err) && jsLoader.hasOwnProperty('ocLazyLoadLoader')) {
  298. $log.error(err);
  299. jsDeferred.reject(err);
  300. } else {
  301. jsDeferred.resolve();
  302. }
  303. }, params);
  304. promises.push(jsDeferred.promise);
  305. }
  306. if(params.serie && params.files.length > 0) {
  307. return $q.all(promises).then(function success() {
  308. return filesLoader(config, params);
  309. });
  310. } else {
  311. return $q.all(promises);
  312. }
  313. }
  314. return {
  315. /**
  316. * Let you get a module config object
  317. * @param moduleName String the name of the module
  318. * @returns {*}
  319. */
  320. getModuleConfig: function(moduleName) {
  321. if(!angular.isString(moduleName)) {
  322. throw new Error('You need to give the name of the module to get');
  323. }
  324. if(!modules[moduleName]) {
  325. return null;
  326. }
  327. return modules[moduleName];
  328. },
  329. /**
  330. * Let you define a module config object
  331. * @param moduleConfig Object the module config object
  332. * @returns {*}
  333. */
  334. setModuleConfig: function(moduleConfig) {
  335. if(!angular.isObject(moduleConfig)) {
  336. throw new Error('You need to give the module config object to set');
  337. }
  338. modules[moduleConfig.name] = moduleConfig;
  339. return moduleConfig;
  340. },
  341. /**
  342. * Returns the list of loaded modules
  343. * @returns {string[]}
  344. */
  345. getModules: function() {
  346. return regModules;
  347. },
  348. /**
  349. * Let you check if a module has been loaded into Angular or not
  350. * @param modulesNames String/Object a module name, or a list of module names
  351. * @returns {boolean}
  352. */
  353. isLoaded: function(modulesNames) {
  354. var moduleLoaded = function(module) {
  355. var isLoaded = regModules.indexOf(module) > -1;
  356. if(!isLoaded) {
  357. isLoaded = !!moduleExists(module);
  358. }
  359. return isLoaded;
  360. }
  361. if(angular.isString(modulesNames)) {
  362. modulesNames = [modulesNames];
  363. }
  364. if(angular.isArray(modulesNames)) {
  365. var i, len;
  366. for(i = 0, len = modulesNames.length; i < len; i++) {
  367. if(!moduleLoaded(modulesNames[i])) {
  368. return false;
  369. }
  370. }
  371. return true;
  372. } else {
  373. throw new Error('You need to define the module(s) name(s)');
  374. }
  375. },
  376. /**
  377. * Load a module or a list of modules into Angular
  378. * @param module Mixed the name of a predefined module config object, or a module config object, or an array of either
  379. * @param params Object optional parameters
  380. * @returns promise
  381. */
  382. load: function(module, params) {
  383. var self = this,
  384. config = null,
  385. moduleCache = [],
  386. deferredList = [],
  387. deferred = $q.defer(),
  388. moduleName,
  389. errText;
  390. if(angular.isUndefined(params)) {
  391. params = {};
  392. }
  393. // If module is an array, break it down
  394. if(angular.isArray(module)) {
  395. // Resubmit each entry as a single module
  396. angular.forEach(module, function(m) {
  397. if(m) {
  398. deferredList.push(self.load(m, params));
  399. }
  400. });
  401. // Resolve the promise once everything has loaded
  402. $q.all(deferredList).then(function success() {
  403. deferred.resolve(module);
  404. }, function error(err) {
  405. deferred.reject(err);
  406. });
  407. return deferred.promise;
  408. }
  409. moduleName = getModuleName(module);
  410. // Get or Set a configuration depending on what was passed in
  411. if(typeof module === 'string') {
  412. config = self.getModuleConfig(module);
  413. if(!config) {
  414. config = {
  415. files: [module]
  416. };
  417. moduleName = null;
  418. }
  419. } else if(typeof module === 'object') {
  420. config = self.setModuleConfig(module);
  421. }
  422. if(config === null) {
  423. errText = 'Module "' + moduleName + '" is not configured, cannot load.';
  424. $log.error(errText);
  425. deferred.reject(new Error(errText));
  426. } else {
  427. // deprecated
  428. if(angular.isDefined(config.template)) {
  429. if(angular.isUndefined(config.files)) {
  430. config.files = [];
  431. }
  432. if(angular.isString(config.template)) {
  433. config.files.push(config.template);
  434. } else if(angular.isArray(config.template)) {
  435. config.files.concat(config.template);
  436. }
  437. }
  438. }
  439. moduleCache.push = function(value) {
  440. if(this.indexOf(value) === -1) {
  441. Array.prototype.push.apply(this, arguments);
  442. }
  443. };
  444. // If this module has been loaded before, re-use it.
  445. if(angular.isDefined(moduleName) && moduleExists(moduleName) && regModules.indexOf(moduleName) !== -1) {
  446. moduleCache.push(moduleName);
  447. // if we don't want to load new files, resolve here
  448. if(angular.isUndefined(config.files)) {
  449. deferred.resolve();
  450. return deferred.promise;
  451. }
  452. }
  453. var localParams = {};
  454. angular.extend(localParams, params, config);
  455. var loadDependencies = function loadDependencies(module) {
  456. var moduleName,
  457. loadedModule,
  458. requires,
  459. diff,
  460. promisesList = [];
  461. moduleName = getModuleName(module);
  462. if(moduleName === null) {
  463. return $q.when();
  464. } else {
  465. try {
  466. loadedModule = getModule(moduleName);
  467. } catch(e) {
  468. var deferred = $q.defer();
  469. $log.error(e.message);
  470. deferred.reject(e);
  471. return deferred.promise;
  472. }
  473. requires = getRequires(loadedModule);
  474. }
  475. angular.forEach(requires, function(requireEntry) {
  476. // If no configuration is provided, try and find one from a previous load.
  477. // If there isn't one, bail and let the normal flow run
  478. if(typeof requireEntry === 'string') {
  479. var config = self.getModuleConfig(requireEntry);
  480. if(config === null) {
  481. moduleCache.push(requireEntry); // We don't know about this module, but something else might, so push it anyway.
  482. return;
  483. }
  484. requireEntry = config;
  485. }
  486. // Check if this dependency has been loaded previously
  487. if(moduleExists(requireEntry.name)) {
  488. if(typeof module !== 'string') {
  489. // compare against the already loaded module to see if the new definition adds any new files
  490. diff = requireEntry.files.filter(function(n) {
  491. return self.getModuleConfig(requireEntry.name).files.indexOf(n) < 0;
  492. });
  493. // If the module was redefined, advise via the console
  494. if(diff.length !== 0) {
  495. $log.warn('Module "', moduleName, '" attempted to redefine configuration for dependency. "', requireEntry.name, '"\n Additional Files Loaded:', diff);
  496. }
  497. // Push everything to the file loader, it will weed out the duplicates.
  498. promisesList.push(filesLoader(requireEntry.files, localParams).then(function() {
  499. return loadDependencies(requireEntry);
  500. }));
  501. }
  502. return;
  503. } else if(typeof requireEntry === 'object') {
  504. if(requireEntry.hasOwnProperty('name') && requireEntry['name']) {
  505. // The dependency doesn't exist in the module cache and is a new configuration, so store and push it.
  506. self.setModuleConfig(requireEntry);
  507. moduleCache.push(requireEntry['name']);
  508. }
  509. // CSS Loading Handler
  510. if(requireEntry.hasOwnProperty('css') && requireEntry['css'].length !== 0) {
  511. // Locate the document insertion point
  512. angular.forEach(requireEntry['css'], function(path) {
  513. buildElement('css', path, localParams);
  514. });
  515. }
  516. // CSS End.
  517. }
  518. // Check if the dependency has any files that need to be loaded. If there are, push a new promise to the promise list.
  519. if(requireEntry.hasOwnProperty('files') && requireEntry.files.length !== 0) {
  520. if(requireEntry.files) {
  521. promisesList.push(filesLoader(requireEntry, localParams).then(function() {
  522. return loadDependencies(requireEntry);
  523. }));
  524. }
  525. }
  526. });
  527. // Create a wrapper promise to watch the promise list and resolve it once everything is done.
  528. return $q.all(promisesList);
  529. }
  530. filesLoader(config, localParams).then(function success() {
  531. if(moduleName === null) {
  532. deferred.resolve(module);
  533. } else {
  534. moduleCache.push(moduleName);
  535. loadDependencies(moduleName).then(function success() {
  536. try {
  537. justLoaded = [];
  538. register(providers, moduleCache, localParams);
  539. } catch(e) {
  540. $log.error(e.message);
  541. deferred.reject(e);
  542. return;
  543. }
  544. $timeout(function() {
  545. deferred.resolve(module);
  546. });
  547. }, function error(err) {
  548. $timeout(function() {
  549. deferred.reject(err);
  550. });
  551. });
  552. }
  553. }, function error(err) {
  554. deferred.reject(err);
  555. });
  556. return deferred.promise;
  557. }
  558. };
  559. }];
  560. this.config = function(config) {
  561. if(angular.isDefined(config.jsLoader) || angular.isDefined(config.asyncLoader)) {
  562. if(!angular.isFunction(config.jsLoader || config.asyncLoader)) {
  563. throw('The js loader needs to be a function');
  564. }
  565. jsLoader = config.jsLoader || config.asyncLoader;
  566. }
  567. if(angular.isDefined(config.cssLoader)) {
  568. if(!angular.isFunction(config.cssLoader)) {
  569. throw('The css loader needs to be a function');
  570. }
  571. cssLoader = config.cssLoader;
  572. }
  573. if(angular.isDefined(config.templatesLoader)) {
  574. if(!angular.isFunction(config.templatesLoader)) {
  575. throw('The template loader needs to be a function');
  576. }
  577. templatesLoader = config.templatesLoader;
  578. }
  579. // for bootstrap apps, we need to define the main module name
  580. if(angular.isDefined(config.loadedModules)) {
  581. var addRegModule = function(loadedModule) {
  582. if(regModules.indexOf(loadedModule) < 0) {
  583. regModules.push(loadedModule);
  584. angular.forEach(angular.module(loadedModule).requires, addRegModule);
  585. }
  586. };
  587. angular.forEach(config.loadedModules, addRegModule);
  588. }
  589. // If we want to define modules configs
  590. if(angular.isDefined(config.modules)) {
  591. if(angular.isArray(config.modules)) {
  592. angular.forEach(config.modules, function(moduleConfig) {
  593. modules[moduleConfig.name] = moduleConfig;
  594. });
  595. } else {
  596. modules[config.modules.name] = config.modules;
  597. }
  598. }
  599. if(angular.isDefined(config.debug)) {
  600. debug = config.debug;
  601. }
  602. if(angular.isDefined(config.events)) {
  603. events = config.events;
  604. }
  605. };
  606. }]);
  607. ocLazyLoad.directive('ocLazyLoad', ['$ocLazyLoad', '$compile', '$animate', '$parse',
  608. function($ocLazyLoad, $compile, $animate, $parse) {
  609. return {
  610. restrict: 'A',
  611. terminal: true,
  612. priority: 1000,
  613. compile: function(element, attrs) {
  614. // we store the content and remove it before compilation
  615. var content = element[0].innerHTML;
  616. element.html('');
  617. return function($scope, $element, $attr) {
  618. var model = $parse($attr.ocLazyLoad);
  619. $scope.$watch(function() {
  620. // it can be a module name (string), an object, an array, or a scope reference to any of this
  621. return model($scope) || $attr.ocLazyLoad;
  622. }, function(moduleName) {
  623. if(angular.isDefined(moduleName)) {
  624. $ocLazyLoad.load(moduleName).then(function success(moduleConfig) {
  625. $animate.enter($compile(content)($scope), null, $element);
  626. });
  627. }
  628. }, true);
  629. };
  630. }
  631. };
  632. }]);
  633. /**
  634. * Get the list of required modules/services/... for this module
  635. * @param module
  636. * @returns {Array}
  637. */
  638. function getRequires(module) {
  639. var requires = [];
  640. angular.forEach(module.requires, function(requireModule) {
  641. if(regModules.indexOf(requireModule) === -1) {
  642. requires.push(requireModule);
  643. }
  644. });
  645. return requires;
  646. }
  647. /**
  648. * Check if a module exists and returns it if it does
  649. * @param moduleName
  650. * @returns {boolean}
  651. */
  652. function moduleExists(moduleName) {
  653. try {
  654. return angular.module(moduleName);
  655. } catch(e) {
  656. if(/No module/.test(e) || (e.message.indexOf('$injector:nomod') > -1)) {
  657. return false;
  658. }
  659. }
  660. }
  661. function getModule(moduleName) {
  662. try {
  663. return angular.module(moduleName);
  664. } catch(e) {
  665. // this error message really suxx
  666. if(/No module/.test(e) || (e.message.indexOf('$injector:nomod') > -1)) {
  667. e.message = 'The module "' + moduleName + '" that you are trying to load does not exist. ' + e.message
  668. }
  669. throw e;
  670. }
  671. }
  672. function invokeQueue(providers, queue, moduleName, reconfig) {
  673. if(!queue) {
  674. return;
  675. }
  676. var i, len, args, provider;
  677. for(i = 0, len = queue.length; i < len; i++) {
  678. args = queue[i];
  679. if(angular.isArray(args)) {
  680. if(providers !== null) {
  681. if(providers.hasOwnProperty(args[0])) {
  682. provider = providers[args[0]];
  683. } else {
  684. throw new Error('unsupported provider ' + args[0]);
  685. }
  686. }
  687. var isNew = registerInvokeList(args, moduleName);
  688. if(args[1] !== 'invoke') {
  689. if(isNew && angular.isDefined(provider)) {
  690. provider[args[1]].apply(provider, args[2]);
  691. }
  692. } else { // config block
  693. var callInvoke = function(fct) {
  694. var invoked = regConfigs.indexOf(moduleName + '-' + fct);
  695. if(invoked === -1 || reconfig) {
  696. if(invoked === -1) {
  697. regConfigs.push(moduleName + '-' + fct);
  698. }
  699. if(angular.isDefined(provider)) {
  700. provider[args[1]].apply(provider, args[2]);
  701. }
  702. }
  703. }
  704. if(angular.isFunction(args[2][0])) {
  705. callInvoke(args[2][0]);
  706. } else if(angular.isArray(args[2][0])) {
  707. for(var j = 0, jlen = args[2][0].length; j < jlen; j++) {
  708. if(angular.isFunction(args[2][0][j])) {
  709. callInvoke(args[2][0][j]);
  710. }
  711. }
  712. }
  713. }
  714. }
  715. }
  716. }
  717. /**
  718. * Register a new module and load it
  719. * @param providers
  720. * @param registerModules
  721. * @returns {*}
  722. */
  723. function register(providers, registerModules, params) {
  724. if(registerModules) {
  725. var k, r, moduleName, moduleFn, tempRunBlocks = [];
  726. for(k = registerModules.length - 1; k >= 0; k--) {
  727. moduleName = registerModules[k];
  728. if(typeof moduleName !== 'string') {
  729. moduleName = getModuleName(moduleName);
  730. }
  731. if(!moduleName || justLoaded.indexOf(moduleName) !== -1) {
  732. continue;
  733. }
  734. var newModule = regModules.indexOf(moduleName) === -1;
  735. moduleFn = angular.module(moduleName);
  736. if(newModule) { // new module
  737. regModules.push(moduleName);
  738. register(providers, moduleFn.requires, params);
  739. }
  740. if(moduleFn._runBlocks.length > 0) {
  741. // new run blocks detected! Replace the old ones (if existing)
  742. runBlocks[moduleName] = [];
  743. while(moduleFn._runBlocks.length > 0) {
  744. runBlocks[moduleName].push(moduleFn._runBlocks.shift());
  745. }
  746. }
  747. if(angular.isDefined(runBlocks[moduleName]) && (newModule || params.rerun)) {
  748. tempRunBlocks = tempRunBlocks.concat(runBlocks[moduleName]);
  749. }
  750. invokeQueue(providers, moduleFn._invokeQueue, moduleName, params.reconfig);
  751. invokeQueue(providers, moduleFn._configBlocks, moduleName, params.reconfig); // angular 1.3+
  752. broadcast(newModule ? 'ocLazyLoad.moduleLoaded' : 'ocLazyLoad.moduleReloaded', moduleName);
  753. registerModules.pop();
  754. justLoaded.push(moduleName);
  755. }
  756. // execute the run blocks at the end
  757. var instanceInjector = providers.getInstanceInjector();
  758. angular.forEach(tempRunBlocks, function(fn) {
  759. instanceInjector.invoke(fn);
  760. });
  761. }
  762. }
  763. /**
  764. * Register an invoke
  765. * @param args
  766. * @returns {*}
  767. */
  768. function registerInvokeList(args, moduleName) {
  769. var invokeList = args[2][0],
  770. type = args[1],
  771. newInvoke = false;
  772. if(angular.isUndefined(regInvokes[moduleName])) {
  773. regInvokes[moduleName] = {};
  774. }
  775. if(angular.isUndefined(regInvokes[moduleName][type])) {
  776. regInvokes[moduleName][type] = [];
  777. }
  778. var onInvoke = function(invokeName) {
  779. newInvoke = true;
  780. regInvokes[moduleName][type].push(invokeName);
  781. broadcast('ocLazyLoad.componentLoaded', [moduleName, type, invokeName]);
  782. }
  783. if(angular.isString(invokeList) && regInvokes[moduleName][type].indexOf(invokeList) === -1) {
  784. onInvoke(invokeList);
  785. } else if(angular.isObject(invokeList)) {
  786. angular.forEach(invokeList, function(invoke) {
  787. if(angular.isString(invoke) && regInvokes[moduleName][type].indexOf(invoke) === -1) {
  788. onInvoke(invoke);
  789. }
  790. });
  791. } else {
  792. return false;
  793. }
  794. return newInvoke;
  795. }
  796. function getModuleName(module) {
  797. if(module === null) {
  798. return null;
  799. }
  800. var moduleName = null;
  801. if(typeof module === 'string') {
  802. moduleName = module;
  803. } else if(typeof module === 'object' && module.hasOwnProperty('name') && typeof module.name === 'string') {
  804. moduleName = module.name;
  805. }
  806. return moduleName;
  807. }
  808. /**
  809. * Get the list of existing registered modules
  810. * @param element
  811. */
  812. function init(element) {
  813. var elements = [element],
  814. appElement,
  815. moduleName,
  816. names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'],
  817. NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;
  818. function append(elm) {
  819. return (elm && elements.push(elm));
  820. }
  821. angular.forEach(names, function(name) {
  822. names[name] = true;
  823. append(document.getElementById(name));
  824. name = name.replace(':', '\\:');
  825. if(element[0].querySelectorAll) {
  826. angular.forEach(element[0].querySelectorAll('.' + name), append);
  827. angular.forEach(element[0].querySelectorAll('.' + name + '\\:'), append);
  828. angular.forEach(element[0].querySelectorAll('[' + name + ']'), append);
  829. }
  830. });
  831. //TODO: search the script tags for angular.bootstrap
  832. angular.forEach(elements, function(elm) {
  833. if(!appElement) {
  834. var className = ' ' + element.className + ' ';
  835. var match = NG_APP_CLASS_REGEXP.exec(className);
  836. if(match) {
  837. appElement = elm;
  838. moduleName = (match[2] || '').replace(/\s+/g, ',');
  839. } else {
  840. angular.forEach(elm.attributes, function(attr) {
  841. if(!appElement && names[attr.name]) {
  842. appElement = elm;
  843. moduleName = attr.value;
  844. }
  845. });
  846. }
  847. }
  848. });
  849. if(appElement) {
  850. (function addReg(moduleName) {
  851. if(regModules.indexOf(moduleName) === -1) {
  852. // register existing modules
  853. regModules.push(moduleName);
  854. var mainModule = angular.module(moduleName);
  855. // register existing components (directives, services, ...)
  856. invokeQueue(null, mainModule._invokeQueue, moduleName);
  857. invokeQueue(null, mainModule._configBlocks, moduleName); // angular 1.3+
  858. angular.forEach(mainModule.requires, addReg);
  859. }
  860. })(moduleName);
  861. }
  862. }
  863. // Array.indexOf polyfill for IE8
  864. if(!Array.prototype.indexOf) {
  865. Array.prototype.indexOf = function(searchElement, fromIndex) {
  866. var k;
  867. // 1. Let O be the result of calling ToObject passing
  868. // the this value as the argument.
  869. if(this == null) {
  870. throw new TypeError('"this" is null or not defined');
  871. }
  872. var O = Object(this);
  873. // 2. Let lenValue be the result of calling the Get
  874. // internal method of O with the argument "length".
  875. // 3. Let len be ToUint32(lenValue).
  876. var len = O.length >>> 0;
  877. // 4. If len is 0, return -1.
  878. if(len === 0) {
  879. return -1;
  880. }
  881. // 5. If argument fromIndex was passed let n be
  882. // ToInteger(fromIndex); else let n be 0.
  883. var n = +fromIndex || 0;
  884. if(Math.abs(n) === Infinity) {
  885. n = 0;
  886. }
  887. // 6. If n >= len, return -1.
  888. if(n >= len) {
  889. return -1;
  890. }
  891. // 7. If n >= 0, then Let k be n.
  892. // 8. Else, n<0, Let k be len - abs(n).
  893. // If k is less than 0, then let k be 0.
  894. k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
  895. // 9. Repeat, while k < len
  896. while(k < len) {
  897. // a. Let Pk be ToString(k).
  898. // This is implicit for LHS operands of the in operator
  899. // b. Let kPresent be the result of calling the
  900. // HasProperty internal method of O with argument Pk.
  901. // This step can be combined with c
  902. // c. If kPresent is true, then
  903. // i. Let elementK be the result of calling the Get
  904. // internal method of O with the argument ToString(k).
  905. // ii. Let same be the result of applying the
  906. // Strict Equality Comparison Algorithm to
  907. // searchElement and elementK.
  908. // iii. If same is true, return k.
  909. if(k in O && O[k] === searchElement) {
  910. return k;
  911. }
  912. k++;
  913. }
  914. return -1;
  915. };
  916. }
  917. })();