text: /*! umbraco
   * https://github.com/umbraco/umbraco-cms/
   * Copyright (c) 2015 Umbraco HQ;
   * Licensed MIT
   */
  
  (function() { 
  
  
  /**
   * @ngdoc controller
   * @name Umbraco.MainController
   * @function
   * 
   * @description
   * The main application controller
   * 
   */
  function MainController($scope, $rootScope, $location, $routeParams, $timeout, $http, $log, appState, treeService, notificationsService, userService, navigationService, historyService, updateChecker, assetsService, eventsService, umbRequestHelper, tmhDynamicLocale) {
  
      //the null is important because we do an explicit bool check on this in the view
      //the avatar is by default the umbraco logo    
      $scope.authenticated = null;
      $scope.avatar = "assets/img/application/logo.png";
      $scope.touchDevice = appState.getGlobalState("touchDevice");
      //subscribes to notifications in the notification service
      $scope.notifications = notificationsService.current;
      $scope.$watch('notificationsService.current', function (newVal, oldVal, scope) {
          if (newVal) {
              $scope.notifications = newVal;
          }
      });
  
      $scope.removeNotification = function (index) {
          notificationsService.remove(index);
      };
  
      $scope.closeDialogs = function (event) {
          //only close dialogs if non-link and non-buttons are clicked
          var el = event.target.nodeName;
          var els = ["INPUT","A","BUTTON"];
  
          if(els.indexOf(el) >= 0){return;}
  
          var parents = $(event.target).parents("a,button");
          if(parents.length > 0){
              return;
          }
  
          //SD: I've updated this so that we don't close the dialog when clicking inside of the dialog
          var nav = $(event.target).parents("#dialog");
          if (nav.length === 1) {
              return;
          }
  
          eventsService.emit("app.closeDialogs", event);
      };
  
      var evts = [];
  
      //when a user logs out or timesout
      evts.push(eventsService.on("app.notAuthenticated", function() {
          $scope.authenticated = null;
          $scope.user = null;
      }));
      
      //when the app is read/user is logged in, setup the data
      evts.push(eventsService.on("app.ready", function (evt, data) {
          
          $scope.authenticated = data.authenticated;
          $scope.user = data.user;
  
          updateChecker.check().then(function(update){
              if(update && update !== "null"){
                  if(update.type !== "None"){
                      var notification = {
                             headline: "Update available",
                             message: "Click to download",
                             sticky: true,
                             type: "info",
                             url: update.url
                      };
                      notificationsService.add(notification);
                  }
              }
          })
  
          //if the user has changed we need to redirect to the root so they don't try to continue editing the
          //last item in the URL (NOTE: the user id can equal zero, so we cannot just do !data.lastUserId since that will resolve to true)
          if (data.lastUserId !== undefined && data.lastUserId !== null && data.lastUserId !== data.user.id) {
              $location.path("/").search("");
              historyService.removeAll();
              treeService.clearCache();
          }
  
          //Load locale file
          if ($scope.user.locale) {
              tmhDynamicLocale.set($scope.user.locale);
          }
  
          if($scope.user.emailHash){
              $timeout(function () {                
                  //yes this is wrong.. 
                  $("#avatar-img").fadeTo(1000, 0, function () {
                      $timeout(function () {
                          //this can be null if they time out
                          if ($scope.user && $scope.user.emailHash) {
                              $scope.avatar = "//www.gravatar.com/avatar/" + $scope.user.emailHash + ".jpg?s=64&d=mm";
                          }
                      });
                      $("#avatar-img").fadeTo(1000, 1);
                  });
                  
                }, 3000);  
          }
  
      }));
  
      //ensure to unregister from all events!
      $scope.$on('$destroy', function () {
          for (var e in evts) {
              eventsService.unsubscribe(evts[e]);
          }
      });
  
  }
  
  
  //register it
  angular.module('umbraco').controller("Umbraco.MainController", MainController).
          config(function (tmhDynamicLocaleProvider) {
              //Set url for locale files
              tmhDynamicLocaleProvider.localeLocationPattern('lib/angular/1.1.5/i18n/angular-locale_{{locale}}.js');
          });
  
  /**
   * @ngdoc controller
   * @name Umbraco.NavigationController
   * @function
   *
   * @description
   * Handles the section area of the app
   *
   * @param {navigationService} navigationService A reference to the navigationService
   */
  function NavigationController($scope, $rootScope, $location, $log, $routeParams, $timeout, appState, navigationService, keyboardService, dialogService, historyService, eventsService, sectionResource, angularHelper) {
  
      //TODO: Need to think about this and an nicer way to acheive what this is doing.
      //the tree event handler i used to subscribe to the main tree click events
      $scope.treeEventHandler = $({});
      navigationService.setupTreeEvents($scope.treeEventHandler);
  
      //Put the navigation service on this scope so we can use it's methods/properties in the view.
      // IMPORTANT: all properties assigned to this scope are generally available on the scope object on dialogs since
      //   when we create a dialog we pass in this scope to be used for the dialog's scope instead of creating a new one.
      $scope.nav = navigationService;
      // TODO: Lets fix this, it is less than ideal to be passing in the navigationController scope to something else to be used as it's scope,
      // this is going to lead to problems/confusion. I really don't think passing scope's around is very good practice.
      $rootScope.nav = navigationService;
  
      //set up our scope vars
      $scope.showContextMenuDialog = false;
      $scope.showContextMenu = false;
      $scope.showSearchResults = false;
      $scope.menuDialogTitle = null;
      $scope.menuActions = [];
      $scope.menuNode = null;
  
      $scope.currentSection = appState.getSectionState("currentSection");
      $scope.showNavigation = appState.getGlobalState("showNavigation");
  
      //trigger search with a hotkey:
      keyboardService.bind("ctrl+shift+s", function () {
          navigationService.showSearch();
      });
  
      //trigger dialods with a hotkey:
      keyboardService.bind("esc", function () {
          eventsService.emit("app.closeDialogs");
      });
  
      $scope.selectedId = navigationService.currentId;
  
      var evts = [];
  
      //Listen for global state changes
      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) {
          if (args.key === "showMenuDialog") {
              $scope.showContextMenuDialog = args.value;
          }
          if (args.key === "showMenu") {
              $scope.showContextMenu = args.value;
          }
          if (args.key === "dialogTitle") {
              $scope.menuDialogTitle = args.value;
          }
          if (args.key === "menuActions") {
              $scope.menuActions = args.value;
          }
          if (args.key === "currentNode") {
              $scope.menuNode = args.value;
          }
      }));
  
      //Listen for section state changes
      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;
          }
          else {
              $rootScope.emptySection = false;
          }
      }));
  
      //Listen for section state changes
      evts.push(eventsService.on("appState.sectionState.changed", function(e, args) {
          //section changed
          if (args.key === "currentSection") {
              $scope.currentSection = args.value;
          }
          //show/hide search results
          if (args.key === "showSearchResults") {
              $scope.showSearchResults = args.value;
          }
      }));
  
      //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) {
          if (appState.getGlobalState("stickyNavigation")) {
              navigationService.hideNavigation();
              //TODO: don't know why we need this? - we are inside of an angular event listener.
              angularHelper.safeApply($scope);
          }
      }));
  
      //when a user logs out or timesout
      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;
      }));
  
      //this reacts to the options item in the tree
      //todo, migrate to nav service
      $scope.searchShowMenu = function (ev, args) {
          //always skip default
          args.skipDefault = true;
          navigationService.showMenu(ev, args);
      };
  
      //todo, migrate to nav service
      $scope.searchHide = function () {
          navigationService.hideSearch();
      };
  
      //the below assists with hiding/showing the tree
      var treeActive = false;
  
      //Sets a service variable as soon as the user hovers the navigation with the mouse
      //used by the leaveTree method to delay hiding
      $scope.enterTree = function (event) {
          treeActive = true;
      };
  
      // Hides navigation tree, with a short delay, is cancelled if the user moves the mouse over the tree again
      $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() {
                  if (!treeActive) {
                      navigationService.hideTree();
                  }
              }, 300);
          }
      };
  
      //ensure to unregister from all events!
      $scope.$on('$destroy', function () {
          for (var e in evts) {
              eventsService.unsubscribe(evts[e]);
          }
      });
  }
  
  //register it
  angular.module('umbraco').controller("Umbraco.NavigationController", NavigationController);
  
  /**
   * @ngdoc controller
   * @name Umbraco.SearchController
   * @function
   * 
   * @description
   * Controls the search functionality in the site
   *  
   */
  function SearchController($scope, searchService, $log, $location, navigationService, $q) {
  
      $scope.searchTerm = null;
      $scope.searchResults = [];
      $scope.isSearching = false;
      $scope.selectedResult = -1;
  
  
      $scope.navigateResults = function(ev){
          //38: up 40: down, 13: enter
  
          switch(ev.keyCode){
              case 38:
                      iterateResults(true);
                  break;
              case 40:
                      iterateResults(false);
                  break;
              case 13:
                  if ($scope.selectedItem) {
                      $location.path($scope.selectedItem.editorPath);
                      navigationService.hideSearch();
                  }                
                  break;
          }
      };
  
  
      var group = undefined;
      var groupIndex = -1;
      var itemIndex = -1;
      $scope.selectedItem = undefined;
          
  
      function iterateResults(up){
          //default group
          if(!group){
              group = $scope.groups[0];
              groupIndex = 0;
          }
  
          if(up){
              if(itemIndex === 0){
                  if(groupIndex === 0){
                      gotoGroup($scope.groups.length-1, true);
                  }else{
                      gotoGroup(groupIndex-1, true);
                  }
              }else{
                  gotoItem(itemIndex-1);
              }
          }else{
              if(itemIndex < group.results.length-1){
                  gotoItem(itemIndex+1);
              }else{
                  if(groupIndex === $scope.groups.length-1){
                      gotoGroup(0);
                  }else{
                      gotoGroup(groupIndex+1);
                  }
              }
          }
      }
  
      function gotoGroup(index, up){
          groupIndex = index;
          group = $scope.groups[groupIndex];
          
          if(up){
              gotoItem(group.results.length-1);
          }else{
              gotoItem(0); 
          }
      }
  
      function gotoItem(index){
          itemIndex = index;
          $scope.selectedItem = group.results[itemIndex];
      }
  
      //used to cancel any request in progress if another one needs to take it's place
      var canceler = null;
  
      $scope.$watch("searchTerm", _.debounce(function (newVal, oldVal) {
          $scope.$apply(function() {
              if ($scope.searchTerm) {
                  if (newVal !== null && newVal !== undefined && newVal !== oldVal) {
                      $scope.isSearching = true;
                      navigationService.showSearch();
                      $scope.selectedItem = undefined;
  
                      //a canceler exists, so perform the cancelation operation and reset
                      if (canceler) {
                          console.log("CANCELED!");
                          canceler.resolve();
                          canceler = $q.defer();
                      }
                      else {
                          canceler = $q.defer();
                      }
  
                      searchService.searchAll({ term: $scope.searchTerm, canceler: canceler }).then(function(result) {
                          $scope.groups = _.filter(result, function (group) { return group.results.length > 0; });
                          //set back to null so it can be re-created
                          canceler = null;
                      });
                  }
              }
              else {
                  $scope.isSearching = false;
                  navigationService.hideSearch();
                  $scope.selectedItem = undefined;
              }
          });
      }, 200));
  
  }
  //register it
  angular.module('umbraco').controller("Umbraco.SearchController", SearchController);
  
  /**
   * @ngdoc controller
   * @name Umbraco.MainController
   * @function
   * 
   * @description
   * The controller for the AuthorizeUpgrade login page
   * 
   */
  function AuthorizeUpgradeController($scope, $window) {
      
      //Add this method to the scope - this method will be called by the login dialog controller when the login is successful
      // then we'll handle the redirect.
      $scope.submit = function (event) {
  
          var qry = $window.location.search.trimStart("?").split("&");
          var redir = _.find(qry, function(item) {
              return item.startsWith("redir=");
          });
          if (redir) {
              $window.location = decodeURIComponent(redir.split("=")[1]);
          }
          else {
              $window.location = "/";
          }
          
      };
  
  }
  
  angular.module('umbraco').controller("Umbraco.AuthorizeUpgradeController", AuthorizeUpgradeController);
  /**
   * @ngdoc controller
   * @name Umbraco.DashboardController
   * @function
   * 
   * @description
   * Controls the dashboards of the application
   * 
   */
   
  function DashboardController($scope, $routeParams, dashboardResource, localizationService) {
      $scope.dashboard = {};
      localizationService.localize("sections_" + $routeParams.section).then(function(name){
      	$scope.dashboard.name = name;
      });
      
      dashboardResource.getDashboard($routeParams.section).then(function(tabs){
     		$scope.dashboard.tabs = tabs;
      });
  }
  
  
  //register it
  angular.module('umbraco').controller("Umbraco.DashboardController", DashboardController);
  angular.module("umbraco")
      .controller("Umbraco.Dialogs.ApprovedColorPickerController", function ($scope, $http, umbPropEditorHelper, assetsService) {
          assetsService.loadJs("lib/cssparser/cssparser.js")
  			    .then(function () {
  
  			        var cssPath = $scope.dialogData.cssPath;
  			        $scope.cssClass = $scope.dialogData.cssClass;
  
  			        $scope.classes = [];
  
  			        $scope.change = function (newClass) {
  			            $scope.model.value = newClass;
  			        }
  
  			        $http.get(cssPath)
                          .success(function (data) {
                              var parser = new CSSParser();
                              $scope.classes = parser.parse(data, false, false).cssRules;
                              $scope.classes.splice(0, 0, "noclass");
                          })
  
  			        assetsService.loadCss("/App_Plugins/Lecoati.uSky.Grid/lib/uSky.Grid.ApprovedColorPicker.css");
  			        assetsService.loadCss(cssPath);
  			    });
  });
  function ContentEditDialogController($scope, editorState, $routeParams, $q, $timeout, $window, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, umbModelMapper, $http) {
      
      $scope.defaultButton = null;
      $scope.subButtons = [];
      var dialogOptions = $scope.$parent.dialogOptions;
  
      // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish
      function performSave(args) {
          contentEditingHelper.contentEditorPerformSave({
              statusMessage: args.statusMessage,
              saveMethod: args.saveMethod,
              scope: $scope,
              content: $scope.content
          }).then(function (content) {
              //success            
              if (dialogOptions.closeOnSave) {
                  $scope.submit(content);
              }
  
          }, function(err) {
              //error
          });
      }
  
      function filterTabs(entity, blackList) {
          if (blackList) {
              _.each(entity.tabs, function (tab) {
                  tab.hide = _.contains(blackList, tab.alias);
              });
          }
  
          return entity;
      };
      
      function init(content) {
          var buttons = contentEditingHelper.configureContentEditorButtons({
              create: $routeParams.create,
              content: content,
              methods: {
                  saveAndPublish: $scope.saveAndPublish,
                  sendToPublish: $scope.sendToPublish,
                  save: $scope.save,
                  unPublish: angular.noop
              }
          });
          $scope.defaultButton = buttons.defaultButton;
          $scope.subButtons = buttons.subButtons;
  
          //This is a total hack but we have really no other way of sharing data to the property editors of this
          // content item, so we'll just set the property on the content item directly
          $scope.content.isDialogEditor = true;
  
          editorState.set($scope.content);
      }
  
      //check if the entity is being passed in, otherwise load it from the server
      if (angular.isObject(dialogOptions.entity)) {
          $scope.loaded = true;
          $scope.content = filterTabs(dialogOptions.entity, dialogOptions.tabFilter);
          init($scope.content);
      }
      else {
          contentResource.getById(dialogOptions.id)
              .then(function(data) {
                  $scope.loaded = true;
                  $scope.content = filterTabs(data, dialogOptions.tabFilter);
                  init($scope.content);
                  //in one particular special case, after we've created a new item we redirect back to the edit
                  // route but there might be server validation errors in the collection which we need to display
                  // after the redirect, so we will bind all subscriptions which will show the server validation errors
                  // if there are any and then clear them so the collection no longer persists them.
                  serverValidationManager.executeAndClearAllSubscriptions();
              });
      }  
  
      $scope.sendToPublish = function () {
          performSave({ saveMethod: contentResource.sendToPublish, statusMessage: "Sending..." });
      };
  
      $scope.saveAndPublish = function () {
          performSave({ saveMethod: contentResource.publish, statusMessage: "Publishing..." });
      };
  
      $scope.save = function () {
          performSave({ saveMethod: contentResource.save, statusMessage: "Saving..." });
      };
  
      // this method is called for all action buttons and then we proxy based on the btn definition
      $scope.performAction = function (btn) {
  
          if (!btn || !angular.isFunction(btn.handler)) {
              throw "btn.handler must be a function reference";
          }
  
          if (!$scope.busy) {
              btn.handler.apply(this);
          }
      };
  
  }
  
  
  angular.module("umbraco")
  	.controller("Umbraco.Dialogs.Content.EditController", ContentEditDialogController);
  angular.module("umbraco")
      .controller("Umbraco.Dialogs.HelpController", function ($scope, $location, $routeParams, helpService, userService) {
          $scope.section = $routeParams.section;
          $scope.version = Umbraco.Sys.ServerVariables.application.version + " assembly: " + Umbraco.Sys.ServerVariables.application.assemblyVersion;
          
          if(!$scope.section){
              $scope.section ="content";
          }
  
          var rq = {};
          rq.section = $scope.section;
          
          userService.getCurrentUser().then(function(user){
          	
          	rq.usertype = user.userType;
          	rq.lang = user.locale;
  
      	    if($routeParams.url){
      	    	rq.path = decodeURIComponent($routeParams.url);
      	    	
      	    	if(rq.path.indexOf(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath) === 0){
      				rq.path = rq.path.substring(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath.length);	
      			}
  
      			if(rq.path.indexOf(".aspx") > 0){
      				rq.path = rq.path.substring(0, rq.path.indexOf(".aspx"));
      			}
      					
      	    }else{
      	    	rq.path = rq.section + "/" + $routeParams.tree + "/" + $routeParams.method;
      	    }
  
      	    helpService.findHelp(rq).then(function(topics){
      	    	$scope.topics = topics;
      	    });
  
      	    helpService.findVideos(rq).then(function(videos){
      	        $scope.videos = videos;
      	    });
  
          });
  
          
      });
  //used for the icon picker dialog
  angular.module("umbraco")
      .controller("Umbraco.Dialogs.IconPickerController",
          function ($scope, iconHelper) {
              
              iconHelper.getIcons().then(function(icons){
              	$scope.icons = icons;
              });
  
  			$scope.submitClass = function(icon){
  				if($scope.color)
  				{
  					$scope.submit(icon + " " + $scope.color);
  				}else{
  					$scope.submit(icon);	
  				}
  			};
  		}
  	);
  /**
   * @ngdoc controller
   * @name Umbraco.Dialogs.InsertMacroController
   * @function
   * 
   * @description
   * The controller for the custom insert macro dialog. Until we upgrade the template editor to be angular this 
   * is actually loaded into an iframe with full html.
   */
  function InsertMacroController($scope, entityResource, macroResource, umbPropEditorHelper, macroService, formHelper) {
  
      /** changes the view to edit the params of the selected macro */
      function editParams() {
          //get the macro params if there are any
          macroResource.getMacroParameters($scope.selectedMacro.id)
              .then(function (data) {
  
                  //go to next page if there are params otherwise we can just exit
                  if (!angular.isArray(data) || data.length === 0) {
                      //we can just exist!
                      submitForm();
  
                  } else {
                      $scope.wizardStep = "paramSelect";
                      $scope.macroParams = data;
                      
                      //fill in the data if we are editing this macro
                      if ($scope.dialogData && $scope.dialogData.macroData && $scope.dialogData.macroData.macroParamsDictionary) {
                          _.each($scope.dialogData.macroData.macroParamsDictionary, function (val, key) {
                              var prop = _.find($scope.macroParams, function (item) {
                                  return item.alias == key;
                              });
                              if (prop) {
  
                                  if (_.isString(val)) {
                                      //we need to unescape values as they have most likely been escaped while inserted 
                                      val = _.unescape(val);
  
                                      //detect if it is a json string
                                      if (val.detectIsJson()) {
                                          try {
                                              //Parse it to json
                                              prop.value = angular.fromJson(val);
                                          }
                                          catch (e) {
                                              // not json
                                              prop.value = val;
                                          }
                                      }
                                      else {
                                          prop.value = val;
                                      }
                                  }
                                  else {
                                      prop.value = val;
                                  }
                              }
                          });
  
                      }
                  }
              });
      }
  
      /** submit the filled out macro params */
      function submitForm() {
          
          //collect the value data, close the dialog and send the data back to the caller
  
          //create a dictionary for the macro params
          var paramDictionary = {};
          _.each($scope.macroParams, function (item) {
  
              var val = item.value;
  
              if (item.value != null && item.value != undefined && !_.isString(item.value)) {
                  try {
                      val = angular.toJson(val);
                  }
                  catch (e) {
                      // not json           
                  }
              }
              
              //each value needs to be xml escaped!! since the value get's stored as an xml attribute
              paramDictionary[item.alias] = _.escape(val);
  
          });
          
          //need to find the macro alias for the selected id
          var macroAlias = $scope.selectedMacro.alias;
  
          //get the syntax based on the rendering engine
          var syntax;
          if ($scope.dialogData.renderingEngine && $scope.dialogData.renderingEngine === "WebForms") {
              syntax = macroService.generateWebFormsSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary });
          }
          else if ($scope.dialogData.renderingEngine && $scope.dialogData.renderingEngine === "Mvc") {
              syntax = macroService.generateMvcSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary });
          }
          else {
              syntax = macroService.generateMacroSyntax({ macroAlias: macroAlias, macroParamsDictionary: paramDictionary });
          }
  
          $scope.submit({ syntax: syntax, macroAlias: macroAlias, macroParamsDictionary: paramDictionary });
      }
  
      $scope.macros = [];
      $scope.selectedMacro = null;
      $scope.wizardStep = "macroSelect";
      $scope.macroParams = [];
      
      $scope.submitForm = function () {
          
          if (formHelper.submitForm({ scope: $scope })) {
          
              formHelper.resetForm({ scope: $scope });
  
              if ($scope.wizardStep === "macroSelect") {
                  editParams();
              }
              else {
                  submitForm();
              }
  
          }
      };
  
      //here we check to see if we've been passed a selected macro and if so we'll set the
      //editor to start with parameter editing
      if ($scope.dialogData && $scope.dialogData.macroData) {
          $scope.wizardStep = "paramSelect";
      }
      
      //get the macro list - pass in a filter if it is only for rte
      entityResource.getAll("Macro", ($scope.dialogData && $scope.dialogData.richTextEditor && $scope.dialogData.richTextEditor === true) ? "UseInEditor=true" : null)
          .then(function (data) {
  
              //if 'allowedMacros' is specified, we need to filter
              if (angular.isArray($scope.dialogData.allowedMacros) && $scope.dialogData.allowedMacros.length > 0) {
                  $scope.macros = _.filter(data, function(d) {
                      return _.contains($scope.dialogData.allowedMacros, d.alias);
                  });
              }
              else {
                  $scope.macros = data;
              }
              
  
              //check if there's a pre-selected macro and if it exists
              if ($scope.dialogData && $scope.dialogData.macroData && $scope.dialogData.macroData.macroAlias) {
                  var found = _.find(data, function (item) {
                      return item.alias === $scope.dialogData.macroData.macroAlias;
                  });
                  if (found) {
                      //select the macro and go to next screen
                      $scope.selectedMacro = found;
                      editParams();
                      return;
                  }
              }
              //we don't have a pre-selected macro so ensure the correct step is set
              $scope.wizardStep = "macroSelect";
          });
  
  
  }
  
  angular.module("umbraco").controller("Umbraco.Dialogs.InsertMacroController", InsertMacroController);
  
  /**
   * @ngdoc controller
   * @name Umbraco.Dialogs.LegacyDeleteController
   * @function
   * 
   * @description
   * The controller for deleting content
   */
  function LegacyDeleteController($scope, legacyResource, treeService, navigationService) {
  
      $scope.performDelete = function() {
  
          //mark it for deletion (used in the UI)
          $scope.currentNode.loading = true;
  
          legacyResource.deleteItem({            
              nodeId: $scope.currentNode.id,
              nodeType: $scope.currentNode.nodeType,
              alias: $scope.currentNode.name,
          }).then(function () {
              $scope.currentNode.loading = false;
              //TODO: Need to sync tree, etc...
              treeService.removeNode($scope.currentNode);
              navigationService.hideMenu();
          });
  
      };
  
      $scope.cancel = function() {
          navigationService.hideDialog();
      };
  }
  
  angular.module("umbraco").controller("Umbraco.Dialogs.LegacyDeleteController", LegacyDeleteController);
  
  //used for the media picker dialog
  angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController",
  	function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService) {
  	    var dialogOptions = $scope.dialogOptions;
  
  	    var searchText = "Search...";
  	    localizationService.localize("general_search").then(function (value) {
  	        searchText = value + "...";
  	    });
  
  	    $scope.dialogTreeEventHandler = $({});
  	    $scope.target = {};
  	    $scope.searchInfo = {
  	        searchFromId: null,
  	        searchFromName: null,
  	        showSearch: false,
  	        results: [],
  	        selectedSearchResults: []
  	    }
  
  	    if (dialogOptions.currentTarget) {
  	        $scope.target = dialogOptions.currentTarget;
  
  	        //if we have a node ID, we fetch the current node to build the form data
  	        if ($scope.target.id) {
  
  	            if (!$scope.target.path) {
  	                entityResource.getPath($scope.target.id, "Document").then(function (path) {
  	                    $scope.target.path = path;
  	                    //now sync the tree to this path
  	                    $scope.dialogTreeEventHandler.syncTree({ path: $scope.target.path, tree: "content" });
  	                });
  	            }
  
  	            contentResource.getNiceUrl($scope.target.id).then(function (url) {
  	                $scope.target.url = url;
  	            });
  	        }
  	    }
  
  	    function nodeSelectHandler(ev, args) {
  	        args.event.preventDefault();
  	        args.event.stopPropagation();
  
  	        if (args.node.metaData.listViewNode) {
  	            //check if list view 'search' node was selected
  
  	            $scope.searchInfo.showSearch = true;
  	            $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
  	            $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
  	        }	      
  	        else {
  	            eventsService.emit("dialogs.linkPicker.select", args);
  
  	            if ($scope.currentNode) {
  	                //un-select if there's a current one selected
  	                $scope.currentNode.selected = false;
  	            }
  
  	            $scope.currentNode = args.node;
  	            $scope.currentNode.selected = true;
  	            $scope.target.id = args.node.id;
  	            $scope.target.name = args.node.name;
  
  	            if (args.node.id < 0) {
  	                $scope.target.url = "/";
  	            }
  	            else {
  	                contentResource.getNiceUrl(args.node.id).then(function (url) {
  	                    $scope.target.url = url;
  	                });
  	            }
  
  	            if (!angular.isUndefined($scope.target.isMedia)) {
  	                delete $scope.target.isMedia;
  	            }
  	        }	        
  	    }
  
  	    function nodeExpandedHandler(ev, args) {
  	        if (angular.isArray(args.children)) {
  
  	            //iterate children
  	            _.each(args.children, function (child) {
  	                //check if any of the items are list views, if so we need to add a custom 
  	                // child: A node to activate the search
  	                if (child.metaData.isContainer) {
  	                    child.hasChildren = true;
  	                    child.children = [
  	                        {
  	                            level: child.level + 1,
  	                            hasChildren: false,
  	                            name: searchText,
  	                            metaData: {
  	                                listViewNode: child,
  	                            },
  	                            cssClass: "icon umb-tree-icon sprTree icon-search",
  	                            cssClasses: ["not-published"]
  	                        }
  	                    ];
  	                }	                
  	            });
  	        }
  	    }
  
  	    $scope.switchToMediaPicker = function () {
  	        userService.getCurrentUser().then(function (userData) {
  	            dialogService.mediaPicker({
  	                startNodeId: userData.startMediaId,
  	                callback: function (media) {
  	                    $scope.target.id = media.id;
  	                    $scope.target.isMedia = true;
  	                    $scope.target.name = media.name;
  	                    $scope.target.url = mediaHelper.resolveFile(media);
  	                }
  	            });
  	        });
  	    };
  
  	    $scope.hideSearch = function () {
  	        $scope.searchInfo.showSearch = false;
  	        $scope.searchInfo.searchFromId = null;
  	        $scope.searchInfo.searchFromName = null;
  	        $scope.searchInfo.results = [];
  	    }
  
  	    // method to select a search result 
  	    $scope.selectResult = function (evt, result) {
  	        result.selected = result.selected === true ? false : true;
  	        nodeSelectHandler(evt, {event: evt, node: result});
  	    };
  
          //callback when there are search results 
  	    $scope.onSearchResults = function (results) {
  	        $scope.searchInfo.results = results;
              $scope.searchInfo.showSearch = true;
  	    };
  
  	    $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
  	    $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
  
  	    $scope.$on('$destroy', function () {
  	        $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
  	        $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
  	    });
  	});
  angular.module("umbraco").controller("Umbraco.Dialogs.LoginController", function ($scope, localizationService, userService) {
      
      /**
       * @ngdoc function
       * @name signin
       * @methodOf MainController
       * @function
       *
       * @description
       * signs the user in
       */
      var d = new Date();
      //var weekday = new Array("Super Sunday", "Manic Monday", "Tremendous Tuesday", "Wonderful Wednesday", "Thunder Thursday", "Friendly Friday", "Shiny Saturday");
      localizationService.localize("login_greeting"+d.getDay()).then(function(label){
          $scope.greeting = label;
      }); // weekday[d.getDay()];
      
      $scope.errorMsg = "";
      
      $scope.loginSubmit = function (login, password) {
          
          //if the login and password are not empty we need to automatically 
          // validate them - this is because if there are validation errors on the server
          // then the user has to change both username & password to resubmit which isn't ideal,
          // so if they're not empty , we'l just make sure to set them to valid.
          if (login && password && login.length > 0 && password.length > 0) {
              $scope.loginForm.username.$setValidity('auth', true);
              $scope.loginForm.password.$setValidity('auth', true);
          }
          
          
          if ($scope.loginForm.$invalid) {
              return;
          }
  
          userService.authenticate(login, password)
              .then(function (data) {               
                  $scope.submit(true);
              }, function (reason) {
                  $scope.errorMsg = reason.errorMsg;
                  
                  //set the form inputs to invalid
                  $scope.loginForm.username.$setValidity("auth", false);
                  $scope.loginForm.password.$setValidity("auth", false);
              });
          
          //setup a watch for both of the model values changing, if they change
          // while the form is invalid, then revalidate them so that the form can 
          // be submitted again.
          $scope.loginForm.username.$viewChangeListeners.push(function () {
              if ($scope.loginForm.username.$invalid) {
                  $scope.loginForm.username.$setValidity('auth', true);
              }
          });
          $scope.loginForm.password.$viewChangeListeners.push(function () {
              if ($scope.loginForm.password.$invalid) {
                  $scope.loginForm.password.$setValidity('auth', true);
              }
          });
      };
  });
  
  //used for the macro picker dialog
  angular.module("umbraco").controller("Umbraco.Dialogs.MacroPickerController", function ($scope, macroFactory, umbPropEditorHelper) {
  	$scope.macros = macroFactory.all(true);
  	$scope.dialogMode = "list";
  
  	$scope.configureMacro = function(macro){
  		$scope.dialogMode = "configure";
  		$scope.dialogData.macro = macroFactory.getMacro(macro.alias);
  	    //set the correct view for each item
  		for (var i = 0; i < dialogData.macro.properties.length; i++) {
  		    dialogData.macro.properties[i].editorView = umbPropEditorHelper.getViewPath(dialogData.macro.properties[i].view);
  		}
  	};
  });
  //used for the media picker dialog
  angular.module("umbraco")
      .controller("Umbraco.Dialogs.MediaPickerController",
          function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, eventsService, treeService, $cookies, $element, $timeout) {
  
              var dialogOptions = $scope.dialogOptions;
  
              $scope.onlyImages = dialogOptions.onlyImages;
              $scope.showDetails = dialogOptions.showDetails;
              $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false;
              $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1;
              $scope.cropSize = dialogOptions.cropSize;
              
              $scope.filesUploading = 0;
              $scope.dropping = false;
              $scope.progress = 0;
  
              $scope.options = {
                  url: umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostAddFile") + "?origin=blueimp",
                  autoUpload: true,
                  dropZone: $element.find(".umb-dialogs-mediapicker.browser"),
                  fileInput: $element.find("input.uploader"),
                  formData: {
                      currentFolder: -1
                  }
              };
  
              //preload selected item
              $scope.target = undefined;
              if(dialogOptions.currentTarget){
                  $scope.target = dialogOptions.currentTarget;
              }
  
              $scope.submitFolder = function(e) {
                  if (e.keyCode === 13) {
                      e.preventDefault();
                      $scope.showFolderInput = false;
  
                      mediaResource
                          .addFolder($scope.newFolderName, $scope.options.formData.currentFolder)
                          .then(function(data) {
  
                              //we've added a new folder so lets clear the tree cache for that specific item
                              treeService.clearCache({
                                  cacheKey: "__media", //this is the main media tree cache key
                                  childrenOf: data.parentId //clear the children of the parent
                              });
  
                              $scope.gotoFolder(data);
                          });
                  }
              };
  
              $scope.gotoFolder = function(folder) {
  
                  if(!folder){
                      folder = {id: -1, name: "Media", icon: "icon-folder"};
                  }
  
                  if (folder.id > 0) {
                      entityResource.getAncestors(folder.id, "media")
                          .then(function(anc) {
                              // anc.splice(0,1);  
                              $scope.path = _.filter(anc, function (f) {
                                  return f.path.indexOf($scope.startNodeId) !== -1;
                              });
                          });
                  }
                  else {
                      $scope.path = [];
                  }
  
                  //mediaResource.rootMedia()
                  mediaResource.getChildren(folder.id)
                      .then(function(data) {
                          $scope.searchTerm = "";
                          $scope.images = data.items ? data.items : [];
                      });
  
                  $scope.options.formData.currentFolder = folder.id;
                  $scope.currentFolder = folder;      
              };
  
              //This executes prior to the whole processing which we can use to get the UI going faster,
              //this also gives us the start callback to invoke to kick of the whole thing
              $scope.$on('fileuploadadd', function (e, data) {
                  $scope.$apply(function () {
                      $scope.filesUploading++;
                  });
              });
  
              //when one is finished
              $scope.$on('fileuploaddone', function (e, data) {
                  $scope.filesUploading--;
                  if ($scope.filesUploading == 0) {
                      $scope.$apply(function () {
                          $scope.progress = 0;
                          $scope.gotoFolder($scope.currentFolder);
                      });
                  }
              });
  
              // All these sit-ups are to add dropzone area and make sure it gets removed if dragging is aborted! 
              $scope.$on('fileuploaddragover', function (e, data) {
                  if (!$scope.dragClearTimeout) {
                      $scope.$apply(function () {
                          $scope.dropping = true;
                      });
                  }
                  else {
                      $timeout.cancel($scope.dragClearTimeout);
                  }
                  $scope.dragClearTimeout = $timeout(function () {
                      $scope.dropping = null;
                      $scope.dragClearTimeout = null;
                  }, 300);
              });
  
              $scope.clickHandler = function(image, ev, select) {
                  ev.preventDefault();
                  
                  if (image.isFolder && !select) {
                      $scope.gotoFolder(image);
                  }else{
                      eventsService.emit("dialogs.mediaPicker.select", image);
                      
                      //we have 3 options add to collection (if multi) show details, or submit it right back to the callback
                      if ($scope.multiPicker) {
                          $scope.select(image);
                          image.cssclass = ($scope.dialogData.selection.indexOf(image) > -1) ? "selected" : "";
                      }else if($scope.showDetails) {
                          $scope.target= image;
                          $scope.target.url = mediaHelper.resolveFile(image);
                      }else{
                          $scope.submit(image);
                      }
                  }
              };
  
              $scope.exitDetails = function(){
                  if(!$scope.currentFolder){
                      $scope.gotoFolder();
                  }
  
                  $scope.target = undefined;
              };
  
             
  
              //default root item
              if(!$scope.target){
                  $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" });  
              }
          });
  //used for the member picker dialog
  angular.module("umbraco").controller("Umbraco.Dialogs.MemberGroupPickerController",
      function($scope, eventsService, entityResource, searchService, $log) {
          var dialogOptions = $scope.dialogOptions;
          $scope.dialogTreeEventHandler = $({});
          $scope.multiPicker = dialogOptions.multiPicker;
  
          /** Method used for selecting a node */
          function select(text, id) {
  
              if (dialogOptions.multiPicker) {
                  $scope.select(id);              
              }
              else {
                  $scope.submit(id);               
              }
          }
          
          function nodeSelectHandler(ev, args) {
              args.event.preventDefault();
              args.event.stopPropagation();
              
              eventsService.emit("dialogs.memberGroupPicker.select", args);
              
              //This is a tree node, so we don't have an entity to pass in, it will need to be looked up
              //from the server in this method.
              select(args.node.name, args.node.id);
  
              //toggle checked state
              args.node.selected = args.node.selected === true ? false : true;
          }
  
          $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
  
          $scope.$on('$destroy', function () {
              $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
          });
      });
  angular.module("umbraco").controller("Umbraco.Dialogs.RteEmbedController", function ($scope, $http, umbRequestHelper) {
      $scope.form = {};
      $scope.form.url = "";
      $scope.form.width = 360;
      $scope.form.height = 240;
      $scope.form.constrain = true;
      $scope.form.preview = "";
      $scope.form.success = false;
      $scope.form.info = "";
      $scope.form.supportsDimensions = false;
      
      var origWidth = 500;
      var origHeight = 300;
      
      $scope.showPreview = function() {
  
          if ($scope.form.url) {
              $scope.form.show = true;
              $scope.form.preview = "<div class=\"umb-loader\" style=\"height: 10px; margin: 10px 0px;\"></div>";
              $scope.form.info = "";
              $scope.form.success = false;
  
              $http({ method: 'GET', url: umbRequestHelper.getApiUrl("embedApiBaseUrl", "GetEmbed"), params: { url: $scope.form.url, width: $scope.form.width, height: $scope.form.height } })
                  .success(function (data) {
                      
                      $scope.form.preview = "";
                      
                      switch (data.Status) {
                          case 0:
                              //not supported
                              $scope.form.info = "Not supported";
                              break;
                          case 1:
                              //error
                              $scope.form.info = "Computer says no";
                              break;
                          case 2:
                              $scope.form.preview = data.Markup;
                              $scope.form.supportsDimensions = data.SupportsDimensions;
                              $scope.form.success = true;
                              break;
                      }
                  })
                  .error(function () {
                      $scope.form.supportsDimensions = false;
                      $scope.form.preview = "";
                      $scope.form.info = "Computer says no";
                  });
          } else {
              $scope.form.supportsDimensions = false;
              $scope.form.preview = "";
              $scope.form.info = "Please enter a URL";
          }
      };
  
      $scope.changeSize = function (type) {
          var width, height;
          
          if ($scope.form.constrain) {
              width = parseInt($scope.form.width, 10);
              height = parseInt($scope.form.height, 10);
              if (type == 'width') {
                  origHeight = Math.round((width / origWidth) * height);
                  $scope.form.height = origHeight;
              } else {
                  origWidth = Math.round((height / origHeight) * width);
                  $scope.form.width = origWidth;
              }
          }
          if ($scope.form.url != "") {
              $scope.showPreview();
          }
  
      };
      
      $scope.insert = function(){
          $scope.submit($scope.form.preview);
      };
  });
  angular.module("umbraco").controller('Umbraco.Dialogs.Template.QueryBuilderController',
  		function($scope, $http, dialogService){
  			
  
              $http.get("backoffice/UmbracoApi/TemplateQuery/GetAllowedProperties").then(function(response) {
                  $scope.properties = response.data;
              });
  
              $http.get("backoffice/UmbracoApi/TemplateQuery/GetContentTypes").then(function (response) {
                  $scope.contentTypes = response.data;
              });
  
              $http.get("backoffice/UmbracoApi/TemplateQuery/GetFilterConditions").then(function (response) {
                  $scope.conditions = response.data;
              });
  
  
  			$scope.query = {
  				contentType: {
  					name: "Everything"
  				},
  				source:{
  					name: "My website"
  				}, 
  				filters:[
  					{
  						property:undefined,
  						operator: undefined
  					}
  				],
  				sort:{
  					property:{
  						alias: "",
  						name: "",
  					},
  					direction: "Ascending"
  				}
  			};
  
  
  
  			$scope.chooseSource = function(query){
  				dialogService.contentPicker({
  				    callback: function (data) {
  
  				        if (data.id > 0) {
  				            query.source = { id: data.id, name: data.name };
  				        } else {
  				            query.source.name = "My website";
  				            delete query.source.id;
  				        }
  					}
  				});
  			};
  
  		    var throttledFunc = _.throttle(function() {
  
  		        $http.post("backoffice/UmbracoApi/TemplateQuery/PostTemplateQuery", $scope.query).then(function (response) {
  		            $scope.result = response.data;
  		        });
  
  		    }, 200);
  
  		    $scope.$watch("query", function(value) {
  		        throttledFunc();
  		    }, true);
  
  			$scope.getPropertyOperators = function (property) {
  
  			    var conditions = _.filter($scope.conditions, function(condition) {
  			        var index = condition.appliesTo.indexOf(property.type);
  			        return index >= 0;
  			    });
  			    return conditions;
  			};
  
  			
  			$scope.addFilter = function(query){				
  			    query.filters.push({});
  			};
  
  			$scope.trashFilter = function (query) {
  			    query.filters.splice(query,1);
  			};
  
  			$scope.changeSortOrder = function(query){
  				if(query.sort.direction === "ascending"){
  					query.sort.direction = "descending";
  				}else{
  					query.sort.direction = "ascending";
  				}
  			};
  
  			$scope.setSortProperty = function(query, property){
  				query.sort.property = property;
  				if(property.type === "datetime"){
  					query.sort.direction = "descending";
  				}else{
  					query.sort.direction = "ascending";
  				}
  			};
  		});
  //used for the media picker dialog
  angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController",
  	function ($scope, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService) {
  
  	    var tree = null;
  	    var dialogOptions = $scope.dialogOptions;
  	    $scope.dialogTreeEventHandler = $({});
  	    $scope.section = dialogOptions.section;
  	    $scope.treeAlias = dialogOptions.treeAlias;
  	    $scope.multiPicker = dialogOptions.multiPicker;
  	    $scope.hideHeader = true; 	    	    
          $scope.searchInfo = {
              searchFromId: dialogOptions.startNodeId,
              searchFromName: null,
              showSearch: false,
              results: [],
              selectedSearchResults: []
          }
  
  	    //create the custom query string param for this tree
  	    $scope.customTreeParams = dialogOptions.startNodeId ? "startNodeId=" + dialogOptions.startNodeId : "";
  	    $scope.customTreeParams += dialogOptions.customTreeParams ? "&" + dialogOptions.customTreeParams : "";
  
  	    var searchText = "Search...";
  	    localizationService.localize("general_search").then(function (value) {
  	        searchText = value + "...";
  	    });
  
  	    var entityType = "Document";
  	    
  
  	    //min / max values
  	    if (dialogOptions.minNumber) {
  	        dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10);
  	    }
  	    if (dialogOptions.maxNumber) {
  	        dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10);
  	    }
  
  	    if (dialogOptions.section === "member") {
  	        entityType = "Member";            
  	    }
  	    else if (dialogOptions.section === "media") {	    
  	        entityType = "Media";
  	    }
  
  	    //Configures filtering
  	    if (dialogOptions.filter) {
  
  	        dialogOptions.filterExclude = false;
  	        dialogOptions.filterAdvanced = false;
  
  	        //used advanced filtering
  	        if (angular.isFunction(dialogOptions.filter)) {
  	            dialogOptions.filterAdvanced = true;
  	        }
              else if (angular.isObject(dialogOptions.filter)) {
                  dialogOptions.filterAdvanced = true;
              }
              else {
                  if (dialogOptions.filter.startsWith("!")) {
                      dialogOptions.filterExclude = true;
                      dialogOptions.filter = dialogOptions.filter.substring(1);
                  }
  
                  //used advanced filtering
                  if (dialogOptions.filter.startsWith("{")) {
                      dialogOptions.filterAdvanced = true;
                      //convert to object
                      dialogOptions.filter = angular.fromJson(dialogOptions.filter);
                  }
              }
  	    } 
  
  	    function nodeExpandedHandler(ev, args) {            
  	        if (angular.isArray(args.children)) {
  
                  //iterate children
  	            _.each(args.children, function (child) {
  
  	                //check if any of the items are list views, if so we need to add some custom 
  	                // children: A node to activate the search, any nodes that have already been 
  	                // selected in the search
  	                if (child.metaData.isContainer) {
  	                    child.hasChildren = true;
  	                    child.children = [
  	                        {
                                  level: child.level + 1,
                                  hasChildren: false,
                                  parent: function () {
                                      return child;
                                  },
  	                            name: searchText,
  	                            metaData: {
  	                                listViewNode: child,
  	                            },
  	                            cssClass: "icon-search",
  	                            cssClasses: ["not-published"]
  	                        }
  	                    ];
                          //add base transition classes to this node
  	                    child.cssClasses.push("tree-node-slide-up");
  
  	                    var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function(i) {
  	                        return i.parentId == child.id;
  	                    });
  	                    _.each(listViewResults, function(item) {
  	                        child.children.unshift({
  	                            id: item.id,
  	                            name: item.name,
  	                            cssClass: "icon umb-tree-icon sprTree " + item.icon,
  	                            level: child.level + 1,
  	                            metaData: {
  	                                isSearchResult: true
  	                            },
  	                            hasChildren: false,
  	                            parent: function () {
  	                                return child;
  	                            }
  	                        });
  	                    });
  	                }
  
  	                //now we need to look in the already selected search results and 
  	                // toggle the check boxes for those ones that are listed
  	                var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
  	                    return child.id == selected.id;
  	                });
  	                if (exists) {
  	                    child.selected = true;
  	                }
  	            });
  
  	            //check filter
  	            performFiltering(args.children);	            
  	        }
  	    }
  
          //gets the tree object when it loads
  	    function treeLoadedHandler(ev, args) {
  	        tree = args.tree;
  	    }
  
  	    //wires up selection
  	    function nodeSelectHandler(ev, args) {
  	        args.event.preventDefault();
  	        args.event.stopPropagation();
  	        
  	        if (args.node.metaData.listViewNode) {
  	            //check if list view 'search' node was selected
  
                  $scope.searchInfo.showSearch = true;                
                  $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
                  $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
  
                  //add transition classes
  	            var listViewNode = args.node.parent();
  	            listViewNode.cssClasses.push('tree-node-slide-up-hide-active');
  	        }
              else if (args.node.metaData.isSearchResult) {
                  //check if the item selected was a search result from a list view
  
                  //unselect
                  select(args.node.name, args.node.id);
  
                  //remove it from the list view children
                  var listView = args.node.parent();
  	            listView.children = _.reject(listView.children, function(child) {
  	                return child.id == args.node.id;
  	            });
  
                  //remove it from the custom tracked search result list
  	            $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) {
  	                return i.id == args.node.id;
  	            });
  	        }
              else {
                  eventsService.emit("dialogs.treePickerController.select", args);
  
                  if (args.node.filtered) {
                      return;
                  }
  
                  //This is a tree node, so we don't have an entity to pass in, it will need to be looked up
                  //from the server in this method.
                  select(args.node.name, args.node.id);
  
                  //toggle checked state
                  args.node.selected = args.node.selected === true ? false : true;
              }	        
  	    }
  
  	    /** Method used for selecting a node */
  	    function select(text, id, entity) {
  	        //if we get the root, we just return a constructed entity, no need for server data
  	        if (id < 0) {
  	            if ($scope.multiPicker) {
  	                $scope.select(id);
  	            }
  	            else {
  	                var node = {
  	                    alias: null,
  	                    icon: "icon-folder",
  	                    id: id,
  	                    name: text
  	                };
  	                $scope.submit(node);
  	            }
  	        }
  	        else {
  	            
  	            if ($scope.multiPicker) {
  	                $scope.select(Number(id));
  	            }
  	            else {
                      
  	                $scope.hideSearch();
  
  	                //if an entity has been passed in, use it
  	                if (entity) {
  	                    $scope.submit(entity);
  	                } else {
  	                    //otherwise we have to get it from the server
  	                    entityResource.getById(id, entityType).then(function (ent) {
  	                        $scope.submit(ent);
  	                    });
  	                }
  	            }
  	        }
  	    }
  
  	    function performFiltering(nodes) {
  
  	        if (!dialogOptions.filter) {
  	            return;
  	        }
  
  	        //remove any list view search nodes from being filtered since these are special nodes that always must
  	        // be allowed to be clicked on
  	        nodes = _.filter(nodes, function(n) {
  	            return !angular.isObject(n.metaData.listViewNode);
  	        });
  
  	        if (dialogOptions.filterAdvanced) {
  
                  //filter either based on a method or an object
  	            var filtered = angular.isFunction(dialogOptions.filter)
  	                ? _.filter(nodes, dialogOptions.filter)
  	                : _.where(nodes, dialogOptions.filter);
  
  	            angular.forEach(filtered, function (value, key) {
  	                value.filtered = true;
  	                if (dialogOptions.filterCssClass) {
                          if (!value.cssClasses) {
                              value.cssClasses = [];
                          }
  	                    value.cssClasses.push(dialogOptions.filterCssClass);
  	                }
  	            });
  	        } else {
  	            var a = dialogOptions.filter.toLowerCase().split(',');
  	            angular.forEach(nodes, function (value, key) {
  
  	                var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0;
  
  	                if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) {
  	                    value.filtered = true;
  
  	                    if (dialogOptions.filterCssClass) {
  	                        if (!value.cssClasses) {
  	                            value.cssClasses = [];
  	                        }
  	                        value.cssClasses.push(dialogOptions.filterCssClass);
  	                    }
  	                }
  	            });
  	        }
  	    }
          
  	    $scope.multiSubmit = function (result) {
  	        entityResource.getByIds(result, entityType).then(function (ents) {
  	            $scope.submit(ents);
  	        });
  	    };
          
  	    /** method to select a search result */
  	    $scope.selectResult = function (evt, result) {
  
              if (result.filtered) {
                  return;
              }
  
  	        result.selected = result.selected === true ? false : true;
  
  	        //since result = an entity, we'll pass it in so we don't have to go back to the server
  	        select(result.name, result.id, result);
  
  	        //add/remove to our custom tracked list of selected search results
              if (result.selected) {
                  $scope.searchInfo.selectedSearchResults.push(result);
              }
              else {
                  $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function(i) {
                      return i.id == result.id;
                  });
              }
  
  	        //ensure the tree node in the tree is checked/unchecked if it already exists there
  	        if (tree) {	            
  	            var found = treeService.getDescendantNode(tree.root, result.id);
                  if (found) {
                      found.selected = result.selected;
                  }
  	        }
  	        
  	    };
  
  	    $scope.hideSearch = function () {
              	    
              //Traverse the entire displayed tree and update each node to sync with the selected search results
  	        if (tree) {
  
  	            //we need to ensure that any currently displayed nodes that get selected
  	            // from the search get updated to have a check box!
                  function checkChildren(children) {
                      _.each(children, function (child) {
                          //check if the id is in the selection, if so ensure it's flagged as selected
                          var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
                              return child.id == selected.id;
                          });
                          //if the curr node exists in selected search results, ensure it's checked
                          if (exists) {
                              child.selected = true;
                          }
                          //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result
                          else if (child.metaData.isSearchResult) {
                              //if this tree node is under a list view it means that the node was added
                              // to the tree dynamically under the list view that was searched, so we actually want to remove
                              // it all together from the tree
                              var listView = child.parent();
                              listView.children = _.reject(listView.children, function(c) {
                                  return c.id == child.id;
                              });
                          }
                          
                          //check if the current node is a list view and if so, check if there's any new results
                          // that need to be added as child nodes to it based on search results selected
                          if (child.metaData.isContainer) {
  
                              child.cssClasses = _.reject(child.cssClasses, function(c) {
                                  return c === 'tree-node-slide-up-hide-active';
                              });
  
                              var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) {
                                  return i.parentId == child.id;
                              });
                              _.each(listViewResults, function (item) {
                                  var childExists = _.find(child.children, function(c) {
                                      return c.id == item.id;
                                  });
                                  if (!childExists) {
                                      var parent = child;
                                      child.children.unshift({
                                          id: item.id,
                                          name: item.name,
                                          cssClass: "icon umb-tree-icon sprTree " + item.icon,
                                          level: child.level + 1,
                                          metaData: {
                                              isSearchResult: true
                                          },
                                          hasChildren: false,
                                          parent: function () {
                                              return parent;
                                          }
                                      });
                                  }                                
                              });
                          }
  
                          //recurse
                          if (child.children && child.children.length > 0) {
                              checkChildren(child.children);
                          }
                      });
                  }
                  checkChildren(tree.root.children);
  	        }
  	        
  
              $scope.searchInfo.showSearch = false;            
              $scope.searchInfo.searchFromId = dialogOptions.startNodeId;
              $scope.searchInfo.searchFromName = null;
              $scope.searchInfo.results = [];
          }
  
  	    $scope.onSearchResults = function(results) {
  	        
              //filter all items - this will mark an item as filtered
  	        performFiltering(results);
  
  	        //now actually remove all filtered items so they are not even displayed
  	        results = _.filter(results, function(item) {
  	            return !item.filtered;
  	        });
  
  	        $scope.searchInfo.results = results;
  
              //sync with the curr selected results
  	        _.each($scope.searchInfo.results, function (result) {
  	            var exists = _.find($scope.dialogData.selection, function (selectedId) {
  	                return result.id == selectedId;
  	            });
  	            if (exists) {
  	                result.selected = true;
  	            }	            
  	        });
  
  	        $scope.searchInfo.showSearch = true;
  	    };
  
  	    $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler);
  	    $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
  	    $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
  
  	    $scope.$on('$destroy', function () {
  	        $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler);
  	        $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
  	        $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
  	    });
  	});
  angular.module("umbraco")
      .controller("Umbraco.Dialogs.UserController", function ($scope, $location, $timeout, userService, historyService, eventsService) {
  
          $scope.user = userService.getCurrentUser();
          $scope.history = historyService.getCurrent();
          $scope.version = Umbraco.Sys.ServerVariables.application.version + " assembly: " + Umbraco.Sys.ServerVariables.application.assemblyVersion;
  
          var evts = [];
          evts.push(eventsService.on("historyService.add", function (e, args) {
              $scope.history = args.all;
          }));
          evts.push(eventsService.on("historyService.remove", function (e, args) {
              $scope.history = args.all;
          }));
          evts.push(eventsService.on("historyService.removeAll", function (e, args) {
              $scope.history = [];
          }));
  
          $scope.logout = function () {
  
              //Add event listener for when there are pending changes on an editor which means our route was not successful
              var pendingChangeEvent = eventsService.on("valFormManager.pendingChanges", function (e, args) {
                  //one time listener, remove the event
                  pendingChangeEvent();
                  $scope.close();
              });
  
  
              //perform the path change, if it is successful then the promise will resolve otherwise it will fail
              $scope.close();
              $location.path("/logout");
          };
  
          $scope.gotoHistory = function (link) {
              $location.path(link);
              $scope.close();
          };
  
          //Manually update the remaining timeout seconds
          function updateTimeout() {
              $timeout(function () {
                  if ($scope.remainingAuthSeconds > 0) {
                      $scope.remainingAuthSeconds--;
                      $scope.$digest();
                      //recurse
                      updateTimeout();
                  }
  
              }, 1000, false); // 1 second, do NOT execute a global digest    
          }
  
          //get the user
          userService.getCurrentUser().then(function (user) {
              $scope.user = user;
              if ($scope.user) {
                  $scope.remainingAuthSeconds = $scope.user.remainingAuthSeconds;
                  $scope.canEditProfile = _.indexOf($scope.user.allowedSections, "users") > -1;
                  //set the timer
                  updateTimeout();
              }
          });
  
          //remove all event handlers
          $scope.$on('$destroy', function () {
              for (var e = 0; e < evts.length; e++) {
                  evts[e]();
              }
  
          });
  
      });
  /**
   * @ngdoc controller
   * @name Umbraco.Dialogs.LegacyDeleteController
   * @function
   * 
   * @description
   * The controller for deleting content
   */
  function YsodController($scope, legacyResource, treeService, navigationService) {
      
      if ($scope.error && $scope.error.data && $scope.error.data.StackTrace) {
          //trim whitespace
          $scope.error.data.StackTrace = $scope.error.data.StackTrace.trim();
      }
  
      $scope.closeDialog = function() {
          $scope.dismiss();
      };
  
  }
  
  angular.module("umbraco").controller("Umbraco.Dialogs.YsodController", YsodController);
  
  /**
   * @ngdoc controller
   * @name Umbraco.LegacyController
   * @function
   * 
   * @description
   * A controller to control the legacy iframe injection
   * 
  */
  function LegacyController($scope, $routeParams, $element) {
  
      var url = decodeURIComponent($routeParams.url.toLowerCase().replace(/javascript\:/g, ""));
      //split into path and query
      var urlParts = url.split("?");
      var extIndex = urlParts[0].lastIndexOf(".");
      var ext = extIndex === -1 ? "" : urlParts[0].substr(extIndex);
      //path cannot be a js file
      if (ext !== ".js" || ext === "") {
          //path cannot contain any of these chars
          var toClean = "*(){}[];:<>\\|'\"";
          for (var i = 0; i < toClean.length; i++) {
              var reg = new RegExp("\\" + toClean[i], "g");
              urlParts[0] = urlParts[0].replace(reg, "");
          }
          //join cleaned path and query back together
          url = urlParts[0] + (urlParts.length === 1 ? "" : ("?" + urlParts[1]));
          $scope.legacyPath = url;
      }
      else {
          throw "Invalid url";
      }
  }
  
  angular.module("umbraco").controller('Umbraco.LegacyController', LegacyController);
  /** This controller is simply here to launch the login dialog when the route is explicitly changed to /login */
  angular.module('umbraco').controller("Umbraco.LoginController", function (eventsService, $scope, userService, $location, $rootScope) {
  
      userService._showLoginDialog(); 
         
      var evtOn = eventsService.on("app.ready", function(evt, data){
          $scope.avatar = "assets/img/application/logo.png";
  
          var path = "/";
  
          //check if there's a returnPath query string, if so redirect to it
          var locationObj = $location.search();
          if (locationObj.returnPath) {
              path = decodeURIComponent(locationObj.returnPath);
          }
  
          $location.url(path);
      });
  
      $scope.$on('$destroy', function () {
          eventsService.unsubscribe(evtOn);
      });
  
  });
  
  //used for the media picker dialog
  angular.module("umbraco").controller("Umbraco.Notifications.ConfirmRouteChangeController",
  	function ($scope, $location, $log, notificationsService) {	
  
  		$scope.discard = function(not){
  			not.args.listener();
  			
  			$location.search("");
  
  		    //we need to break the path up into path and query
  		    var parts = not.args.path.split("?");
  		    var query = {};
              if (parts.length > 1) {
                  _.each(parts[1].split("&"), function(q) {
                      var keyVal = q.split("=");
                      query[keyVal[0]] = keyVal[1];
                  });
              }
  
              $location.path(parts[0]).search(query);
  			notificationsService.remove(not);
  		};
  
  		$scope.stay = function(not){
  			notificationsService.remove(not);
  		};
  
  	});
  angular.module("umbraco").controller("Umbraco.Editors.Content.CopyController",
  	function ($scope, eventsService, contentResource, navigationService, appState, treeService, localizationService) {
  
  	    var dialogOptions = $scope.dialogOptions;
  	    var searchText = "Search...";
  	    localizationService.localize("general_search").then(function (value) {
  	        searchText = value + "...";
  	    });
  
  	    $scope.relateToOriginal = false;
  	    $scope.dialogTreeEventHandler = $({});
  	    $scope.busy = false;
  	    $scope.searchInfo = {
  	        searchFromId: null,
  	        searchFromName: null,
  	        showSearch: false,
  	        results: [],
  	        selectedSearchResults: []
  	    }
  
  	    var node = dialogOptions.currentNode;
  
  	    function nodeSelectHandler(ev, args) {
  	        args.event.preventDefault();
  	        args.event.stopPropagation();
  
  	        if (args.node.metaData.listViewNode) {
  	            //check if list view 'search' node was selected
  
  	            $scope.searchInfo.showSearch = true;
  	            $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
  	            $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
  	        }
  	        else {
  	            eventsService.emit("editors.content.copyController.select", args);
  
  	            if ($scope.target) {
  	                //un-select if there's a current one selected
  	                $scope.target.selected = false;
  	            }
  
  	            $scope.target = args.node;
  	            $scope.target.selected = true;
  	        }
  	        
  	    }
  
  	    function nodeExpandedHandler(ev, args) {
  	        if (angular.isArray(args.children)) {
  
  	            //iterate children
  	            _.each(args.children, function (child) {
  	                //check if any of the items are list views, if so we need to add a custom 
  	                // child: A node to activate the search
  	                if (child.metaData.isContainer) {
  	                    child.hasChildren = true;
  	                    child.children = [
  	                        {
  	                            level: child.level + 1,
  	                            hasChildren: false,
  	                            name: searchText,
  	                            metaData: {
  	                                listViewNode: child,
  	                            },
  	                            cssClass: "icon umb-tree-icon sprTree icon-search",
  	                            cssClasses: ["not-published"]
  	                        }
  	                    ];
  	                }
  	            });
  	        }
  	    }
  
  	    $scope.hideSearch = function () {
  	        $scope.searchInfo.showSearch = false;
  	        $scope.searchInfo.searchFromId = null;
  	        $scope.searchInfo.searchFromName = null;
  	        $scope.searchInfo.results = [];
  	    }
  
  	    // method to select a search result 
  	    $scope.selectResult = function (evt, result) {
  	        result.selected = result.selected === true ? false : true;
  	        nodeSelectHandler(evt, { event: evt, node: result });
  	    };
  
  	    //callback when there are search results 
  	    $scope.onSearchResults = function (results) {
  	        $scope.searchInfo.results = results;
  	        $scope.searchInfo.showSearch = true;
  	    };
          
  	    $scope.copy = function () {
  
  	        $scope.busy = true;
  	        $scope.error = false;
  
  	        contentResource.copy({ parentId: $scope.target.id, id: node.id, relateToOriginal: $scope.relateToOriginal })
                  .then(function (path) {
                      $scope.error = false;
                      $scope.success = true;
                      $scope.busy = false;
  
                      //get the currently edited node (if any)
                      var activeNode = appState.getTreeState("selectedNode");
  
                      //we need to do a double sync here: first sync to the copied content - but don't activate the node,
                      //then sync to the currenlty edited content (note: this might not be the content that was copied!!)
  
                      navigationService.syncTree({ tree: "content", path: path, forceReload: true, activate: false }).then(function (args) {
                          if (activeNode) {
                              var activeNodePath = treeService.getPath(activeNode).join();
                              //sync to this node now - depending on what was copied this might already be synced but might not be
                              navigationService.syncTree({ tree: "content", path: activeNodePath, forceReload: false, activate: true });
                          }
                      });
  
                  }, function (err) {
                      $scope.success = false;
                      $scope.error = err;
                      $scope.busy = false;
                  });
  	    };
  
  	    $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
  	    $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
  
  	    $scope.$on('$destroy', function () {
  	        $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
  	        $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
  	    });
  	});
  /**
   * @ngdoc controller
   * @name Umbraco.Editors.Content.CreateController
   * @function
   * 
   * @description
   * The controller for the content creation dialog
   */
  function contentCreateController($scope, $routeParams, contentTypeResource, iconHelper) {
  
      contentTypeResource.getAllowedTypes($scope.currentNode.id).then(function(data) {
          $scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
      });
  }
  
  angular.module('umbraco').controller("Umbraco.Editors.Content.CreateController", contentCreateController);
  /**
   * @ngdoc controller
   * @name Umbraco.Editors.ContentDeleteController
   * @function
   * 
   * @description
   * The controller for deleting content
   */
  function ContentDeleteController($scope, contentResource, treeService, navigationService, editorState, $location) {
  
      $scope.performDelete = function() {
  
          //mark it for deletion (used in the UI)
          $scope.currentNode.loading = true;
  
          contentResource.deleteById($scope.currentNode.id).then(function () {
              $scope.currentNode.loading = false;
  
              //get the root node before we remove it
              var rootNode = treeService.getTreeRoot($scope.currentNode);
  
              treeService.removeNode($scope.currentNode);
  
              if (rootNode) {
                  //ensure the recycle bin has child nodes now            
                  var recycleBin = treeService.getDescendantNode(rootNode, -20);
                  if (recycleBin) {
                      recycleBin.hasChildren = true;
                  }
              }
              
              //if the current edited item is the same one as we're deleting, we need to navigate elsewhere
              if (editorState.current && editorState.current.id == $scope.currentNode.id) {
                  $location.path("/content/content/edit/" + $scope.currentNode.parentId);
              }
  
              navigationService.hideMenu();
          });
  
      };
  
      $scope.cancel = function() {
          navigationService.hideDialog();
      };
  }
  
  angular.module("umbraco").controller("Umbraco.Editors.Content.DeleteController", ContentDeleteController);
  
  /**
   * @ngdoc controller
   * @name Umbraco.Editors.Content.EditController
   * @function
   * 
   * @description
   * The controller for the content editor
   */
  function ContentEditController($scope, $rootScope, $routeParams, $q, $timeout, $window, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, keyboardService, umbModelMapper, editorState, $http) {
  
      //setup scope vars
      $scope.defaultButton = null;
      $scope.subButtons = [];    
      $scope.currentSection = appState.getSectionState("currentSection");
      $scope.currentNode = null; //the editors affiliated node
      $scope.isNew = $routeParams.create;
      
      function init(content) {
  
          var buttons = contentEditingHelper.configureContentEditorButtons({
              create: $routeParams.create,
              content: content,
              methods: {
                  saveAndPublish: $scope.saveAndPublish,
                  sendToPublish: $scope.sendToPublish,
                  save: $scope.save,
                  unPublish: $scope.unPublish
              }
          });
          $scope.defaultButton = buttons.defaultButton;
          $scope.subButtons = buttons.subButtons;
  
          editorState.set($scope.content);
  
          //We fetch all ancestors of the node to generate the footer breadcrumb navigation
          if (!$routeParams.create) {
              if (content.parentId && content.parentId != -1) {
                  entityResource.getAncestors(content.id, "document")
                 .then(function (anc) {
                     $scope.ancestors = anc;
                 });
              }
          }
      }
  
      /** Syncs the content item to it's tree node - this occurs on first load and after saving */
      function syncTreeNode(content, path, initialLoad) {
  
          if (!$scope.content.isChildOfListView) {
              navigationService.syncTree({ tree: "content", path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) {
                  $scope.currentNode = syncArgs.node;
              });
          }
          else if (initialLoad === true) {
  
              //it's a child item, just sync the ui node to the parent
              navigationService.syncTree({ tree: "content", path: path.substring(0, path.lastIndexOf(",")).split(","), forceReload: initialLoad !== true });
              
              //if this is a child of a list view and it's the initial load of the editor, we need to get the tree node 
              // from the server so that we can load in the actions menu.
              umbRequestHelper.resourcePromise(
                  $http.get(content.treeNodeUrl),
                  'Failed to retrieve data for child node ' + content.id).then(function (node) {
                      $scope.currentNode = node;
                  });
          }
      }
  
      // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish
      function performSave(args) {
          var deferred = $q.defer();
  
          contentEditingHelper.contentEditorPerformSave({
              statusMessage: args.statusMessage,
              saveMethod: args.saveMethod,
              scope: $scope,
              content: $scope.content
          }).then(function (data) {
              //success            
              init($scope.content);
              syncTreeNode($scope.content, data.path);
  
              deferred.resolve(data);
          }, function (err) {
              //error
              if (err) {
                  editorState.set($scope.content);
              }
              deferred.reject(err);
          });
  
          return deferred.promise;
      }
  
      function resetLastListPageNumber(content) {
          // We're using rootScope to store the page number for list views, so if returning to the list
          // we can restore the page.  If we've moved on to edit a piece of content that's not the list or it's children
          // we should remove this so as not to confuse if navigating to a different list
          if (!content.isChildOfListView && !content.isContainer) {
              $rootScope.lastListViewPageViewed = null;
          }
      }
  
      if ($routeParams.create) {
          //we are creating so get an empty content item
          contentResource.getScaffold($routeParams.id, $routeParams.doctype)
              .then(function (data) {
                  $scope.loaded = true;
                  $scope.content = data;
  
                  init($scope.content);                
  
                  resetLastListPageNumber($scope.content);
              });
      }
      else {
          //we are editing so get the content item from the server
          contentResource.getById($routeParams.id)
              .then(function (data) {
                  $scope.loaded = true;
                  $scope.content = data;
  
                  if (data.isChildOfListView && data.trashed === false) {
                      $scope.listViewPath = ($routeParams.page)
                          ? "/content/content/edit/" + data.parentId + "?page=" + $routeParams.page
                          : "/content/content/edit/" + data.parentId;
                  }
  
                  init($scope.content);
  
                  //in one particular special case, after we've created a new item we redirect back to the edit
                  // route but there might be server validation errors in the collection which we need to display
                  // after the redirect, so we will bind all subscriptions which will show the server validation errors
                  // if there are any and then clear them so the collection no longer persists them.
                  serverValidationManager.executeAndClearAllSubscriptions();
  
                  syncTreeNode($scope.content, data.path, true);
  
                  resetLastListPageNumber($scope.content);
              });
      }
  
  
      $scope.unPublish = function () {
  
          if (formHelper.submitForm({ scope: $scope, statusMessage: "Unpublishing...", skipValidation: true })) {
  
              contentResource.unPublish($scope.content.id)
                  .then(function (data) {
  
                      formHelper.resetForm({ scope: $scope, notifications: data.notifications });
  
                      contentEditingHelper.handleSuccessfulSave({
                          scope: $scope,
                          savedContent: data,
                          rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
                      });
  
                      init($scope.content);
  
                      syncTreeNode($scope.content, data.path);
  
                  });
          }
  
      };
  
      $scope.sendToPublish = function () {
          return performSave({ saveMethod: contentResource.sendToPublish, statusMessage: "Sending..." });
      };
  
      $scope.saveAndPublish = function () {
          return performSave({ saveMethod: contentResource.publish, statusMessage: "Publishing..." });
      };
  
      $scope.save = function () {
          return performSave({ saveMethod: contentResource.save, statusMessage: "Saving..." });
      };
  
      $scope.preview = function (content) {
  
  
          if (!$scope.busy) {
  
              // Chromes popup blocker will kick in if a window is opened 
              // outwith the initial scoped request. This trick will fix that.
              //  
              var previewWindow = $window.open('preview/?id=' + content.id, 'umbpreview');
              $scope.save().then(function (data) {
                  // Build the correct path so both /#/ and #/ work.
                  var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?id=' + data.id;
                  previewWindow.location.href = redirect;
              });
  
  
          }
   
      };
  
      // this method is called for all action buttons and then we proxy based on the btn definition
      $scope.performAction = function (btn) {
  
          if (!btn || !angular.isFunction(btn.handler)) {
              throw "btn.handler must be a function reference";
          }
  
          if (!$scope.busy) {
              btn.handler.apply(this);
          }
      };
  
  }
  
  angular.module("umbraco").controller("Umbraco.Editors.Content.EditController", ContentEditController);
  
  /**
   * @ngdoc controller
   * @name Umbraco.Editors.Content.EmptyRecycleBinController
   * @function
   * 
   * @description
   * The controller for deleting content
   */
  function ContentEmptyRecycleBinController($scope, contentResource, treeService, navigationService) {
  
      $scope.performDelete = function() {
  
          //(used in the UI)
          $scope.currentNode.loading = true;
  
          contentResource.emptyRecycleBin($scope.currentNode.id).then(function () {
              $scope.currentNode.loading = false;
              //TODO: Need to sync tree, etc...
              treeService.removeChildNodes($scope.currentNode);
              navigationService.hideMenu();
          });
  
      };
  
      $scope.cancel = function() {
          navigationService.hideDialog();
      };
  }
  
  angular.module("umbraco").controller("Umbraco.Editors.Content.EmptyRecycleBinController", ContentEmptyRecycleBinController);
  
  angular.module("umbraco").controller("Umbraco.Editors.Content.MoveController",
  	function ($scope, eventsService, contentResource, navigationService, appState, treeService, localizationService) {
  
  	    var dialogOptions = $scope.dialogOptions;
  	    var searchText = "Search...";
  	    localizationService.localize("general_search").then(function (value) {
  	        searchText = value + "...";
  	    });
  
  	    $scope.dialogTreeEventHandler = $({});
  	    $scope.busy = false;
  	    $scope.searchInfo = {
  	        searchFromId: null,
  	        searchFromName: null,
  	        showSearch: false,
  	        results: [],
  	        selectedSearchResults: []
  	    }
  
  	    var node = dialogOptions.currentNode;
  
  	    function nodeSelectHandler(ev, args) {
  	        args.event.preventDefault();
  	        args.event.stopPropagation();
  
  	        if (args.node.metaData.listViewNode) {
  	            //check if list view 'search' node was selected
  
  	            $scope.searchInfo.showSearch = true;
  	            $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
  	            $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
  	        }
  	        else {
  	            eventsService.emit("editors.content.moveController.select", args);
  
  	            if ($scope.target) {
  	                //un-select if there's a current one selected
  	                $scope.target.selected = false;
  	            }
  
  	            $scope.target = args.node;
  	            $scope.target.selected = true;
  	        }	        
  	    }
  
  	    function nodeExpandedHandler(ev, args) {
  	        if (angular.isArray(args.children)) {
  
  	            //iterate children
  	            _.each(args.children, function (child) {
  	                //check if any of the items are list views, if so we need to add a custom 
  	                // child: A node to activate the search
  	                if (child.metaData.isContainer) {
  	                    child.hasChildren = true;
  	                    child.children = [
  	                        {
  	                            level: child.level + 1,
  	                            hasChildren: false,
  	                            name: searchText,
  	                            metaData: {
  	                                listViewNode: child,
  	                            },
  	                            cssClass: "icon umb-tree-icon sprTree icon-search",
  	                            cssClasses: ["not-published"]
  	                        }
  	                    ];
  	                }
  	            });
  	        }
  	    }
  
  	    $scope.hideSearch = function () {
  	        $scope.searchInfo.showSearch = false;
  	        $scope.searchInfo.searchFromId = null;
  	        $scope.searchInfo.searchFromName = null;
  	        $scope.searchInfo.results = [];
  	    }
  
  	    // method to select a search result 
  	    $scope.selectResult = function (evt, result) {
  	        result.selected = result.selected === true ? false : true;
  	        nodeSelectHandler(evt, { event: evt, node: result });
  	    };
  
  	    //callback when there are search results 
  	    $scope.onSearchResults = function (results) {
  	        $scope.searchInfo.results = results;
  	        $scope.searchInfo.showSearch = true;
  	    };
  
  	    $scope.move = function () {
  
  	        $scope.busy = true;
  	        $scope.error = false;
  
  	        contentResource.move({ parentId: $scope.target.id, id: node.id })
                  .then(function (path) {
                      $scope.error = false;
                      $scope.success = true;
                      $scope.busy = false;
  
                      //first we need to remove the node that launched the dialog
                      treeService.removeNode($scope.currentNode);
  
                      //get the currently edited node (if any)
                      var activeNode = appState.getTreeState("selectedNode");
  
                      //we need to do a double sync here: first sync to the moved content - but don't activate the node,
                      //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
  
                      navigationService.syncTree({ tree: "content", path: path, forceReload: true, activate: false }).then(function (args) {
                          if (activeNode) {
                              var activeNodePath = treeService.getPath(activeNode).join();
                              //sync to this node now - depending on what was copied this might already be synced but might not be
                              navigationService.syncTree({ tree: "content", path: activeNodePath, forceReload: false, activate: true });
                          }
                      });
  
                  }, function (err) {
                      $scope.success = false;
                      $scope.error = err;
                      $scope.busy = false;
                  });
  	    };
  
  	    $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
  	    $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
  
  	    $scope.$on('$destroy', function () {
  	        $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
  	        $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
  	    });
  	});
  /**
   * @ngdoc controller
   * @name Umbraco.Editors.Content.RecycleBinController
   * @function
   * 
   * @description
   * Controls the recycle bin for content
   * 
   */
  
  function ContentRecycleBinController($scope, $routeParams, dataTypeResource) {
  
      //ensures the list view doesn't actually load until we query for the list view config
      // for the section
      $scope.listViewPath = null;
  
      $routeParams.id = "-20";
      dataTypeResource.getById(-95).then(function (result) {
          _.each(result.preValues, function (i) {
              $scope.model.config[i.key] = i.value;
          });
          $scope.listViewPath = 'views/propertyeditors/listview/listview.html';
      });
  
      $scope.model = { config: { entityType: $routeParams.section } };
  
  }
  
  angular.module('umbraco').controller("Umbraco.Editors.Content.RecycleBinController", ContentRecycleBinController);
  
  /**
   * @ngdoc controller
   * @name Umbraco.Editors.ContentType.EditController
   * @function
   * 
   * @description
   * The controller for the content type editor
   */
  function ContentTypeEditController($scope, $routeParams, $log, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, entityResource) {
      
      $scope.tabs = [];
      $scope.page = {};
      $scope.contentType = {tabs: [], name: "My content type", alias:"myType", icon:"icon-folder", allowedChildren: [], allowedTemplate: []};
      $scope.contentType.tabs = [
              {name: "Content", properties:[ {name: "test"}]},
              {name: "Generic Properties", properties:[]}
          ];
  
  
          
      $scope.dataTypesOptions ={
      	group: "properties",
      	onDropHandler: function(item, args){
      		args.sourceScope.move(args);
      	},
      	onReleaseHandler: function(item, args){
      		var a = args;
      	}
      };
  
      $scope.tabOptions ={
      	group: "tabs",
      	drop: false,
      	nested: true,
      	onDropHandler: function(item, args){
      		
      	},
      	onReleaseHandler: function(item, args){
      		
      	}
      };
  
      $scope.propertiesOptions ={
      	group: "properties",
      	onDropHandler: function(item, args){
      		//alert("dropped on properties");
  			//args.targetScope.ngModel.$modelValue.push({name: "bong"});
      	},
      	onReleaseHandler: function(item, args){
      		//alert("released from properties");
  			//args.targetScope.ngModel.$modelValue.push({name: "bong"});
      	},
      };
  
  
      $scope.omg = function(){
      	alert("wat");
      };   
  
      entityResource.getAll("Datatype").then(function(data){
          $scope.page.datatypes = data;
      });
  }
  
  angular.module("umbraco").controller("Umbraco.Editors.ContentType.EditController", ContentTypeEditController);
  function startUpVideosDashboardController($scope, xmlhelper, $log, $http) {
      $scope.videos = [];
      $scope.init = function(url){
          var proxyUrl = "dashboard/feedproxy.aspx?url=" + url;
          $http.get(proxyUrl).then(function(data){
                var feed = $(data.data);
                $('item', feed).each(function (i, item) {
                    var video = {};
                    video.thumbnail = $(item).find('thumbnail').attr('url');
                    video.title = $("title", item).text();
                    video.link = $("guid", item).text();
                    $scope.videos.push(video);
                });
          });
      };
  }
  angular.module("umbraco").controller("Umbraco.Dashboard.StartupVideosController", startUpVideosDashboardController);
  
  
  function FormsController($scope, $route, $cookieStore, packageResource) {
      $scope.installForms = function(){
          $scope.state = "Installng package";
          packageResource
              .fetch("CD44CF39-3D71-4C19-B6EE-948E1FAF0525")
              .then(function(pack){
                $scope.state = "importing";
                return packageResource.import(pack);
              }, $scope.error)
              .then(function(pack){
                $scope.state = "Installing";
                return packageResource.installFiles(pack);
              }, $scope.error)
              .then(function(pack){
                $scope.state = "Restarting, please hold...";
                return packageResource.installData(pack);
              }, $scope.error)
              .then(function(pack){
                $scope.state = "All done, your browser will now refresh";
                return packageResource.cleanUp(pack);
              }, $scope.error)
              .then($scope.complete, $scope.error);
      };
  
      $scope.complete = function(result){
          var url = window.location.href + "?init=true";
          $cookieStore.put("umbPackageInstallId", result.packageGuid); 
          window.location.reload(true);
      };
  
      $scope.error = function(err){
          $scope.state = undefined;
          $scope.error = err;
      };
  
  
      function Video_player (videoId) {
        // Get dom elements
        this.container      = document.getElementById(videoId);
        this.video          = this.container.getElementsByTagName('video')[0];
  
        //Create controls
        this.controls = document.createElement('div');
        this.controls.className="video-controls";
  
        this.seek_bar = document.createElement('input');
        this.seek_bar.className="seek-bar";
        this.seek_bar.type="range";
        this.seek_bar.setAttribute('value', '0');
  
        this.loader = document.createElement('div');
        this.loader.className="loader";
  
        this.progress_bar = document.createElement('span');
        this.progress_bar.className="progress-bar";
  
        // Insert controls
        this.controls.appendChild(this.seek_bar);
        this.container.appendChild(this.controls);
        this.controls.appendChild(this.loader);
        this.loader.appendChild(this.progress_bar);
      }
  
  
      Video_player.prototype
        .seeking = function() {
          // get the value of the seekbar (hidden input[type="range"])
          var time = this.video.duration * (this.seek_bar.value / 100);
  
          // Update video to seekbar value
          this.video.currentTime = time;
        };
  
      // Stop video when user initiates seeking
      Video_player.prototype
        .start_seek = function() {
          this.video.pause();
        };
  
      // Start video when user stops seeking
      Video_player.prototype
        .stop_seek = function() {
          this.video.play();
        };
  
      // Update the progressbar (span.loader) according to video.currentTime
      Video_player.prototype
        .update_progress_bar = function() {
          // Get video progress in %
          var value = (100 / this.video.duration) * this.video.currentTime;
  
          // Update progressbar
          this.progress_bar.style.width = value + '%';
        };
  
      // Bind progressbar to mouse when seeking
      Video_player.prototype
        .handle_mouse_move = function(event) {
          // Get position of progressbar relative to browser window
          var pos = this.progress_bar.getBoundingClientRect().left;
  
          // Make sure event is reckonized cross-browser
          event = event || window.event;
  
          // Update progressbar
          this.progress_bar.style.width = (event.clientX - pos) + "px";
        };
  
      // Eventlisteners for seeking
      Video_player.prototype
        .video_event_handler = function(videoPlayer, interval) {
          // Update the progress bar
          var animate_progress_bar = setInterval(function () {
                videoPlayer.update_progress_bar();
              }, interval);
  
          // Fire when input value changes (user seeking)
          videoPlayer.seek_bar
            .addEventListener("change", function() {
                videoPlayer.seeking();
            });
  
          // Fire when user clicks on seekbar
          videoPlayer.seek_bar
            .addEventListener("mousedown", function (clickEvent) {
                // Pause video playback
                videoPlayer.start_seek();
  
                // Stop updating progressbar according to video progress
                clearInterval(animate_progress_bar);
  
                // Update progressbar to where user clicks
                videoPlayer.handle_mouse_move(clickEvent);
  
                // Bind progressbar to cursor
                window.onmousemove = function(moveEvent){
                  videoPlayer.handle_mouse_move(moveEvent);
                };
            });
  
          // Fire when user releases seekbar
          videoPlayer.seek_bar
            .addEventListener("mouseup", function () {
  
                // Unbind progressbar from cursor
                window.onmousemove = null;
  
                // Start video playback
                videoPlayer.stop_seek();
  
                // Animate the progressbar
                animate_progress_bar = setInterval(function () {
                    videoPlayer.update_progress_bar();
                }, interval);
            });
        };
  
  
      var videoPlayer = new Video_player('video_1');
      videoPlayer.video_event_handler(videoPlayer, 17);
  }
  
  angular.module("umbraco").controller("Umbraco.Dashboard.FormsDashboardController", FormsController);
  
  function startupLatestEditsController($scope) {
  
  }
  angular.module("umbraco").controller("Umbraco.Dashboard.StartupLatestEditsController", startupLatestEditsController);
  
  function MediaFolderBrowserDashboardController($rootScope, $scope, assetsService, $routeParams, $timeout, $element, $location, umbRequestHelper,navigationService, mediaResource, $cookies) {
          var dialogOptions = $scope.dialogOptions;
  
          $scope.filesUploading = [];
          $scope.nodeId = -1;
  
          $scope.onUploadComplete = function () {
              navigationService.reloadSection("media");
          }
  
  }
  angular.module("umbraco").controller("Umbraco.Dashboard.MediaFolderBrowserDashboardController", MediaFolderBrowserDashboardController);
  
  
  function ChangePasswordDashboardController($scope, xmlhelper, $log, currentUserResource, formHelper) {
  
      //create the initial model for change password property editor
      $scope.changePasswordModel = {
          alias: "_umb_password",
          view: "changepassword",
          config: {},
          value: {}
      };
  
      //go get the config for the membership provider and add it to the model
      currentUserResource.getMembershipProviderConfig().then(function(data) {
          $scope.changePasswordModel.config = data;
          //ensure the hasPassword config option is set to true (the user of course has a password already assigned)
          //this will ensure the oldPassword is shown so they can change it
          $scope.changePasswordModel.config.hasPassword = true;
          $scope.changePasswordModel.config.disableToggle = true;
      });
  
      ////this is the model we will pass to the service
      //$scope.profile = {};
  
      $scope.changePassword = function() {
  
          if (formHelper.submitForm({ scope: $scope })) {
              currentUserResource.changePassword($scope.changePasswordModel.value).then(function(data) {
  
                  //if the password has been reset, then update our model
                  if (data.value) {
                      $scope.changePasswordModel.value.generatedPassword = data.value;
                  }
  
                  formHelper.resetForm({ scope: $scope, notifications: data.notifications });
  
              }, function (err) {
  
                  formHelper.handleError(err);
  
              });
          }
      };
  }
  angular.module("umbraco").controller("Umbraco.Dashboard.StartupChangePasswordController", ChangePasswordDashboardController);
  
  function examineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeout) {
  
      $scope.indexerDetails = [];
      $scope.searcherDetails = [];
      $scope.loading = true;
  
      function checkProcessing(indexer, checkActionName) {
          umbRequestHelper.resourcePromise(
                  $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", checkActionName, { indexerName: indexer.name })),
                  'Failed to check index processing')
              .then(function(data) {
  
                  if (data) {
  
                      //copy all resulting properties
                      for (var k in data) {
                          indexer[k] = data[k];
                      }
                      indexer.isProcessing = false;
                  }
                  else {
                      $timeout(function () {
                          //don't continue if we've tried 100 times
                          if (indexer.processingAttempts < 100) {
                              checkProcessing(indexer, checkActionName);
                              //add an attempt
                              indexer.processingAttempts++;
                          }
                          else {
                              //we've exceeded 100 attempts, stop processing
                              indexer.isProcessing = false;
                          }
                      }, 1000);
                  }
              });
      }
  
      $scope.search = function (searcher, e) {
          if (e && e.keyCode !== 13) {
              return;
          }
  
          umbRequestHelper.resourcePromise(
                  $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetSearchResults", {
                      searcherName: searcher.name,
                      query: searcher.searchText,
                      queryType: searcher.searchType
                  })),
                  'Failed to search')
              .then(function(searchResults) {
                  searcher.isSearching = true;
                  searcher.searchResults = searchResults;
              });
      }
      
      $scope.toggle = function(provider, propName) {
          if (provider[propName] !== undefined) {
              provider[propName] = !provider[propName];
          }
          else {
              provider[propName] = true;
          }
      }
  
      $scope.rebuildIndex = function(indexer) {
          if (confirm("This will cause the index to be rebuilt. " +
                          "Depending on how much content there is in your site this could take a while. " +
                          "It is not recommended to rebuild an index during times of high website traffic " +
                          "or when editors are editing content.")) {
              indexer.isProcessing = true;
  
              umbRequestHelper.resourcePromise(
                      $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "PostRebuildIndex", { indexerName: indexer.name })),
                      'Failed to rebuild index')
                  .then(function () {
  
                      //rebuilding has started, nothing is returned accept a 200 status code.
                      //lets poll to see if it is done.
                      $timeout(function () {
                          checkProcessing(indexer, "PostCheckRebuildIndex");
                      }, 1000);
  
                  });
          }
      }
  
      $scope.optimizeIndex = function(indexer) {
          if (confirm("This will cause the index to be optimized which will improve its performance. " +
                          "It is not recommended to optimize an index during times of high website traffic " +
                          "or when editors are editing content.")) {
              indexer.isProcessing = true;
  
              umbRequestHelper.resourcePromise(
                      $http.post(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "PostOptimizeIndex", { indexerName: indexer.name })),
                      'Failed to optimize index')
                  .then(function () {
  
                      //optimizing has started, nothing is returned accept a 200 status code.
                      //lets poll to see if it is done.
                      $timeout(function () {
                          checkProcessing(indexer, "PostCheckOptimizeIndex");
                      }, 1000);
  
                  });
          }
      }
  
      $scope.closeSearch = function(searcher) {
          searcher.isSearching = true;
      }
  
       
      //go get the data
  
      //combine two promises and execute when they are both done
      $q.all([
  
          //get the indexer details
          umbRequestHelper.resourcePromise(
              $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetIndexerDetails")),
              'Failed to retrieve indexer details')
          .then(function(data) {
              $scope.indexerDetails = data; 
          }),
  
          //get the searcher details
          umbRequestHelper.resourcePromise(
              $http.get(umbRequestHelper.getApiUrl("examineMgmtBaseUrl", "GetSearcherDetails")),
              'Failed to retrieve searcher details')
          .then(function(data) {
              $scope.searcherDetails = data;
              for (var s in $scope.searcherDetails) {
                  $scope.searcherDetails[s].searchType = "text";
              }
          })
  
      ]).then(function () {
          //all init loading is complete
          $scope.loading = false;
      });
  
  
  }
  angular.module("umbraco").controller("Umbraco.Dashboard.ExamineMgmtController", examineMgmtController);
  function xmlDataIntegrityReportController($scope, umbRequestHelper, $log, $http, $q, $timeout) {
  
      function check(item) {
          var action = item.check;
          umbRequestHelper.resourcePromise(
                  $http.get(umbRequestHelper.getApiUrl("xmlDataIntegrityBaseUrl", action)),
                  'Failed to retrieve data integrity status')
              .then(function(result) {
                  item.checking = false;
                  item.invalid = result === "false";
              });
      }
  
      $scope.fix = function(item) {
          var action = item.fix;
          if (item.fix) {
              if (confirm("This will cause all xml structures for this type to be rebuilt. " +
                  "Depending on how much content there is in your site this could take a while. " +
                  "It is not recommended to rebuild xml structures if they are not out of sync, during times of high website traffic " +
                  "or when editors are editing content.")) {
                  item.fixing = true;
                  umbRequestHelper.resourcePromise(
                      $http.post(umbRequestHelper.getApiUrl("xmlDataIntegrityBaseUrl", action)),
                      'Failed to retrieve data integrity status')
                  .then(function (result) {
                      item.fixing = false;
                      item.invalid = result === "false";
                  });
              }
          }
      }
  
      $scope.items = {
          "contentXml": {
              label: "Content in the cmsContentXml table",
              checking: true,
              fixing: false,
              fix: "FixContentXmlTable",
              check: "CheckContentXmlTable"
          },
          "mediaXml": {
              label: "Media in the cmsContentXml table",
              checking: true,
              fixing: false,
              fix: "FixMediaXmlTable",
              check: "CheckMediaXmlTable"
          },
          "memberXml": {
              label: "Members in the cmsContentXml table",
              checking: true,
              fixing: false,
              fix: "FixMembersXmlTable",
              check: "CheckMembersXmlTable"
          }
      };
  
      for (var i in $scope.items) {
          check($scope.items[i]);
      }
  
  }
  angular.module("umbraco").controller("Umbraco.Dashboard.XmlDataIntegrityReportController", xmlDataIntegrityReportController);
  /**
   * @ngdoc controller
   * @name Umbraco.Editors.ContentDeleteController
   * @function
   * 
   * @description
   * The controller for deleting content
   */
  function DataTypeDeleteController($scope, dataTypeResource, treeService, navigationService) {
  
      $scope.performDelete = function() {
  
          //mark it for deletion (used in the UI)
          $scope.currentNode.loading = true;
          dataTypeResource.deleteById($scope.currentNode.id).then(function () {
              $scope.currentNode.loading = false;
  
              //get the root node before we remove it
              var rootNode = treeService.getTreeRoot($scope.currentNode);
              
              //TODO: Need to sync tree, etc...
              treeService.removeNode($scope.currentNode);
              navigationService.hideMenu();
          });
  
      };
  
      $scope.cancel = function() {
          navigationService.hideDialog();
      };
  }
  
  angular.module("umbraco").controller("Umbraco.Editors.DataType.DeleteController", DataTypeDeleteController);
  
  /**
   * @ngdoc controller
   * @name Umbraco.Editors.DataType.EditController
   * @function
   * 
   * @description
   * The controller for the content editor
   */
  function DataTypeEditController($scope, $routeParams, $location, appState, navigationService, treeService, dataTypeResource, notificationsService,  angularHelper, serverValidationManager, contentEditingHelper, formHelper, editorState) {
  
      //setup scope vars    
      $scope.currentSection = appState.getSectionState("currentSection");
      $scope.currentNode = null; //the editors affiliated node
  
      //method used to configure the pre-values when we retrieve them from the server
      function createPreValueProps(preVals) {
          $scope.preValues = [];
          for (var i = 0; i < preVals.length; i++) {
              $scope.preValues.push({
                  hideLabel: preVals[i].hideLabel,
                  alias: preVals[i].key,
                  description: preVals[i].description,
                  label: preVals[i].label,
                  view: preVals[i].view,
                  value: preVals[i].value
              });
          }
      }
  
      //set up the standard data type props
      $scope.properties = {
          selectedEditor: {
              alias: "selectedEditor",
              description: "Select a property editor",
              label: "Property editor"
          },
          selectedEditorId: {
              alias: "selectedEditorId",
              label: "Property editor alias"
          }
      };
      
      //setup the pre-values as props
      $scope.preValues = [];
  
      if ($routeParams.create) {
          //we are creating so get an empty data type item
          dataTypeResource.getScaffold()
              .then(function(data) {
                  $scope.loaded = true;
                  $scope.preValuesLoaded = true;
                  $scope.content = data;
  
                  //set a shared state
                  editorState.set($scope.content);
              });
      }
      else {
          //we are editing so get the content item from the server
          dataTypeResource.getById($routeParams.id)
              .then(function(data) {
                  $scope.loaded = true;
                  $scope.preValuesLoaded = true;
                  $scope.content = data;
  
                  createPreValueProps($scope.content.preValues);
                  
                  //share state
                  editorState.set($scope.content);
  
                  //in one particular special case, after we've created a new item we redirect back to the edit
                  // route but there might be server validation errors in the collection which we need to display
                  // after the redirect, so we will bind all subscriptions which will show the server validation errors
                  // if there are any and then clear them so the collection no longer persists them.
                  serverValidationManager.executeAndClearAllSubscriptions();
                  
                  navigationService.syncTree({ tree: "datatype", path: [String(data.id)] }).then(function (syncArgs) {
                      $scope.currentNode = syncArgs.node;
                  });
              });
      }
      
      $scope.$watch("content.selectedEditor", function (newVal, oldVal) {
  
          //when the value changes, we need to dynamically load in the new editor
          if (newVal && (newVal != oldVal && (oldVal || $routeParams.create))) {
              //we are editing so get the content item from the server
              var currDataTypeId = $routeParams.create ? undefined : $routeParams.id;
              dataTypeResource.getPreValues(newVal, currDataTypeId)
                  .then(function (data) {
                      $scope.preValuesLoaded = true;
                      $scope.content.preValues = data;
                      createPreValueProps($scope.content.preValues);
                      
                      //share state
                      editorState.set($scope.content);
                  });
          }
      });
  
      $scope.save = function() {
  
          if (formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) {
              
              dataTypeResource.save($scope.content, $scope.preValues, $routeParams.create)
                  .then(function(data) {
  
                      formHelper.resetForm({ scope: $scope, notifications: data.notifications });
  
                      contentEditingHelper.handleSuccessfulSave({
                          scope: $scope,
                          savedContent: data,
                          rebindCallback: function() {
                              createPreValueProps(data.preValues);
                          }
                      });
  
                      //share state
                      editorState.set($scope.content);
  
                      navigationService.syncTree({ tree: "datatype", path: [String(data.id)], forceReload: true }).then(function (syncArgs) {
                          $scope.currentNode = syncArgs.node;
                      });
                      
                  }, function(err) {
  
                      //NOTE: in the case of data type values we are setting the orig/new props 
                      // to be the same thing since that only really matters for content/media.
                      contentEditingHelper.handleSaveError({
                          redirectOnFailure: false,
                          err: err
                      });
                      
                      //share state
                      editorState.set($scope.content);
                  });
          }
  
      };
  
  }
  
  angular.module("umbraco").controller("Umbraco.Editors.DataType.EditController", DataTypeEditController);
  
  /**
   * @ngdoc controller
   * @name Umbraco.Editors.Media.CreateController
   * @function
   * 
   * @description
   * The controller for the media creation dialog
   */
  function mediaCreateController($scope, $routeParams, mediaTypeResource, iconHelper) {
      
      mediaTypeResource.getAllowedTypes($scope.currentNode.id).then(function(data) {
          $scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
      });
      
  }
  
  angular.module('umbraco').controller("Umbraco.Editors.Media.CreateController", mediaCreateController);
  /**
   * @ngdoc controller
   * @name Umbraco.Editors.ContentDeleteController
   * @function
   * 
   * @description
   * The controller for deleting content
   */
  function MediaDeleteController($scope, mediaResource, treeService, navigationService, editorState, $location) {
  
      $scope.performDelete = function() {
  
          //mark it for deletion (used in the UI)
          $scope.currentNode.loading = true;
  
          mediaResource.deleteById($scope.currentNode.id).then(function () {
              $scope.currentNode.loading = false;
  
              //get the root node before we remove it
              var rootNode = treeService.getTreeRoot($scope.currentNode);
  
              treeService.removeNode($scope.currentNode);
  
              if (rootNode) {
                  //ensure the recycle bin has child nodes now            
                  var recycleBin = treeService.getDescendantNode(rootNode, -21);
                  if (recycleBin) {
                      recycleBin.hasChildren = true;
                  }
              }
              
              //if the current edited item is the same one as we're deleting, we need to navigate elsewhere
              if (editorState.current && editorState.current.id == $scope.currentNode.id) {
                  $location.path("/media/media/edit/" + $scope.currentNode.parentId);
              }
  
              navigationService.hideMenu();
  
          },function() {
              $scope.currentNode.loading = false;
          });
      };
  
      $scope.cancel = function() {
          navigationService.hideDialog();
      };
  }
  
  angular.module("umbraco").controller("Umbraco.Editors.Media.DeleteController", MediaDeleteController);
  
  /**
   * @ngdoc controller
   * @name Umbraco.Editors.Media.EditController
   * @function
   * 
   * @description
   * The controller for the media editor
   */
  function mediaEditController($scope, $routeParams, appState, mediaResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, treeService, formHelper, umbModelMapper, editorState, umbRequestHelper, $http) {
  
      //setup scope vars
      $scope.currentSection = appState.getSectionState("currentSection");
      $scope.currentNode = null; //the editors affiliated node
  
      /** Syncs the content item to it's tree node - this occurs on first load and after saving */
      function syncTreeNode(content, path, initialLoad) {
  
          if (!$scope.content.isChildOfListView) {
              navigationService.syncTree({ tree: "media", path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) {
                  $scope.currentNode = syncArgs.node;
              });
          }
          else if (initialLoad === true) {
  
              //it's a child item, just sync the ui node to the parent
              navigationService.syncTree({ tree: "media", path: path.substring(0, path.lastIndexOf(",")).split(","), forceReload: initialLoad !== true });
  
              //if this is a child of a list view and it's the initial load of the editor, we need to get the tree node 
              // from the server so that we can load in the actions menu.
              umbRequestHelper.resourcePromise(
                  $http.get(content.treeNodeUrl),
                  'Failed to retrieve data for child node ' + content.id).then(function (node) {
                      $scope.currentNode = node;
                  });
          }
      }
  
      if ($routeParams.create) {
  
          mediaResource.getScaffold($routeParams.id, $routeParams.doctype)
              .then(function (data) {
                  $scope.loaded = true;
                  $scope.content = data;
  
                  editorState.set($scope.content);
              });
      }
      else {
          mediaResource.getById($routeParams.id)
              .then(function (data) {
                  $scope.loaded = true;
                  $scope.content = data;
                  
                  if (data.isChildOfListView && data.trashed === false) {
                      $scope.listViewPath = ($routeParams.page)
                          ? "/media/media/edit/" + data.parentId + "?page=" + $routeParams.page
                          : "/media/media/edit/" + data.parentId;
                  }
  
                  editorState.set($scope.content);
  
                  //in one particular special case, after we've created a new item we redirect back to the edit
                  // route but there might be server validation errors in the collection which we need to display
                  // after the redirect, so we will bind all subscriptions which will show the server validation errors
                  // if there are any and then clear them so the collection no longer persists them.
                  serverValidationManager.executeAndClearAllSubscriptions();
  
                  syncTreeNode($scope.content, data.path, true);
                 
                  if ($scope.content.parentId && $scope.content.parentId != -1) {
                      //We fetch all ancestors of the node to generate the footer breadcrump navigation
                      entityResource.getAncestors($routeParams.id, "media")
                          .then(function (anc) {
                              $scope.ancestors = anc;
                          });
                  }
  
              });  
      }
      
      $scope.save = function () {
  
          if (!$scope.busy && formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) {
  
              $scope.busy = true;
  
              mediaResource.save($scope.content, $routeParams.create, fileManager.getFiles())
                  .then(function(data) {
  
                      formHelper.resetForm({ scope: $scope, notifications: data.notifications });
  
                      contentEditingHelper.handleSuccessfulSave({
                          scope: $scope,
                          savedContent: data,
                          rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
                      });
  
                      editorState.set($scope.content);
                      $scope.busy = false;
  
                      syncTreeNode($scope.content, data.path);
  
                  }, function(err) {
  
                      contentEditingHelper.handleSaveError({
                          err: err,
                          redirectOnFailure: true,
                          rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data)
                      });
                      
                      editorState.set($scope.content);
                      $scope.busy = false;
                  });
          }else{
              $scope.busy = false;
          }
          
      };
  }
  
  angular.module("umbraco")
      .controller("Umbraco.Editors.Media.EditController", mediaEditController);
  
  /**
   * @ngdoc controller
   * @name Umbraco.Editors.Media.EmptyRecycleBinController
   * @function
   * 
   * @description
   * The controller for deleting media
   */
  function MediaEmptyRecycleBinController($scope, mediaResource, treeService, navigationService) {
  
      $scope.performDelete = function() {
  
          //(used in the UI)
          $scope.currentNode.loading = true;
  
          mediaResource.emptyRecycleBin($scope.currentNode.id).then(function () {
              $scope.currentNode.loading = false;
              //TODO: Need to sync tree, etc...
              treeService.removeChildNodes($scope.currentNode);
              navigationService.hideMenu();
          });
  
      };
  
      $scope.cancel = function() {
          navigationService.hideDialog();
      };
  }
  
  angular.module("umbraco").controller("Umbraco.Editors.Media.EmptyRecycleBinController", MediaEmptyRecycleBinController);
  
  //used for the media picker dialog
  angular.module("umbraco").controller("Umbraco.Editors.Media.MoveController",
  	function ($scope, eventsService, mediaResource, appState, treeService, navigationService) {
  	    var dialogOptions = $scope.dialogOptions;
  
  	    $scope.dialogTreeEventHandler = $({});
  	    var node = dialogOptions.currentNode;
  
  	    function nodeSelectHandler(ev, args) {
  	        args.event.preventDefault();
  	        args.event.stopPropagation();
  
  	        eventsService.emit("editors.media.moveController.select", args);
  
  	        if ($scope.target) {
  	            //un-select if there's a current one selected
  	            $scope.target.selected = false;
  	        }
  
  	        $scope.target = args.node;
  	        $scope.target.selected = true;
  	    }
  
  	    $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
  
  
  	    $scope.move = function () {
  	        mediaResource.move({ parentId: $scope.target.id, id: node.id })
                  .then(function (path) {
                      $scope.error = false;
                      $scope.success = true;
  
                      //first we need to remove the node that launched the dialog
                      treeService.removeNode($scope.currentNode);
  
                      //get the currently edited node (if any)
                      var activeNode = appState.getTreeState("selectedNode");
  
                      //we need to do a double sync here: first sync to the moved content - but don't activate the node,
                      //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
  
                      navigationService.syncTree({ tree: "media", path: path, forceReload: true, activate: false }).then(function (args) {
                          if (activeNode) {
                              var activeNodePath = treeService.getPath(activeNode).join();
                              //sync to this node now - depending on what was copied this might already be synced but might not be
                              navigationService.syncTree({ tree: "media", path: activeNodePath, forceReload: false, activate: true });
                          }
                      });
  
                  }, function (err) {
                      $scope.success = false;
                      $scope.error = err;
                  });
  	    };
  
  	    $scope.$on('$destroy', function () {
  	        $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
  	    });
  	});
  /**
   * @ngdoc controller
   * @name Umbraco.Editors.Content.MediaRecycleBinController
   * @function
   * 
   * @description
   * Controls the recycle bin for media
   * 
   */
  
  function MediaRecycleBinController($scope, $routeParams, dataTypeResource) {
  
      //ensures the list view doesn't actually load until we query for the list view config
      // for the section
      $scope.listViewPath = null;
  
      $routeParams.id = "-21";
      dataTypeResource.getById(-96).then(function (result) {
          _.each(result.preValues, function (i) {
              $scope.model.config[i.key] = i.value;
          });
          $scope.listViewPath = 'views/propertyeditors/listview/listview.html';
      });
  
      $scope.model = { config: { entityType: $routeParams.section } };
  
  }
  
  angular.module('umbraco').controller("Umbraco.Editors.Media.RecycleBinController", MediaRecycleBinController);
  
  /**
   * @ngdoc controller
   * @name Umbraco.Editors.Member.CreateController
   * @function
   * 
   * @description
   * The controller for the member creation dialog
   */
  function memberCreateController($scope, $routeParams, memberTypeResource, iconHelper) {
      
      memberTypeResource.getTypes($scope.currentNode.id).then(function (data) {
          $scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
      });
      
  }
  
  angular.module('umbraco').controller("Umbraco.Editors.Member.CreateController", memberCreateController);
  /**
   * @ngdoc controller
   * @name Umbraco.Editors.Member.DeleteController
   * @function
   * 
   * @description
   * The controller for deleting content
   */
  function MemberDeleteController($scope, memberResource, treeService, navigationService, editorState, $location, $routeParams) {
  
      $scope.performDelete = function() {
  
          //mark it for deletion (used in the UI)
          $scope.currentNode.loading = true;
  
          memberResource.deleteByKey($scope.currentNode.id).then(function () {
              $scope.currentNode.loading = false;
  
              treeService.removeNode($scope.currentNode);
              
              //if the current edited item is the same one as we're deleting, we need to navigate elsewhere
              if (editorState.current && editorState.current.key == $scope.currentNode.id) {
                  $location.path("/member/member/list/" + ($routeParams.listName ? $routeParams.listName : 'all-members'));
              }
  
              navigationService.hideMenu();
          });
  
      };
  
      $scope.cancel = function() {
          navigationService.hideDialog();
      };
  }
  
  angular.module("umbraco").controller("Umbraco.Editors.Member.DeleteController", MemberDeleteController);
  
  /**
   * @ngdoc controller
   * @name Umbraco.Editors.Member.EditController
   * @function
   * 
   * @description
   * The controller for the member editor
   */
  function MemberEditController($scope, $routeParams, $location, $q, $window, appState, memberResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, formHelper, umbModelMapper, editorState, umbRequestHelper, $http) {
      
      //setup scope vars
      $scope.currentSection = appState.getSectionState("currentSection");
      $scope.currentNode = null; //the editors affiliated node
  
      $scope.listViewPath = ($routeParams.page && $routeParams.listName)
          ? "/member/member/list/" + $routeParams.listName + "?page=" + $routeParams.page
          : null;
  
      //build a path to sync the tree with
      function buildTreePath(data) {
          return $routeParams.listName ? "-1," + $routeParams.listName : "-1";
      }
  
      if ($routeParams.create) {
          
          //if there is no doc type specified then we are going to assume that 
          // we are not using the umbraco membership provider
          if ($routeParams.doctype) {
              //we are creating so get an empty member item
              memberResource.getScaffold($routeParams.doctype)
                  .then(function(data) {
                      $scope.loaded = true;
                      $scope.content = data;
  
                      editorState.set($scope.content);
                  });
          }
          else {
              memberResource.getScaffold()
                  .then(function (data) {
                      $scope.loaded = true;
                      $scope.content = data;
  
                      editorState.set($scope.content);
                  });
          }
          
      }
      else {
          //so, we usually refernce all editors with the Int ID, but with members we have
          //a different pattern, adding a route-redirect here to handle this: 
          //isNumber doesnt work here since its seen as a string
  
          //TODO: Why is this here - I don't understand why this would ever be an integer? This will not work when we support non-umbraco membership providers.
  
          if ($routeParams.id && $routeParams.id.length < 9) {
              entityResource.getById($routeParams.id, "Member").then(function(entity) {
                  $location.path("member/member/edit/" + entity.key);
              });
          }
          else {
              //we are editing so get the content item from the server
              memberResource.getByKey($routeParams.id)
                  .then(function(data) {
                      $scope.loaded = true;
                      $scope.content = data;
  
                      editorState.set($scope.content);
                      
                      var path = buildTreePath(data);
  
                      //sync the tree (only for ui purposes)
                      navigationService.syncTree({ tree: "member", path: path.split(",") });
  
                      //it's the initial load of the editor, we need to get the tree node 
                      // from the server so that we can load in the actions menu.
                      umbRequestHelper.resourcePromise(
                          $http.get(data.treeNodeUrl),
                          'Failed to retrieve data for child node ' + data.key).then(function (node) {
                              $scope.currentNode = node;
                          });
  
                      //in one particular special case, after we've created a new item we redirect back to the edit
                      // route but there might be server validation errors in the collection which we need to display
                      // after the redirect, so we will bind all subscriptions which will show the server validation errors
                      // if there are any and then clear them so the collection no longer persists them.
                      serverValidationManager.executeAndClearAllSubscriptions();
                  });
          }
  
      }
      
      $scope.save = function() {
  
          if (!$scope.busy && formHelper.submitForm({ scope: $scope, statusMessage: "Saving..." })) {
              
              $scope.busy = true;
  
              memberResource.save($scope.content, $routeParams.create, fileManager.getFiles())
                  .then(function(data) {
  
                      formHelper.resetForm({ scope: $scope, notifications: data.notifications });
  
                      contentEditingHelper.handleSuccessfulSave({
                          scope: $scope,
                          savedContent: data,
                          //specify a custom id to redirect to since we want to use the GUID
                          redirectId: data.key,
                          rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
                      });
                      
                      editorState.set($scope.content);
                      $scope.busy = false;
                      
                      var path = buildTreePath(data);
  
                      //sync the tree (only for ui purposes)
                      navigationService.syncTree({ tree: "member", path: path.split(","), forceReload: true });
  
              }, function (err) {
                      
                      contentEditingHelper.handleSaveError({
                          redirectOnFailure: false,
                          err: err,
                          rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data)
                      });
                      
                      editorState.set($scope.content);
                      $scope.busy = false;
                  });
          }else{
              $scope.busy = false;
          }
          
      };
  
  }
  
  angular.module("umbraco").controller("Umbraco.Editors.Member.EditController", MemberEditController);
  
  /**
   * @ngdoc controller
   * @name Umbraco.Editors.Member.ListController
   * @function
   * 
   * @description
   * The controller for the member list view
   */
  function MemberListController($scope, $routeParams, $location, $q, $window, appState, memberResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, formHelper, umbModelMapper, editorState) {
      
      //setup scope vars
      $scope.currentSection = appState.getSectionState("currentSection");
      $scope.currentNode = null; //the editors affiliated node
      
      //we are editing so get the content item from the server
      memberResource.getListNode($routeParams.id)
          .then(function (data) {
              $scope.loaded = true;
              $scope.content = data;
  
              editorState.set($scope.content);
  
              navigationService.syncTree({ tree: "member", path: data.path.split(",") }).then(function (syncArgs) {
                  $scope.currentNode = syncArgs.node;
              });
  
              //in one particular special case, after we've created a new item we redirect back to the edit
              // route but there might be server validation errors in the collection which we need to display
              // after the redirect, so we will bind all subscriptions which will show the server validation errors
              // if there are any and then clear them so the collection no longer persists them.
              serverValidationManager.executeAndClearAllSubscriptions();
          });
  }
  
  angular.module("umbraco").controller("Umbraco.Editors.Member.ListController", MemberListController);
  
  function imageFilePickerController($scope, dialogService, mediaHelper) {
  
      $scope.pick = function() {
          dialogService.mediaPicker({
              multiPicker: false,
              callback: function(data) {
                   $scope.model.value = mediaHelper.resolveFile(data, false);
              }
          });
      };
  
  }
  
  angular.module('umbraco').controller("Umbraco.PrevalueEditors.ImageFilePickerController",imageFilePickerController);
  
  //this controller simply tells the dialogs service to open a mediaPicker window
  //with a specified callback, this callback will receive an object with a selection on it
  function mediaPickerController($scope, dialogService, entityResource, $log, iconHelper) {
  
      function trim(str, chr) {
          var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
          return str.replace(rgxtrim, '');
      }
  
      $scope.renderModel = [];   
  
      var dialogOptions = {
          multiPicker: false,
          entityType: "Media",
          section: "media",
          treeAlias: "media",
          callback: function(data) {
              if (angular.isArray(data)) {
                  _.each(data, function (item, i) {
                      $scope.add(item);
                  });
              }
              else {
                  $scope.clear();
                  $scope.add(data);
              }
          }
      };
  
      $scope.openContentPicker = function(){
          var d = dialogService.treePicker(dialogOptions);
      };
  
      $scope.remove =function(index){
          $scope.renderModel.splice(index, 1);
      };
  
      $scope.clear = function() {
          $scope.renderModel = [];
      };
  
      $scope.add = function (item) {
          var currIds = _.map($scope.renderModel, function (i) {
              return i.id;
          });
          if (currIds.indexOf(item.id) < 0) {
              item.icon = iconHelper.convertFromLegacyIcon(item.icon);
              $scope.renderModel.push({name: item.name, id: item.id, icon: item.icon});
          }	
      };
  
      var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
          var currIds = _.map($scope.renderModel, function (i) {
              return i.id;
          });
          $scope.model.value = trim(currIds.join(), ",");
      });
  
      //when the scope is destroyed we need to unsubscribe
      $scope.$on('$destroy', function () {
          unsubscribe();
      });
  
      //load media data
      var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
      entityResource.getByIds(modelIds, dialogOptions.entityType).then(function (data) {
          _.each(data, function (item, i) {
              item.icon = iconHelper.convertFromLegacyIcon(item.icon);
              $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon });
          });
      });
      
  }
  
  angular.module('umbraco').controller("Umbraco.PrevalueEditors.MediaPickerController",mediaPickerController);
  angular.module("umbraco").controller("Umbraco.PrevalueEditors.MultiValuesController",
      function ($scope, $timeout) {
         
          //NOTE: We need to make each item an object, not just a string because you cannot 2-way bind to a primitive.
  
          $scope.newItem = "";
          $scope.hasError = false;
         
          if (!angular.isArray($scope.model.value)) {
  
              //make an array from the dictionary
              var items = [];
              for (var i in $scope.model.value) { 
                  items.push({ 
                      value: $scope.model.value[i].value,
                      sortOrder: $scope.model.value[i].sortOrder,
                      id: i
                  });
              }
  
              //ensure the items are sorted by the provided sort order
              items.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); });
              
              //now make the editor model the array
              $scope.model.value = items;
          }
  
          $scope.remove = function(item, evt) {
              evt.preventDefault();
  
              $scope.model.value = _.reject($scope.model.value, function (x) {
                  return x.value === item.value;
              });
              
          };
  
          $scope.add = function (evt) {
              evt.preventDefault();
              
              
              if ($scope.newItem) {
                  if (!_.contains($scope.model.value, $scope.newItem)) {                
                      $scope.model.value.push({ value: $scope.newItem });
                      $scope.newItem = "";
                      $scope.hasError = false;
                      return;
                  }
              }
  
              //there was an error, do the highlight (will be set back by the directive)
              $scope.hasError = true;            
          };
  
          $scope.sortableOptions = {
              axis: 'y',
              containment: 'parent',
              cursor: 'move',
              items: '> div.control-group',
              tolerance: 'pointer',
              update: function (e, ui) {
                  // Get the new and old index for the moved element (using the text as the identifier, so 
                  // we'd have a problem if two prevalues were the same, but that would be unlikely)
                  var newIndex = ui.item.index();
                  var movedPrevalueText = $('input[type="text"]', ui.item).val();
                  var originalIndex = getElementIndexByPrevalueText(movedPrevalueText);
  
                  // Move the element in the model
                  if (originalIndex > -1) {
                      var movedElement = $scope.model.value[originalIndex];
                      $scope.model.value.splice(originalIndex, 1);
                      $scope.model.value.splice(newIndex, 0, movedElement);
                  }
              }
          };
  
          function getElementIndexByPrevalueText(value) {
              for (var i = 0; i < $scope.model.value.length; i++) {
                  if ($scope.model.value[i].value === value) {
                      return i;
                  }
              }
  
              return -1;
          }
  
      });
  
  //this controller simply tells the dialogs service to open a mediaPicker window
  //with a specified callback, this callback will receive an object with a selection on it
  angular.module('umbraco')
  .controller("Umbraco.PrevalueEditors.TreePickerController",
  	
  	function($scope, dialogService, entityResource, $log, iconHelper){
  		$scope.renderModel = [];
  		$scope.ids = [];
  
  
  	    var config = {
  	        multiPicker: false,
  	        entityType: "Document",
  	        type: "content",
  	        treeAlias: "content"
  	    };
  		
  		if($scope.model.value){
  			$scope.ids = $scope.model.value.split(',');
  			entityResource.getByIds($scope.ids, config.entityType).then(function (data) {
  			    _.each(data, function (item, i) {
  					item.icon = iconHelper.convertFromLegacyIcon(item.icon);
  					$scope.renderModel.push({name: item.name, id: item.id, icon: item.icon});
  				});
  			});
  		}
  		
  
  		$scope.openContentPicker =function() {
  		    var d = dialogService.treePicker({
  		        section: config.type,
  		        treeAlias: config.type,
  		        multiPicker: config.multiPicker,
  		        callback: populate
  		    });
  		};
  
  		$scope.remove =function(index){
  			$scope.renderModel.splice(index, 1);
  			$scope.ids.splice(index, 1);
  			$scope.model.value = trim($scope.ids.join(), ",");
  		};
  
  		$scope.clear = function() {
  		    $scope.model.value = "";
  		    $scope.renderModel = [];
  		    $scope.ids = [];
  		};
  		
  		$scope.add =function(item){
  			if($scope.ids.indexOf(item.id) < 0){
  				item.icon = iconHelper.convertFromLegacyIcon(item.icon);
  
  				$scope.ids.push(item.id);
  				$scope.renderModel.push({name: item.name, id: item.id, icon: item.icon});
  				$scope.model.value = trim($scope.ids.join(), ",");
  			}	
  		};
  
  
  	    var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
  			$scope.model.value = trim($scope.ids.join(), ",");
  	    });
  
  	    //when the scope is destroyed we need to unsubscribe
  	    $scope.$on('$destroy', function () {
  	        unsubscribe();
  	    });
  
  		function trim(str, chr) {
  			var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^'+chr+'+|'+chr+'+$', 'g');
  			return str.replace(rgxtrim, '');
  		}
  
  		function populate(data){
  			if(angular.isArray(data)){
  			    _.each(data, function (item, i) {
  					$scope.add(item);
  				});
  			}else{
  				$scope.clear();
  				$scope.add(data);
  			}
  		}
  });
  //this controller simply tells the dialogs service to open a mediaPicker window
  //with a specified callback, this callback will receive an object with a selection on it
  angular.module('umbraco')
  .controller("Umbraco.PrevalueEditors.TreeSourceController",
  	
  	function($scope, dialogService, entityResource, $log, iconHelper){
  
  	    if (!$scope.model) {
  	        $scope.model = {};
  	    }
  	    if (!$scope.model.value) {
  	        $scope.model.value = {
  	            type: "content"
  	        };
  	    }
  
  		if($scope.model.value.id && $scope.model.value.type !== "member"){
  			var ent = "Document";
  			if($scope.model.value.type === "media"){
  				ent = "Media";
  			}
  			
  			entityResource.getById($scope.model.value.id, ent).then(function(item){
  				item.icon = iconHelper.convertFromLegacyIcon(item.icon);
  				$scope.node = item;
  			});
  		}
  
  
  		$scope.openContentPicker =function(){
  			var d = dialogService.treePicker({
  								section: $scope.model.value.type,
  								treeAlias: $scope.model.value.type,
  								multiPicker: false,
  								callback: populate});
  		};
  
  		$scope.clear = function() {
  			$scope.model.value.id = undefined;
  			$scope.node = undefined;
  			$scope.model.value.query = undefined;
  		};
  		
  
  		//we always need to ensure we dont submit anything broken
  	    var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
  	    	if($scope.model.value.type === "member"){
  	    		$scope.model.value.id = -1;
  	    		$scope.model.value.query = "";
  	    	}
  	    });
  
  	    //when the scope is destroyed we need to unsubscribe
  	    $scope.$on('$destroy', function () {
  	        unsubscribe();
  	    });
  
  		function populate(item){
  				$scope.clear();
  				item.icon = iconHelper.convertFromLegacyIcon(item.icon);
  				$scope.node = item;
  				$scope.model.value.id = item.id;
  		}
  });
  function booleanEditorController($scope, $rootScope, assetsService) {
  
      function setupViewModel() {
          $scope.renderModel = {
              value: false
          };
          if ($scope.model && $scope.model.value && ($scope.model.value.toString() === "1" || angular.lowercase($scope.model.value) === "true")) {
              $scope.renderModel.value = true;
          }
      }
  
      setupViewModel();
  
      $scope.$watch("renderModel.value", function (newVal) {
          $scope.model.value = newVal === true ? "1" : "0";
      });
      
      //here we declare a special method which will be called whenever the value has changed from the server
      //this is instead of doing a watch on the model.value = faster
      $scope.model.onValueChanged = function (newVal, oldVal) {
          //update the display val again if it has changed from the server
          setupViewModel();
      };
  
  }
  angular.module("umbraco").controller("Umbraco.PropertyEditors.BooleanController", booleanEditorController);
  angular.module("umbraco").controller("Umbraco.PropertyEditors.ChangePasswordController",
      function ($scope, $routeParams) {
          
          function resetModel(isNew) {
              //the model config will contain an object, if it does not we'll create defaults
              //NOTE: We will not support doing the password regex on the client side because the regex on the server side
              //based on the membership provider cannot always be ported to js from .net directly.        
              /*
              {
                  hasPassword: true/false,
                  requiresQuestionAnswer: true/false,
                  enableReset: true/false,
                  enablePasswordRetrieval: true/false,
                  minPasswordLength: 10
              }
              */
  
              //set defaults if they are not available
              if (!$scope.model.config || $scope.model.config.disableToggle === undefined) {
                  $scope.model.config.disableToggle = false;
              }
              if (!$scope.model.config || $scope.model.config.hasPassword === undefined) {
                  $scope.model.config.hasPassword = false;
              }
              if (!$scope.model.config || $scope.model.config.enablePasswordRetrieval === undefined) {
                  $scope.model.config.enablePasswordRetrieval = true;
              }
              if (!$scope.model.config || $scope.model.config.requiresQuestionAnswer === undefined) {
                  $scope.model.config.requiresQuestionAnswer = false;
              }
              if (!$scope.model.config || $scope.model.config.enableReset === undefined) {
                  $scope.model.config.enableReset = true;
              }
              if (!$scope.model.config || $scope.model.config.minPasswordLength === undefined) {
                  $scope.model.config.minPasswordLength = 0;
              }
              
              //set the model defaults
              if (!angular.isObject($scope.model.value)) {
                  //if it's not an object then just create a new one
                  $scope.model.value = {
                      newPassword: null,
                      oldPassword: null,
                      reset: null,
                      answer: null
                  };
              }
              else {
                  //just reset the values
  
                  if (!isNew) {
                      //if it is new, then leave the generated pass displayed
                      $scope.model.value.newPassword = null;
                      $scope.model.value.oldPassword = null;
                  }
                  $scope.model.value.reset = null;
                  $scope.model.value.answer = null;
              }
  
              //the value to compare to match passwords
              if (!isNew) {
                  $scope.model.confirm = "";
              }
              else if ($scope.model.value.newPassword.length > 0) {
                  //if it is new and a new password has been set, then set the confirm password too
                  $scope.model.confirm = $scope.model.value.newPassword;
              }
              
          }
  
          resetModel($routeParams.create);
  
          //if there is no password saved for this entity , it must be new so we do not allow toggling of the change password, it is always there
          //with validators turned on.
          $scope.changing = $scope.model.config.disableToggle === true || !$scope.model.config.hasPassword;
  
          //we're not currently changing so set the model to null
          if (!$scope.changing) {
              $scope.model.value = null;
          }
  
          $scope.doChange = function() {
              resetModel();
              $scope.changing = true;
              //if there was a previously generated password displaying, clear it
              $scope.model.value.generatedPassword = null;
          };
  
          $scope.cancelChange = function() {
              $scope.changing = false;
              //set model to null
              $scope.model.value = null;
          };
  
          var unsubscribe = [];
  
          //listen for the saved event, when that occurs we'll 
          //change to changing = false;
          unsubscribe.push($scope.$on("formSubmitted", function() {
              if ($scope.model.config.disableToggle === false) {
                  $scope.changing = false;
              }
          }));
          unsubscribe.push($scope.$on("formSubmitting", function() {
              //if there was a previously generated password displaying, clear it
              if ($scope.changing && $scope.model.value) {
                  $scope.model.value.generatedPassword = null;
              }
              else if (!$scope.changing) {
                  //we are not changing, so the model needs to be null
                  $scope.model.value = null;
              }
          }));
  
          //when the scope is destroyed we need to unsubscribe
          $scope.$on('$destroy', function () {
              for (var u in unsubscribe) {
                  unsubscribe[u]();
              }
          });
  
          $scope.showReset = function() {
              return $scope.model.config.hasPassword && $scope.model.config.enableReset;
          };
  
          $scope.showOldPass = function() {
              return $scope.model.config.hasPassword &&
                  !$scope.model.config.allowManuallyChangingPassword &&
                  !$scope.model.config.enablePasswordRetrieval && !$scope.model.value.reset;
          };
  
          $scope.showNewPass = function () {
              return !$scope.model.value.reset;
          };
  
          $scope.showConfirmPass = function() {
              return !$scope.model.value.reset;
          };
          
          $scope.showCancelBtn = function() {
              return $scope.model.config.disableToggle !== true && $scope.model.config.hasPassword;
          };
  
      });
  
  angular.module("umbraco").controller("Umbraco.PropertyEditors.CheckboxListController",
      function($scope) {
          
          if (angular.isObject($scope.model.config.items)) {
              
              //now we need to format the items in the dictionary because we always want to have an array
              var newItems = [];
              var vals = _.values($scope.model.config.items);
              var keys = _.keys($scope.model.config.items);
              for (var i = 0; i < vals.length; i++) {
                  newItems.push({ id: keys[i], sortOrder: vals[i].sortOrder, value: vals[i].value });
              }
  
              //ensure the items are sorted by the provided sort order
              newItems.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); });
  
              //re-assign
              $scope.model.config.items = newItems;
  
          }
  
          function setupViewModel() {
              $scope.selectedItems = [];
  
              //now we need to check if the value is null/undefined, if it is we need to set it to "" so that any value that is set
              // to "" gets selected by default
              if ($scope.model.value === null || $scope.model.value === undefined) {
                  $scope.model.value = [];
              }
  
              for (var i = 0; i < $scope.model.config.items.length; i++) {
                  var isChecked = _.contains($scope.model.value, $scope.model.config.items[i].id);
                  $scope.selectedItems.push({
                      checked: isChecked,
                      key: $scope.model.config.items[i].id,
                      val: $scope.model.config.items[i].value
                  });
              }
  
          }
  
          setupViewModel();
          
  
          //update the model when the items checked changes
          $scope.$watch("selectedItems", function(newVal, oldVal) {
  
              $scope.model.value = [];
              for (var x = 0; x < $scope.selectedItems.length; x++) {
                  if ($scope.selectedItems[x].checked) {
                      $scope.model.value.push($scope.selectedItems[x].key);
                  }
              }
  
          }, true);
          
          //here we declare a special method which will be called whenever the value has changed from the server
          //this is instead of doing a watch on the model.value = faster
          $scope.model.onValueChanged = function (newVal, oldVal) {
              //update the display val again if it has changed from the server
              setupViewModel();
          };
  
      });
  
  function ColorPickerController($scope) {
      $scope.toggleItem = function (color) {
          if ($scope.model.value == color) {
              $scope.model.value = "";
              //this is required to re-validate
              $scope.propertyForm.modelValue.$setViewValue($scope.model.value);
          }
          else {
              $scope.model.value = color;
              //this is required to re-validate
              $scope.propertyForm.modelValue.$setViewValue($scope.model.value);
          }
      };
      // Method required by the valPropertyValidator directive (returns true if the property editor has at least one color selected)
      $scope.validateMandatory = function () {
          return {
              isValid: !$scope.model.validation.mandatory || ($scope.model.value != null && $scope.model.value != ""),
              errorMsg: "Value cannot be empty",
              errorKey: "required"
          };
      }
      $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0;
  }
  
  angular.module("umbraco").controller("Umbraco.PropertyEditors.ColorPickerController", ColorPickerController);
  
  angular.module("umbraco").controller("Umbraco.PrevalueEditors.MultiColorPickerController",
      function ($scope, $timeout, assetsService, angularHelper, $element) {
          //NOTE: We need to make each color an object, not just a string because you cannot 2-way bind to a primitive.
          var defaultColor = "000000";
          
          $scope.newColor = defaultColor;
          $scope.hasError = false;
  
          assetsService.load([
              //"lib/spectrum/tinycolor.js",
              "lib/spectrum/spectrum.js"          
          ]).then(function () {
              var elem = $element.find("input");
              elem.spectrum({
                  color: null,
                  showInitial: false,
                  chooseText: "choose", // TODO: These can be localised
                  cancelText: "cancel", // TODO: These can be localised
                  preferredFormat: "hex",
                  showInput: true,
                  clickoutFiresChange: true,
                  hide: function (color) {
                      //show the add butotn
                      $element.find(".btn.add").show();                    
                  },
                  change: function (color) {
                      angularHelper.safeApply($scope, function () {
                          $scope.newColor = color.toHexString().trimStart("#"); // #ff0000
                      });
                  },
                  show: function() {
                      //hide the add butotn
                      $element.find(".btn.add").hide();
                  }
              });
          });
  
          if (!angular.isArray($scope.model.value)) {
              //make an array from the dictionary
              var items = [];
              for (var i in $scope.model.value) {
                  items.push({
                      value: $scope.model.value[i],
                      id: i
                  });
              }
              //now make the editor model the array
              $scope.model.value = items;
          }
  
          $scope.remove = function (item, evt) {
  
              evt.preventDefault();
  
              $scope.model.value = _.reject($scope.model.value, function (x) {
                  return x.value === item.value;
              });
  
          };
  
          $scope.add = function (evt) {
  
              evt.preventDefault();
  
              if ($scope.newColor) {
                  var exists = _.find($scope.model.value, function(item) {
                      return item.value.toUpperCase() == $scope.newColor.toUpperCase();
                  });
                  if (!exists) {
                      $scope.model.value.push({ value: $scope.newColor });
                      //$scope.newColor = defaultColor;
                      // set colorpicker to default color
                      //var elem = $element.find("input");
                      //elem.spectrum("set", $scope.newColor);
                      $scope.hasError = false;
                      return;
                  }
  
                  //there was an error, do the highlight (will be set back by the directive)
                  $scope.hasError = true;
              }
  
          };
  
          //load the separate css for the editor to avoid it blocking our js loading
          assetsService.loadCss("lib/spectrum/spectrum.css");
      });
  
  //this controller simply tells the dialogs service to open a mediaPicker window
  //with a specified callback, this callback will receive an object with a selection on it
  
  function contentPickerController($scope, dialogService, entityResource, editorState, $log, iconHelper, $routeParams, fileManager, contentEditingHelper) {
  
      function trim(str, chr) {
          var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
          return str.replace(rgxtrim, '');
      }
  
      function startWatch() {
          //We need to watch our renderModel so that we can update the underlying $scope.model.value properly, this is required
          // because the ui-sortable doesn't dispatch an event after the digest of the sort operation. Any of the events for UI sortable
          // occur after the DOM has updated but BEFORE the digest has occured so the model has NOT changed yet - it even states so in the docs.
          // In their source code there is no event so we need to just subscribe to our model changes here.
          //This also makes it easier to manage models, we update one and the rest will just work.
          $scope.$watch(function () {
              //return the joined Ids as a string to watch
              return _.map($scope.renderModel, function (i) {
                  return i.id;
              }).join();
          }, function (newVal) {
              var currIds = _.map($scope.renderModel, function (i) {
                  return i.id;
              });
              $scope.model.value = trim(currIds.join(), ",");
  
              //Validate!
              if ($scope.model.config && $scope.model.config.minNumber && parseInt($scope.model.config.minNumber) > $scope.renderModel.length) {
                  $scope.contentPickerForm.minCount.$setValidity("minCount", false);
              }
              else {
                  $scope.contentPickerForm.minCount.$setValidity("minCount", true);
              }
  
              if ($scope.model.config && $scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) < $scope.renderModel.length) {
                  $scope.contentPickerForm.maxCount.$setValidity("maxCount", false);
              }
              else {
                  $scope.contentPickerForm.maxCount.$setValidity("maxCount", true);
              }
          });
      }
  
      $scope.renderModel = [];
  	    
      $scope.dialogEditor = editorState && editorState.current && editorState.current.isDialogEditor === true;
  
      //the default pre-values
      var defaultConfig = {
          multiPicker: false,
          showEditButton: false,        
          startNode: {
              query: "",
              type: "content",
  	            id: $scope.model.config.startNodeId ? $scope.model.config.startNodeId : -1 // get start node for simple Content Picker
          }
      };
  
      if ($scope.model.config) {
          //merge the server config on top of the default config, then set the server config to use the result
          $scope.model.config = angular.extend(defaultConfig, $scope.model.config);
      }
  
      //Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that!
      $scope.model.config.multiPicker = ($scope.model.config.multiPicker === "1" ? true : false);
      $scope.model.config.showEditButton = ($scope.model.config.showEditButton === "1" ? true : false);
  
      var entityType = $scope.model.config.startNode.type === "member"
          ? "Member"
          : $scope.model.config.startNode.type === "media"
          ? "Media"
          : "Document";
  
      //the dialog options for the picker
      var dialogOptions = {
          multiPicker: $scope.model.config.multiPicker,
          entityType: entityType,
          filterCssClass: "not-allowed not-published",
          startNodeId: null,
          callback: function (data) {
              if (angular.isArray(data)) {
                  _.each(data, function (item, i) {
                      $scope.add(item);
                  });
              } else {
                  $scope.clear();
                  $scope.add(data);
              }
          },
          treeAlias: $scope.model.config.startNode.type,
          section: $scope.model.config.startNode.type
      };
  
      //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the 
      // pre-value config on to the dialog options
      angular.extend(dialogOptions, $scope.model.config);
  
      //We need to manually handle the filter for members here since the tree displayed is different and only contains
      // searchable list views
      if (entityType === "Member") {
          //first change the not allowed filter css class
          dialogOptions.filterCssClass = "not-allowed";
          var currFilter = dialogOptions.filter;
          //now change the filter to be a method
          dialogOptions.filter = function(i) {
              //filter out the list view nodes
              if (i.metaData.isContainer) {
                  return true;
              }
              if (!currFilter) {
                  return false;
              }
              //now we need to filter based on what is stored in the pre-vals, this logic duplicates what is in the treepicker.controller, 
              // but not much we can do about that since members require special filtering.
              var filterItem = currFilter.toLowerCase().split(',');
              var found = filterItem.indexOf(i.metaData.contentType.toLowerCase()) >= 0;
              if (!currFilter.startsWith("!") && !found || currFilter.startsWith("!") && found) {
                  return true;
              }
  
              return false;
          }
      }
  
  
      //if we have a query for the startnode, we will use that. 
      if ($scope.model.config.startNode.query) {
          var rootId = $routeParams.id;
          entityResource.getByQuery($scope.model.config.startNode.query, rootId, "Document").then(function (ent) {
              dialogOptions.startNodeId = ent.id;
          });
      } else {
          dialogOptions.startNodeId = $scope.model.config.startNode.id;
      }        
      
      //dialog
      $scope.openContentPicker = function () {                
          var d = dialogService.treePicker(dialogOptions);
      };
  
      $scope.remove = function (index) {
          $scope.renderModel.splice(index, 1);
      };
          
      $scope.add = function (item) {
          var currIds = _.map($scope.renderModel, function (i) {
              return i.id;
          });
  
          if (currIds.indexOf(item.id) < 0) {
              item.icon = iconHelper.convertFromLegacyIcon(item.icon);
              $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon });
          }
      };
  
      $scope.clear = function () {
          $scope.renderModel = [];
      };
          
      var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
          var currIds = _.map($scope.renderModel, function (i) {
              return i.id;
          });
          $scope.model.value = trim(currIds.join(), ",");
      });
  
      //when the scope is destroyed we need to unsubscribe
      $scope.$on('$destroy', function () {
          unsubscribe();
      });
  
      //load current data
      var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
      entityResource.getByIds(modelIds, entityType).then(function (data) {
  
          //Ensure we populate the render model in the same order that the ids were stored!
          _.each(modelIds, function (id, i) {
              var entity = _.find(data, function (d) {                
                  return d.id == id;
              });
             
              if(entity) {
                  entity.icon = iconHelper.convertFromLegacyIcon(entity.icon);
                  $scope.renderModel.push({ name: entity.name, id: entity.id, icon: entity.icon });
              }
             
          });
  
          //everything is loaded, start the watch on the model
          startWatch();
  
      });
  }
  
  angular.module('umbraco').controller("Umbraco.PropertyEditors.ContentPickerController", contentPickerController);
  
  function dateTimePickerController($scope, notificationsService, assetsService, angularHelper, userService, $element) {
  
      //setup the default config
      var config = {
          pickDate: true,
          pickTime: true,
  		useSeconds: true,
          format: "YYYY-MM-DD HH:mm:ss",
  		icons: {
                      time: "icon-time",
                      date: "icon-calendar",
                      up: "icon-chevron-up",
                      down: "icon-chevron-down"
                  }
  
      };
  
      //map the user config
      $scope.model.config = angular.extend(config, $scope.model.config);
  
      $scope.datetimePickerValue = $scope.model.value;
  
      //hide picker if clicking on the document 
      $scope.hidePicker = function () {
          //$element.find("div:first").datetimepicker("hide");
          // Sometimes the statement above fails and generates errors in the browser console. The following statements fix that.
          var dtp = $element.find("div:first");
          if (dtp && dtp.datetimepicker) {
              dtp.datetimepicker("hide");
          }
      };
      $(document).bind("click", $scope.hidePicker);
  
      //handles the date changing via the api
      function applyDate(e) {
          angularHelper.safeApply($scope, function() {
              // when a date is changed, update the model
              if (e.date) {
                  if ($scope.model.config.pickTime) {
                      $scope.model.value = e.date.format("YYYY-MM-DD HH:mm:ss");
                  }
                  else {
                      $scope.model.value = e.date.format("YYYY-MM-DD");
                  }
              }
              
              if (!$scope.model.config.pickTime) {
                  $element.find("div:first").datetimepicker("hide", 0);
              }
          });
      }
  
      //get the current user to see if we can localize this picker
      userService.getCurrentUser().then(function (user) {
  
          assetsService.loadCss('lib/datetimepicker/bootstrap-datetimepicker.min.css').then(function() {
  
          var filesToLoad = ["lib/moment/moment-with-locales.js",
  						   "lib/datetimepicker/bootstrap-datetimepicker.js"];
  
              
  		$scope.model.config.language = user.locale;
  		
  
  		assetsService.load(filesToLoad).then(
  			function () {
  				//The Datepicker js and css files are available and all components are ready to use.
  
  				// Get the id of the datepicker button that was clicked
  				var pickerId = $scope.model.alias;
  
  				// Open the datepicker and add a changeDate eventlistener
  				$element.find("div:first")
  					.datetimepicker($scope.model.config)
  					.on("dp.change", applyDate);
  
  			    //manually assign the date to the plugin
  				if (!$scope.model.config.format) {
  				    $element.find("div:first").datetimepicker("setValue", $scope.model.value ? $scope.model.value : null);
  				}
  				else {
  				    $element.find("div:first").datetimepicker("setValue", $scope.model.value ? new Date($scope.model.value) : null);
  				    if ($scope.model.value && $scope.model.config.format) {
  				        $scope.datetimePickerValue = moment($scope.model.value).format($scope.model.config.format);
  				    }
  				}
  
  				//Ensure to remove the event handler when this instance is destroyted
  				$scope.$on('$destroy', function () {
  					$element.find("div:first").datetimepicker("destroy");
  				});
  			});
          });
  
          
      });
  
      //unbind doc click event!
      $scope.$on('$destroy', function () {
          $(document).unbind("click", $scope.hidePicker);
      });
  }
  
  angular.module("umbraco").controller("Umbraco.PropertyEditors.DatepickerController", dateTimePickerController);
  
  angular.module("umbraco").controller("Umbraco.PropertyEditors.DropdownController",
      function($scope) {
  
          //setup the default config
          var config = {
              items: [],
              multiple: false
          };
  
          //map the user config
          angular.extend(config, $scope.model.config);
  
          //map back to the model
          $scope.model.config = config;
          
          function convertArrayToDictionaryArray(model){
              //now we need to format the items in the dictionary because we always want to have an array
              var newItems = [];
              for (var i = 0; i < model.length; i++) {
                  newItems.push({ id: model[i], sortOrder: 0, value: model[i] });
              }
  
              return newItems;
          }
  
  
          function convertObjectToDictionaryArray(model){
              //now we need to format the items in the dictionary because we always want to have an array
              var newItems = [];
              var vals = _.values($scope.model.config.items);
              var keys = _.keys($scope.model.config.items);
  
              for (var i = 0; i < vals.length; i++) {
                  var label = vals[i].value ? vals[i].value : vals[i]; 
                  newItems.push({ id: keys[i], sortOrder: vals[i].sortOrder, value: label });
              }
  
              return newItems;
          }
  
          if (angular.isArray($scope.model.config.items)) {
              //PP: I dont think this will happen, but we have tests that expect it to happen..
              //if array is simple values, convert to array of objects
              if(!angular.isObject($scope.model.config.items[0])){
                  $scope.model.config.items = convertArrayToDictionaryArray($scope.model.config.items);
              }
          }
          else if (angular.isObject($scope.model.config.items)) {
              $scope.model.config.items = convertObjectToDictionaryArray($scope.model.config.items);
          }
          else {
              throw "The items property must be either an array or a dictionary";
          }
          
  
          //sort the values
          $scope.model.config.items.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); });
  
          //now we need to check if the value is null/undefined, if it is we need to set it to "" so that any value that is set
          // to "" gets selected by default
          if ($scope.model.value === null || $scope.model.value === undefined) {
              if ($scope.model.config.multiple) {
                  $scope.model.value = [];
              }
              else {
                  $scope.model.value = "";
              }
          }
          
      });
  
  /** A drop down list or multi value select list based on an entity type, this can be re-used for any entity types */
  function entityPicker($scope, entityResource) {
  
      //set the default to DocumentType
      if (!$scope.model.config.entityType) {
          $scope.model.config.entityType = "DocumentType";
      }
  
      //Determine the select list options and which value to publish
      if (!$scope.model.config.publishBy) {
          $scope.selectOptions = "entity.id as entity.name for entity in entities";
      }
      else {
          $scope.selectOptions = "entity." + $scope.model.config.publishBy + " as entity.name for entity in entities";
      }
  
      entityResource.getAll($scope.model.config.entityType).then(function (data) {
          //convert the ids to strings so the drop downs work properly when comparing
          _.each(data, function(d) {
              d.id = d.id.toString();
          });
          $scope.entities = data;
      });
  
      if ($scope.model.value === null || $scope.model.value === undefined) {
          if ($scope.model.config.multiple) {
              $scope.model.value = [];
          }
          else {
              $scope.model.value = "";
          }
      }
      else {
          //if it's multiple, change the value to an array
          if ($scope.model.config.multiple === "1") {
              if (_.isString($scope.model.value)) {
                  $scope.model.value = $scope.model.value.split(',');
              }
          }
      }
  }
  angular.module('umbraco').controller("Umbraco.PropertyEditors.EntityPickerController", entityPicker);
  /**
   * @ngdoc controller
   * @name Umbraco.Editors.FileUploadController
   * @function
   * 
   * @description
   * The controller for the file upload property editor. It is important to note that the $scope.model.value
   *  doesn't necessarily depict what is saved for this property editor. $scope.model.value can be empty when we 
   *  are submitting files because in that case, we are adding files to the fileManager which is what gets peristed
   *  on the server. However, when we are clearing files, we are setting $scope.model.value to "{clearFiles: true}"
   *  to indicate on the server that we are removing files for this property. We will keep the $scope.model.value to 
   *  be the name of the file selected (if it is a newly selected file) or keep it to be it's original value, this allows
   *  for the editors to check if the value has changed and to re-bind the property if that is true.
   * 
  */
  function fileUploadController($scope, $element, $compile, imageHelper, fileManager, umbRequestHelper, mediaHelper) {
  
      /** Clears the file collections when content is saving (if we need to clear) or after saved */
      function clearFiles() {
          //clear the files collection (we don't want to upload any!)
          fileManager.setFiles($scope.model.alias, []);
          //clear the current files
          $scope.files = [];
          if ($scope.propertyForm.fileCount) {
              //this is required to re-validate
              $scope.propertyForm.fileCount.$setViewValue($scope.files.length);
          }
         
      }
  
      /** this method is used to initialize the data and to re-initialize it if the server value is changed */
      function initialize(index) {
  
          clearFiles();
  
          if (!index) {
              index = 1;
          }
  
          //this is used in order to tell the umb-single-file-upload directive to 
          //rebuild the html input control (and thus clearing the selected file) since
          //that is the only way to manipulate the html for the file input control.
          $scope.rebuildInput = {
              index: index
          };
          //clear the current files
          $scope.files = [];
          //store the original value so we can restore it if the user clears and then cancels clearing.
          $scope.originalValue = $scope.model.value;
  
          //create the property to show the list of files currently saved
          if ($scope.model.value != "") {
  
              var images = $scope.model.value.split(",");
  
              $scope.persistedFiles = _.map(images, function (item) {
                  return { file: item, isImage: imageHelper.detectIfImageByExtension(item) };
              });
          }
          else {
              $scope.persistedFiles = [];
          }
  
          _.each($scope.persistedFiles, function (file) {
  
              var thumbnailUrl = umbRequestHelper.getApiUrl(
                          "imagesApiBaseUrl",
                          "GetBigThumbnail",
                          [{ originalImagePath: file.file }]);
  
              file.thumbnail = thumbnailUrl;
          });
  
          $scope.clearFiles = false;
      }
  
      initialize();
  
      // Method required by the valPropertyValidator directive (returns true if the property editor has at least one file selected)
      $scope.validateMandatory = function () {
          return {
              isValid: !$scope.model.validation.mandatory || ((($scope.persistedFiles != null && $scope.persistedFiles.length > 0) || ($scope.files != null && $scope.files.length > 0)) && !$scope.clearFiles),
              errorMsg: "Value cannot be empty",
              errorKey: "required"
          };
      }
  
      //listen for clear files changes to set our model to be sent up to the server
      $scope.$watch("clearFiles", function (isCleared) {
          if (isCleared == true) {
              $scope.model.value = { clearFiles: true };
              clearFiles();
          }
          else {
              //reset to original value
              $scope.model.value = $scope.originalValue;
              //this is required to re-validate
              $scope.propertyForm.fileCount.$setViewValue($scope.files.length);
          }
      });
  
      //listen for when a file is selected
      $scope.$on("filesSelected", function (event, args) {
          $scope.$apply(function () {
              //set the files collection
              fileManager.setFiles($scope.model.alias, args.files);
              //clear the current files
              $scope.files = [];
              var newVal = "";
              for (var i = 0; i < args.files.length; i++) {
                  //save the file object to the scope's files collection
                  $scope.files.push({ alias: $scope.model.alias, file: args.files[i] });
                  newVal += args.files[i].name + ",";
              }
  
              //this is required to re-validate
              $scope.propertyForm.fileCount.$setViewValue($scope.files.length);
  
              //set clear files to false, this will reset the model too
              $scope.clearFiles = false;
              //set the model value to be the concatenation of files selected. Please see the notes
              // in the description of this controller, it states that this value isn't actually used for persistence,
              // but we need to set it so that the editor and the server can detect that it's been changed, and it is used for validation.
              $scope.model.value = { selectedFiles: newVal.trimEnd(",") };
          });
      });
  
      //listen for when the model value has changed
      $scope.$watch("model.value", function (newVal, oldVal) {
          //cannot just check for !newVal because it might be an empty string which we 
          //want to look for.
          if (newVal !== null && newVal !== undefined && newVal !== oldVal) {
              //now we need to check if we need to re-initialize our structure which is kind of tricky
              // since we only want to do that if the server has changed the value, not if this controller
              // has changed the value. There's only 2 scenarios where we change the value internall so 
              // we know what those values can be, if they are not either of them, then we'll re-initialize.
  
              if (newVal.clearFiles !== true && newVal !== $scope.originalValue && !newVal.selectedFiles) {
                  initialize($scope.rebuildInput.index + 1);
              }
  
          }
      });
  };
  angular.module("umbraco")
      .controller('Umbraco.PropertyEditors.FileUploadController', fileUploadController)
      .run(function(mediaHelper, umbRequestHelper){
          if (mediaHelper && mediaHelper.registerFileResolver) {
  
              //NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource
              // they contain different data structures so if we need to query against it we need to be aware of this.
              mediaHelper.registerFileResolver("Umbraco.UploadField", function(property, entity, thumbnail){
                  if (thumbnail) {
  
                      if (mediaHelper.detectIfImageByExtension(property.value)) {
  
                          var thumbnailUrl = umbRequestHelper.getApiUrl(
                              "imagesApiBaseUrl",
                              "GetBigThumbnail",
                              [{ originalImagePath: property.value }]);
                              
                          return thumbnailUrl;
                      }
                      else {
                          return null;
                      }
                      
                  }
                  else {
                      return property.value;
                  }
              });
          }
      });
  angular.module("umbraco")
  
  .controller("Umbraco.PropertyEditors.FolderBrowserController",
      function ($rootScope, $scope, $routeParams, $timeout, editorState, navigationService) {
  
          var dialogOptions = $scope.dialogOptions;
          $scope.creating = $routeParams.create;
          $scope.nodeId = $routeParams.id;
  
          $scope.onUploadComplete = function () {
  
              //sync the tree - don't force reload since we're not updating this particular node (i.e. its name or anything),
              // then we'll get the resulting tree node which we can then use to reload it's children.
              var path = editorState.current.path;
              navigationService.syncTree({ tree: "media", path: path, forceReload: false }).then(function (syncArgs) {
                  navigationService.reloadNode(syncArgs.node);
              });
  
          }
  });
  
  angular.module("umbraco")
  .controller("Umbraco.PropertyEditors.GoogleMapsController",
      function ($rootScope, $scope, notificationsService, dialogService, assetsService, $log, $timeout) {
  
          assetsService.loadJs('http://www.google.com/jsapi')
              .then(function () {
                  google.load("maps", "3",
                              {
                                  callback: initMap,
                                  other_params: "sensor=false"
                              });
              });
  
          function initMap() {
              //Google maps is available and all components are ready to use.
              var valueArray = $scope.model.value.split(',');
              var latLng = new google.maps.LatLng(valueArray[0], valueArray[1]);
              var mapDiv = document.getElementById($scope.model.alias + '_map');
              var mapOptions = {
                  zoom: $scope.model.config.zoom,
                  center: latLng,
                  mapTypeId: google.maps.MapTypeId[$scope.model.config.mapType]
              };
              var geocoder = new google.maps.Geocoder();
              var map = new google.maps.Map(mapDiv, mapOptions);
  
              var marker = new google.maps.Marker({
                  map: map,
                  position: latLng,
                  draggable: true
              });
  
              google.maps.event.addListener(map, 'click', function (event) {
  
                  dialogService.mediaPicker({
                      callback: function (data) {
                          var image = data.selection[0].src;
  
                          var latLng = event.latLng;
                          var marker = new google.maps.Marker({
                              map: map,
                              icon: image,
                              position: latLng,
                              draggable: true
                          });
  
                          google.maps.event.addListener(marker, "dragend", function (e) {
                              var newLat = marker.getPosition().lat();
                              var newLng = marker.getPosition().lng();
  
                              codeLatLng(marker.getPosition(), geocoder);
  
                              //set the model value
                              $scope.model.vvalue = newLat + "," + newLng;
                          });
  
                      }
                  });
              });
  
              $('a[data-toggle="tab"]').on('shown', function (e) {
                  google.maps.event.trigger(map, 'resize');
              });
          }
  
          function codeLatLng(latLng, geocoder) {
              geocoder.geocode({ 'latLng': latLng },
                  function (results, status) {
                      if (status == google.maps.GeocoderStatus.OK) {
                          var location = results[0].formatted_address;
                          $rootScope.$apply(function () {
                              notificationsService.success("Peter just went to: ", location);
                          });
                      }
                  });
          }
  
          //here we declare a special method which will be called whenever the value has changed from the server
          //this is instead of doing a watch on the model.value = faster
          $scope.model.onValueChanged = function (newVal, oldVal) {
              //update the display val again if it has changed from the server
              initMap();
          };
      });
  angular.module("umbraco")
      .controller("Umbraco.PropertyEditors.GridController.Dialogs.Config",
      function ($scope, $http) {
  
          var placeHolder = "{0}";
          var addModifier = function(val, modifier){
              if (!modifier || modifier.indexOf(placeHolder) < 0) {
                  return val;
              } else {
                  return modifier.replace(placeHolder, val);
              }
          }
  
          var stripModifier = function (val, modifier) {
              if (!val || !modifier || modifier.indexOf(placeHolder) < 0) {
                  return val;
              } else {
                  var paddArray = modifier.split(placeHolder);
                  if(paddArray.length == 1){
                      if (modifier.indexOf(placeHolder) === 0) {
                          return val.slice(0, -paddArray[0].length);
                      } else {
                          return val.slice(paddArray[0].length, 0);
                      }
                  }else{
                      return val.slice(paddArray[0].length, -paddArray[1].length); 
                  }
              }
          }
  
  
          $scope.styles = _.filter( angular.copy($scope.dialogOptions.config.items.styles), function(item){return (item.applyTo === undefined || item.applyTo === $scope.dialogOptions.itemType); });
          $scope.config = _.filter( angular.copy($scope.dialogOptions.config.items.config), function(item){return (item.applyTo === undefined || item.applyTo === $scope.dialogOptions.itemType); });
  
  
          var element = $scope.dialogOptions.gridItem;
          if(angular.isObject(element.config)){
              _.each($scope.config, function(cfg){
                  var val = element.config[cfg.key];
                  if(val){
                      cfg.value = stripModifier(val, cfg.modifier);
                  }
              });
          }
  
          if(angular.isObject(element.styles)){
              _.each($scope.styles, function(style){
                  var val = element.styles[style.key];
                  if(val){
                      style.value = stripModifier(val, style.modifier);
                  }
              });
          }
  
  
          $scope.saveAndClose = function(){
              var styleObject = {};
              var configObject = {};
  
              _.each($scope.styles, function(style){
                  if(style.value){
                      styleObject[style.key] = addModifier(style.value, style.modifier);
                  }
              });
              _.each($scope.config, function (cfg) {
                  if (cfg.value) {
                      configObject[cfg.key] = addModifier(cfg.value, cfg.modifier);
                  }
              });
  
              $scope.submit({config: configObject, styles: styleObject});
          };
  
      });
  
  angular.module("umbraco")
      .controller("Umbraco.PropertyEditors.GridPrevalueEditorController.Dialogs.EditConfig",
      function ($scope, $http) {
  
              $scope.renderModel = {};
              $scope.renderModel.name = $scope.dialogOptions.name;
              $scope.renderModel.config = $scope.dialogOptions.config;
  
              $scope.saveAndClose = function(isValid){
                  if(isValid){
                      $scope.submit($scope.renderModel.config);
                  }
              };
  
      });
  
  angular.module("umbraco")
      .controller("Umbraco.PropertyEditors.GridPrevalueEditor.LayoutConfigController",
      function ($scope) {
  
      		$scope.currentLayout = $scope.dialogOptions.currentLayout;
      		$scope.columns = $scope.dialogOptions.columns;
      		$scope.rows = $scope.dialogOptions.rows;
  
      		$scope.scaleUp = function(section, max, overflow){
      		   var add = 1;
      		   if(overflow !== true){
      		        add = (max > 1) ? 1 : max;
      		   }
      		   //var add = (max > 1) ? 1 : max;
      		   section.grid = section.grid+add;
      		};
  
      		$scope.scaleDown = function(section){
      		   var remove = (section.grid > 1) ? 1 : section.grid;
      		   section.grid = section.grid-remove;
      		};
  
      		$scope.percentage = function(spans){
      		    return ((spans / $scope.columns) * 100).toFixed(1);
      		};
  
      		$scope.toggleCollection = function(collection, toggle){
      		    if(toggle){
      		        collection = [];
      		    }else{
      		        delete collection;
      		    }
      		};
  
  
  
      		/****************
      		    Section
      		*****************/
      		$scope.configureSection = function(section, template){
      		   if(section === undefined){
      		        var space = ($scope.availableLayoutSpace > 4) ? 4 : $scope.availableLayoutSpace;
      		        section = {
      		            grid: space
      		        };
      		        template.sections.push(section);
      		    }
      		    
      		    $scope.currentSection = section;
      		};
  
  
      		$scope.deleteSection = function(index){
      		    $scope.currentTemplate.sections.splice(index, 1);
      		};
      		
      		$scope.closeSection = function(){
      		    $scope.currentSection = undefined;
      		};
  
      		$scope.$watch("currentLayout", function(layout){
      		    if(layout){
      		        var total = 0;
      		        _.forEach(layout.sections, function(section){
      		            total = (total + section.grid);
      		        });
  
      		        $scope.availableLayoutSpace = $scope.columns - total;
      		    }
      		}, true);
      });
  function RowConfigController($scope) {
      
      $scope.currentRow = angular.copy($scope.dialogOptions.currentRow);
      $scope.editors = $scope.dialogOptions.editors;
      $scope.columns = $scope.dialogOptions.columns;
  
      $scope.scaleUp = function(section, max, overflow) {
          var add = 1;
          if (overflow !== true) {
              add = (max > 1) ? 1 : max;
          }
          //var add = (max > 1) ? 1 : max;
          section.grid = section.grid + add;
      };
  
      $scope.scaleDown = function(section) {
          var remove = (section.grid > 1) ? 1 : section.grid;
          section.grid = section.grid - remove;
      };
  
      $scope.percentage = function(spans) {
          return ((spans / $scope.columns) * 100).toFixed(1);
      };
  
      $scope.toggleCollection = function(collection, toggle) {
          if (toggle) {
              collection = [];
          }
          else {
              delete collection;
          }
      };
  
  
      /****************
          area
      *****************/
      $scope.configureCell = function(cell, row) {
          if ($scope.currentCell && $scope.currentCell === cell) {
              delete $scope.currentCell;
          }
          else {
              if (cell === undefined) {
                  var available = $scope.availableRowSpace;
                  var space = 4;
  
                  if (available < 4 && available > 0) {
                      space = available;
                  }
  
                  cell = {
                      grid: space
                  };
  
                  row.areas.push(cell);
              }
              $scope.currentCell = cell;
          }
      };
  
      $scope.deleteArea = function(index) {
          $scope.currentRow.areas.splice(index, 1);
      };
      $scope.closeArea = function() {
          $scope.currentCell = undefined;
      };
  
      $scope.nameChanged = false;
      var originalName = $scope.currentRow.name;
      $scope.$watch("currentRow", function(row) {
          if (row) {
  
              var total = 0;
              _.forEach(row.areas, function(area) {
                  total = (total + area.grid);
              });
  
              $scope.availableRowSpace = $scope.columns - total;
  
              if (originalName) {
                  if (originalName != row.name) {
                      $scope.nameChanged = true;
                  }
                  else {
                      $scope.nameChanged = false;
                  }
              }
          }
      }, true);
  
      $scope.complete = function () {
          angular.extend($scope.dialogOptions.currentRow, $scope.currentRow);
          $scope.close();
      }
  }
  
  angular.module("umbraco").controller("Umbraco.PropertyEditors.GridPrevalueEditor.RowConfigController", RowConfigController);
  angular.module("umbraco")
      .controller("Umbraco.PropertyEditors.Grid.EmbedController",
      function ($scope, $rootScope, $timeout, dialogService) {
  
      	$scope.setEmbed = function(){
      		dialogService.embedDialog({
                      callback: function (data) {
                          $scope.control.value = data;
                      }
                  });
      	};
  
      	$timeout(function(){
      		if($scope.control.$initializing){
      			$scope.setEmbed();
      		}
      	}, 200);
  });
  
  
  angular.module("umbraco")
      .controller("Umbraco.PropertyEditors.Grid.MacroController",
      function ($scope, $rootScope, $timeout, dialogService, macroResource, macroService,  $routeParams) {
  
          $scope.title = "Click to insert macro";
  
          $scope.setMacro = function(){
              dialogService.macroPicker({
                  dialogData: {
                      richTextEditor: true,  
                      macroData: $scope.control.value || {
                          macroAlias: $scope.control.editor.config && $scope.control.editor.config.macroAlias
                            ? $scope.control.editor.config.macroAlias : ""
                      }
                  },
                  callback: function (data) {
                      $scope.control.value = {
                              macroAlias: data.macroAlias,
                              macroParamsDictionary: data.macroParamsDictionary
                      };
  
                      $scope.setPreview($scope.control.value );
                  }
              });
      	};
  
          $scope.setPreview = function(macro){
              var contentId = $routeParams.id;
  
              macroResource.getMacroResultAsHtmlForEditor(macro.macroAlias, contentId, macro.macroParamsDictionary)
              .then(function (htmlResult) {
                  $scope.title = macro.macroAlias;
                  if(htmlResult.trim().length > 0 && htmlResult.indexOf("Macro:") < 0){
                      $scope.preview = htmlResult;
                  }
              });
  
          };
  
      	$timeout(function(){
      		if($scope.control.$initializing){
      			$scope.setMacro();
      		}else if($scope.control.value){
                  $scope.setPreview($scope.control.value);
              }
      	}, 200);
  });
  
  
  angular.module("umbraco")
      .controller("Umbraco.PropertyEditors.Grid.MediaController",
      function ($scope, $rootScope, $timeout, dialogService) {
  
          $scope.setImage = function(){
  
              dialogService.mediaPicker({
                  startNodeId: $scope.control.editor.config && $scope.control.editor.config.startNodeId ? $scope.control.editor.config.startNodeId : undefined,
                  multiPicker: false,
                  cropSize:  $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined,
                  showDetails: true,
                  callback: function (data) {
  
                      $scope.control.value = {
                          focalPoint: data.focalPoint,
                          id: data.id,
                          image: data.image
                      };
  
                      $scope.setUrl();
                  }
              });
          };
  
          $scope.setUrl = function(){
  
              if($scope.control.value.image){
                  var url = $scope.control.value.image;
  
                  if($scope.control.editor.config && $scope.control.editor.config.size){
                      url += "?width=" + $scope.control.editor.config.size.width;
                      url += "&height=" + $scope.control.editor.config.size.height;
  
                      if($scope.control.value.focalPoint){
                          url += "&center=" + $scope.control.value.focalPoint.top +"," + $scope.control.value.focalPoint.left;
                          url += "&mode=crop";
                      }
                  }
  
                  $scope.url = url;
              }
          };
  
          $timeout(function(){
              if($scope.control.$initializing){
                  $scope.setImage();
              }else{
                  $scope.setUrl();
              }
          }, 200);
  });
  
  angular.module("umbraco")
      .controller("Umbraco.PropertyEditors.Grid.TextStringController",
      function ($scope, $rootScope, $timeout, dialogService) {
  
          
  
      });
  
  
  angular.module("umbraco")
      .controller("Umbraco.PropertyEditors.GridController",
      function ($scope, $http, assetsService, $rootScope, dialogService, gridService, mediaResource, imageHelper, $timeout, umbRequestHelper) {
  
          // Grid status variables
          $scope.currentRow = null;
          $scope.currentCell = null;
          $scope.currentToolsControl = null;
          $scope.currentControl = null;
          $scope.openRTEToolbarId = null;
          $scope.hasSettings = false;
  
          // *********************************************
          // Sortable options
          // *********************************************
  
          var draggedRteSettings;
  
          $scope.sortableOptions = {
              distance: 10,
              cursor: "move",
              placeholder: 'ui-sortable-placeholder',
              handle: '.cell-tools-move',
              forcePlaceholderSize: true,
              tolerance: "pointer",
              zIndex: 999999999999999999,
              scrollSensitivity: 100,
              cursorAt: {
                  top: 45,
                  left: 90
              },
  
              sort: function (event, ui) {
                  /* prevent vertical scroll out of the screen */
                  var max = $(".usky-grid").width() - 150;
                  if (parseInt(ui.helper.css('left')) > max) {
                      ui.helper.css({ 'left': max + "px" })
                  }
                  if (parseInt(ui.helper.css('left')) < 20) {
                      ui.helper.css({ 'left': 20 })
                  }
              },
  
              start: function (e, ui) {
                  draggedRteSettings = {};
                  ui.item.find('.mceNoEditor').each(function () {
                      // remove all RTEs in the dragged row and save their settings
                      var id = $(this).attr('id');
                      draggedRteSettings[id] = _.findWhere(tinyMCE.editors, { id: id }).settings;
                      tinyMCE.execCommand('mceRemoveEditor', false, id);
                  });
              },
  
              stop: function (e, ui) {
                  // reset all RTEs affected by the dragging
                  ui.item.parents(".usky-column").find('.mceNoEditor').each(function () {
                      var id = $(this).attr('id');
                      draggedRteSettings[id] = draggedRteSettings[id] || _.findWhere(tinyMCE.editors, { id: id }).settings;
                      tinyMCE.execCommand('mceRemoveEditor', false, id);
                      tinyMCE.init(draggedRteSettings[id]);
                  });
              }
          };
  
          var notIncludedRte = [];
          var cancelMove = false;
  
          $scope.sortableOptionsCell = {
              distance: 10,
              cursor: "move",
              placeholder: "ui-sortable-placeholder",
              handle: '.cell-tools-move',
              connectWith: ".usky-cell",
              forcePlaceholderSize: true,
              tolerance: "pointer",
              zIndex: 999999999999999999,
              scrollSensitivity: 100,
              cursorAt: {
                  top: 45,
                  left: 90
              },
  
              sort: function (event, ui) {
                  /* prevent vertical scroll out of the screen */
                  var position = parseInt(ui.item.parent().offset().left) + parseInt(ui.helper.css('left')) - parseInt($(".usky-grid").offset().left);
                  var max = $(".usky-grid").width() - 220;
                  if (position > max) {
                      ui.helper.css({ 'left': max - parseInt(ui.item.parent().offset().left) + parseInt($(".usky-grid").offset().left) + "px" })
                  }
                  if (position < 0) {
                      ui.helper.css({ 'left': 0 - parseInt(ui.item.parent().offset().left) + parseInt($(".usky-grid").offset().left) + "px" })
                  }
              },
  
              over: function (event, ui) {
                  allowedEditors = $(event.target).scope().area.allowed;
  
                  if ($.inArray(ui.item.scope().control.editor.alias, allowedEditors) < 0 && allowedEditors) {
                      ui.placeholder.hide();
                      cancelMove = true;
                  }
                  else {
                      ui.placeholder.show();
                      cancelMove = false;
                  }
  
              },
  
              update: function (event, ui) {
                  // add all RTEs which are affected by the dragging
                  if (!ui.sender) {
                      if (cancelMove) {
                          ui.item.sortable.cancel();
                      }
                      ui.item.parents(".usky-cell").find('.mceNoEditor').each(function () {
                          if ($.inArray($(this).attr('id'), notIncludedRte) < 0) {
                              notIncludedRte.splice(0, 0, $(this).attr('id'));
                          }
                      });
                  }
                  else {
                      $(event.target).find('.mceNoEditor').each(function () {
                          if ($.inArray($(this).attr('id'), notIncludedRte) < 0) {
                              notIncludedRte.splice(0, 0, $(this).attr('id'));
                          }
                      });
                  }
  
              },
  
              start: function (e, ui) {
                  ui.item.find('.mceNoEditor').each(function () {
                      notIncludedRte = [];
  
                      // save the dragged RTE settings
                      draggedRteSettings = _.findWhere(tinyMCE.editors, { id: $(this).attr('id') }).settings;
  
                      // remove the dragged RTE
                      tinyMCE.execCommand('mceRemoveEditor', false, $(this).attr('id'));
                  });
              },
  
              stop: function (e, ui) {
                  ui.item.parents(".usky-cell").find('.mceNoEditor').each(function () {
                      if ($.inArray($(this).attr('id'), notIncludedRte) < 0) {
                          // add all dragged's neighbouring RTEs in the new cell
                          notIncludedRte.splice(0, 0, $(this).attr('id'));
                      }
                  });
                  $timeout(function () {
                      // reconstruct the dragged RTE
                      tinyMCE.init(draggedRteSettings);
  
                      _.forEach(notIncludedRte, function (id) {
                          // reset all the other RTEs
                          if (id != draggedRteSettings.id) {
                              var rteSettings = _.findWhere(tinyMCE.editors, { id: id }).settings;
                              tinyMCE.execCommand('mceRemoveEditor', false, id);
                              tinyMCE.init(rteSettings);
                          }
                      });
                  }, 500, false);
              }
  
          };
  
          // *********************************************
          // Add items overlay menu
          // *********************************************
          $scope.overlayMenu = {
              show: false,
              style: {},
              area: undefined,
              key: undefined
          };
  
          $scope.addItemOverlay = function (event, area, index, key) {
              $scope.overlayMenu.area = area;
              $scope.overlayMenu.index = index;
              $scope.overlayMenu.style = {};
              $scope.overlayMenu.key = key;
  
              //todo calculate position...
              var offset = $(event.target).offset();
              var height = $(window).height();
              var width = $(window).width();
  
              if ((height - offset.top) < 250) {
                  $scope.overlayMenu.style.bottom = 0;
                  $scope.overlayMenu.style.top = "initial";
              } else if (offset.top < 300) {
                  $scope.overlayMenu.style.top = 190;
              }
  
              $scope.overlayMenu.show = true;
          };
  
          $scope.closeItemOverlay = function () {
              $scope.currentControl = null;
              $scope.overlayMenu.show = false;
              $scope.overlayMenu.key = undefined;
          };
  
          // *********************************************
          // Template management functions
          // *********************************************
  
          $scope.addTemplate = function (template) {
              $scope.model.value = angular.copy(template);
  
              //default row data
              _.forEach($scope.model.value.sections, function (section) {
                  $scope.initSection(section);
              });
          };
  
  
          // *********************************************
          // Row management function
          // *********************************************
  
          $scope.setCurrentRow = function (row) {
              $scope.currentRow = row;
          };
  
          $scope.disableCurrentRow = function () {
              $scope.currentRow = null;
          };
  
          $scope.setWarnighlightRow = function (row) {
              $scope.currentWarnhighlightRow = row;
          };
  
          $scope.disableWarnhighlightRow = function () {
              $scope.currentWarnhighlightRow = null;
          };
  
          $scope.setInfohighlightRow = function (row) {
              $scope.currentInfohighlightRow = row;
          };
  
          $scope.disableInfohighlightRow = function () {
              $scope.currentInfohighlightRow = null;
          };
  
          $scope.getAllowedLayouts = function (column) {
              var layouts = $scope.model.config.items.layouts;
  
              if (column.allowed && column.allowed.length > 0) {
                  return _.filter(layouts, function (layout) {
                      return _.indexOf(column.allowed, layout.name) >= 0;
                  });
              } else {
                  return layouts;
              }
          };
  
          $scope.addRow = function (section, layout) {
  
              //copy the selected layout into the rows collection
              var row = angular.copy(layout);
  
              // Init row value
              row = $scope.initRow(row);
  
              // Push the new row
              if (row) {
                  section.rows.push(row);
              }
          };
  
          $scope.removeRow = function (section, $index) {
              if (section.rows.length > 0) {
                  section.rows.splice($index, 1);
                  $scope.currentRow = null;
                  $scope.openRTEToolbarId = null;
                  $scope.initContent();
              }
          };
  
          $scope.editGridItemSettings = function (gridItem, itemType) {
  
              dialogService.open(
                  {
                      template: "views/propertyeditors/grid/dialogs/config.html",
                      gridItem: gridItem,
                      config: $scope.model.config,
                      itemType: itemType,
                      callback: function (data) {
  
                          gridItem.styles = data.styles;
                          gridItem.config = data.config;
  
                      }
                  });
  
          };
  
          // *********************************************
          // Area management functions
          // *********************************************
  
          $scope.setCurrentCell = function (cell) {
              $scope.currentCell = cell;
          };
  
          $scope.disableCurrentCell = function () {
              $scope.currentCell = null;
          };
  
          $scope.cellPreview = function (cell) {
              if (cell && cell.$allowedEditors) {
                  var editor = cell.$allowedEditors[0];
                  return editor.icon;
              } else {
                  return "icon-layout";
              }
          };
  
          $scope.setInfohighlightArea = function (cell) {
              $scope.currentInfohighlightArea = cell;
          };
  
          $scope.disableInfohighlightArea = function () {
              $scope.currentInfohighlightArea = null;
          };
  
  
          // *********************************************
          // Control management functions
          // *********************************************
          $scope.setCurrentControl = function (Control) {
              $scope.currentControl = Control;
          };
  
          $scope.disableCurrentControl = function (Control) {
              $scope.currentControl = null;
          };
  
          $scope.setCurrentToolsControl = function (Control) {
              $scope.currentToolsControl = Control;
          };
  
          $scope.disableCurrentToolsControl = function (Control) {
              $scope.currentToolsControl = null;
          };
  
          $scope.setWarnhighlightControl = function (Control) {
              $scope.currentWarnhighlightControl = Control;
          };
  
          $scope.disableWarnhighlightControl = function (Control) {
              $scope.currentWarnhighlightControl = null;
          };
  
          $scope.setInfohighlightControl = function (Control) {
              $scope.currentInfohighlightControl = Control;
          };
  
          $scope.disableInfohighlightControl = function (Control) {
              $scope.currentInfohighlightControl = null;
          };
  
          $scope.setUniqueId = function (cell, index) {
              return guid();
          };
  
          var guid = (function () {
              function s4() {
                  return Math.floor((1 + Math.random()) * 0x10000)
                             .toString(16)
                             .substring(1);
              }
              return function () {
                  return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
                         s4() + '-' + s4() + s4() + s4();
              };
          })();
  
          $scope.addControl = function (editor, cell, index) {
              $scope.closeItemOverlay();
  
              var newControl = {
                  value: null,
                  editor: editor,
                  $initializing: true
              };
  
              if (index === undefined) {
                  index = cell.controls.length;
              }
  
              //populate control
              $scope.initControl(newControl, index + 1);
  
              cell.controls.splice(index + 1, 0, newControl);
          };
  
          $scope.addTinyMce = function (cell) {
              var rte = $scope.getEditor("rte");
              $scope.addControl(rte, cell);
          };
  
          $scope.getEditor = function (alias) {
              return _.find($scope.availableEditors, function (editor) { return editor.alias === alias; });
          };
  
          $scope.removeControl = function (cell, $index) {
              $scope.currentControl = null;
              cell.controls.splice($index, 1);
          };
  
          $scope.percentage = function (spans) {
              return ((spans / $scope.model.config.items.columns) * 100).toFixed(1);
          };
  
  
          $scope.clearPrompt = function (scopedObject, e) {
              scopedObject.deletePrompt = false;
              e.preventDefault();
              e.stopPropagation();
          }
  
          $scope.showPrompt = function (scopedObject) {
              scopedObject.deletePrompt = true;
          }
  
  
          // *********************************************
          // INITIALISATION
          // these methods are called from ng-init on the template
          // so we can controll their first load data
          //
          // intialisation sets non-saved data like percentage sizing, allowed editors and
          // other data that should all be pre-fixed with $ to strip it out on save
          // *********************************************
  
          // *********************************************
          // Init template + sections
          // *********************************************
          $scope.initContent = function () {
              var clear = true;
  
              //settings indicator shortcut
              if ($scope.model.config.items.config || $scope.model.config.items.styles) {
                  $scope.hasSettings = true;
              }
  
              //ensure the grid has a column value set, if nothing is found, set it to 12
              if ($scope.model.config.items.columns && angular.isString($scope.model.config.items.columns)) {
                  $scope.model.config.items.columns = parseInt($scope.model.config.items.columns);
              } else {
                  $scope.model.config.items.columns = 12;
              }
  
              if ($scope.model.value && $scope.model.value.sections && $scope.model.value.sections.length > 0) {
                  _.forEach($scope.model.value.sections, function (section, index) {
  
                      if (section.grid > 0) {
                          $scope.initSection(section);
  
                          //we do this to ensure that the grid can be reset by deleting the last row
                          if (section.rows.length > 0) {
                              clear = false;
                          }
                      } else {
                          $scope.model.value.sections.splice(index, 1);
                      }
                  });
              } else if ($scope.model.config.items.templates && $scope.model.config.items.templates.length === 1) {
                  $scope.addTemplate($scope.model.config.items.templates[0]);
              }
  
              if (clear) {
                  $scope.model.value = undefined;
              }
          };
  
          $scope.initSection = function (section) {
              section.$percentage = $scope.percentage(section.grid);
  
              var layouts = $scope.model.config.items.layouts;
  
              if (section.allowed && section.allowed.length > 0) {
                  section.$allowedLayouts = _.filter(layouts, function (layout) {
                      return _.indexOf(section.allowed, layout.name) >= 0;
                  });
              } else {
                  section.$allowedLayouts = layouts;
              }
  
              if (!section.rows) {
                  section.rows = [];
              } else {
                  _.forEach(section.rows, function (row, index) {
                      if (!row.$initialized) {
                          var initd = $scope.initRow(row);
  
                          //if init fails, remove
                          if (!initd) {
                              section.rows.splice(index, 1);
                          } else {
                              section.rows[index] = initd;
                          }
                      }
                  });
              }
          };
  
  
          // *********************************************
          // Init layout / row
          // *********************************************
          $scope.initRow = function (row) {
  
              //merge the layout data with the original config data
              //if there are no config info on this, splice it out
              var original = _.find($scope.model.config.items.layouts, function (o) { return o.name === row.name; });
  
              if (!original) {
                  return null;
              } else {
                  //make a copy to not touch the original config
                  original = angular.copy(original);
                  original.styles = row.styles;
                  original.config = row.config;
  
                  //sync area configuration
                  _.each(original.areas, function (area, areaIndex) {
  
  
                      if (area.grid > 0) {
                          var currentArea = row.areas[areaIndex];
  
                          if (currentArea) {
                              area.config = currentArea.config;
                              area.styles = currentArea.styles;
                          }
  
                          //copy over existing controls into the new areas
                          if (row.areas.length > areaIndex && row.areas[areaIndex].controls) {
                              area.controls = currentArea.controls;
  
                              _.forEach(area.controls, function (control, controlIndex) {
                                  $scope.initControl(control, controlIndex);
                              });
  
                          } else {
                              area.controls = [];
                          }
  
                          //set width
                          area.$percentage = $scope.percentage(area.grid);
                          area.$uniqueId = $scope.setUniqueId();
  
                          //set editor permissions
                          if (!area.allowed || area.allowAll === true) {
                              area.$allowedEditors = $scope.availableEditors;
                              area.$allowsRTE = true;
                          } else {
                              area.$allowedEditors = _.filter($scope.availableEditors, function (editor) {
                                  return _.indexOf(area.allowed, editor.alias) >= 0;
                              });
  
                              if (_.indexOf(area.allowed, "rte") >= 0) {
                                  area.$allowsRTE = true;
                              }
                          }
                      } else {
                          original.areas.splice(areaIndex, 1);
                      }
                  });
  
                  //replace the old row
                  original.$initialized = true;
  
                  //set a disposable unique ID
                  original.$uniqueId = $scope.setUniqueId();
  
                  //set a no disposable unique ID (util for row styling)
                  original.id = !row.id ? $scope.setUniqueId() : row.id;
  
                  return original;
              }
  
          };
  
  
          // *********************************************
          // Init control
          // *********************************************
  
          $scope.initControl = function (control, index) {
              control.$index = index;
              control.$uniqueId = $scope.setUniqueId();
  
              //error handling in case of missing editor..
              //should only happen if stripped earlier 
              if (!control.editor) {
                  control.$editorPath = "views/propertyeditors/grid/editors/error.html";
              }
  
              if (!control.$editorPath) {
                  var editorConfig = $scope.getEditor(control.editor.alias);
  
                  if (editorConfig) {
                      control.editor = editorConfig;
  
                      //if its an absolute path
                      if (control.editor.view.startsWith("/") || control.editor.view.startsWith("~/")) {
                          control.$editorPath = umbRequestHelper.convertVirtualToAbsolutePath(control.editor.view);
                      }
                      else {
                          //use convention
                          control.$editorPath = "views/propertyeditors/grid/editors/" + control.editor.view + ".html";
                      }
                  }
                  else {
                      control.$editorPath = "views/propertyeditors/grid/editors/error.html";
                  }
              }
  
  
          };
  
  
          gridService.getGridEditors().then(function (response) {
              $scope.availableEditors = response.data;
  
              $scope.contentReady = true;
  
              // *********************************************
              // Init grid
              // *********************************************
              $scope.initContent();
  
          });
      });
  
  angular.module("umbraco")
      .controller("Umbraco.PropertyEditors.GridPrevalueEditorController",
      function ($scope, $http, assetsService, $rootScope, dialogService, mediaResource, gridService, imageHelper, $timeout) {
  
          var emptyModel = {
              styles:[
                  {
                      label: "Set a background image",
                      description: "Set a row background",
                      key: "background-image",
                      view: "imagepicker",
                      modifier: "url({0})"
                  }
              ],
  
              config:[
                  {
                      label: "Class",
                      description: "Set a css class",
                      key: "class",
                      view: "textstring"
                  }
              ],
  
              columns: 12,
              templates:[
                  {
                      name: "1 column layout",
                      sections: [
                          {
                              grid: 12,
                          }
                      ]
                  },
                  {
                      name: "2 column layout",
                      sections: [
                          {
                              grid: 4,
                          },
                          {
                              grid: 8
                          }
                      ]
                  }
              ],
  
  
              layouts:[
                  {
                      name: "Headline",
                      areas: [
                          {
                              grid: 12,
                              editors: ["headline"]
                          }
                      ]
                  },
                  {
                      name: "Article",
                      areas: [
                          {
                              grid: 4
                          },
                          {
                              grid: 8
                          }
                      ]
                  }
              ]
          };
  
          /****************
              template
          *****************/
  
          $scope.configureTemplate = function(template){
             if(template === undefined){
                  template = {
                      name: "",
                      sections:[
  
                      ]
                  };
                  $scope.model.value.templates.push(template);
             }    
             
             dialogService.open(
                 {
                     template: "views/propertyEditors/grid/dialogs/layoutconfig.html",
                     currentLayout: template,
                     rows: $scope.model.value.layouts,
                     columns: $scope.model.value.columns
                 }
             );
  
          };
  
          $scope.deleteTemplate = function(index){
              $scope.model.value.templates.splice(index, 1);
          };
          
  
          /****************
              Row
          *****************/
  
          $scope.configureLayout = function(layout){
  
              if(layout === undefined){
                   layout = {
                       name: "",
                       areas:[
  
                       ]
                   };
                   $scope.model.value.layouts.push(layout);
              }
  
              dialogService.open(
                  {
                      template: "views/propertyEditors/grid/dialogs/rowconfig.html",
                      currentRow: layout,
                      editors: $scope.editors,
                      columns: $scope.model.value.columns
                  }
              );
          };
  
          //var rowDeletesPending = false;
          $scope.deleteLayout = function (index) {
              //rowDeletesPending = true;
  
              //show ok/cancel dialog
              var confirmDialog = dialogService.open(
                 {
                     template: "views/propertyEditors/grid/dialogs/rowdeleteconfirm.html",
                     show: true,
                     callback: function() {
                         $scope.model.value.layouts.splice(index, 1);
                     },
                     dialogData: {
                         rowName: $scope.model.value.layouts[index].name
                     }
                 }
             );
              
          };
          
  
  
          /****************
              utillities
          *****************/
          $scope.toggleCollection = function(collection, toggle){
              if(toggle){
                  collection = [];
              }else{
                  delete collection;
              }
          };
  
          $scope.percentage = function(spans){
              return ((spans / $scope.model.value.columns) * 100).toFixed(1);
          };
  
          $scope.zeroWidthFilter = function (cell) {
                  return cell.grid > 0;
          };
  
          /****************
              Config
          *****************/
  
          $scope.removeConfigValue = function(collection, index){
              collection.splice(index, 1);
          };
  
          var editConfigCollection = function(configValues, title, callbackOnSave){
              dialogService.open(
                  {
                      template: "views/propertyeditors/grid/dialogs/editconfig.html",
                      config: configValues,
                      name: title,
                      callback: function(data){
                          callbackOnSave(data);
                      }
                  });
          };
  
          $scope.editConfig = function(){
              editConfigCollection($scope.model.value.config, "Settings", function(data){
                  $scope.model.value.config = data;
              });
  	    };
  
          $scope.editStyles = function(){
              editConfigCollection($scope.model.value.styles, "Styling", function(data){
                  $scope.model.value.styles = data;
              });
          };
  
  
          /****************
              editors
          *****************/
          gridService.getGridEditors().then(function(response){
              $scope.editors = response.data;
          });
  
  
          /* init grid data */
          if (!$scope.model.value || $scope.model.value === "" || !$scope.model.value.templates) {
              $scope.model.value = emptyModel;
          } else {
  
              if (!$scope.model.value.columns) {
                  $scope.model.value.columns = emptyModel.columns;
              }
  
  
              if (!$scope.model.value.config) {
                  $scope.model.value.config = [];
              }
  
              if (!$scope.model.value.styles) {
                  $scope.model.value.styles = [];
              }
          }
  
          /****************
              Clean up
          *****************/
          var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
              var ts = $scope.model.value.templates;
              var ls = $scope.model.value.layouts;
  
              _.each(ts, function(t){
                  _.each(t.sections, function(section, index){
                     if(section.grid === 0){
                      t.sections.splice(index, 1);
                     }
                 });
              });
  
              _.each(ls, function(l){
                  _.each(l.areas, function(area, index){
                     if(area.grid === 0){
                      l.areas.splice(index, 1);
                     }
                 });
              });
          });
  
          //when the scope is destroyed we need to unsubscribe
          $scope.$on('$destroy', function () {
              unsubscribe();
          });
  
      });
  
  //this controller simply tells the dialogs service to open a mediaPicker window
  //with a specified callback, this callback will receive an object with a selection on it
  angular.module('umbraco')
      .controller("Umbraco.PropertyEditors.ImageCropperController",
      function ($rootScope, $routeParams, $scope, $log, mediaHelper, cropperHelper, $timeout, editorState, umbRequestHelper, fileManager) {
  
          var config = angular.copy($scope.model.config);
  
          //move previously saved value to the editor
          if ($scope.model.value) {
              //backwards compat with the old file upload (incase some-one swaps them..)
              if (angular.isString($scope.model.value)) {
                  config.src = $scope.model.value;
                  $scope.model.value = config;
              } else if ($scope.model.value.crops) {
                  //sync any config changes with the editor and drop outdated crops
                  _.each($scope.model.value.crops, function (saved) {
                      var configured = _.find(config.crops, function (item) { return item.alias === saved.alias });
  
                      if (configured && configured.height === saved.height && configured.width === saved.width) {
                          configured.coordinates = saved.coordinates;
                      }
                  });
                  $scope.model.value.crops = config.crops;
  
                  //restore focalpoint if missing
                  if (!$scope.model.value.focalPoint) {
                      $scope.model.value.focalPoint = { left: 0.5, top: 0.5 };
                  }
              }
  
              $scope.imageSrc = $scope.model.value.src;
          }
  
  
          //crop a specific crop
          $scope.crop = function (crop) {
              $scope.currentCrop = crop;
              $scope.currentPoint = undefined;
          };
  
          //done cropping
          $scope.done = function () {
              $scope.currentCrop = undefined;
              $scope.currentPoint = undefined;
          };
  
          //crop a specific crop
          $scope.clear = function (crop) {
              //clear current uploaded files
              fileManager.setFiles($scope.model.alias, []);
  
              //clear the ui
              $scope.imageSrc = undefined;
              if ($scope.model.value) {
                  delete $scope.model.value;
              }
          };
  
          //show previews
          $scope.togglePreviews = function () {
              if ($scope.showPreviews) {
                  $scope.showPreviews = false;
                  $scope.tempShowPreviews = false;
              } else {
                  $scope.showPreviews = true;
              }
          };
  
          //on image selected, update the cropper
          $scope.$on("filesSelected", function (ev, args) {
              $scope.model.value = config;
  
              if (args.files && args.files[0]) {
  
                  fileManager.setFiles($scope.model.alias, args.files);
  
                  var reader = new FileReader();
                  reader.onload = function (e) {
  
                      $scope.$apply(function () {
                          $scope.imageSrc = e.target.result;
                      });
  
                  };
  
                  reader.readAsDataURL(args.files[0]);
              }
          });
  
  
          //here we declare a special method which will be called whenever the value has changed from the server
          $scope.model.onValueChanged = function (newVal, oldVal) {
              //clear current uploaded files
              fileManager.setFiles($scope.model.alias, []);
          };
  
          var unsubscribe = $scope.$on("formSubmitting", function () {
              $scope.done();
          });
  
          $scope.$on('$destroy', function () {
              unsubscribe();
          });
      })
      .run(function (mediaHelper, umbRequestHelper) {
          if (mediaHelper && mediaHelper.registerFileResolver) {
  
              //NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource
              // they contain different data structures so if we need to query against it we need to be aware of this.
              mediaHelper.registerFileResolver("Umbraco.ImageCropper", function (property, entity, thumbnail) {
                  if (property.value.src) {
  
                      if (thumbnail === true) {
                          return property.value.src + "?width=500&mode=max";
                      }
                      else {
                          return property.value.src;
                      }
  
                      //this is a fallback in case the cropper has been asssigned a upload field
                  }
                  else if (angular.isString(property.value)) {
                      if (thumbnail) {
  
                          if (mediaHelper.detectIfImageByExtension(property.value)) {
  
                              var thumbnailUrl = umbRequestHelper.getApiUrl(
                                  "imagesApiBaseUrl",
                                  "GetBigThumbnail",
                                  [{ originalImagePath: property.value }]);
  
                              return thumbnailUrl;
                          }
                          else {
                              return null;
                          }
  
                      }
                      else {
                          return property.value;
                      }
                  }
  
                  return null;
              });
          }
      });
  angular.module("umbraco").controller("Umbraco.PrevalueEditors.CropSizesController",
  	function ($scope, $timeout) {
  
  	    if (!$scope.model.value) {
  	        $scope.model.value = [];
  	    }
  
  	    $scope.remove = function (item, evt) {
  	        evt.preventDefault();
  	        $scope.model.value = _.reject($scope.model.value, function (x) {
  	            return x.alias === item.alias;
  	        });
  	    };
  
  	    $scope.edit = function (item, evt) {
  	        evt.preventDefault();
  	        $scope.newItem = item;
  	    };
  
  	    $scope.cancel = function (evt) {
  	        evt.preventDefault();
  	        $scope.newItem = null;
  	    };
  
  	    $scope.add = function (evt) {
  	        evt.preventDefault();
  
  	        if ($scope.newItem && $scope.newItem.alias && 
                  angular.isNumber($scope.newItem.width) && angular.isNumber($scope.newItem.height) &&
                  $scope.newItem.width > 0 && $scope.newItem.height > 0) {
  
  	            var exists = _.find($scope.model.value, function (item) { return $scope.newItem.alias === item.alias; });
  	            if (!exists) {
  	                $scope.model.value.push($scope.newItem);
  	                $scope.newItem = {};
  	                $scope.hasError = false;
  	                return;
  	            }
  	        }
  
  	        //there was an error, do the highlight (will be set back by the directive)
  	        $scope.hasError = true;
  	    };
  	});
  function includePropsPreValsController($rootScope, $scope, localizationService, contentTypeResource) {
  
      if (!$scope.model.value) {
          $scope.model.value = [];
      }
  
      $scope.propertyAliases = [];
      $scope.selectedField = null;
      $scope.systemFields = [
          { value: "sortOrder" },
          { value: "updateDate" },
          { value: "updater" },
          { value: "createDate" },
          { value: "owner" },
          { value: "published"},
          { value: "contentTypeAlias" },
          { value: "email" },
          { value: "username" }
      ];
  
      $scope.getLocalizedKey = function(alias) {
          switch (alias) {
              case "name":
                  return "general_name";
              case "sortOrder":
                  return "general_sort";
              case "updateDate":
                  return "content_updateDate";
              case "updater":
                  return "content_updatedBy";
              case "createDate":
                  return "content_createDate";
              case "owner":
                  return "content_createBy";
              case "published":
                  return "content_isPublished";
              case "contentTypeAlias":
                  //NOTE: This will just be 'Document' type even if it's for media/members since this is just a pre-val editor and we don't have a key for 'Content Type Alias'
                  return "content_documentType";
              case "email":
                  return "general_email";
              case "username":
                  return "general_username";
          }
          return alias;
      }
  
      $scope.removeField = function(e) {
          $scope.model.value = _.reject($scope.model.value, function (x) {
              return x.alias === e.alias;
          }); 
      }
  
      //now we'll localize these strings, for some reason the directive doesn't work inside of the select group with an ng-model declared
      _.each($scope.systemFields, function (e, i) {
          var key = $scope.getLocalizedKey(e.value);
          localizationService.localize(key).then(function (v) {
              e.name = v;
  
              switch (e.value) {
                  case "updater":
                      e.name += " (Content only)";
                      break;
                  case "published":
                      e.name += " (Content only)";
                      break;
                  case "email":
                      e.name += " (Members only)";
                      break;
                  case "username":
                      e.name += " (Members only)";
                      break;
              }
  
          });
      });
  
      // Return a helper with preserved width of cells
      var fixHelper = function (e, ui) {
          var h = ui.clone();
  
          h.children().each(function () {
              $(this).width($(this).width());            
          });
          h.css("background-color", "lightgray");
  
          return h;
      };
  
      $scope.sortableOptions = {
          helper: fixHelper,
          handle: ".handle",
          opacity: 0.5,
          axis: 'y',
          containment: 'parent',
          cursor: 'move',
          items: '> tr',
          tolerance: 'pointer',
          update: function (e, ui) {
              
              // Get the new and old index for the moved element (using the text as the identifier)
              var newIndex = ui.item.index();
              var movedAlias = $('.alias-value', ui.item).text().trim();
              var originalIndex = getAliasIndexByText(movedAlias);
  
              // Move the element in the model
              if (originalIndex > -1) {
                  var movedElement = $scope.model.value[originalIndex];
                  $scope.model.value.splice(originalIndex, 1);
                  $scope.model.value.splice(newIndex, 0, movedElement);
              }
          }
      };
  
      contentTypeResource.getAllPropertyTypeAliases().then(function(data) {
          $scope.propertyAliases = data;
      });
  
      $scope.addField = function () {
  
          var val = $scope.selectedField;
          var isSystem = val.startsWith("_system_");
          if (isSystem) {
              val = val.trimStart("_system_");
          }
  
          var exists = _.find($scope.model.value, function (i) {
              return i.alias === val;
          });
          if (!exists) {
              $scope.model.value.push({
                  alias: val,
                  isSystem: isSystem ? 1 : 0
              });
          }
      }
  
      function getAliasIndexByText(value) {
          for (var i = 0; i < $scope.model.value.length; i++) {
              if ($scope.model.value[i].alias === value) {
                  return i;
              }
          }
  
          return -1;
      }
  
  }
  
  
  angular.module("umbraco").controller("Umbraco.PrevalueEditors.IncludePropertiesListViewController", includePropsPreValsController);
  function listViewController($rootScope, $scope, $routeParams, $injector, notificationsService, iconHelper, dialogService, editorState, localizationService, $location, appState) {
  
      //this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content 
      // that isn't created yet, if we continue this will use the parent id in the route params which isn't what
      // we want. NOTE: This is just a safety check since when we scaffold an empty model on the server we remove
      // the list view tab entirely when it's new.
      if ($routeParams.create) {
          $scope.isNew = true;
          return;
      }
  
      //Now we need to check if this is for media, members or content because that will depend on the resources we use
      var contentResource, getContentTypesCallback, getListResultsCallback, deleteItemCallback, getIdCallback, createEditUrlCallback;
      
      //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals)
      if (($scope.model.config.entityType && $scope.model.config.entityType === "member") || (appState.getSectionState("currentSection") === "member")) {
          $scope.entityType = "member";
          contentResource = $injector.get('memberResource');
          getContentTypesCallback = $injector.get('memberTypeResource').getTypes;
          getListResultsCallback = contentResource.getPagedResults;
          deleteItemCallback = contentResource.deleteByKey;
          getIdCallback = function(selected) {
              return selected.key;
          };
          createEditUrlCallback = function(item) {
              return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.key + "?page=" + $scope.options.pageNumber + "&listName=" + $scope.contentId;
          };
      }
      else {
          //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals)
          if (($scope.model.config.entityType && $scope.model.config.entityType === "media") || (appState.getSectionState("currentSection") === "media")) {
              $scope.entityType = "media";
              contentResource = $injector.get('mediaResource');
              getContentTypesCallback = $injector.get('mediaTypeResource').getAllowedTypes;                        
          }
          else {
              $scope.entityType = "content";
              contentResource = $injector.get('contentResource');
              getContentTypesCallback = $injector.get('contentTypeResource').getAllowedTypes;            
          }
          getListResultsCallback = contentResource.getChildren;
          deleteItemCallback = contentResource.deleteById;
          getIdCallback = function(selected) {
              return selected.id;
          };
          createEditUrlCallback = function(item) {
              return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.id + "?page=" + $scope.options.pageNumber;
          };
      }
  
      $scope.pagination = [];
      $scope.isNew = false;
      $scope.actionInProgress = false;
      $scope.listViewResultSet = {
          totalPages: 0,
          items: []
      };
  
      $scope.options = {
          pageSize: $scope.model.config.pageSize ? $scope.model.config.pageSize : 10,
          pageNumber: ($routeParams.page && Number($routeParams.page) != NaN && Number($routeParams.page) > 0) ? $routeParams.page : 1,
          filter: '',
          orderBy: ($scope.model.config.orderBy ? $scope.model.config.orderBy : 'VersionDate').trim(),
          orderDirection: $scope.model.config.orderDirection ? $scope.model.config.orderDirection.trim() : "desc",
          includeProperties: $scope.model.config.includeProperties ? $scope.model.config.includeProperties : [
              { alias: 'updateDate', header: 'Last edited', isSystem: 1 },
              { alias: 'updater', header: 'Last edited by', isSystem: 1 }
          ],
          allowBulkPublish: true,
          allowBulkUnpublish: true,
          allowBulkDelete: true,        
      };
  
      //update all of the system includeProperties to enable sorting
      _.each($scope.options.includeProperties, function(e, i) {
          
          if (e.isSystem) {
  
              //NOTE: special case for contentTypeAlias, it's a system property that cannot be sorted
              // to do that, we'd need to update the base query for content to include the content type alias column
              // which requires another join and would be slower. BUT We are doing this for members so not sure it makes a diff?
              if (e.alias != "contentTypeAlias") {
                  e.allowSorting = true;
              }
              
              //localize the header
              var key = getLocalizedKey(e.alias);
              localizationService.localize(key).then(function (v) {
                  e.header = v;
              });
          }
      });
  
      $scope.isSortDirection = function (col, direction) {
          return $scope.options.orderBy.toUpperCase() == col.toUpperCase() && $scope.options.orderDirection == direction;
      }
  
      $scope.next = function() {
          if ($scope.options.pageNumber < $scope.listViewResultSet.totalPages) {
              $scope.options.pageNumber++;
              $scope.reloadView($scope.contentId);
              //TODO: this would be nice but causes the whole view to reload
              //$location.search("page", $scope.options.pageNumber);
          }
      };
  
      $scope.goToPage = function(pageNumber) {
          $scope.options.pageNumber = pageNumber + 1;
          $scope.reloadView($scope.contentId);
          //TODO: this would be nice but causes the whole view to reload
          //$location.search("page", $scope.options.pageNumber);
      };
  
      $scope.sort = function(field, allow) {
          if (allow) {
              $scope.options.orderBy = field;
  
              if ($scope.options.orderDirection === "desc") {
                  $scope.options.orderDirection = "asc";
              }
              else {
                  $scope.options.orderDirection = "desc";
              }
  
              $scope.reloadView($scope.contentId);
          }
      };
  
      $scope.prev = function() {
          if ($scope.options.pageNumber > 1) {
              $scope.options.pageNumber--;
              $scope.reloadView($scope.contentId);
              //TODO: this would be nice but causes the whole view to reload
              //$location.search("page", $scope.options.pageNumber);
          }
      };
      
  
      /*Loads the search results, based on parameters set in prev,next,sort and so on*/
      /*Pagination is done by an array of objects, due angularJS's funky way of monitoring state
      with simple values */
  
      $scope.reloadView = function(id) {
          getListResultsCallback(id, $scope.options).then(function (data) {
              $scope.listViewResultSet = data;
  
              //update all values for display
              if ($scope.listViewResultSet.items) {
                  _.each($scope.listViewResultSet.items, function (e, index) {
                      setPropertyValues(e);
                  });
              }
  
              //NOTE: This might occur if we are requesting a higher page number than what is actually available, for example
              // if you have more than one page and you delete all items on the last page. In this case, we need to reset to the last
              // available page and then re-load again
              if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) {
                  $scope.options.pageNumber = $scope.listViewResultSet.totalPages;
  
                  //reload!
                  $scope.reloadView(id);
              }
  
              $scope.pagination = [];
  
              //list 10 pages as per normal
              if ($scope.listViewResultSet.totalPages <= 10) {
                  for (var i = 0; i < $scope.listViewResultSet.totalPages; i++) {
                      $scope.pagination.push({
                          val: (i + 1),
                          isActive: $scope.options.pageNumber == (i + 1)
                      });
                  }
              }
              else {
                  //if there is more than 10 pages, we need to do some fancy bits
  
                  //get the max index to start
                  var maxIndex = $scope.listViewResultSet.totalPages - 10;
                  //set the start, but it can't be below zero
                  var start = Math.max($scope.options.pageNumber - 5, 0);
                  //ensure that it's not too far either
                  start = Math.min(maxIndex, start);
  
                  for (var i = start; i < (10 + start) ; i++) {
                      $scope.pagination.push({
                          val: (i + 1),
                          isActive: $scope.options.pageNumber == (i + 1)
                      });
                  }
  
                  //now, if the start is greater than 0 then '1' will not be displayed, so do the elipses thing
                  if (start > 0) {
                      $scope.pagination.unshift({ name: "First", val: 1, isActive: false }, {val: "...",isActive: false});
                  }
  
                  //same for the end
                  if (start < maxIndex) {
                      $scope.pagination.push({ val: "...", isActive: false }, { name: "Last", val: $scope.listViewResultSet.totalPages, isActive: false });
                  }
              }
  
          });
      };
  
      //assign debounce method to the search to limit the queries
      $scope.search = _.debounce(function() {
          $scope.options.pageNumber = 1;
          $scope.reloadView($scope.contentId);
      }, 100);
  
      $scope.enterSearch = function($event) {
          $($event.target).next().focus();
      }
  
      $scope.selectAll = function($event) {
          var checkbox = $event.target;
          if (!angular.isArray($scope.listViewResultSet.items)) {
              return;
          }
          for (var i = 0; i < $scope.listViewResultSet.items.length; i++) {
              var entity = $scope.listViewResultSet.items[i];
              entity.selected = checkbox.checked;
          }
      };
  
      $scope.isSelectedAll = function() {
          if (!angular.isArray($scope.listViewResultSet.items)) {
              return false;
          }
          return _.every($scope.listViewResultSet.items, function(item) {
              return item.selected;
          });
      };
  
      $scope.isAnythingSelected = function() {
          if (!angular.isArray($scope.listViewResultSet.items)) {
              return false;
          }
          return _.some($scope.listViewResultSet.items, function(item) {
              return item.selected;
          });
      };
  
      $scope.getIcon = function(entry) {
          return iconHelper.convertFromLegacyIcon(entry.icon);
      };
  
      $scope.delete = function() {
          var selected = _.filter($scope.listViewResultSet.items, function(item) {
              return item.selected;
          });
          var total = selected.length;
          if (total === 0) {
              return;
          }
  
          if (confirm("Sure you want to delete?") == true) {
              $scope.actionInProgress = true;
              $scope.bulkStatus = "Starting with delete";
              var current = 1;
  
              var pluralSuffix = total == 1 ? "" : "s";
  
              for (var i = 0; i < selected.length; i++) {
                  $scope.bulkStatus = "Deleted item " + current + " out of " + total + " item" + pluralSuffix;
                  deleteItemCallback(getIdCallback(selected[i])).then(function (data) {
                      if (current === total) {
                          notificationsService.success("Bulk action", "Deleted " + total + " item" + pluralSuffix);
                          $scope.bulkStatus = "";
                          $scope.reloadView($scope.contentId);
                          $scope.actionInProgress = false;
                      }
                      current++;
                  });
              }
          }
  
      };
  
      $scope.publish = function() {
          var selected = _.filter($scope.listViewResultSet.items, function(item) {
              return item.selected;
          });
          var total = selected.length;
          if (total === 0) {
              return;
          }
  
          $scope.actionInProgress = true;
          $scope.bulkStatus = "Starting with publish";
          var current = 1;
  
          var pluralSuffix = total == 1 ? "" : "s";
  
          for (var i = 0; i < selected.length; i++) {
              $scope.bulkStatus = "Publishing " + current + " out of " + total + " document" + pluralSuffix;
  
              contentResource.publishById(getIdCallback(selected[i]))
                  .then(function(content) {
                      if (current == total) {
                          notificationsService.success("Bulk action", "Published " + total + " document" + pluralSuffix);
                          $scope.bulkStatus = "";
                          $scope.reloadView($scope.contentId);
                          $scope.actionInProgress = false;
                      }
                      current++;
                  }, function(err) {
  
                      $scope.bulkStatus = "";
                      $scope.reloadView($scope.contentId);
                      $scope.actionInProgress = false;
  
                      //if there are validation errors for publishing then we need to show them
                      if (err.status === 400 && err.data && err.data.Message) {
                          notificationsService.error("Publish error", err.data.Message);
                      }
                      else {
                          dialogService.ysodDialog(err);
                      }
                  });
  
          }
      };
  
      $scope.unpublish = function() {
          var selected = _.filter($scope.listViewResultSet.items, function(item) {
              return item.selected;
          });
          var total = selected.length;
          if (total === 0) {
              return;
          }
  
          $scope.actionInProgress = true;
          $scope.bulkStatus = "Starting with publish";
          var current = 1;
  
          var pluralSuffix = total == 1 ? "" : "s";
  
          for (var i = 0; i < selected.length; i++) {
              $scope.bulkStatus = "Unpublishing " + current + " out of " + total + " document" + pluralSuffix;
  
              contentResource.unPublish(getIdCallback(selected[i]))
                  .then(function(content) {
  
                      if (current == total) {
                          notificationsService.success("Bulk action", "Unpublished " + total + " document" + pluralSuffix);
                          $scope.bulkStatus = "";
                          $scope.reloadView($scope.contentId);
                          $scope.actionInProgress = false;
                      }
  
                      current++;
                  });
          }
      };
  
      function getCustomPropertyValue(alias, properties) {
          var value = '';
          var index = 0;
          var foundAlias = false;
          for (var i = 0; i < properties.length; i++) {
              if (properties[i].alias == alias) {
                  foundAlias = true;
                  break;
              }
              index++;
          }
  
          if (foundAlias) {
              value = properties[index].value;
          }
  
          return value;
      };
  
      /** This ensures that the correct value is set for each item in a row, we don't want to call a function during interpolation or ng-bind as performance is really bad that way */
      function setPropertyValues(result) {
  
          //set the edit url
          result.editPath = createEditUrlCallback(result);
  
          _.each($scope.options.includeProperties, function (e, i) {
  
              var alias = e.alias;
  
              // First try to pull the value directly from the alias (e.g. updatedBy)        
              var value = result[alias];
              
              // If this returns an object, look for the name property of that (e.g. owner.name)
              if (value === Object(value)) {
                  value = value['name'];
              }
  
              // If we've got nothing yet, look at a user defined property
              if (typeof value === 'undefined') {
                  value = getCustomPropertyValue(alias, result.properties);
              }
  
              // If we have a date, format it
              if (isDate(value)) {
                  value = value.substring(0, value.length - 3);
              }
  
              // set what we've got on the result
              result[alias] = value;
          });
  
  
      };
  
      function isDate(val) {
          if (angular.isString(val)) {
              return val.match(/^(\d{4})\-(\d{2})\-(\d{2})\ (\d{2})\:(\d{2})\:(\d{2})$/);
          }
          return false;
      };
  
      function initView() {
          if ($routeParams.id) {
              $scope.listViewAllowedTypes = getContentTypesCallback($routeParams.id);
  
              $scope.contentId = $routeParams.id;
              $scope.isTrashed = $routeParams.id === "-20" || $routeParams.id === "-21";
  
              $scope.reloadView($scope.contentId);
          }
      };
  
      function getLocalizedKey(alias) {
  
          switch (alias) {
              case "sortOrder":
                  return "general_sort";
              case "updateDate":
                  return "content_updateDate";
              case "updater":
                  return "content_updatedBy";
              case "createDate":
                  return "content_createDate";
              case "owner":
                  return "content_createBy";
              case "published":
                  return "content_isPublished";
              case "contentTypeAlias":
                  //TODO: Check for members
                  return $scope.entityType === "content" ? "content_documentType" : "content_mediatype";
              case "email":
                  return "general_email";
              case "username":
                  return "general_username";
          }
          return alias;
      }
  
      //GO!
      initView();
  }
  
  
  angular.module("umbraco").controller("Umbraco.PropertyEditors.ListViewController", listViewController);
  function sortByPreValsController($rootScope, $scope, localizationService) {
  
      $scope.sortByFields = [
          { value: "SortOrder", key: "general_sort" },
          { value: "Name", key: "general_name" },
          { value: "VersionDate", key: "content_updateDate" },
          { value: "Updater", key: "content_updatedBy" },
          { value: "CreateDate", key: "content_createDate" },
          { value: "Owner", key: "content_createBy" },
          { value: "ContentTypeAlias", key: "content_documentType" },
          { value: "Published", key: "content_isPublished" },
          { value: "Email", key: "general_email" },
          { value: "Username", key: "general_username" }
      ];
      
      //now we'll localize these strings, for some reason the directive doesn't work inside of the select group with an ng-model declared
      _.each($scope.sortByFields, function (e, i) {
          localizationService.localize(e.key).then(function (v) {
              e.name = v;
  
              switch (e.value) {
                  case "Updater":
                      e.name += " (Content only)";
                      break;
                  case "Published":
                      e.name += " (Content only)";
                      break;
                  case "Email":
                      e.name += " (Members only)";
                      break;
                  case "Username":
                      e.name += " (Members only)";
                      break;
              }
          });
      });
  
  }
  
  
  angular.module("umbraco").controller("Umbraco.PrevalueEditors.SortByListViewController", sortByPreValsController);
  //DO NOT DELETE THIS, this is in use... 
  angular.module('umbraco')
  .controller("Umbraco.PropertyEditors.MacroContainerController",
  	
  	function($scope, dialogService, entityResource, macroService){
  		$scope.renderModel = [];
  		
  		if($scope.model.value){
  			var macros = $scope.model.value.split('>');
  
  			angular.forEach(macros, function(syntax, key){
  				if(syntax && syntax.length > 10){
  					//re-add the char we split on
  					syntax = syntax + ">";
  					var parsed = macroService.parseMacroSyntax(syntax);
  					if(!parsed){
  						parsed = {};
  					}
  					
  					parsed.syntax = syntax;
  					collectDetails(parsed);
  					$scope.renderModel.push(parsed);
  				}
  			});
  		}
  
  		
  		function collectDetails(macro){
  			macro.details = "";
  			if(macro.macroParamsDictionary){
  				angular.forEach((macro.macroParamsDictionary), function(value, key){
  					macro.details += key + ": " + value + " ";	
  				});	
  			}		
  		}
  
  		function openDialog(index){
  			var dialogData = {
  			    allowedMacros: $scope.model.config.allowed
  			};
  
  			if(index !== null && $scope.renderModel[index]) {
  				var macro = $scope.renderModel[index];
  				dialogData["macroData"] = macro;
  			}
  			
  			dialogService.macroPicker({
  				dialogData : dialogData,
  				callback: function(data) {
  
  					collectDetails(data);
  
  					//update the raw syntax and the list...
  					if(index !== null && $scope.renderModel[index]) {
  						$scope.renderModel[index] = data;
  					} else {
  						$scope.renderModel.push(data);
  					}
  				}
  			});
  		}
  
  
  
  		$scope.edit =function(index){
  				openDialog(index);
  		};
  
  		$scope.add = function () {
  
  		    if ($scope.model.config.max && $scope.model.config.max > 0 && $scope.renderModel.length >= $scope.model.config.max) {
                  //cannot add more than the max
  		        return;
  		    }
  		    
  			openDialog();
  		};
  
  		$scope.remove =function(index){
  			$scope.renderModel.splice(index, 1);
  		};
  
  	    $scope.clear = function() {
  	        $scope.model.value = "";
  	        $scope.renderModel = [];
  	    };
  
  	    var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {	
  			var syntax = [];
  	    	angular.forEach($scope.renderModel, function(value, key){
  	    		syntax.push(value.syntax);
  	    	});
  
  			$scope.model.value = syntax.join("");
  	    });
  
  	    //when the scope is destroyed we need to unsubscribe
  	    $scope.$on('$destroy', function () {
  	        unsubscribe();
  	    });
  
  
  		function trim(str, chr) {
  			var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^'+chr+'+|'+chr+'+$', 'g');
  			return str.replace(rgxtrim, '');
  		}
  
  });
  
  function MacroListController($scope, entityResource) {
  
      $scope.items = [];
      
      entityResource.getAll("Macro").then(function(items) {
          _.each(items, function(i) {
              $scope.items.push({ name: i.name, alias: i.alias });
          });
          
      });
  
  
  }
  
  angular.module("umbraco").controller("Umbraco.PrevalueEditors.MacroList", MacroListController);
  
  //inject umbracos assetsServce and dialog service
  function MarkdownEditorController($scope, assetsService, dialogService, $timeout) {
  
      //tell the assets service to load the markdown.editor libs from the markdown editors
      //plugin folder
  
      if ($scope.model.value === null || $scope.model.value === "") {
          $scope.model.value = $scope.model.config.defaultValue;
      }
  
      assetsService
          .load([
              "lib/markdown/markdown.converter.js",
              "lib/markdown/markdown.sanitizer.js",
              "lib/markdown/markdown.editor.js"
          ])
          .then(function () {
  
              // we need a short delay to wait for the textbox to appear.
              setTimeout(function () {
                  //this function will execute when all dependencies have loaded
                  // but in the case that they've been previously loaded, we can only 
                  // init the md editor after this digest because the DOM needs to be ready first
                  // so run the init on a timeout
                  $timeout(function () {
                      var converter2 = new Markdown.Converter();
                      var editor2 = new Markdown.Editor(converter2, "-" + $scope.model.alias);
                      editor2.run();
  
                      //subscribe to the image dialog clicks
                      editor2.hooks.set("insertImageDialog", function (callback) {
  
                          dialogService.mediaPicker({
                              callback: function (data) {
                                  callback(data.image);
                              }
                          });
  
                          return true; // tell the editor that we'll take care of getting the image url
                      });
                  }, 200);
              });
  
              //load the seperat css for the editor to avoid it blocking our js loading TEMP HACK
              assetsService.loadCss("lib/markdown/markdown.css");
          })
  }
  
  angular.module("umbraco").controller("Umbraco.PropertyEditors.MarkdownEditorController", MarkdownEditorController);
  //this controller simply tells the dialogs service to open a mediaPicker window
  //with a specified callback, this callback will receive an object with a selection on it
  angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerController",
      function ($rootScope, $scope, dialogService, entityResource, mediaResource, mediaHelper, $timeout, userService) {
  
          //check the pre-values for multi-picker
          var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false;
  
          if (!$scope.model.config.startNodeId) {
              userService.getCurrentUser().then(function (userData) {
                  $scope.model.config.startNodeId = userData.startMediaId;
              });
          }
              
  
           
          function setupViewModel() {
              $scope.images = [];
              $scope.ids = []; 
  
              if ($scope.model.value) {
                  var ids = $scope.model.value.split(',');
  
                  //NOTE: We need to use the entityResource NOT the mediaResource here because
                  // the mediaResource has server side auth configured for which the user must have
                  // access to the media section, if they don't they'll get auth errors. The entityResource
                  // acts differently in that it allows access if the user has access to any of the apps that
                  // might require it's use. Therefore we need to use the metatData property to get at the thumbnail
                  // value.
  
                  entityResource.getByIds(ids, "Media").then(function (medias) {
  
                      _.each(medias, function (media, i) {
                          
                          //only show non-trashed items
                          if (media.parentId >= -1) {
  
                              if (!media.thumbnail) { 
                                  media.thumbnail = mediaHelper.resolveFileFromEntity(media, true);
                              }
  
                              $scope.images.push(media);
                              $scope.ids.push(media.id);   
                          }
                      });
  
                      $scope.sync();
                  });
              }
          }
  
          setupViewModel();
  
          $scope.remove = function(index) {
              $scope.images.splice(index, 1);
              $scope.ids.splice(index, 1);
              $scope.sync();
          };
  
          $scope.add = function() {
              dialogService.mediaPicker({
                  startNodeId: $scope.model.config.startNodeId,
                  multiPicker: multiPicker,
                  callback: function(data) {
                      
                      //it's only a single selector, so make it into an array
                      if (!multiPicker) {
                          data = [data];
                      }
                      
                      _.each(data, function(media, i) {
  
                          if (!media.thumbnail) {
                              media.thumbnail = mediaHelper.resolveFileFromEntity(media, true);
                          }
  
                          $scope.images.push(media);
                          $scope.ids.push(media.id);
                      });
  
                      $scope.sync();
                  }
              });
          };
  
         $scope.sortableOptions = {
             update: function(e, ui) {
                 var r = [];
                 //TODO: Instead of doing this with a half second delay would be better to use a watch like we do in the 
                 // content picker. THen we don't have to worry about setting ids, render models, models, we just set one and let the 
                 // watch do all the rest.
                  $timeout(function(){
                      angular.forEach($scope.images, function(value, key){
                          r.push(value.id);
                      });
  
                      $scope.ids = r;
                      $scope.sync();
                  }, 500, false);
              }
          };
  
          $scope.sync = function() {
              $scope.model.value = $scope.ids.join();
          };
  
          $scope.showAdd = function () {
              if (!multiPicker) {
                  if ($scope.model.value && $scope.model.value !== "") {
                      return false;
                  }
              }
              return true;
          };
  
          //here we declare a special method which will be called whenever the value has changed from the server
          //this is instead of doing a watch on the model.value = faster
          $scope.model.onValueChanged = function (newVal, oldVal) {
              //update the display val again if it has changed from the server
              setupViewModel();
          };
  
      });
  
  //this controller simply tells the dialogs service to open a memberPicker window
  //with a specified callback, this callback will receive an object with a selection on it
  function memberGroupPicker($scope, dialogService){
  
      function trim(str, chr) {
          var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
          return str.replace(rgxtrim, '');
      }
  
      $scope.renderModel = [];
      
      if ($scope.model.value) {
          var modelIds = $scope.model.value.split(',');
          _.each(modelIds, function (item, i) {
              $scope.renderModel.push({ name: item, id: item, icon: 'icon-users' });
          });
      }
  	    
      var dialogOptions = {
          multiPicker: true,
          entityType: "MemberGroup",
          section: "membergroup",
          treeAlias: "memberGroup",
          filter: "",
          filterCssClass: "not-allowed",
          callback: function (data) {
              if (angular.isArray(data)) {
                  _.each(data, function (item, i) {
                      $scope.add(item);
                  });
              } else {
                  $scope.clear();
                  $scope.add(data);
  
              }
          }
      };
  
      //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the 
      // pre-value config on to the dialog options
      if($scope.model.config){
          angular.extend(dialogOptions, $scope.model.config);
      }
  
      $scope.openMemberGroupPicker =function() {
          var d = dialogService.memberGroupPicker(dialogOptions);
      };
  
  
      $scope.remove =function(index){
          $scope.renderModel.splice(index, 1);
      };
  
      $scope.add = function (item) {
          var currIds = _.map($scope.renderModel, function (i) {
              return i.id;
          });
  
          if (currIds.indexOf(item) < 0) {
              $scope.renderModel.push({ name: item, id: item, icon: 'icon-users' });
          }	
      };
  
      $scope.clear = function() {
          $scope.renderModel = [];
      };
  
      var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
          var currIds = _.map($scope.renderModel, function (i) {
              return i.id;
          });
          $scope.model.value = trim(currIds.join(), ",");
      });
  
      //when the scope is destroyed we need to unsubscribe
      $scope.$on('$destroy', function () {
          unsubscribe();
      });
  
  }
  
  angular.module('umbraco').controller("Umbraco.PropertyEditors.MemberGroupPickerController", memberGroupPicker);
  function memberGroupController($rootScope, $scope, dialogService, mediaResource, imageHelper, $log) {
  
      //set the available to the keys of the dictionary who's value is true
      $scope.getAvailable = function () {
          var available = [];
          for (var n in $scope.model.value) {
              if ($scope.model.value[n] === false) {
                  available.push(n);
              }
          }
          return available;
      };
      //set the selected to the keys of the dictionary who's value is true
      $scope.getSelected = function () {
          var selected = [];
          for (var n in $scope.model.value) {
              if ($scope.model.value[n] === true) {
                  selected.push(n);
              }
          }
          return selected;
      };
  
      $scope.addItem = function(item) {
          //keep the model up to date
          $scope.model.value[item] = true;
      };
      
      $scope.removeItem = function (item) {
          //keep the model up to date
          $scope.model.value[item] = false;
      };
  
  
  }
  angular.module('umbraco').controller("Umbraco.PropertyEditors.MemberGroupController", memberGroupController);
  //this controller simply tells the dialogs service to open a memberPicker window
  //with a specified callback, this callback will receive an object with a selection on it
  function memberPickerController($scope, dialogService, entityResource, $log, iconHelper){
  
      function trim(str, chr) {
          var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
          return str.replace(rgxtrim, '');
      }
  
      $scope.renderModel = [];
  
      var dialogOptions = {
          multiPicker: false,
          entityType: "Member",
          section: "member",
          treeAlias: "member",
          filter: function(i) {
              return i.metaData.isContainer == true;
          },
          filterCssClass: "not-allowed",
          callback: function(data) {
              if (angular.isArray(data)) {
                  _.each(data, function (item, i) {
                      $scope.add(item);
                  });
              } else {
                  $scope.clear();
                  $scope.add(data);
              }
          }
      };
  
      //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the 
      // pre-value config on to the dialog options
      if ($scope.model.config) {
          angular.extend(dialogOptions, $scope.model.config);
      }
      
      $scope.openMemberPicker =function() {
          var d = dialogService.memberPicker(dialogOptions);
      };
  
  
      $scope.remove =function(index){
          $scope.renderModel.splice(index, 1);
      };
  
      $scope.add = function (item) {
          var currIds = _.map($scope.renderModel, function (i) {
              return i.id;
          });
  
          if (currIds.indexOf(item.id) < 0) {
              item.icon = iconHelper.convertFromLegacyIcon(item.icon);
              $scope.renderModel.push({name: item.name, id: item.id, icon: item.icon});				
          }	
      };
  
      $scope.clear = function() {
          $scope.renderModel = [];
      };
  	
      var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
          var currIds = _.map($scope.renderModel, function (i) {
              return i.id;
          });
          $scope.model.value = trim(currIds.join(), ",");
      });
  
      //when the scope is destroyed we need to unsubscribe
      $scope.$on('$destroy', function () {
          unsubscribe();
      });
  
      //load member data
      var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
      entityResource.getByIds(modelIds, "Member").then(function (data) {
          _.each(data, function (item, i) {
              item.icon = iconHelper.convertFromLegacyIcon(item.icon);
              $scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon });
          });
      });
  }
  
  
  angular.module('umbraco').controller("Umbraco.PropertyEditors.MemberPickerController", memberPickerController);
  function MultipleTextBoxController($scope) {
  
      $scope.sortableOptions = {
          axis: 'y',
          containment: 'parent',
          cursor: 'move',
          items: '> div.control-group',
          tolerance: 'pointer'
      };
  
      if (!$scope.model.value) {
          $scope.model.value = [];
      }
      
      //add any fields that there isn't values for
      if ($scope.model.config.min > 0) {
          for (var i = 0; i < $scope.model.config.min; i++) {
              if ((i + 1) > $scope.model.value.length) {
                  $scope.model.value.push({ value: "" });
              }
          }
      }
  
      $scope.add = function () {
          if ($scope.model.config.max <= 0 || $scope.model.value.length < $scope.model.config.max) {
              $scope.model.value.push({ value: "" });
          }
      };
  
      $scope.remove = function(index) {
          var remainder = [];
          for (var x = 0; x < $scope.model.value.length; x++) {
              if (x !== index) {
                  remainder.push($scope.model.value[x]);
              }
          }
          $scope.model.value = remainder;
      };
  
  }
  
  angular.module("umbraco").controller("Umbraco.PropertyEditors.MultipleTextBoxController", MultipleTextBoxController);
  
  angular.module("umbraco").controller("Umbraco.PropertyEditors.RadioButtonsController",
      function($scope) {
          
          if (angular.isObject($scope.model.config.items)) {
              
              //now we need to format the items in the dictionary because we always want to have an array
              var newItems = [];
              var vals = _.values($scope.model.config.items);
              var keys = _.keys($scope.model.config.items);
              for (var i = 0; i < vals.length; i++) {
                  newItems.push({ id: keys[i], sortOrder: vals[i].sortOrder, value: vals[i].value });
              }
  
              //ensure the items are sorted by the provided sort order
              newItems.sort(function (a, b) { return (a.sortOrder > b.sortOrder) ? 1 : ((b.sortOrder > a.sortOrder) ? -1 : 0); });
  
              //re-assign
              $scope.model.config.items = newItems;
  
          }
  
      });
  
  /**
   * @ngdoc controller
   * @name Umbraco.Editors.ReadOnlyValueController
   * @function
   * 
   * @description
   * The controller for the readonlyvalue property editor. 
   *  This controller offer more functionality than just a simple label as it will be able to apply formatting to the 
   *  value to be displayed. This means that we also have to apply more complex logic of watching the model value when 
   *  it changes because we are creating a new scope value called displayvalue which will never change based on the server data.
   *  In some cases after a form submission, the server will modify the data that has been persisted, especially in the cases of 
   *  readonlyvalues so we need to ensure that after the form is submitted that the new data is reflected here.
  */
  function ReadOnlyValueController($rootScope, $scope, $filter) {
  
      function formatDisplayValue() {
          
          if ($scope.model.config &&
          angular.isArray($scope.model.config) &&
          $scope.model.config.length > 0 &&
          $scope.model.config[0] &&
          $scope.model.config.filter) {
  
              if ($scope.model.config.format) {
                  $scope.displayvalue = $filter($scope.model.config.filter)($scope.model.value, $scope.model.config.format);
              } else {
                  $scope.displayvalue = $filter($scope.model.config.filter)($scope.model.value);
              }
          } else {
              $scope.displayvalue = $scope.model.value;
          }
  
      }
  
      //format the display value on init:
      formatDisplayValue();
      
      $scope.$watch("model.value", function (newVal, oldVal) {
          //cannot just check for !newVal because it might be an empty string which we 
          //want to look for.
          if (newVal !== null && newVal !== undefined && newVal !== oldVal) {
              //update the display val again
              formatDisplayValue();
          }
      });
  }
  
  angular.module('umbraco').controller("Umbraco.PropertyEditors.ReadOnlyValueController", ReadOnlyValueController);
  angular.module("umbraco")
      .controller("Umbraco.PropertyEditors.RelatedLinksController",
          function ($rootScope, $scope, dialogService) {
  
              if (!$scope.model.value) {
                  $scope.model.value = [];
              }
              
              $scope.newCaption = '';
              $scope.newLink = 'http://';
              $scope.newNewWindow = false;
              $scope.newInternal = null;
              $scope.newInternalName = '';
              $scope.addExternal = true;
              $scope.currentEditLink = null;
              $scope.hasError = false;
  
              $scope.internal = function ($event) {
                  $scope.currentEditLink = null;
                  var d = dialogService.contentPicker({ multipicker: false, callback: select });
                  $event.preventDefault();
              };
              
              $scope.selectInternal = function ($event, link) {
  
                  $scope.currentEditLink = link;
                  var d = dialogService.contentPicker({ multipicker: false, callback: select });
                  $event.preventDefault();
              };
  
              $scope.edit = function (idx) {
                  for (var i = 0; i < $scope.model.value.length; i++) {
                      $scope.model.value[i].edit = false;
                  }
                  $scope.model.value[idx].edit = true;
              };
    
  
              $scope.saveEdit = function (idx) {
                  $scope.model.value[idx].title = $scope.model.value[idx].caption;
                  $scope.model.value[idx].edit = false;
              };
  
              $scope.delete = function (idx) {               
                  $scope.model.value.splice(idx, 1);               
              };
  
              $scope.add = function ($event) {
                  if ($scope.newCaption == "") {
                      $scope.hasError = true;
                  } else {
                      if ($scope.addExternal) {
                          var newExtLink = new function() {
                              this.caption = $scope.newCaption;
                              this.link = $scope.newLink;
                              this.newWindow = $scope.newNewWindow;
                              this.edit = false;
                              this.isInternal = false;
                              this.type = "external";
                              this.title = $scope.newCaption;
                          };
                          $scope.model.value.push(newExtLink);
                      } else {
                          var newIntLink = new function() {
                              this.caption = $scope.newCaption;
                              this.link = $scope.newInternal;
                              this.newWindow = $scope.newNewWindow;
                              this.internal = $scope.newInternal;
                              this.edit = false;
                              this.isInternal = true;
                              this.internalName = $scope.newInternalName;
                              this.type = "internal";
                              this.title = $scope.newCaption;
                          };
                          $scope.model.value.push(newIntLink);
                      }
                      $scope.newCaption = '';
                      $scope.newLink = 'http://';
                      $scope.newNewWindow = false;
                      $scope.newInternal = null;
                      $scope.newInternalName = '';
  
                  }
                  $event.preventDefault();
              };
  
              $scope.switch = function ($event) {
                  $scope.addExternal = !$scope.addExternal;
                  $event.preventDefault();
              };
              
              $scope.switchLinkType = function ($event, link) {
                  link.isInternal = !link.isInternal;                
                  link.type = link.isInternal ? "internal" : "external";
                  if (!link.isInternal)
                      link.link = $scope.newLink;
                  $event.preventDefault();
              };
  
              $scope.move = function (index, direction) {
                  var temp = $scope.model.value[index];
                  $scope.model.value[index] = $scope.model.value[index + direction];
                  $scope.model.value[index + direction] = temp;                
              };
  
              $scope.sortableOptions = {
                  containment: 'parent',
                  cursor: 'move',
                  helper: function (e, ui) {
                      // When sorting <trs>, the cells collapse.  This helper fixes that: http://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/
                      ui.children().each(function () {
                          $(this).width($(this).width());
                      });
                      return ui;
                  },
                  items: '> tr',
                  tolerance: 'pointer',
                  update: function (e, ui) {
                      // Get the new and old index for the moved element (using the URL as the identifier)
                      var newIndex = ui.item.index();
                      var movedLinkUrl = ui.item.attr('data-link');
                      var originalIndex = getElementIndexByUrl(movedLinkUrl);
  
                      // Move the element in the model
                      var movedElement = $scope.model.value[originalIndex];
                      $scope.model.value.splice(originalIndex, 1);
                      $scope.model.value.splice(newIndex, 0, movedElement);
                  }
              };
  
              function getElementIndexByUrl(url) {
                  for (var i = 0; i < $scope.model.value.length; i++) {
                      if ($scope.model.value[i].link == url) {
                          return i;
                      }
                  }
  
                  return -1;
              }
  
              function select(data) {
                  if ($scope.currentEditLink != null) {
                      $scope.currentEditLink.internal = data.id;
                      $scope.currentEditLink.internalName = data.name;
                      $scope.currentEditLink.link = data.id;
                  } else {
                      $scope.newInternal = data.id;
                      $scope.newInternalName = data.name;
                  }
              }            
          });
  angular.module("umbraco")
      .controller("Umbraco.PropertyEditors.RTEController",
      function ($rootScope, $scope, $q, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper, stylesheetResource) {
          
          $scope.isLoading = true;
  
          //To id the html textarea we need to use the datetime ticks because we can have multiple rte's per a single property alias
          // because now we have to support having 2x (maybe more at some stage) content editors being displayed at once. This is because
          // we have this mini content editor panel that can be launched with MNTP.
          var d = new Date();
          var n = d.getTime();
          $scope.textAreaHtmlId = $scope.model.alias + "_" + n + "_rte";
  
          var alreadyDirty = false;
          function syncContent(editor){
              editor.save();
              angularHelper.safeApply($scope, function () {
                  $scope.model.value = editor.getContent();
              });
  
              if (!alreadyDirty) {
                  //make the form dirty manually so that the track changes works, setting our model doesn't trigger
                  // the angular bits because tinymce replaces the textarea.
                  var currForm = angularHelper.getCurrentForm($scope);
                  currForm.$setDirty();
                  alreadyDirty = true;
              }
          }
  
          tinyMceService.configuration().then(function (tinyMceConfig) {
  
              //config value from general tinymce.config file
              var validElements = tinyMceConfig.validElements;
  
              //These are absolutely required in order for the macros to render inline
              //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce
              var extendedValidElements = "@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style]";
  
              var invalidElements = tinyMceConfig.inValidElements;
              var plugins = _.map(tinyMceConfig.plugins, function (plugin) {
                  if (plugin.useOnFrontend) {
                      return plugin.name;
                  }
              }).join(" ");
  
              var editorConfig = $scope.model.config.editor;
              if (!editorConfig || angular.isString(editorConfig)) {
                  editorConfig = tinyMceService.defaultPrevalues();
              }
  
              //config value on the data type
              var toolbar = editorConfig.toolbar.join(" | ");
              var stylesheets = [];
              var styleFormats = [];
              var await = [];
              if (!editorConfig.maxImageSize && editorConfig.maxImageSize != 0) {
                  editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize;
              }
  
              //queue file loading
              if (typeof tinymce === "undefined") { // Don't reload tinymce if already loaded
                  await.push(assetsService.loadJs("lib/tinymce/tinymce.min.js", $scope));
              }
  
              //queue rules loading
              angular.forEach(editorConfig.stylesheets, function (val, key) {
                  stylesheets.push("../css/" + val + ".css?" + new Date().getTime());
  
                  await.push(stylesheetResource.getRulesByName(val).then(function (rules) {
                      angular.forEach(rules, function (rule) {
                          var r = {};
                          r.title = rule.name;
                          if (rule.selector[0] == ".") {
                              r.inline = "span";
                              r.classes = rule.selector.substring(1);
                          }
                          else if (rule.selector[0] == "#") {
                              r.inline = "span";
                              r.attributes = { id: rule.selector.substring(1) };
                          }
                          else if (rule.selector[0] != "." && rule.selector.indexOf(".") > -1) {
                              var split = rule.selector.split(".");
                              r.block = split[0];
                              r.classes = rule.selector.substring(rule.selector.indexOf(".") + 1).replace(".", " ");
                          }
                          else if (rule.selector[0] != "#" && rule.selector.indexOf("#") > -1) {
                              var split = rule.selector.split("#");
                              r.block = split[0];
                              r.classes = rule.selector.substring(rule.selector.indexOf("#") + 1);
                          }
                          else {
                              r.block = rule.selector;
                          }
  
                          styleFormats.push(r);
                      });
                  }));
              });
  
  
              //stores a reference to the editor
              var tinyMceEditor = null;
  
              //wait for queue to end
              $q.all(await).then(function () {
  
                  //create a baseline Config to exten upon
                  var baseLineConfigObj = {
                      mode: "exact",
                      skin: "umbraco",
                      plugins: plugins,
                      valid_elements: validElements,
                      invalid_elements: invalidElements,
                      extended_valid_elements: extendedValidElements,
                      menubar: false,
                      statusbar: false,
                      height: editorConfig.dimensions.height,
                      width: editorConfig.dimensions.width,
                      maxImageSize: editorConfig.maxImageSize,
                      toolbar: toolbar,
                      content_css: stylesheets.join(','),
                      relative_urls: false,
                      style_formats: styleFormats
                  };
  
  
                  if (tinyMceConfig.customConfig) {
  
                      //if there is some custom config, we need to see if the string value of each item might actually be json and if so, we need to 
                      // convert it to json instead of having it as a string since this is what tinymce requires
                      for (var i in tinyMceConfig.customConfig) {
                          var val = tinyMceConfig.customConfig[i];
                          if (val) {
                              val = val.toString().trim();
                              if (val.detectIsJson()) {
                                  try {
                                      tinyMceConfig.customConfig[i] = JSON.parse(val);
                                      //now we need to check if this custom config key is defined in our baseline, if it is we don't want to 
                                      //overwrite the baseline config item if it is an array, we want to concat the items in the array, otherwise
                                      //if it's an object it will overwrite the baseline
                                      if (angular.isArray(baseLineConfigObj[i]) && angular.isArray(tinyMceConfig.customConfig[i])) {
                                          //concat it and below this concat'd array will overwrite the baseline in angular.extend
                                          tinyMceConfig.customConfig[i] = baseLineConfigObj[i].concat(tinyMceConfig.customConfig[i]);
                                      }
                                  }
                                  catch (e) {
                                      //cannot parse, we'll just leave it
                                  } 
                              }
                          }
                      }
  
                      angular.extend(baseLineConfigObj, tinyMceConfig.customConfig);
                  }
  
                  //set all the things that user configs should not be able to override
                  baseLineConfigObj.elements = $scope.textAreaHtmlId; //this is the exact textarea id to replace!
                  baseLineConfigObj.setup = function (editor) {
  
                      //set the reference
                      tinyMceEditor = editor;
  
                      //enable browser based spell checking
                      editor.on('init', function (e) {
                          editor.getBody().setAttribute('spellcheck', true);
                      });
  
                      //We need to listen on multiple things here because of the nature of tinymce, it doesn't 
                      //fire events when you think!
                      //The change event doesn't fire when content changes, only when cursor points are changed and undo points
                      //are created. the blur event doesn't fire if you insert content into the editor with a button and then 
                      //press save. 
                      //We have a couple of options, one is to do a set timeout and check for isDirty on the editor, or we can 
                      //listen to both change and blur and also on our own 'saving' event. I think this will be best because a 
                      //timer might end up using unwanted cpu and we'd still have to listen to our saving event in case they clicked
                      //save before the timeout elapsed.
  
                      //TODO: We need to re-enable something like this to ensure the track changes is working with tinymce
                      // so we can detect if the form is dirty or not, Per has some better events to use as this one triggers
                      // even if you just enter/exit with mouse cursuor which doesn't really mean it's changed.
                      // see: http://issues.umbraco.org/issue/U4-4485
                      //var alreadyDirty = false;
                      //editor.on('change', function (e) {
                      //    angularHelper.safeApply($scope, function () {
                      //        $scope.model.value = editor.getContent();
  
                      //        if (!alreadyDirty) {
                      //            //make the form dirty manually so that the track changes works, setting our model doesn't trigger
                      //            // the angular bits because tinymce replaces the textarea.
                      //            var currForm = angularHelper.getCurrentForm($scope);
                      //            currForm.$setDirty();
                      //            alreadyDirty = true;
                      //        }
                              
                      //    });
                      //});
  
                      //when we leave the editor (maybe)
                      editor.on('blur', function (e) {
                          editor.save();
                          angularHelper.safeApply($scope, function () {
                              $scope.model.value = editor.getContent();
                          });
                      });
  
                      //when buttons modify content
                      editor.on('ExecCommand', function (e) {
                          syncContent(editor);
                      });
  
                      // Update model on keypress
                      editor.on('KeyUp', function (e) {
                          syncContent(editor);
                      });
  
                      // Update model on change, i.e. copy/pasted text, plugins altering content
                      editor.on('SetContent', function (e) {
                          if (!e.initial) {
                              syncContent(editor);
                          }
                      });
  
  
                      editor.on('ObjectResized', function (e) {
                          var qs = "?width=" + e.width + "px&height=" + e.height + "px";
                          var srcAttr = $(e.target).attr("src");
                          var path = srcAttr.split("?")[0];
                          $(e.target).attr("data-mce-src", path + qs);
  
                          syncContent(editor);
                      });
  
  
                      //Create the insert media plugin
                      tinyMceService.createMediaPicker(editor, $scope);
  
                      //Create the embedded plugin
                      tinyMceService.createInsertEmbeddedMedia(editor, $scope);
  
                      //Create the insert macro plugin
                      tinyMceService.createInsertMacro(editor, $scope);
                  };
  
  
  
  
                  /** Loads in the editor */
                  function loadTinyMce() {
  
                      //we need to add a timeout here, to force a redraw so TinyMCE can find
                      //the elements needed
                      $timeout(function () {
                          tinymce.DOM.events.domLoaded = true;
                          tinymce.init(baseLineConfigObj);
  
                          $scope.isLoading = false;
  
                      }, 200, false);
                  }
  
  
  
  
                  loadTinyMce();
  
                  //here we declare a special method which will be called whenever the value has changed from the server
                  //this is instead of doing a watch on the model.value = faster
                  $scope.model.onValueChanged = function (newVal, oldVal) {
                      //update the display val again if it has changed from the server;
                      tinyMceEditor.setContent(newVal, { format: 'raw' });
                      //we need to manually fire this event since it is only ever fired based on loading from the DOM, this
                      // is required for our plugins listening to this event to execute
                      tinyMceEditor.fire('LoadContent', null);
                  };
  
                  //listen for formSubmitting event (the result is callback used to remove the event subscription)
                  var unsubscribe = $scope.$on("formSubmitting", function () {
                      //TODO: Here we should parse out the macro rendered content so we can save on a lot of bytes in data xfer
                      // we do parse it out on the server side but would be nice to do that on the client side before as well.
                      $scope.model.value = tinyMceEditor.getContent();
                  });
  
                  //when the element is disposed we need to unsubscribe!
                  // NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom 
                  // element might still be there even after the modal has been hidden.
                  $scope.$on('$destroy', function () {
                      unsubscribe();
                  });
              });
          });
  
      });
  
  angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController",
      function ($scope, $timeout, $log, tinyMceService, stylesheetResource) {
          var cfg = tinyMceService.defaultPrevalues();
  
          if($scope.model.value){
              if(angular.isString($scope.model.value)){
                  $scope.model.value = cfg;
              }
          }else{
              $scope.model.value = cfg;
          }
  
          if (!$scope.model.value.stylesheets) {
              $scope.model.value.stylesheets = [];
          }
          if (!$scope.model.value.toolbar) {
              $scope.model.value.toolbar = [];
          }
          if (!$scope.model.value.maxImageSize && $scope.model.value.maxImageSize != 0) {
              $scope.model.value.maxImageSize = cfg.maxImageSize;
          }
  
          tinyMceService.configuration().then(function(config){
              $scope.tinyMceConfig = config;
  
          });
  
          stylesheetResource.getAll().then(function(stylesheets){
              $scope.stylesheets = stylesheets;
          });
  
          $scope.selected = function(cmd, alias, lookup){
              if (lookup && angular.isArray(lookup)) {
                  cmd.selected = lookup.indexOf(alias) >= 0;
                  return cmd.selected;
              }
              return false;
          };
  
          $scope.selectCommand = function(command){
              var index = $scope.model.value.toolbar.indexOf(command.frontEndCommand);
  
              if(command.selected && index === -1){
                  $scope.model.value.toolbar.push(command.frontEndCommand);
              }else if(index >= 0){
                  $scope.model.value.toolbar.splice(index, 1);
              }
          };
  
          $scope.selectStylesheet = function (css) {
              
              var index = $scope.model.value.stylesheets.indexOf(css.name);
  
              if(css.selected && index === -1){
                  $scope.model.value.stylesheets.push(css.name);
              }else if(index >= 0){
                  $scope.model.value.stylesheets.splice(index, 1);
              }
          };
  
          var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
  
              var commands = _.where($scope.tinyMceConfig.commands, {selected: true});
              $scope.model.value.toolbar = _.pluck(commands, "frontEndCommand");
              
          });
  
          //when the scope is destroyed we need to unsubscribe
          $scope.$on('$destroy', function () {
              unsubscribe();
          });
  
      });
  
  function sliderController($scope, $log, $element, assetsService, angularHelper) {
  
      //configure some defaults
      if (!$scope.model.config.orientation) {
          $scope.model.config.orientation = "horizontal";
      }
      if (!$scope.model.config.initVal1) {
          $scope.model.config.initVal1 = 0;
      }
      else {
          $scope.model.config.initVal1 = parseFloat($scope.model.config.initVal1);
      }
      if (!$scope.model.config.initVal2) {
          $scope.model.config.initVal2 = 0;
      }
      else {
          $scope.model.config.initVal2 = parseFloat($scope.model.config.initVal2);
      }
      if (!$scope.model.config.minVal) {
          $scope.model.config.minVal = 0;
      }
      else {
          $scope.model.config.minVal = parseFloat($scope.model.config.minVal);
      }
      if (!$scope.model.config.maxVal) {
          $scope.model.config.maxVal = 100;
      }
      else {
          $scope.model.config.maxVal = parseFloat($scope.model.config.maxVal);
      }
      if (!$scope.model.config.step) {
          $scope.model.config.step = 1;
      }
      else {
          $scope.model.config.step = parseFloat($scope.model.config.step);
      }
      
      /** This creates the slider with the model values - it's called on startup and if the model value changes */
      function createSlider() {
  
          //the value that we'll give the slider - if it's a range, we store our value as a comma seperated val but this slider expects an array
          var sliderVal = null;
  
          //configure the model value based on if range is enabled or not
          if ($scope.model.config.enableRange === "1") {
              //If no value saved yet - then use default value
              if (!$scope.model.value) {
                  var i1 = parseFloat($scope.model.config.initVal1);
                  var i2 = parseFloat($scope.model.config.initVal2);
                  sliderVal = [
                      isNaN(i1) ? $scope.model.config.minVal : (i1 >= $scope.model.config.minVal ? i1 : $scope.model.config.minVal),
                      isNaN(i2) ? $scope.model.config.maxVal : (i2 > i1 ? (i2 <= $scope.model.config.maxVal ? i2 : $scope.model.config.maxVal) : $scope.model.config.maxVal)
                  ];
              }
              else {
                  //this will mean it's a delimited value stored in the db, convert it to an array
                  sliderVal = _.map($scope.model.value.split(','), function (item) {
                      return parseFloat(item);
                  });
              }
          }
          else {
              //If no value saved yet - then use default value
              if ($scope.model.value) {
                  sliderVal = parseFloat($scope.model.value);
              }
              else {
                  sliderVal = $scope.model.config.initVal1;
              }
          }
  
          // Initialise model value if not set
          if (!$scope.model.value) {
              setModelValueFromSlider(sliderVal);
          }
  
          //initiate slider, add event handler and get the instance reference (stored in data)
          var slider = $element.find('.slider-item').slider({
              max: $scope.model.config.maxVal,
              min: $scope.model.config.minVal,
              orientation: $scope.model.config.orientation,
              selection: "after",
              step: $scope.model.config.step,
              tooltip: "show",
              //set the slider val - we cannot do this with data- attributes when using ranges
              value: sliderVal
          }).on('slideStop', function () {
              angularHelper.safeApply($scope, function () {
                  setModelValueFromSlider(slider.getValue());
              });
          }).data('slider');
      }
  
      /** Called on start-up when no model value has been applied and on change of the slider via the UI - updates
          the model with the currently selected slider value(s) **/
      function setModelValueFromSlider(sliderVal) {
          //Get the value from the slider and format it correctly, if it is a range we want a comma delimited value
          if ($scope.model.config.enableRange === "1") {
              $scope.model.value = sliderVal.join(",");
          }
          else {
              $scope.model.value = sliderVal.toString();
          }
      }
  
      //tell the assetsService to load the bootstrap slider
      //libs from the plugin folder
      assetsService
          .loadJs("lib/slider/js/bootstrap-slider.js")
          .then(function () {
  
              createSlider();
              
              //here we declare a special method which will be called whenever the value has changed from the server
              //this is instead of doing a watch on the model.value = faster
              $scope.model.onValueChanged = function (newVal, oldVal) {                
                  if (newVal != oldVal) {
                      createSlider();
                  }
              };
  
          });
  
      //load the seperate css for the editor to avoid it blocking our js loading
      assetsService.loadCss("lib/slider/slider.css");
  
  }
  angular.module("umbraco").controller("Umbraco.PropertyEditors.SliderController", sliderController);
  angular.module("umbraco")
  .controller("Umbraco.PropertyEditors.TagsController",
      function ($rootScope, $scope, $log, assetsService, umbRequestHelper, angularHelper, $timeout, $element) {
  
          var $typeahead;
  
          $scope.isLoading = true;
          $scope.tagToAdd = "";
  
          assetsService.loadJs("lib/typeahead-js/typeahead.bundle.min.js").then(function () {
  
              $scope.isLoading = false;
  
              //load current value
  
              if ($scope.model.value) {
                  if (!$scope.model.config.storageType || $scope.model.config.storageType !== "Json") {
                      //it is csv
                      if (!$scope.model.value) {
                          $scope.model.value = [];
                      }
                      else {
                          $scope.model.value = $scope.model.value.split(",");
                      }
                  }
              }
              else {
                  $scope.model.value = [];
              }
  
              // Method required by the valPropertyValidator directive (returns true if the property editor has at least one tag selected)
              $scope.validateMandatory = function () {
                  return {
                      isValid: !$scope.model.validation.mandatory || ($scope.model.value != null && $scope.model.value.length > 0),
                      errorMsg: "Value cannot be empty",
                      errorKey: "required"
                  };
              }
  
              //Helper method to add a tag on enter or on typeahead select
              function addTag(tagToAdd) {
                  if (tagToAdd != null && tagToAdd.length > 0) {
                      if ($scope.model.value.indexOf(tagToAdd) < 0) {
                          $scope.model.value.push(tagToAdd);
                          //this is required to re-validate
                          $scope.propertyForm.tagCount.$setViewValue($scope.model.value.length);
                      }
                  }
              }
  
              $scope.addTagOnEnter = function (e) {
                  var code = e.keyCode || e.which;
                  if (code == 13) { //Enter keycode   
                      if ($element.find('.tags-' + $scope.model.alias).parent().find(".tt-dropdown-menu .tt-cursor").length === 0) {
                          //this is required, otherwise the html form will attempt to submit.
                          e.preventDefault();
                          $scope.addTag();
                      }
                  }
              };
  
              $scope.addTag = function () {
                  //ensure that we're not pressing the enter key whilst selecting a typeahead value from the drop down
                  //we need to use jquery because typeahead duplicates the text box
                  addTag($scope.tagToAdd);
                  $scope.tagToAdd = "";
                  //this clears the value stored in typeahead so it doesn't try to add the text again
                  // http://issues.umbraco.org/issue/U4-4947
                  $typeahead.typeahead('val', '');
              };
  
  
  
              $scope.removeTag = function (tag) {
                  var i = $scope.model.value.indexOf(tag);
                  if (i >= 0) {
                      $scope.model.value.splice(i, 1);
                      //this is required to re-validate
                      $scope.propertyForm.tagCount.$setViewValue($scope.model.value.length);
                  }
              };
  
              //vice versa
              $scope.model.onValueChanged = function (newVal, oldVal) {
                  //update the display val again if it has changed from the server
                  $scope.model.value = newVal;
  
                  if (!$scope.model.config.storageType || $scope.model.config.storageType !== "Json") {
                      //it is csv
                      if (!$scope.model.value) {
                          $scope.model.value = [];
                      }
                      else {
                          $scope.model.value = $scope.model.value.split(",");
                      }
                  }
              };
  
              //configure the tags data source
  
              //helper method to format the data for bloodhound
              function dataTransform(list) {
                  //transform the result to what bloodhound wants
                  var tagList = _.map(list, function (i) {
                      return { value: i.text };
                  });
                  // remove current tags from the list
                  return $.grep(tagList, function (tag) {
                      return ($.inArray(tag.value, $scope.model.value) === -1);
                  });
              }
  
              // helper method to remove current tags
              function removeCurrentTagsFromSuggestions(suggestions) {
                  return $.grep(suggestions, function (suggestion) {
                      return ($.inArray(suggestion.value, $scope.model.value) === -1);
                  });
              }
  
              var tagsHound = new Bloodhound({
                  datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
                  queryTokenizer: Bloodhound.tokenizers.whitespace,
                  dupDetector : function(remoteMatch, localMatch) {
                      return (remoteMatch["value"] == localMatch["value"]);
                  },
                  //pre-fetch the tags for this category
                  prefetch: {
                      url: umbRequestHelper.getApiUrl("tagsDataBaseUrl", "GetTags", [{ tagGroup: $scope.model.config.group }]),
                      //TTL = 5 minutes
                      ttl: 300000,
                      filter: dataTransform
                  },
                  //dynamically get the tags for this category (they may have changed on the server)
                  remote: {
                      url: umbRequestHelper.getApiUrl("tagsDataBaseUrl", "GetTags", [{ tagGroup: $scope.model.config.group }]),
                      filter: dataTransform
                  }
              });
  
              tagsHound.initialize(true);
  
              //configure the type ahead
              $timeout(function () {
  
                  $typeahead = $element.find('.tags-' + $scope.model.alias).typeahead(
                  {
                      //This causes some strangeness as it duplicates the textbox, best leave off for now.
                      hint: false,
                      highlight: true,
                      cacheKey: new Date(),  // Force a cache refresh each time the control is initialized
                      minLength: 1
                  }, {
                      //see: https://github.com/twitter/typeahead.js/blob/master/doc/jquery_typeahead.md#options
                      // name = the data set name, we'll make this the tag group name
                      name: $scope.model.config.group,
                      displayKey: "value",
                      source: function (query, cb) {
                          tagsHound.get(query, function (suggestions) {
                              cb(removeCurrentTagsFromSuggestions(suggestions));
                          });
                      },
                  }).bind("typeahead:selected", function (obj, datum, name) {
                      angularHelper.safeApply($scope, function () {
                          addTag(datum["value"]);
                          $scope.tagToAdd = "";
                          // clear the typed text
                          $typeahead.typeahead('val', '');
                      });
  
                  }).bind("typeahead:autocompleted", function (obj, datum, name) {
                      angularHelper.safeApply($scope, function () {
                          addTag(datum["value"]);
                          $scope.tagToAdd = "";
                      });
  
                  }).bind("typeahead:opened", function (obj) {
                      //console.log("opened ");
                  });
              });
  
              $scope.$on('$destroy', function () {
                  tagsHound.clearPrefetchCache();
                  tagsHound.clearRemoteCache();
                  $element.find('.tags-' + $scope.model.alias).typeahead('destroy');
                  delete tagsHound;
              });
  
          });
  
      }
  );
  //this controller simply tells the dialogs service to open a mediaPicker window
  //with a specified callback, this callback will receive an object with a selection on it
  angular.module('umbraco').controller("Umbraco.PropertyEditors.EmbeddedContentController",
  	function($rootScope, $scope, $log){
      
  	$scope.showForm = false;
  	$scope.fakeData = [];
  
  	$scope.create = function(){
  		$scope.showForm = true;
  		$scope.fakeData = angular.copy($scope.model.config.fields);
  	};
  
  	$scope.show = function(){
  		$scope.showCode = true;
  	};
  
  	$scope.add = function(){
  		$scope.showForm = false;
  		if ( !($scope.model.value instanceof Array)) {
  			$scope.model.value = [];
  		}
  
  		$scope.model.value.push(angular.copy($scope.fakeData));
  		$scope.fakeData = [];
  	};
  });
  angular.module('umbraco').controller("Umbraco.PropertyEditors.UrlListController",
  	function($rootScope, $scope, $filter) {
  
          function formatDisplayValue() {
              $scope.renderModel = _.map($scope.model.value.split(","), function (item) {
                  return {
                      url: item,
                      urlTarget: ($scope.config && $scope.config.target) ? $scope.config.target : "_blank"
                  };
              });
          }
  
  	    $scope.getUrl = function(valueUrl) {
  	        if (valueUrl.indexOf("/") >= 0) {
  	            return valueUrl;
  	        }
  	        return "#";
  	    };
  
  	    formatDisplayValue();
  	    
  	    //here we declare a special method which will be called whenever the value has changed from the server
  	    //this is instead of doing a watch on the model.value = faster
  	    $scope.model.onValueChanged = function(newVal, oldVal) {
  	        //update the display val again
  	        formatDisplayValue();
  	    };
  
  	});
  
  })();