viewDirective.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. /**
  2. * @ngdoc directive
  3. * @name ui.router.state.directive:ui-view
  4. *
  5. * @requires ui.router.state.$state
  6. * @requires $compile
  7. * @requires $controller
  8. * @requires $injector
  9. * @requires ui.router.state.$uiViewScroll
  10. * @requires $document
  11. *
  12. * @restrict ECA
  13. *
  14. * @description
  15. * The ui-view directive tells $state where to place your templates.
  16. *
  17. * @param {string=} name A view name. The name should be unique amongst the other views in the
  18. * same state. You can have views of the same name that live in different states.
  19. *
  20. * @param {string=} autoscroll It allows you to set the scroll behavior of the browser window
  21. * when a view is populated. By default, $anchorScroll is overridden by ui-router's custom scroll
  22. * service, {@link ui.router.state.$uiViewScroll}. This custom service let's you
  23. * scroll ui-view elements into view when they are populated during a state activation.
  24. *
  25. * *Note: To revert back to old [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll)
  26. * functionality, call `$uiViewScrollProvider.useAnchorScroll()`.*
  27. *
  28. * @param {string=} onload Expression to evaluate whenever the view updates.
  29. *
  30. * @example
  31. * A view can be unnamed or named.
  32. * <pre>
  33. * <!-- Unnamed -->
  34. * <div ui-view></div>
  35. *
  36. * <!-- Named -->
  37. * <div ui-view="viewName"></div>
  38. * </pre>
  39. *
  40. * You can only have one unnamed view within any template (or root html). If you are only using a
  41. * single view and it is unnamed then you can populate it like so:
  42. * <pre>
  43. * <div ui-view></div>
  44. * $stateProvider.state("home", {
  45. * template: "<h1>HELLO!</h1>"
  46. * })
  47. * </pre>
  48. *
  49. * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#views `views`}
  50. * config property, by name, in this case an empty name:
  51. * <pre>
  52. * $stateProvider.state("home", {
  53. * views: {
  54. * "": {
  55. * template: "<h1>HELLO!</h1>"
  56. * }
  57. * }
  58. * })
  59. * </pre>
  60. *
  61. * But typically you'll only use the views property if you name your view or have more than one view
  62. * in the same template. There's not really a compelling reason to name a view if its the only one,
  63. * but you could if you wanted, like so:
  64. * <pre>
  65. * <div ui-view="main"></div>
  66. * </pre>
  67. * <pre>
  68. * $stateProvider.state("home", {
  69. * views: {
  70. * "main": {
  71. * template: "<h1>HELLO!</h1>"
  72. * }
  73. * }
  74. * })
  75. * </pre>
  76. *
  77. * Really though, you'll use views to set up multiple views:
  78. * <pre>
  79. * <div ui-view></div>
  80. * <div ui-view="chart"></div>
  81. * <div ui-view="data"></div>
  82. * </pre>
  83. *
  84. * <pre>
  85. * $stateProvider.state("home", {
  86. * views: {
  87. * "": {
  88. * template: "<h1>HELLO!</h1>"
  89. * },
  90. * "chart": {
  91. * template: "<chart_thing/>"
  92. * },
  93. * "data": {
  94. * template: "<data_thing/>"
  95. * }
  96. * }
  97. * })
  98. * </pre>
  99. *
  100. * Examples for `autoscroll`:
  101. *
  102. * <pre>
  103. * <!-- If autoscroll present with no expression,
  104. * then scroll ui-view into view -->
  105. * <ui-view autoscroll/>
  106. *
  107. * <!-- If autoscroll present with valid expression,
  108. * then scroll ui-view into view if expression evaluates to true -->
  109. * <ui-view autoscroll='true'/>
  110. * <ui-view autoscroll='false'/>
  111. * <ui-view autoscroll='scopeVariable'/>
  112. * </pre>
  113. */
  114. $ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll', '$interpolate'];
  115. function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate) {
  116. function getService() {
  117. return ($injector.has) ? function(service) {
  118. return $injector.has(service) ? $injector.get(service) : null;
  119. } : function(service) {
  120. try {
  121. return $injector.get(service);
  122. } catch (e) {
  123. return null;
  124. }
  125. };
  126. }
  127. var service = getService(),
  128. $animator = service('$animator'),
  129. $animate = service('$animate');
  130. // Returns a set of DOM manipulation functions based on which Angular version
  131. // it should use
  132. function getRenderer(attrs, scope) {
  133. var statics = function() {
  134. return {
  135. enter: function (element, target, cb) { target.after(element); cb(); },
  136. leave: function (element, cb) { element.remove(); cb(); }
  137. };
  138. };
  139. if ($animate) {
  140. return {
  141. enter: function(element, target, cb) {
  142. var promise = $animate.enter(element, null, target, cb);
  143. if (promise && promise.then) promise.then(cb);
  144. },
  145. leave: function(element, cb) {
  146. var promise = $animate.leave(element, cb);
  147. if (promise && promise.then) promise.then(cb);
  148. }
  149. };
  150. }
  151. if ($animator) {
  152. var animate = $animator && $animator(scope, attrs);
  153. return {
  154. enter: function(element, target, cb) {animate.enter(element, null, target); cb(); },
  155. leave: function(element, cb) { animate.leave(element); cb(); }
  156. };
  157. }
  158. return statics();
  159. }
  160. var directive = {
  161. restrict: 'ECA',
  162. terminal: true,
  163. priority: 400,
  164. transclude: 'element',
  165. compile: function (tElement, tAttrs, $transclude) {
  166. return function (scope, $element, attrs) {
  167. var previousEl, currentEl, currentScope, latestLocals,
  168. onloadExp = attrs.onload || '',
  169. autoScrollExp = attrs.autoscroll,
  170. renderer = getRenderer(attrs, scope);
  171. scope.$on('$stateChangeSuccess', function() {
  172. updateView(false);
  173. });
  174. scope.$on('$viewContentLoading', function() {
  175. updateView(false);
  176. });
  177. updateView(true);
  178. function cleanupLastView() {
  179. if (previousEl) {
  180. previousEl.remove();
  181. previousEl = null;
  182. }
  183. if (currentScope) {
  184. currentScope.$destroy();
  185. currentScope = null;
  186. }
  187. if (currentEl) {
  188. renderer.leave(currentEl, function() {
  189. previousEl = null;
  190. });
  191. previousEl = currentEl;
  192. currentEl = null;
  193. }
  194. }
  195. function updateView(firstTime) {
  196. var newScope,
  197. name = getUiViewName(scope, attrs, $element, $interpolate),
  198. previousLocals = name && $state.$current && $state.$current.locals[name];
  199. if (!firstTime && previousLocals === latestLocals) return; // nothing to do
  200. newScope = scope.$new();
  201. latestLocals = $state.$current.locals[name];
  202. var clone = $transclude(newScope, function(clone) {
  203. renderer.enter(clone, $element, function onUiViewEnter() {
  204. if(currentScope) {
  205. currentScope.$emit('$viewContentAnimationEnded');
  206. }
  207. if (angular.isDefined(autoScrollExp) && !autoScrollExp || scope.$eval(autoScrollExp)) {
  208. $uiViewScroll(clone);
  209. }
  210. });
  211. cleanupLastView();
  212. });
  213. currentEl = clone;
  214. currentScope = newScope;
  215. /**
  216. * @ngdoc event
  217. * @name ui.router.state.directive:ui-view#$viewContentLoaded
  218. * @eventOf ui.router.state.directive:ui-view
  219. * @eventType emits on ui-view directive scope
  220. * @description *
  221. * Fired once the view is **loaded**, *after* the DOM is rendered.
  222. *
  223. * @param {Object} event Event object.
  224. */
  225. currentScope.$emit('$viewContentLoaded');
  226. currentScope.$eval(onloadExp);
  227. }
  228. };
  229. }
  230. };
  231. return directive;
  232. }
  233. $ViewDirectiveFill.$inject = ['$compile', '$controller', '$state', '$interpolate'];
  234. function $ViewDirectiveFill ( $compile, $controller, $state, $interpolate) {
  235. return {
  236. restrict: 'ECA',
  237. priority: -400,
  238. compile: function (tElement) {
  239. var initial = tElement.html();
  240. return function (scope, $element, attrs) {
  241. var current = $state.$current,
  242. name = getUiViewName(scope, attrs, $element, $interpolate),
  243. locals = current && current.locals[name];
  244. if (! locals) {
  245. return;
  246. }
  247. $element.data('$uiView', { name: name, state: locals.$$state });
  248. $element.html(locals.$template ? locals.$template : initial);
  249. var link = $compile($element.contents());
  250. if (locals.$$controller) {
  251. locals.$scope = scope;
  252. var controller = $controller(locals.$$controller, locals);
  253. if (locals.$$controllerAs) {
  254. scope[locals.$$controllerAs] = controller;
  255. }
  256. $element.data('$ngControllerController', controller);
  257. $element.children().data('$ngControllerController', controller);
  258. }
  259. link(scope);
  260. };
  261. }
  262. };
  263. }
  264. /**
  265. * Shared ui-view code for both directives:
  266. * Given scope, element, and its attributes, return the view's name
  267. */
  268. function getUiViewName(scope, attrs, element, $interpolate) {
  269. var name = $interpolate(attrs.uiView || attrs.name || '')(scope);
  270. var inherited = element.inheritedData('$uiView');
  271. return name.indexOf('@') >= 0 ? name : (name + '@' + (inherited ? inherited.state.name : ''));
  272. }
  273. angular.module('ui.router.state').directive('uiView', $ViewDirective);
  274. angular.module('ui.router.state').directive('uiView', $ViewDirectiveFill);