From 912711bfa09eb178a96c82997ab47639332007e8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 4 May 2018 00:58:18 +1000 Subject: [PATCH] Fixing tree, navs, initializing, sequence of events, oh my. The init logic is much more stable but we have issues with tracking the mculture query string --- .../components/content/edit.controller.js | 26 +- .../components/tree/umbtree.directive.js | 81 +-- .../src/common/resources/content.resource.js | 8 +- .../javascriptlibrary.resource.js} | 9 +- .../src/common/services/assets.service.js | 476 ++++++++++-------- .../src/common/services/navigation.service.js | 73 +-- .../src/common/services/user.service.js | 38 +- .../src/controllers/main.controller.js | 8 +- .../src/controllers/navigation.controller.js | 221 +++++--- src/Umbraco.Web.UI.Client/src/init.js | 37 +- src/Umbraco.Web.UI.Client/src/routes.js | 18 +- .../application/umb-navigation.html | 5 +- .../views/content/content.edit.controller.js | 2 +- .../umbraco/Views/Default.cshtml | 2 +- 14 files changed, 562 insertions(+), 442 deletions(-) rename src/Umbraco.Web.UI.Client/src/common/{services/javascriptlibrary.service.js => resources/javascriptlibrary.resource.js} (72%) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 63bdb9c92e..c7db9addf8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -124,7 +124,7 @@ * This does the content loading and initializes everything, called on load and changing variants * @param {any} culture */ - function getNode(culture) { + function loadContent(culture) { $scope.page.loading = true; @@ -258,17 +258,33 @@ else { //Browse content nodes based on the selected tree language variant - $scope.page.culture ? getNode($scope.page.culture) : getNode(); - + if ($scope.page.culture) { + loadContent($scope.page.culture); + } + else { + loadContent(); + } } $scope.unPublish = function () { + //if there's any variants than we need to set the language and include the variants to publish + var culture = null; + if ($scope.content.variants.length > 0) { + _.each($scope.content.variants, + function (d) { + //set the culture if this is current + if (d.current === true) { + culture = d.language.culture; + } + }); + } + if (formHelper.submitForm({ scope: $scope, skipValidation: true })) { $scope.page.buttonGroupState = "busy"; - contentResource.unPublish($scope.content.id) + contentResource.unPublish($scope.content.id, culture) .then(function (data) { formHelper.resetForm({ scope: $scope, notifications: data.notifications }); @@ -494,7 +510,7 @@ notificationsService.success("Successfully restored " + node.name + " to " + target.name); // reload the node - getNode(); + loadContent(); }, function (err) { $scope.page.buttonRestore = "error"; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index 2552a22a70..be743ea21c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -131,8 +131,29 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat treeService.clearCache({ section: section }); } - function load(section) { - $scope.section = section; + /** + * Re-loads the tree with the updated parameters + * @param {any} args either a string representing the 'section' or an object containing: 'section', 'treeAlias', 'customTreeParams', 'cacheKey' + */ + function load(args) { + if (angular.isString(args)) { + $scope.section = args; + } + else if (args) { + if (args.section) { + $scope.section = args.section; + } + if (args.customTreeParams) { + $scope.customtreeparams = args.customTreeParams; + } + if (args.treeAlias) { + $scope.treealias = args.treeAlias; + } + if (args.cacheKey) { + $scope.cachekey = args.cacheKey; + } + } + return loadTree(); } @@ -399,33 +420,37 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat $scope.altSelect = function (n, ev) { emitEvent("treeNodeAltSelect", { element: $element, tree: $scope.tree, node: n, event: ev }); - }; - - //watch for section changes - $scope.$watch("section", function (newVal, oldVal) { - - if (!$scope.tree) { - loadTree(); - } - - if (!newVal) { - //store the last section loaded - lastSection = oldVal; - } - else if (newVal !== oldVal && newVal !== lastSection) { - //only reload the tree data and Dom if the newval is different from the old one - // and if the last section loaded is different from the requested one. - loadTree(); - - //store the new section to be loaded as the last section - //clear any active trees to reset lookups - lastSection = newVal; - } - }); + }; - loadTree(); - - $scope.onInit(); + //call the onInit method, if the result is a promise then load the tree after that resolves (if it's not a promise this will just resolve automatically). + //NOTE: The promise cannot be rejected, else the tree won't be loaded and we'll get exceptions if some API calls syncTree or similar. + $q.when($scope.onInit(), function (args) { + + //the promise resolution can pass in parameters + if (args) { + if (args.section) { + $scope.section = args.section; + } + if (args.customTreeParams) { + $scope.customtreeparams = args.customTreeParams; + } + if (args.treealias) { + $scope.treealias = args.treealias; + } + if (args.cacheKey) { + $scope.cachekey = args.cacheKey; + } + } + + //load the tree + loadTree().then(function () { + //because angular doesn't return a promise for the resolve method, we need to resort to some hackery, else + //like normal JS promises we could do resolve(...).then() + if (args.onLoaded && angular.isFunction(args.onLoaded)) { + args.onLoaded(); + } + }); + }); } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 1f2a611fe5..a831ecd4f1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -215,17 +215,21 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * @returns {Promise} resourcePromise object. * */ - unPublish: function (id) { + unPublish: function (id, culture) { if (!id) { throw "id cannot be null"; } + + if (!culture) { + culture = null; + } return umbRequestHelper.resourcePromise( $http.post( umbRequestHelper.getApiUrl( "contentApiBaseUrl", "PostUnPublish", - [{ id: id }])), + { id: id, culture: culture })), 'Failed to publish content with id ' + id); }, /** diff --git a/src/Umbraco.Web.UI.Client/src/common/services/javascriptlibrary.service.js b/src/Umbraco.Web.UI.Client/src/common/resources/javascriptlibrary.resource.js similarity index 72% rename from src/Umbraco.Web.UI.Client/src/common/services/javascriptlibrary.service.js rename to src/Umbraco.Web.UI.Client/src/common/resources/javascriptlibrary.resource.js index 4829dff506..fbd54433d7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/javascriptlibrary.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/javascriptlibrary.resource.js @@ -1,7 +1,12 @@ (function () { "use strict"; - function javascriptLibraryService($q, $http, umbRequestHelper) { + /** + * @ngdoc service + * @name umbraco.resources.javascriptLibraryResource + * @description Handles retrieving data for javascript libraries on the server + **/ + function javascriptLibraryResource($q, $http, umbRequestHelper) { var existingLocales = []; @@ -32,6 +37,6 @@ return service; } - angular.module("umbraco.services").factory("javascriptLibraryService", javascriptLibraryService); + angular.module("umbraco.resources").factory("javascriptLibraryResource", javascriptLibraryResource); })(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js index 3b49df10a6..a1257c00b3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js @@ -41,237 +41,271 @@ * */ angular.module('umbraco.services') -.factory('assetsService', function ($q, $log, angularHelper, umbRequestHelper, $rootScope, $http) { + .factory('assetsService', function ($q, $log, angularHelper, umbRequestHelper, $rootScope, $http, userService, javascriptLibraryResource) { - var initAssetsLoaded = false; + var initAssetsLoaded = false; - function appendRnd (url) { - //if we don't have a global umbraco obj yet, the app is bootstrapping - if (!Umbraco.Sys.ServerVariables.application) { + function appendRnd(url) { + //if we don't have a global umbraco obj yet, the app is bootstrapping + if (!Umbraco.Sys.ServerVariables.application) { + return url; + } + + var rnd = Umbraco.Sys.ServerVariables.application.cacheBuster; + var _op = (url.indexOf("?") > 0) ? "&" : "?"; + url = url + _op + "umb__rnd=" + rnd; return url; + }; + + function convertVirtualPath(path) { + //make this work for virtual paths + if (path.startsWith("~/")) { + path = umbRequestHelper.convertVirtualToAbsolutePath(path); + } + return path; } - - var rnd = Umbraco.Sys.ServerVariables.application.cacheBuster; - var _op = (url.indexOf("?") > 0) ? "&" : "?"; - url = url + _op + "umb__rnd=" + rnd; - return url; - }; - - function convertVirtualPath(path) { - //make this work for virtual paths - if (path.startsWith("~/")) { - path = umbRequestHelper.convertVirtualToAbsolutePath(path); - } - return path; - } - - var service = { - loadedAssets: {}, - - _getAssetPromise: function (path) { - - if (this.loadedAssets[path]) { - return this.loadedAssets[path]; - } else { - var deferred = $q.defer(); - this.loadedAssets[path] = { deferred: deferred, state: "new", path: path }; - return this.loadedAssets[path]; - } - }, - + /** - Internal method. This is called when the application is loading and the user is already authenticated, or once the user is authenticated. - There's a few assets the need to be loaded for the application to function but these assets require authentication to load. - */ - _loadInitAssets: function () { - var deferred = $q.defer(); - //here we need to ensure the required application assets are loaded - if (initAssetsLoaded === false) { - var self = this; - self.loadJs(umbRequestHelper.getApiUrl("serverVarsJs", "", ""), $rootScope).then(function () { - initAssetsLoaded = true; - - //now we need to go get the legacyTreeJs - but this can be done async without waiting. - self.loadJs(umbRequestHelper.getApiUrl("legacyTreeJs", "", ""), $rootScope); - - deferred.resolve(); - }); - } - else { - deferred.resolve(); - } - return deferred.promise; - }, - - /** - * @ngdoc method - * @name umbraco.services.assetsService#loadCss - * @methodOf umbraco.services.assetsService - * - * @description - * Injects a file as a stylesheet into the document head - * - * @param {String} path path to the css file to load - * @param {Scope} scope optional scope to pass into the loader - * @param {Object} keyvalue collection of attributes to pass to the stylesheet element - * @param {Number} timeout in milliseconds - * @returns {Promise} Promise object which resolves when the file has loaded - */ - loadCss: function (path, scope, attributes, timeout) { - - path = convertVirtualPath(path); - - var asset = this._getAssetPromise(path); // $q.defer(); - var t = timeout || 5000; - var a = attributes || undefined; - - if (asset.state === "new") { - asset.state = "loading"; - LazyLoad.css(appendRnd(path), function () { - if (!scope) { - scope = $rootScope; + * Loads in moment.js requirements during the _loadInitAssets call + */ + function loadMomentLocaleForCurrentUser() { + + var self = this; + + function loadLocales(currentUser, supportedLocales) { + var locale = currentUser.locale.toLowerCase(); + if (locale !== 'en-us') { + var localeUrls = []; + if (supportedLocales.indexOf(locale + '.js') > -1) { + localeUrls.push('lib/moment/' + locale + '.js'); } - asset.state = "loaded"; - angularHelper.safeApply(scope, function () { - asset.deferred.resolve(true); - }); - }); - } else if (asset.state === "loaded") { - asset.deferred.resolve(true); - } - return asset.deferred.promise; - }, - - /** - * @ngdoc method - * @name umbraco.services.assetsService#loadJs - * @methodOf umbraco.services.assetsService - * - * @description - * Injects a file as a javascript into the document - * - * @param {String} path path to the js file to load - * @param {Scope} scope optional scope to pass into the loader - * @param {Object} keyvalue collection of attributes to pass to the script element - * @param {Number} timeout in milliseconds - * @returns {Promise} Promise object which resolves when the file has loaded - */ - loadJs: function (path, scope, attributes, timeout) { - - path = convertVirtualPath(path); - - var asset = this._getAssetPromise(path); // $q.defer(); - var t = timeout || 5000; - var a = attributes || undefined; - - if (asset.state === "new") { - asset.state = "loading"; - - LazyLoad.js(appendRnd(path), function () { - if (!scope) { - scope = $rootScope; + if (locale.indexOf('-') > -1) { + var majorLocale = locale.split('-')[0] + '.js'; + if (supportedLocales.indexOf(majorLocale) > -1) { + localeUrls.push('lib/moment/' + majorLocale); + } } - asset.state = "loaded"; - angularHelper.safeApply(scope, function () { - asset.deferred.resolve(true); - }); + return self.load(localeUrls, $rootScope); + } + else { + $q.when(true); + } + } + + userService.getCurrentUser().then(function (currentUser) { + return javascriptLibraryResource.getSupportedLocalesForMoment().then(function (supportedLocales) { + return loadLocales(currentUser, supportedLocales); }); - - } else if (asset.state === "loaded") { - asset.deferred.resolve(true); - } - - return asset.deferred.promise; - }, - - /** - * @ngdoc method - * @name umbraco.services.assetsService#load - * @methodOf umbraco.services.assetsService - * - * @description - * Injects a collection of css and js files - * - * - * @param {Array} pathArray string array of paths to the files to load - * @param {Scope} scope optional scope to pass into the loader - * @returns {Promise} Promise object which resolves when all the files has loaded - */ - load: function (pathArray, scope) { - var promise; - - if (!angular.isArray(pathArray)) { - throw "pathArray must be an array"; - } - - // Check to see if there's anything to load, resolve promise if not - var nonEmpty = _.reject(pathArray, function (item) { - return item === undefined || item === ""; }); - if (nonEmpty.length === 0) { - var deferred = $q.defer(); - promise = deferred.promise; - deferred.resolve(true); + } + + var service = { + loadedAssets: {}, + + _getAssetPromise: function (path) { + + if (this.loadedAssets[path]) { + return this.loadedAssets[path]; + } else { + var deferred = $q.defer(); + this.loadedAssets[path] = { deferred: deferred, state: "new", path: path }; + return this.loadedAssets[path]; + } + }, + + /** + Internal method. This is called when the application is loading and the user is already authenticated, or once the user is authenticated. + There's a few assets the need to be loaded for the application to function but these assets require authentication to load. + */ + _loadInitAssets: function () { + + //here we need to ensure the required application assets are loaded + if (initAssetsLoaded === false) { + var self = this; + return self.loadJs(umbRequestHelper.getApiUrl("serverVarsJs", "", ""), $rootScope).then(function () { + initAssetsLoaded = true; + + //now we need to go get the legacyTreeJs - but this can be done async without waiting. + self.loadJs(umbRequestHelper.getApiUrl("legacyTreeJs", "", ""), $rootScope); + + return loadMomentLocaleForCurrentUser(); + }); + } + else { + return $q.when(true); + } + }, + + /** + * @ngdoc method + * @name umbraco.services.assetsService#loadCss + * @methodOf umbraco.services.assetsService + * + * @description + * Injects a file as a stylesheet into the document head + * + * @param {String} path path to the css file to load + * @param {Scope} scope optional scope to pass into the loader + * @param {Object} keyvalue collection of attributes to pass to the stylesheet element + * @param {Number} timeout in milliseconds + * @returns {Promise} Promise object which resolves when the file has loaded + */ + loadCss: function (path, scope, attributes, timeout) { + + path = convertVirtualPath(path); + + var asset = this._getAssetPromise(path); // $q.defer(); + var t = timeout || 5000; + var a = attributes || undefined; + + if (asset.state === "new") { + asset.state = "loading"; + LazyLoad.css(appendRnd(path), function () { + if (!scope) { + scope = $rootScope; + } + asset.state = "loaded"; + angularHelper.safeApply(scope, function () { + asset.deferred.resolve(true); + }); + }); + } else if (asset.state === "loaded") { + asset.deferred.resolve(true); + } + return asset.deferred.promise; + }, + + /** + * @ngdoc method + * @name umbraco.services.assetsService#loadJs + * @methodOf umbraco.services.assetsService + * + * @description + * Injects a file as a javascript into the document + * + * @param {String} path path to the js file to load + * @param {Scope} scope optional scope to pass into the loader + * @param {Object} keyvalue collection of attributes to pass to the script element + * @param {Number} timeout in milliseconds + * @returns {Promise} Promise object which resolves when the file has loaded + */ + loadJs: function (path, scope, attributes, timeout) { + + path = convertVirtualPath(path); + + var asset = this._getAssetPromise(path); // $q.defer(); + var t = timeout || 5000; + var a = attributes || undefined; + + if (asset.state === "new") { + asset.state = "loading"; + + LazyLoad.js(appendRnd(path), function () { + if (!scope) { + scope = $rootScope; + } + asset.state = "loaded"; + angularHelper.safeApply(scope, function () { + asset.deferred.resolve(true); + }); + }); + + } else if (asset.state === "loaded") { + asset.deferred.resolve(true); + } + + return asset.deferred.promise; + }, + + /** + * @ngdoc method + * @name umbraco.services.assetsService#load + * @methodOf umbraco.services.assetsService + * + * @description + * Injects a collection of css and js files + * + * + * @param {Array} pathArray string array of paths to the files to load + * @param {Scope} scope optional scope to pass into the loader + * @returns {Promise} Promise object which resolves when all the files has loaded + */ + load: function (pathArray, scope) { + var promise; + + if (!angular.isArray(pathArray)) { + throw "pathArray must be an array"; + } + + // Check to see if there's anything to load, resolve promise if not + var nonEmpty = _.reject(pathArray, function (item) { + return item === undefined || item === ""; + }); + + if (nonEmpty.length === 0) { + var deferred = $q.defer(); + promise = deferred.promise; + deferred.resolve(true); + return promise; + } + + //compile a list of promises + //blocking + var promises = []; + var assets = []; + _.each(nonEmpty, function (path) { + path = convertVirtualPath(path); + var asset = service._getAssetPromise(path); + //if not previously loaded, add to list of promises + if (asset.state !== "loaded") { + if (asset.state === "new") { + asset.state = "loading"; + assets.push(asset); + } + + //we need to always push to the promises collection to monitor correct execution + promises.push(asset.deferred.promise); + } + }); + + //gives a central monitoring of all assets to load + promise = $q.all(promises); + + // Split into css and js asset arrays, and use LazyLoad on each array + var cssAssets = _.filter(assets, + function (asset) { + return asset.path.match(/(\.css$|\.css\?)/ig); + }); + var jsAssets = _.filter(assets, + function (asset) { + return asset.path.match(/(\.js$|\.js\?)/ig); + }); + + function assetLoaded(asset) { + asset.state = "loaded"; + if (!scope) { + scope = $rootScope; + } + angularHelper.safeApply(scope, + function () { + asset.deferred.resolve(true); + }); + } + + if (cssAssets.length > 0) { + var cssPaths = _.map(cssAssets, function (asset) { return appendRnd(asset.path) }); + LazyLoad.css(cssPaths, function () { _.each(cssAssets, assetLoaded); }); + } + + if (jsAssets.length > 0) { + var jsPaths = _.map(jsAssets, function (asset) { return appendRnd(asset.path) }); + LazyLoad.js(jsPaths, function () { _.each(jsAssets, assetLoaded); }); + } + return promise; } + }; - //compile a list of promises - //blocking - var promises = []; - var assets = []; - _.each(nonEmpty, function (path) { - path = convertVirtualPath(path); - var asset = service._getAssetPromise(path); - //if not previously loaded, add to list of promises - if (asset.state !== "loaded") { - if (asset.state === "new") { - asset.state = "loading"; - assets.push(asset); - } - - //we need to always push to the promises collection to monitor correct execution - promises.push(asset.deferred.promise); - } - }); - - //gives a central monitoring of all assets to load - promise = $q.all(promises); - - // Split into css and js asset arrays, and use LazyLoad on each array - var cssAssets = _.filter(assets, - function (asset) { - return asset.path.match(/(\.css$|\.css\?)/ig); - }); - var jsAssets = _.filter(assets, - function (asset) { - return asset.path.match(/(\.js$|\.js\?)/ig); - }); - - function assetLoaded(asset) { - asset.state = "loaded"; - if (!scope) { - scope = $rootScope; - } - angularHelper.safeApply(scope, - function () { - asset.deferred.resolve(true); - }); - } - - if (cssAssets.length > 0) { - var cssPaths = _.map(cssAssets, function (asset) { return appendRnd(asset.path) }); - LazyLoad.css(cssPaths, function() { _.each(cssAssets, assetLoaded); }); - } - - if (jsAssets.length > 0) { - var jsPaths = _.map(jsAssets, function (asset) { return appendRnd(asset.path) }); - LazyLoad.js(jsPaths, function () { _.each(jsAssets, assetLoaded); }); - } - - return promise; - } - }; - - return service; -}); + return service; + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 9a19304288..27da6be9d3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -17,11 +17,15 @@ */ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeout, $injector, eventsService, dialogService, umbModelMapper, treeService, notificationsService, historyService, appState, angularHelper) { + //the promise that will be resolved when the navigation is ready + var navReadyPromise = $q.defer(); + //the main tree's API reference, this is acquired when the tree has initialized var mainTreeApi = null; eventsService.on("app.navigationReady", function (e, args) { mainTreeApi = args.treeApi; + navReadyPromise.resolve(mainTreeApi); }); //used to track the current dialog object @@ -88,18 +92,35 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo var service = { - /** initializes the navigation service */ - init: function() { - - //keep track of the current section - initially this will always be undefined so - // no point in setting it now until it changes. - $rootScope.$watch(function () { - return $routeParams.section; - }, function (newVal, oldVal) { - appState.setSectionState("currentSection", newVal); - }); + /** + * @ngdoc method + * @name umbraco.services.navigationService#waitForNavReady + * @methodOf umbraco.services.navigationService + * + * @description + * returns a promise that will resolve when the navigation is ready + */ + waitForNavReady: function () { + return navReadyPromise.promise; + }, + /** + * @ngdoc method + * @name umbraco.services.navigationService#setMainCulture + * @methodOf umbraco.services.navigationService + * + * @description + * Utility to set the mculture query string without changing the route. This is a hack to work around angular's limitations + */ + setMainCulture: function (culture) { + + $location.search("mculture", culture); + //fixme: This can work but interferes with our other $locationChangeStart + //var un = $rootScope.$on('$locationChangeStart', function (event) { + // event.preventDefault(); + // un(); + //}); }, /** @@ -174,10 +195,12 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo appState.setSectionState("currentSection", sectionAlias); if (syncArgs) { - this.syncTree(syncArgs); + return this.syncTree(syncArgs); } } setMode("tree"); + + return $q.when(true); }, showTray: function () { @@ -217,13 +240,9 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo throw "args.tree cannot be null"; } - if (mainTreeApi) { - //returns a promise, + return navReadyPromise.promise.then(function () { return mainTreeApi.syncTree(args); - } - - //couldn't sync - return $q.reject(); + }); }, /** @@ -231,28 +250,22 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo have to set an active tree and then sync, the new API does this in one method by using syncTree */ _syncPath: function(path, forceReload) { - if (mainTreeApi) { - mainTreeApi.syncTree({ path: path, forceReload: forceReload }); - } + return navReadyPromise.promise.then(function () { + return mainTreeApi.syncTree({ path: path, forceReload: forceReload }); + }); }, reloadNode: function(node) { - if (mainTreeApi) { + return navReadyPromise.promise.then(function () { return mainTreeApi.reloadNode(node); - } - else { - return $q.reject(); - } + }); }, reloadSection: function(sectionAlias) { - if (mainTreeApi) { + return navReadyPromise.promise.then(function () { mainTreeApi.clearCache({ section: sectionAlias }); return mainTreeApi.load(sectionAlias); - } - else { - return $q.reject(); - } + }); }, /** diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index 316a75117b..34aa6295b9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -1,5 +1,5 @@ angular.module('umbraco.services') - .factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, assetsService, dialogService, $timeout, angularHelper, $http, javascriptLibraryService) { + .factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, dialogService, $timeout, angularHelper, $http) { var currentUser = null; var lastUserId = null; @@ -273,42 +273,6 @@ angular.module('umbraco.services') } }, - /** Loads the Moment.js Locale for the current user. */ - loadMomentLocaleForCurrentUser: function () { - - function loadLocales(currentUser, supportedLocales) { - var locale = currentUser.locale.toLowerCase(); - if (locale !== 'en-us') { - var localeUrls = []; - if (supportedLocales.indexOf(locale + '.js') > -1) { - localeUrls.push('lib/moment/' + locale + '.js'); - } - if (locale.indexOf('-') > -1) { - var majorLocale = locale.split('-')[0] + '.js'; - if (supportedLocales.indexOf(majorLocale) > -1) { - localeUrls.push('lib/moment/' + majorLocale); - } - } - return assetsService.load(localeUrls, $rootScope); - } - else { - $q.when(true); - } - } - - var promises = { - currentUser: this.getCurrentUser(), - supportedLocales: javascriptLibraryService.getSupportedLocalesForMoment() - } - - return $q.all(promises).then(function (values) { - return loadLocales(values.currentUser, values.supportedLocales); - }); - - - - }, - /** Called whenever a server request is made that contains a x-umb-user-seconds response header for which we can update the user's remaining timeout seconds */ setUserTimeout: function (newTimeout) { setUserTimeoutInternal(newTimeout); diff --git a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js index b4a1f284d5..caa830c075 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js @@ -14,9 +14,7 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $ $scope.authenticated = null; $scope.touchDevice = appState.getGlobalState("touchDevice"); $scope.overlay = {}; - $scope.navReady = false; - - + $scope.removeNotification = function (index) { notificationsService.remove(index); }; @@ -61,10 +59,6 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $ }); })); - evts.push(eventsService.on('app.navigationReady', function () { - $scope.navReady = true; - })); - //when the app is ready/user is logged in, setup the data evts.push(eventsService.on("app.ready", function (evt, data) { diff --git a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js index 00458aa6b4..2c92d1d10c 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js @@ -11,6 +11,9 @@ */ function NavigationController($scope, $rootScope, $location, $log, $q, $routeParams, $timeout, treeService, appState, navigationService, keyboardService, dialogService, historyService, eventsService, sectionResource, angularHelper, languageResource) { + //this is used to trigger the tree to start loading once everything is ready + var treeInitPromise = $q.defer(); + $scope.treeApi = {}; //Bind to the main tree events @@ -107,8 +110,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar navigationService.hideNavigation(); }); - //once this is wired up, the nav is ready - eventsService.emit("app.navigationReady", { treeApi: $scope.treeApi}); + return treeInitPromise.promise; } //TODO: Remove this, this is not healthy @@ -133,7 +135,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar $scope.page = {}; $scope.page.languageSelectorIsOpen = false; - $scope.currentSection = appState.getSectionState("currentSection"); + $scope.currentSection = null; $scope.customTreeParams = null; $scope.treeCacheKey = "_"; $scope.showNavigation = appState.getGlobalState("showNavigation"); @@ -155,14 +157,14 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar var evts = []; //Listen for global state changes - evts.push(eventsService.on("appState.globalState.changed", function(e, args) { + evts.push(eventsService.on("appState.globalState.changed", function (e, args) { if (args.key === "showNavigation") { $scope.showNavigation = args.value; } })); //Listen for menu state changes - evts.push(eventsService.on("appState.menuState.changed", function(e, args) { + evts.push(eventsService.on("appState.menuState.changed", function (e, args) { if (args.key === "showMenuDialog") { $scope.showContextMenuDialog = args.value; } @@ -181,7 +183,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar })); //Listen for section state changes - evts.push(eventsService.on("appState.treeState.changed", function(e, args) { + evts.push(eventsService.on("appState.treeState.changed", function (e, args) { var f = args; if (args.value.root && args.value.root.metaData.containsTrees === false) { $rootScope.emptySection = true; @@ -192,32 +194,38 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar })); //Listen for section state changes - evts.push(eventsService.on("appState.sectionState.changed", function(e, args) { + evts.push(eventsService.on("appState.sectionState.changed", function (e, args) { //section changed if (args.key === "currentSection") { $scope.currentSection = args.value; + + //load the tree + configureTreeAndLanguages(); + $scope.treeApi.load({ section: $scope.currentSection, customTreeParams: $scope.customTreeParams, cacheKey: $scope.treeCacheKey }); + } //show/hide search results if (args.key === "showSearchResults") { $scope.showSearchResults = args.value; } + })); // Listen for language updates - evts.push(eventsService.on("editors.languages.languageDeleted", function(e, args) { - languageResource.getAll().then(function(languages) { + evts.push(eventsService.on("editors.languages.languageDeleted", function (e, args) { + languageResource.getAll().then(function (languages) { $scope.languages = languages; }); })); - evts.push(eventsService.on("editors.languages.languageCreated", function(e, args) { - languageResource.getAll().then(function(languages) { + evts.push(eventsService.on("editors.languages.languageCreated", function (e, args) { + languageResource.getAll().then(function (languages) { $scope.languages = languages; }); })); //This reacts to clicks passed to the body element which emits a global call to close all dialogs - evts.push(eventsService.on("app.closeDialogs", function(event) { + evts.push(eventsService.on("app.closeDialogs", function (event) { if (appState.getGlobalState("stickyNavigation")) { navigationService.hideNavigation(); //TODO: don't know why we need this? - we are inside of an angular event listener. @@ -226,52 +234,43 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar })); //when a user logs out or timesout - evts.push(eventsService.on("app.notAuthenticated", function() { + evts.push(eventsService.on("app.notAuthenticated", function () { $scope.authenticated = false; })); //when the application is ready and the user is authorized setup the data - evts.push(eventsService.on("app.ready", function(evt, data) { - $scope.authenticated = true; - - // load languages - languageResource.getAll().then(function(languages) { - $scope.languages = languages; + evts.push(eventsService.on("app.ready", function (evt, data) { + init(); + })); - if ($scope.languages.length > 1) { - var defaultLang = _.find($scope.languages, function (l) { - return l.isDefault; + /** + * Based on the current state of the application, this configures the scope variables that control the main tree and language drop down + */ + function configureTreeAndLanguages() { + + //create the custom query string param for this tree, this is currently only relevant for content + if ($scope.currentSection === "content") { + + //must use $location here because $routeParams isn't available until after the route change + var mainCulture = $location.search().mculture; + //select the current language if set in the query string + if (mainCulture && $scope.languages && $scope.languages.length > 1) { + var found = _.find($scope.languages, function (l) { + return l.culture.toLowerCase() === mainCulture.toLowerCase(); }); - if (defaultLang) { + if (found) { //set the route param - $location.search("mculture", defaultLang.culture); + $scope.selectedLanguage = found; } } - init(); - }); - })); - - function init() { - //select the current language if set in the query string - var mainCulture = $location.search().mculture; - if (mainCulture && $scope.languages && $scope.languages.length > 1) { - var found = _.find($scope.languages, function (l) { - return l.culture === mainCulture; - }); - if (found) { - //set the route param - $scope.selectedLanguage = found; + var queryParams = {}; + if ($scope.selectedLanguage && $scope.selectedLanguage.culture) { + queryParams["culture"] = $scope.selectedLanguage.culture; } + var queryString = $.param(queryParams); //create the query string from the params object } - //create the custom query string param for this tree - var queryParams = {}; - if ($scope.selectedLanguage && $scope.selectedLanguage.culture) { - queryParams["culture"] = $scope.selectedLanguage.culture; - } - var queryString = $.param(queryParams); //create the query string from the params object - if (queryString) { $scope.customTreeParams = queryString; $scope.treeCacheKey = queryString; // this tree uses caching but we need to change it's cache key per lang @@ -279,8 +278,87 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar else { $scope.treeCacheKey = "_"; // this tree uses caching, there's no lang selected so use the default } + } + /** + * Called when the app is ready and sets up the navigation (should only be called once) + */ + function init() { + + $scope.authenticated = true; + + var navInit = false; + + //$routeParams will be populated after $routeChangeSuccess since this controller is used outside ng-view, + //* we listen for the first route change with a section to setup the navigation. + //* we listen for all route changes to track the current section. + $rootScope.$on('$routeChangeSuccess', function () { + + //only continue if there's a section available + if ($routeParams.section) { + + if (!navInit) { + navInit = true; + initNav(); + } + else { + //keep track of the current section, when it changes change the state, and we listen for that event change above + if ($scope.currentSection != $routeParams.section) { + appState.setSectionState("currentSection", $routeParams.section); + } + } + } + }); + } + + /** + * Called once during init to initialize the navigation/tree/languages + */ + function initNav() { + // load languages + languageResource.getAll().then(function (languages) { + + $scope.languages = languages; + + if ($scope.languages.length > 1) { + var defaultLang = _.find($scope.languages, function (l) { + return l.isDefault; + }); + //if there's already one set, check if it exists + var currCulture = null; + var mainCulture = $location.search().mculture; + if (mainCulture) { + currCulture = _.find($scope.languages, function (l) { + return l.culture.toLowerCase() === mainCulture.toLowerCase(); + }); + } + if (!currCulture) { + navigationService.setMainCulture(defaultLang ? defaultLang.culture : null); + } + } + + $scope.currentSection = $routeParams.section; + + configureTreeAndLanguages(); + + //resolve the tree promise, set it's property values for loading the tree which will make the tree load + treeInitPromise.resolve({ + section: $scope.currentSection, + customTreeParams: $scope.customTreeParams, + cacheKey: $scope.treeCacheKey, + + //because angular doesn't return a promise for the resolve method, we need to resort to some hackery, else + //like normal JS promises we could do resolve(...).then() + onLoaded: function () { + //the nav is ready, let the app know + eventsService.emit("app.navigationReady", { treeApi: $scope.treeApi }); + //finally set the section state + appState.setSectionState("currentSection", $routeParams.section); + } + }); + }); + } function nodeExpandedHandler(args) { //store the reference to the expanded node path @@ -289,40 +367,37 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar } } - $scope.selectLanguage = function(language) { + $scope.selectLanguage = function (language) { - $location.search("mculture", language.culture); + navigationService.setMainCulture(language.culture); //$scope.selectedLanguage = language; // close the language selector $scope.page.languageSelectorIsOpen = false; - init(); //re-bind language to the query string and update the tree params + configureTreeAndLanguages(); //re-bind language to the query string and update the tree params - //execute after next digest because the internal watch on the customtreeparams needs to be bound now that we've changed it - $timeout(function () { - //reload the tree with it's updated querystring args - $scope.treeApi.load($scope.currentSection).then(function () { + //reload the tree with it's updated querystring args + $scope.treeApi.load({ section: $scope.currentSection, customTreeParams: $scope.customTreeParams, cacheKey: $scope.treeCacheKey }).then(function () { - //re-sync to currently edited node - var currNode = appState.getTreeState("selectedNode"); - //create the list of promises - var promises = []; - //starting with syncing to the currently selected node if there is one - if (currNode) { - var path = treeService.getPath(currNode); - promises.push($scope.treeApi.syncTree({ path: path, activate: true })); - } - //TODO: If we want to keep all paths expanded ... but we need more testing since we need to deal with unexpanding - //for (var i = 0; i < expandedPaths.length; i++) { - // promises.push($scope.treeApi.syncTree({ path: expandedPaths[i], activate: false, forceReload: true })); - //} - //execute them sequentially - angularHelper.executeSequentialPromises(promises); - }); + //re-sync to currently edited node + var currNode = appState.getTreeState("selectedNode"); + //create the list of promises + var promises = []; + //starting with syncing to the currently selected node if there is one + if (currNode) { + var path = treeService.getPath(currNode); + promises.push($scope.treeApi.syncTree({ path: path, activate: true })); + } + //TODO: If we want to keep all paths expanded ... but we need more testing since we need to deal with unexpanding + //for (var i = 0; i < expandedPaths.length; i++) { + // promises.push($scope.treeApi.syncTree({ path: expandedPaths[i], activate: false, forceReload: true })); + //} + //execute them sequentially + angularHelper.executeSequentialPromises(promises); }); - + }; //this reacts to the options item in the tree @@ -350,22 +425,22 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar }; // Hides navigation tree, with a short delay, is cancelled if the user moves the mouse over the tree again - $scope.leaveTree = function(event) { + $scope.leaveTree = function (event) { //this is a hack to handle IE touch events which freaks out due to no mouse events so the tree instantly shuts down if (!event) { return; } if (!appState.getGlobalState("touchDevice")) { treeActive = false; - $timeout(function() { + $timeout(function () { if (!treeActive) { navigationService.hideTree(); } }, 300); } }; - - $scope.toggleLanguageSelector = function() { + + $scope.toggleLanguageSelector = function () { $scope.page.languageSelectorIsOpen = !$scope.page.languageSelectorIsOpen; }; diff --git a/src/Umbraco.Web.UI.Client/src/init.js b/src/Umbraco.Web.UI.Client/src/init.js index 7fb35a4cd0..20b80b26d9 100644 --- a/src/Umbraco.Web.UI.Client/src/init.js +++ b/src/Umbraco.Web.UI.Client/src/init.js @@ -1,7 +1,11 @@ /** Executed when the application starts, binds to events and set global state */ app.run(['userService', '$q', '$log', '$rootScope', '$location', 'queryStrings', 'navigationService', 'appState', 'editorState', 'fileManager', 'assetsService', 'eventsService', '$cookies', '$templateCache', 'localStorageService', 'tourService', 'dashboardResource', function (userService, $q, $log, $rootScope, $location, queryStrings, navigationService, appState, editorState, fileManager, assetsService, eventsService, $cookies, $templateCache, localStorageService, tourService, dashboardResource) { - + + $rootScope.$on('$locationChangeStart', function (event, next, current, newState, oldState) { + $log.info("location changing to:" + next); + }); + //This sets the default jquery ajax headers to include our csrf token, we // need to user the beforeSend method because our token changes per user/login so // it cannot be static @@ -17,27 +21,17 @@ app.run(['userService', '$q', '$log', '$rootScope', '$location', 'queryStrings', /** Listens for authentication and checks if our required assets are loaded, if/once they are we'll broadcast a ready event */ eventsService.on("app.authenticated", function (evt, data) { - assetsService._loadInitAssets().then(function() { - $q.all([ - userService.loadMomentLocaleForCurrentUser(), - tourService.registerAllTours() - ]).then(function () { + assetsService._loadInitAssets().then(function () { - //Register all of the tours on the server - tourService.registerAllTours().then(function () { - appReady(data); + appReady(data); - // Auto start intro tour - tourService.getTourByAlias("umbIntroIntroduction").then(function (introTour) { - // start intro tour if it hasn't been completed or disabled - if (introTour && introTour.disabled !== true && introTour.completed !== true) { - tourService.startTour(introTour); - } - }); - - }, function () { - appAuthenticated = true; - appReady(data); + tourService.registerAllTours().then(function () { + // Auto start intro tour + tourService.getTourByAlias("umbIntroIntroduction").then(function (introTour) { + // start intro tour if it hasn't been completed or disabled + if (introTour && introTour.disabled !== true && introTour.completed !== true) { + tourService.startTour(introTour); + } }); }); }); @@ -113,9 +107,6 @@ app.run(['userService', '$q', '$log', '$rootScope', '$location', 'queryStrings', }); - /* this will initialize the navigation service once the application has started */ - navigationService.init(); - //check for touch device, add to global appState //var touchDevice = ("ontouchstart" in window || window.touch || window.navigator.msMaxTouchPoints === 5 || window.DocumentTouch && document instanceof DocumentTouch); var touchDevice = /android|webos|iphone|ipad|ipod|blackberry|iemobile|touch/i.test(navigator.userAgent.toLowerCase()); diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js index e4f9852a11..84a1db5aeb 100644 --- a/src/Umbraco.Web.UI.Client/src/routes.js +++ b/src/Umbraco.Web.UI.Client/src/routes.js @@ -1,11 +1,10 @@ app.config(function ($routeProvider) { - + /** * This determines if the route can continue depending on authentication and initialization requirements - * @param {boolean} authRequired If true, it first checks if the user is authenticated and will resolve successfully + * @param {boolean} authRequired If true, it checks if the user is authenticated and will resolve successfully otherwise the route will fail and the $routeChangeError event will execute, in that handler we will redirect to the rejected path that is resolved from this method and prevent default (prevent the route from executing) - * @param {boolean} navRequired if true, the route can only continue once the main navigation is ready * @returns {promise} */ var canRoute = function(authRequired) { @@ -13,7 +12,7 @@ app.config(function ($routeProvider) { return { /** Checks that the user is authenticated, then ensures that are requires assets are loaded */ isAuthenticatedAndReady: function ($q, userService, $route, assetsService, appState) { - + //don't need to check if we've redirected to login and we've already checked auth if (!$route.current.params.section && ($route.current.params.check === false || $route.current.params.check === "false")) { @@ -22,7 +21,8 @@ app.config(function ($routeProvider) { return userService.isAuthenticated() .then(function () { - + + //before proceeding all initial assets must be loaded return assetsService._loadInitAssets().then(function () { //This could be the first time has loaded after the user has logged in, in this case @@ -42,7 +42,7 @@ app.config(function ($routeProvider) { // U4-5430, Benjamin Howarth // We need to change the current route params if the user only has access to a single section // To do this we need to grab the current user's allowed sections, then reject the promise with the correct path. - if (user.allowedSections.indexOf($route.current.params.section) > -1) { + if (user.allowedSections.indexOf($route.current.params.section) > -1) { //this will resolve successfully so the route will continue return $q.when(true); } else { @@ -123,7 +123,7 @@ app.config(function ($routeProvider) { else { //there's no custom route path so continue as normal $routeParams.url = "dashboard.aspx?app=" + $routeParams.section; - $scope.templateUrl = 'views/common/dashboard.html'; + $scope.templateUrl = 'views/common/dashboard.html'; } }); }, @@ -185,5 +185,7 @@ app.config(function ($routeProvider) { }).config(function ($locationProvider) { //$locationProvider.html5Mode(false).hashPrefix('!'); //turn html5 mode off - // $locationProvider.html5Mode(true); //turn html5 mode on + // $locationProvider.html5Mode(true); //turn html5 mode on + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html index a470bfe307..b04eeeda2f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html @@ -18,11 +18,8 @@
+ on-init="onTreeInit()" >
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js index 1bd924e9f0..ec55549c78 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js @@ -21,7 +21,7 @@ function ContentEditController($scope, $routeParams, contentResource) { $scope.getScaffoldMethod = $routeParams.blueprintId ? scaffoldBlueprint : scaffoldEmpty; $scope.page = $routeParams.page; $scope.isNew = $routeParams.create; - $scope.culture = $routeParams.cculture; + $scope.culture = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture; //load the default culture selected in the main tree if any } angular.module("umbraco").controller("Umbraco.Editors.Content.EditController", ContentEditController); diff --git a/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml b/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml index d00cea9657..288ee4cf9c 100644 --- a/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml +++ b/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml @@ -64,7 +64,7 @@ -
+