/AarhusWebDevCoop/AarhusWebDevCoop/Umbraco/Js/umbraco.directives.js
https://bitbucket.org/andreas_nuppenau/obligatorisk_opgave · JavaScript · 11772 lines · 8821 code · 0 blank · 2951 comment · 627 complexity · f1ae19a5fa6ba03b62e106fad6525912 MD5 · raw file
- (function () {
- angular.module('umbraco.directives', [
- 'umbraco.directives.editors',
- 'umbraco.directives.html',
- 'umbraco.directives.validation',
- 'ui.sortable'
- ]);
- angular.module('umbraco.directives.editors', []);
- angular.module('umbraco.directives.html', []);
- angular.module('umbraco.directives.validation', []);
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:autoScale
- * @element div
- * @deprecated
- * We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
- * @function
- * @description
- * Resize div's automatically to fit to the bottom of the screen, as an optional parameter an y-axis offset can be set
- * So if you only want to scale the div to 70 pixels from the bottom you pass "70"
- * @example
- * <example module="umbraco.directives">
- * <file name="index.html">
- * <div auto-scale="70" class="input-block-level"></div>
- * </file>
- * </example>
- **/
- angular.module('umbraco.directives').directive('autoScale', function ($window) {
- return function (scope, el, attrs) {
- var totalOffset = 0;
- var offsety = parseInt(attrs.autoScale, 10);
- var window = angular.element($window);
- if (offsety !== undefined) {
- totalOffset += offsety;
- }
- setTimeout(function () {
- el.height(window.height() - (el.offset().top + totalOffset));
- }, 500);
- window.bind('resize', function () {
- el.height(window.height() - (el.offset().top + totalOffset));
- });
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:detectFold
- * @deprecated
- * We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
- * @description This is used for the editor buttons to ensure they are displayed correctly if the horizontal overflow of the editor
- * exceeds the height of the window
- **/
- angular.module('umbraco.directives.html').directive('detectFold', function ($timeout, $log, windowResizeListener) {
- return {
- require: '^?umbTabs',
- restrict: 'A',
- link: function (scope, el, attrs, tabsCtrl) {
- var firstRun = false;
- var parent = $('.umb-panel-body');
- var winHeight = $(window).height();
- var calculate = function () {
- if (el && el.is(':visible') && !el.hasClass('umb-bottom-bar')) {
- //now that the element is visible, set the flag in a couple of seconds,
- // this will ensure that loading time of a current tab get's completed and that
- // we eventually stop watching to save on CPU time
- $timeout(function () {
- firstRun = true;
- }, 4000);
- //var parent = el.parent();
- var hasOverflow = parent.innerHeight() < parent[0].scrollHeight;
- //var belowFold = (el.offset().top + el.height()) > winHeight;
- if (hasOverflow) {
- el.addClass('umb-bottom-bar');
- //I wish we didn't have to put this logic here but unfortunately we
- // do. This needs to calculate the left offest to place the bottom bar
- // depending on if the left column splitter has been moved by the user
- // (based on the nav-resize directive)
- var wrapper = $('#mainwrapper');
- var contentPanel = $('#leftcolumn').next();
- var contentPanelLeftPx = contentPanel.css('left');
- el.css({ left: contentPanelLeftPx });
- }
- }
- return firstRun;
- };
- var resizeCallback = function (size) {
- winHeight = size.height;
- el.removeClass('umb-bottom-bar');
- calculate();
- };
- windowResizeListener.register(resizeCallback);
- //Only execute the watcher if this tab is the active (first) tab on load, otherwise there's no reason to execute
- // the watcher since it will be recalculated when the tab changes!
- if (el.closest('.umb-tab-pane').index() === 0) {
- //run a watcher to ensure that the calculation occurs until it's firstRun but ensure
- // the calculations are throttled to save a bit of CPU
- var listener = scope.$watch(_.throttle(calculate, 1000), function (newVal, oldVal) {
- if (newVal !== oldVal) {
- listener();
- }
- });
- }
- //listen for tab changes
- if (tabsCtrl != null) {
- tabsCtrl.onTabShown(function (args) {
- calculate();
- });
- }
- //ensure to unregister
- scope.$on('$destroy', function () {
- windowResizeListener.unregister(resizeCallback);
- });
- }
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbContentName
- * @deprecated
- * We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
- * @restrict E
- * @function
- * @description
- * Used by editors that require naming an entity. Shows a textbox/headline with a required validator within it's own form.
- **/
- angular.module('umbraco.directives').directive('umbContentName', function ($timeout, localizationService) {
- return {
- require: 'ngModel',
- restrict: 'E',
- replace: true,
- templateUrl: 'views/directives/_obsolete/umb-content-name.html',
- scope: {
- placeholder: '@placeholder',
- model: '=ngModel',
- ngDisabled: '='
- },
- link: function (scope, element, attrs, ngModel) {
- var inputElement = element.find('input');
- if (scope.placeholder && scope.placeholder[0] === '@') {
- localizationService.localize(scope.placeholder.substring(1)).then(function (value) {
- scope.placeholder = value;
- });
- }
- var mX, mY, distance;
- function calculateDistance(elem, mouseX, mouseY) {
- var cx = Math.max(Math.min(mouseX, elem.offset().left + elem.width()), elem.offset().left);
- var cy = Math.max(Math.min(mouseY, elem.offset().top + elem.height()), elem.offset().top);
- return Math.sqrt((mouseX - cx) * (mouseX - cx) + (mouseY - cy) * (mouseY - cy));
- }
- var mouseMoveDebounce = _.throttle(function (e) {
- mX = e.pageX;
- mY = e.pageY;
- // not focused and not over element
- if (!inputElement.is(':focus') && !inputElement.hasClass('ng-invalid')) {
- // on page
- if (mX >= inputElement.offset().left) {
- distance = calculateDistance(inputElement, mX, mY);
- if (distance <= 155) {
- distance = 1 - 100 / 150 * distance / 100;
- inputElement.css('border', '1px solid rgba(175,175,175, ' + distance + ')');
- inputElement.css('background-color', 'rgba(255,255,255, ' + distance + ')');
- }
- }
- }
- }, 15);
- $(document).bind('mousemove', mouseMoveDebounce);
- $timeout(function () {
- if (!scope.model) {
- scope.goEdit();
- }
- }, 100, false);
- scope.goEdit = function () {
- scope.editMode = true;
- $timeout(function () {
- inputElement.focus();
- }, 100, false);
- };
- scope.exitEdit = function () {
- if (scope.model && scope.model !== '') {
- scope.editMode = false;
- }
- };
- //unbind doc event!
- scope.$on('$destroy', function () {
- $(document).unbind('mousemove', mouseMoveDebounce);
- });
- }
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbHeader
- * @deprecated
- * We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
- * @restrict E
- * @function
- * @description
- * The header on an editor that contains tabs using bootstrap tabs - THIS IS OBSOLETE, use umbTabHeader instead
- **/
- angular.module('umbraco.directives').directive('umbHeader', function ($parse, $timeout) {
- return {
- restrict: 'E',
- replace: true,
- transclude: 'true',
- templateUrl: 'views/directives/_obsolete/umb-header.html',
- //create a new isolated scope assigning a tabs property from the attribute 'tabs'
- //which is bound to the parent scope property passed in
- scope: { tabs: '=' },
- link: function (scope, iElement, iAttrs) {
- scope.showTabs = iAttrs.tabs ? true : false;
- scope.visibleTabs = [];
- //since tabs are loaded async, we need to put a watch on them to determine
- // when they are loaded, then we can close the watch
- var tabWatch = scope.$watch('tabs', function (newValue, oldValue) {
- angular.forEach(newValue, function (val, index) {
- var tab = {
- id: val.id,
- label: val.label
- };
- scope.visibleTabs.push(tab);
- });
- //don't process if we cannot or have already done so
- if (!newValue) {
- return;
- }
- if (!newValue.length || newValue.length === 0) {
- return;
- }
- //we need to do a timeout here so that the current sync operation can complete
- // and update the UI, then this will fire and the UI elements will be available.
- $timeout(function () {
- //use bootstrap tabs API to show the first one
- iElement.find('.nav-tabs a:first').tab('show');
- //enable the tab drop
- iElement.find('.nav-pills, .nav-tabs').tabdrop();
- //ensure to destroy tabdrop (unbinds window resize listeners)
- scope.$on('$destroy', function () {
- iElement.find('.nav-pills, .nav-tabs').tabdrop('destroy');
- });
- //stop watching now
- tabWatch();
- }, 200);
- });
- }
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbItemSorter
- * @deprecated
- * We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
- * @function
- * @element ANY
- * @restrict E
- * @description A re-usable directive for sorting items
- **/
- function umbItemSorter(angularHelper) {
- return {
- scope: { model: '=' },
- restrict: 'E',
- // restrict to an element
- replace: true,
- // replace the html element with the template
- templateUrl: 'views/directives/_obsolete/umb-item-sorter.html',
- link: function (scope, element, attrs, ctrl) {
- var defaultModel = {
- okButton: 'Ok',
- successMsg: 'Sorting successful',
- complete: false
- };
- //assign user vals to default
- angular.extend(defaultModel, scope.model);
- //re-assign merged to user
- scope.model = defaultModel;
- scope.performSort = function () {
- scope.$emit('umbItemSorter.sorting', { sortedItems: scope.model.itemsToSort });
- };
- scope.handleCancel = function () {
- scope.$emit('umbItemSorter.cancel');
- };
- scope.handleOk = function () {
- scope.$emit('umbItemSorter.ok');
- };
- //defines the options for the jquery sortable
- scope.sortableOptions = {
- axis: 'y',
- cursor: 'move',
- placeholder: 'ui-sortable-placeholder',
- update: function (ev, ui) {
- //highlight the item when the position is changed
- $(ui.item).effect('highlight', { color: '#049cdb' }, 500);
- },
- stop: function (ev, ui) {
- //the ui-sortable directive already ensures that our list is re-sorted, so now we just
- // need to update the sortOrder to the index of each item
- angularHelper.safeApply(scope, function () {
- angular.forEach(scope.itemsToSort, function (val, index) {
- val.sortOrder = index + 1;
- });
- });
- }
- };
- }
- };
- }
- angular.module('umbraco.directives').directive('umbItemSorter', umbItemSorter);
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbLogin
- * @deprecated
- * We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
- * @function
- * @element ANY
- * @restrict E
- **/
- function loginDirective() {
- return {
- restrict: 'E',
- // restrict to an element
- replace: true,
- // replace the html element with the template
- templateUrl: 'views/directives/_obsolete/umb-login.html'
- };
- }
- angular.module('umbraco.directives').directive('umbLogin', loginDirective);
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbOptionsMenu
- * @deprecated
- * We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
- * @function
- * @element ANY
- * @restrict E
- **/
- angular.module('umbraco.directives').directive('umbOptionsMenu', function ($injector, treeService, navigationService, umbModelMapper, appState) {
- return {
- scope: {
- currentSection: '@',
- currentNode: '='
- },
- restrict: 'E',
- replace: true,
- templateUrl: 'views/directives/_obsolete/umb-optionsmenu.html',
- link: function (scope, element, attrs, ctrl) {
- //adds a handler to the context menu item click, we need to handle this differently
- //depending on what the menu item is supposed to do.
- scope.executeMenuItem = function (action) {
- navigationService.executeMenuAction(action, scope.currentNode, scope.currentSection);
- };
- //callback method to go and get the options async
- scope.getOptions = function () {
- if (!scope.currentNode) {
- return;
- }
- //when the options item is selected, we need to set the current menu item in appState (since this is synonymous with a menu)
- appState.setMenuState('currentNode', scope.currentNode);
- if (!scope.actions) {
- treeService.getMenu({ treeNode: scope.currentNode }).then(function (data) {
- scope.actions = data.menuItems;
- });
- }
- };
- }
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbPhotoFolder
- * @deprecated
- * We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
- * @restrict E
- **/
- angular.module('umbraco.directives.html').directive('umbPhotoFolder', function ($compile, $log, $timeout, $filter, umbPhotoFolderHelper) {
- return {
- restrict: 'E',
- replace: true,
- require: '?ngModel',
- terminate: true,
- templateUrl: 'views/directives/_obsolete/umb-photo-folder.html',
- link: function (scope, element, attrs, ngModel) {
- var lastWatch = null;
- ngModel.$render = function () {
- if (ngModel.$modelValue) {
- $timeout(function () {
- var photos = ngModel.$modelValue;
- scope.clickHandler = scope.$eval(element.attr('on-click'));
- var imagesOnly = element.attr('images-only') === 'true';
- var margin = element.attr('border') ? parseInt(element.attr('border'), 10) : 5;
- var startingIndex = element.attr('baseline') ? parseInt(element.attr('baseline'), 10) : 0;
- var minWidth = element.attr('min-width') ? parseInt(element.attr('min-width'), 10) : 420;
- var minHeight = element.attr('min-height') ? parseInt(element.attr('min-height'), 10) : 100;
- var maxHeight = element.attr('max-height') ? parseInt(element.attr('max-height'), 10) : 300;
- var idealImgPerRow = element.attr('ideal-items-per-row') ? parseInt(element.attr('ideal-items-per-row'), 10) : 5;
- var fixedRowWidth = Math.max(element.width(), minWidth);
- scope.containerStyle = { width: fixedRowWidth + 'px' };
- scope.rows = umbPhotoFolderHelper.buildGrid(photos, fixedRowWidth, maxHeight, startingIndex, minHeight, idealImgPerRow, margin, imagesOnly);
- if (attrs.filterBy) {
- //we track the watches that we create, we don't want to create multiple, so clear it
- // if it already exists before creating another.
- if (lastWatch) {
- lastWatch();
- }
- //TODO: Need to debounce this so it doesn't filter too often!
- lastWatch = scope.$watch(attrs.filterBy, function (newVal, oldVal) {
- if (newVal && newVal !== oldVal) {
- var p = $filter('filter')(photos, newVal, false);
- scope.baseline = 0;
- var m = umbPhotoFolderHelper.buildGrid(p, fixedRowWidth, maxHeight, startingIndex, minHeight, idealImgPerRow, margin, imagesOnly);
- scope.rows = m;
- }
- });
- }
- }, 500); //end timeout
- } //end if modelValue
- }; //end $render
- }
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbSort
- * @deprecated
- * We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
- *
- * @element div
- * @function
- *
- * @description
- * Resize div's automatically to fit to the bottom of the screen, as an optional parameter an y-axis offset can be set
- * So if you only want to scale the div to 70 pixels from the bottom you pass "70"
- *
- * @example
- * <example module="umbraco.directives">
- * <file name="index.html">
- * <div umb-sort="70" class="input-block-level"></div>
- * </file>
- * </example>
- **/
- angular.module('umbraco.directives').value('umbSortContextInternal', {}).directive('umbSort', function ($log, umbSortContextInternal) {
- return {
- require: '?ngModel',
- link: function (scope, element, attrs, ngModel) {
- var adjustment;
- var cfg = scope.$eval(element.attr('umb-sort')) || {};
- scope.model = ngModel;
- scope.opts = cfg;
- scope.opts.containerSelector = cfg.containerSelector || '.umb-' + cfg.group + '-container', scope.opts.nested = cfg.nested || true, scope.opts.drop = cfg.drop || true, scope.opts.drag = cfg.drag || true, scope.opts.clone = cfg.clone || '<li/>';
- scope.opts.mode = cfg.mode || 'list';
- scope.opts.itemSelectorFull = $.trim(scope.opts.itemPath + ' ' + scope.opts.itemSelector);
- /*
- scope.opts.isValidTarget = function(item, container) {
- if(container.el.is(".umb-" + scope.opts.group + "-container")){
- return true;
- }
- return false;
- };
- */
- element.addClass('umb-sort');
- element.addClass('umb-' + cfg.group + '-container');
- scope.opts.onDrag = function (item, position) {
- if (scope.opts.mode === 'list') {
- item.css({
- left: position.left - adjustment.left,
- top: position.top - adjustment.top
- });
- }
- };
- scope.opts.onDrop = function (item, targetContainer, _super) {
- if (scope.opts.mode === 'list') {
- //list mode
- var clonedItem = $(scope.opts.clone).css({ height: 0 });
- item.after(clonedItem);
- clonedItem.animate({ 'height': item.height() });
- item.animate(clonedItem.position(), function () {
- clonedItem.detach();
- _super(item);
- });
- }
- var children = $(scope.opts.itemSelectorFull, targetContainer.el);
- var targetIndex = children.index(item);
- var targetScope = $(targetContainer.el[0]).scope();
- if (targetScope === umbSortContextInternal.sourceScope) {
- if (umbSortContextInternal.sourceScope.opts.onSortHandler) {
- var _largs = {
- oldIndex: umbSortContextInternal.sourceIndex,
- newIndex: targetIndex,
- scope: umbSortContextInternal.sourceScope
- };
- umbSortContextInternal.sourceScope.opts.onSortHandler.call(this, item, _largs);
- }
- } else {
- if (targetScope.opts.onDropHandler) {
- var args = {
- sourceScope: umbSortContextInternal.sourceScope,
- sourceIndex: umbSortContextInternal.sourceIndex,
- sourceContainer: umbSortContextInternal.sourceContainer,
- targetScope: targetScope,
- targetIndex: targetIndex,
- targetContainer: targetContainer
- };
- targetScope.opts.onDropHandler.call(this, item, args);
- }
- if (umbSortContextInternal.sourceScope.opts.onReleaseHandler) {
- var _args = {
- sourceScope: umbSortContextInternal.sourceScope,
- sourceIndex: umbSortContextInternal.sourceIndex,
- sourceContainer: umbSortContextInternal.sourceContainer,
- targetScope: targetScope,
- targetIndex: targetIndex,
- targetContainer: targetContainer
- };
- umbSortContextInternal.sourceScope.opts.onReleaseHandler.call(this, item, _args);
- }
- }
- };
- scope.changeIndex = function (from, to) {
- scope.$apply(function () {
- var i = ngModel.$modelValue.splice(from, 1)[0];
- ngModel.$modelValue.splice(to, 0, i);
- });
- };
- scope.move = function (args) {
- var from = args.sourceIndex;
- var to = args.targetIndex;
- if (args.sourceContainer === args.targetContainer) {
- scope.changeIndex(from, to);
- } else {
- scope.$apply(function () {
- var i = args.sourceScope.model.$modelValue.splice(from, 1)[0];
- args.targetScope.model.$modelvalue.splice(to, 0, i);
- });
- }
- };
- scope.opts.onDragStart = function (item, container, _super) {
- var children = $(scope.opts.itemSelectorFull, container.el);
- var offset = item.offset();
- umbSortContextInternal.sourceIndex = children.index(item);
- umbSortContextInternal.sourceScope = $(container.el[0]).scope();
- umbSortContextInternal.sourceContainer = container;
- //current.item = ngModel.$modelValue.splice(current.index, 1)[0];
- var pointer = container.rootGroup.pointer;
- adjustment = {
- left: pointer.left - offset.left,
- top: pointer.top - offset.top
- };
- _super(item, container);
- };
- element.sortable(scope.opts);
- }
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbTabView
- * @deprecated
- * We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
- *
- * @restrict E
- **/
- angular.module('umbraco.directives').directive('umbTabView', function ($timeout, $log) {
- return {
- restrict: 'E',
- replace: true,
- transclude: 'true',
- templateUrl: 'views/directives/_obsolete/umb-tab-view.html'
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbUploadDropzone
- * @deprecated
- * We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
- *
- * @restrict E
- **/
- angular.module('umbraco.directives.html').directive('umbUploadDropzone', function () {
- return {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/directives/_obsolete/umb-upload-dropzone.html'
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:navResize
- * @restrict A
- *
- * @description
- * Handles how the navigation responds to window resizing and controls how the draggable resize panel works
- **/
- angular.module('umbraco.directives').directive('navResize', function (appState, eventsService, windowResizeListener) {
- return {
- restrict: 'A',
- link: function (scope, element, attrs, ctrl) {
- var minScreenSize = 1100;
- var resizeEnabled = false;
- function setTreeMode() {
- appState.setGlobalState('showNavigation', appState.getGlobalState('isTablet') === false);
- }
- function enableResize() {
- //only enable when the size is correct and it's not already enabled
- if (!resizeEnabled && appState.getGlobalState('isTablet') === false) {
- element.resizable({
- containment: $('#mainwrapper'),
- autoHide: true,
- handles: 'e',
- alsoResize: '.navigation-inner-container',
- resize: function (e, ui) {
- var wrapper = $('#mainwrapper');
- var contentPanel = $('#contentwrapper');
- var umbNotification = $('#umb-notifications-wrapper');
- var apps = $('#applications');
- var bottomBar = contentPanel.find('.umb-bottom-bar');
- var navOffeset = $('#navOffset');
- var leftPanelWidth = ui.element.width() + apps.width();
- contentPanel.css({ left: leftPanelWidth });
- bottomBar.css({ left: leftPanelWidth });
- umbNotification.css({ left: leftPanelWidth });
- navOffeset.css({ 'margin-left': ui.element.outerWidth() });
- },
- stop: function (e, ui) {
- }
- });
- resizeEnabled = true;
- }
- }
- function resetResize() {
- if (resizeEnabled) {
- //kill the resize
- element.resizable('destroy');
- element.css('width', '');
- var navInnerContainer = element.find('.navigation-inner-container');
- navInnerContainer.css('width', '');
- $('#contentwrapper').css('left', '');
- $('#umb-notifications-wrapper').css('left', '');
- $('#navOffset').css('margin-left', '');
- resizeEnabled = false;
- }
- }
- var evts = [];
- //Listen for global state changes
- evts.push(eventsService.on('appState.globalState.changed', function (e, args) {
- if (args.key === 'showNavigation') {
- if (args.value === false) {
- resetResize();
- } else {
- enableResize();
- }
- }
- }));
- var resizeCallback = function (size) {
- //set the global app state
- appState.setGlobalState('isTablet', size.width <= minScreenSize);
- setTreeMode();
- };
- windowResizeListener.register(resizeCallback);
- //ensure to unregister from all events and kill jquery plugins
- scope.$on('$destroy', function () {
- windowResizeListener.unregister(resizeCallback);
- for (var e in evts) {
- eventsService.unsubscribe(evts[e]);
- }
- var navInnerContainer = element.find('.navigation-inner-container');
- navInnerContainer.resizable('destroy');
- });
- //init
- //set the global app state
- appState.setGlobalState('isTablet', $(window).width() <= minScreenSize);
- setTreeMode();
- }
- };
- });
- angular.module('umbraco.directives').directive('sectionIcon', function ($compile, iconHelper) {
- return {
- restrict: 'E',
- replace: true,
- link: function (scope, element, attrs) {
- var icon = attrs.icon;
- if (iconHelper.isLegacyIcon(icon)) {
- //its a known legacy icon, convert to a new one
- element.html('<i class=\'' + iconHelper.convertFromLegacyIcon(icon) + '\'></i>');
- } else if (iconHelper.isFileBasedIcon(icon)) {
- var convert = iconHelper.convertFromLegacyImage(icon);
- if (convert) {
- element.html('<i class=\'icon-section ' + convert + '\'></i>');
- } else {
- element.html('<img class=\'icon-section\' src=\'images/tray/' + icon + '\'>');
- } //it's a file, normally legacy so look in the icon tray images
- } else {
- //it's normal
- element.html('<i class=\'icon-section ' + icon + '\'></i>');
- }
- }
- };
- });
- angular.module('umbraco.directives').directive('umbContextMenu', function (navigationService) {
- return {
- scope: {
- menuDialogTitle: '@',
- currentSection: '@',
- currentNode: '=',
- menuActions: '='
- },
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/application/umb-contextmenu.html',
- link: function (scope, element, attrs, ctrl) {
- //adds a handler to the context menu item click, we need to handle this differently
- //depending on what the menu item is supposed to do.
- scope.executeMenuItem = function (action) {
- navigationService.executeMenuAction(action, scope.currentNode, scope.currentSection);
- };
- }
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbNavigation
- * @restrict E
- **/
- function umbNavigationDirective() {
- return {
- restrict: 'E',
- // restrict to an element
- replace: true,
- // replace the html element with the template
- templateUrl: 'views/components/application/umb-navigation.html'
- };
- }
- angular.module('umbraco.directives').directive('umbNavigation', umbNavigationDirective);
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbSections
- * @restrict E
- **/
- function sectionsDirective($timeout, $window, navigationService, treeService, sectionService, appState, eventsService, $location, historyService) {
- return {
- restrict: 'E',
- // restrict to an element
- replace: true,
- // replace the html element with the template
- templateUrl: 'views/components/application/umb-sections.html',
- link: function (scope, element, attr, ctrl) {
- //setup scope vars
- scope.maxSections = 7;
- scope.overflowingSections = 0;
- scope.sections = [];
- scope.currentSection = appState.getSectionState('currentSection');
- scope.showTray = false;
- //appState.getGlobalState("showTray");
- scope.stickyNavigation = appState.getGlobalState('stickyNavigation');
- scope.needTray = false;
- scope.trayAnimation = function () {
- if (scope.showTray) {
- return 'slide';
- } else if (scope.showTray === false) {
- return 'slide';
- } else {
- return '';
- }
- };
- function loadSections() {
- sectionService.getSectionsForUser().then(function (result) {
- scope.sections = result;
- calculateHeight();
- });
- }
- function calculateHeight() {
- $timeout(function () {
- //total height minus room for avatar and help icon
- var height = $(window).height() - 200;
- scope.totalSections = scope.sections.length;
- scope.maxSections = Math.floor(height / 70);
- scope.needTray = false;
- if (scope.totalSections > scope.maxSections) {
- scope.needTray = true;
- scope.overflowingSections = scope.maxSections - scope.totalSections;
- }
- });
- }
- var evts = [];
- //Listen for global state changes
- evts.push(eventsService.on('appState.globalState.changed', function (e, args) {
- if (args.key === 'showTray') {
- scope.showTray = args.value;
- }
- if (args.key === 'stickyNavigation') {
- scope.stickyNavigation = args.value;
- }
- }));
- evts.push(eventsService.on('appState.sectionState.changed', function (e, args) {
- if (args.key === 'currentSection') {
- scope.currentSection = args.value;
- }
- }));
- evts.push(eventsService.on('app.reInitialize', function (e, args) {
- //re-load the sections if we're re-initializing (i.e. package installed)
- loadSections();
- }));
- //ensure to unregister from all events!
- scope.$on('$destroy', function () {
- for (var e in evts) {
- eventsService.unsubscribe(evts[e]);
- }
- });
- //on page resize
- window.onresize = calculateHeight;
- scope.avatarClick = function () {
- if (scope.helpDialog) {
- closeHelpDialog();
- }
- if (!scope.userDialog) {
- scope.userDialog = {
- view: 'user',
- show: true,
- close: function (oldModel) {
- closeUserDialog();
- }
- };
- } else {
- closeUserDialog();
- }
- };
- function closeUserDialog() {
- scope.userDialog.show = false;
- scope.userDialog = null;
- }
- scope.helpClick = function () {
- if (scope.userDialog) {
- closeUserDialog();
- }
- if (!scope.helpDialog) {
- scope.helpDialog = {
- view: 'help',
- show: true,
- close: function (oldModel) {
- closeHelpDialog();
- }
- };
- } else {
- closeHelpDialog();
- }
- };
- function closeHelpDialog() {
- scope.helpDialog.show = false;
- scope.helpDialog = null;
- }
- scope.sectionClick = function (event, section) {
- if (event.ctrlKey || event.shiftKey || event.metaKey || event.button && event.button === 1 // middle click, >IE9 + everyone else
- ) {
- return;
- }
- if (scope.userDialog) {
- closeUserDialog();
- }
- if (scope.helpDialog) {
- closeHelpDialog();
- }
- navigationService.hideSearch();
- navigationService.showTree(section.alias);
- //in some cases the section will have a custom route path specified, if there is one we'll use it
- if (section.routePath) {
- $location.path(section.routePath);
- } else {
- var lastAccessed = historyService.getLastAccessedItemForSection(section.alias);
- var path = lastAccessed != null ? lastAccessed.link : section.alias;
- $location.path(path).search('');
- }
- };
- scope.sectionDblClick = function (section) {
- navigationService.reloadSection(section.alias);
- };
- scope.trayClick = function () {
- // close dialogs
- if (scope.userDialog) {
- closeUserDialog();
- }
- if (scope.helpDialog) {
- closeHelpDialog();
- }
- if (appState.getGlobalState('showTray') === true) {
- navigationService.hideTray();
- } else {
- navigationService.showTray();
- }
- };
- loadSections();
- }
- };
- }
- angular.module('umbraco.directives').directive('umbSections', sectionsDirective);
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbButton
- @restrict E
- @scope
- @description
- Use this directive to render an umbraco button. The directive can be used to generate all types of buttons, set type, style, translation, shortcut and much more.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.Controller as vm">
- <umb-button
- action="vm.clickButton()"
- type="button"
- button-style="success"
- state="vm.buttonState"
- shortcut="ctrl+c"
- label="My button"
- disabled="vm.buttonState === 'busy'">
- </umb-button>
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
- function Controller(myService) {
- var vm = this;
- vm.buttonState = "init";
- vm.clickButton = clickButton;
- function clickButton() {
- vm.buttonState = "busy";
- myService.clickButton().then(function() {
- vm.buttonState = "success";
- }, function() {
- vm.buttonState = "error";
- });
- }
- }
- angular.module("umbraco").controller("My.Controller", Controller);
- })();
- </pre>
- @param {callback} action The button action which should be performed when the button is clicked.
- @param {string=} href Url/Path to navigato to.
- @param {string=} type Set the button type ("button" or "submit").
- @param {string=} buttonStyle Set the style of the button. The directive uses the default bootstrap styles ("primary", "info", "success", "warning", "danger", "inverse", "link", "block"). Pass in array to add multple styles [success,block].
- @param {string=} state Set a progress state on the button ("init", "busy", "success", "error").
- @param {string=} shortcut Set a keyboard shortcut for the button ("ctrl+c").
- @param {string=} label Set the button label.
- @param {string=} labelKey Set a localization key to make a multi lingual button ("general_buttonText").
- @param {string=} icon Set a button icon.
- @param {string=} size Set a button icon ("xs", "m", "l", "xl").
- @param {boolean=} disabled Set to <code>true</code> to disable the button.
- **/
- (function () {
- 'use strict';
- function ButtonDirective($timeout) {
- function link(scope, el, attr, ctrl) {
- scope.style = null;
- function activate() {
- scope.blockElement = false;
- if (!scope.state) {
- scope.state = 'init';
- }
- if (scope.buttonStyle) {
- // make it possible to pass in multiple styles
- if (scope.buttonStyle.startsWith('[') && scope.buttonStyle.endsWith(']')) {
- // when using an attr it will always be a string so we need to remove square brackets
- // and turn it into and array
- var withoutBrackets = scope.buttonStyle.replace(/[\[\]']+/g, '');
- // split array by , + make sure to catch whitespaces
- var array = withoutBrackets.split(/\s?,\s?/g);
- angular.forEach(array, function (item) {
- scope.style = scope.style + ' ' + 'btn-' + item;
- if (item === 'block') {
- scope.blockElement = true;
- }
- });
- } else {
- scope.style = 'btn-' + scope.buttonStyle;
- if (scope.buttonStyle === 'block') {
- scope.blockElement = true;
- }
- }
- }
- }
- activate();
- var unbindStateWatcher = scope.$watch('state', function (newValue, oldValue) {
- if (newValue === 'success' || newValue === 'error') {
- $timeout(function () {
- scope.state = 'init';
- }, 2000);
- }
- });
- scope.$on('$destroy', function () {
- unbindStateWatcher();
- });
- }
- var directive = {
- transclude: true,
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/buttons/umb-button.html',
- link: link,
- scope: {
- action: '&?',
- href: '@?',
- type: '@',
- buttonStyle: '@?',
- state: '=?',
- shortcut: '@?',
- shortcutWhenHidden: '@',
- label: '@?',
- labelKey: '@?',
- icon: '@?',
- disabled: '=',
- size: '@?'
- }
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbButton', ButtonDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbButtonGroup
- @restrict E
- @scope
- @description
- Use this directive to render a button with a dropdown of alternative actions.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.Controller as vm">
- <umb-button-group
- ng-if="vm.buttonGroup"
- default-button="vm.buttonGroup.defaultButton"
- sub-buttons="vm.buttonGroup.subButtons"
- direction="down"
- float="right">
- </umb-button-group>
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
- function Controller() {
- var vm = this;
- vm.buttonGroup = {
- defaultButton: {
- labelKey: "general_defaultButton",
- hotKey: "ctrl+d",
- hotKeyWhenHidden: true,
- handler: function() {
- // do magic here
- }
- },
- subButtons: [
- {
- labelKey: "general_subButton",
- hotKey: "ctrl+b",
- hotKeyWhenHidden: true,
- handler: function() {
- // do magic here
- }
- }
- ]
- };
- }
- angular.module("umbraco").controller("My.Controller", Controller);
- })();
- </pre>
- <h3>Button model description</h3>
- <ul>
- <li>
- <strong>labekKey</strong>
- <small>(string)</small> -
- Set a localization key to make a multi lingual button ("general_buttonText").
- </li>
- <li>
- <strong>hotKey</strong>
- <small>(array)</small> -
- Set a keyboard shortcut for the button ("ctrl+c").
- </li>
- <li>
- <strong>hotKeyWhenHidden</strong>
- <small>(boolean)</small> -
- As a default the hotkeys only works on elements visible in the UI. Set to <code>true</code> to set a hotkey on the hidden sub buttons.
- </li>
- <li>
- <strong>handler</strong>
- <small>(callback)</small> -
- Set a callback to handle button click events.
- </li>
- </ul>
- @param {object} defaultButton The model of the default button.
- @param {array} subButtons Array of sub buttons.
- @param {string=} state Set a progress state on the button ("init", "busy", "success", "error").
- @param {string=} direction Set the direction of the dropdown ("up", "down").
- @param {string=} float Set the float of the dropdown. ("left", "right").
- **/
- (function () {
- 'use strict';
- function ButtonGroupDirective() {
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/buttons/umb-button-group.html',
- scope: {
- defaultButton: '=',
- subButtons: '=',
- state: '=?',
- direction: '@?',
- float: '@?'
- }
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbButtonGroup', ButtonGroupDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbToggle
- @restrict E
- @scope
- @description
- <b>Added in Umbraco version 7.7.0</b> Use this directive to render an umbraco toggle.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.Controller as vm">
- <umb-toggle
- checked="vm.checked"
- on-click="vm.toggle()">
- </umb-toggle>
- <umb-toggle
- checked="vm.checked"
- on-click="vm.toggle()"
- show-labels="true"
- label-on="Start"
- label-off="Stop"
- label-position="right"
- hide-icons="true">
- </umb-toggle>
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
- function Controller() {
- var vm = this;
- vm.checked = false;
- vm.toggle = toggle;
- function toggle() {
- vm.checked = !vm.checked;
- }
- }
- angular.module("umbraco").controller("My.Controller", Controller);
- })();
- </pre>
- @param {boolean} checked Set to <code>true</code> or <code>false</code> to toggle the switch.
- @param {callback} onClick The function which should be called when the toggle is clicked.
- @param {string=} showLabels Set to <code>true</code> or <code>false</code> to show a "On" or "Off" label next to the switch.
- @param {string=} labelOn Set a custom label for when the switched is turned on. It will default to "On".
- @param {string=} labelOff Set a custom label for when the switched is turned off. It will default to "Off".
- @param {string=} labelPosition Sets the label position to the left or right of the switch. It will default to "left" ("left", "right").
- @param {string=} hideIcons Set to <code>true</code> or <code>false</code> to hide the icons on the switch.
- **/
- (function () {
- 'use strict';
- function ToggleDirective(localizationService) {
- function link(scope, el, attr, ctrl) {
- scope.displayLabelOn = '';
- scope.displayLabelOff = '';
- function onInit() {
- setLabelText();
- }
- function setLabelText() {
- // set default label for "on"
- if (scope.labelOn) {
- scope.displayLabelOn = scope.labelOn;
- } else {
- localizationService.localize('general_on').then(function (value) {
- scope.displayLabelOn = value;
- });
- }
- // set default label for "Off"
- if (scope.labelOff) {
- scope.displayLabelOff = scope.labelOff;
- } else {
- localizationService.localize('general_off').then(function (value) {
- scope.displayLabelOff = value;
- });
- }
- }
- scope.click = function () {
- if (scope.onClick) {
- scope.onClick();
- }
- };
- onInit();
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/buttons/umb-toggle.html',
- scope: {
- checked: '=',
- onClick: '&',
- labelOn: '@?',
- labelOff: '@?',
- labelPosition: '@?',
- showLabels: '@?',
- hideIcons: '@?'
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbToggle', ToggleDirective);
- }());
- (function () {
- 'use strict';
- function ContentEditController($rootScope, $scope, $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.page = {};
- $scope.page.loading = false;
- $scope.page.menu = {};
- $scope.page.menu.currentNode = null;
- $scope.page.menu.currentSection = appState.getSectionState('currentSection');
- $scope.page.listViewPath = null;
- $scope.page.isNew = $scope.isNew ? true : false;
- $scope.page.buttonGroupState = 'init';
- function init(content) {
- var buttons = contentEditingHelper.configureContentEditorButtons({
- create: $scope.page.isNew,
- 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 (!$scope.page.isNew) {
- 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: $scope.treeAlias,
- path: path.split(','),
- forceReload: initialLoad !== true
- }).then(function (syncArgs) {
- $scope.page.menu.currentNode = syncArgs.node;
- });
- } else if (initialLoad === true) {
- //it's a child item, just sync the ui node to the parent
- navigationService.syncTree({
- tree: $scope.treeAlias,
- 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.page.menu.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();
- $scope.page.buttonGroupState = 'busy';
- contentEditingHelper.contentEditorPerformSave({
- statusMessage: args.statusMessage,
- saveMethod: args.saveMethod,
- scope: $scope,
- content: $scope.content,
- action: args.action
- }).then(function (data) {
- //success
- init($scope.content);
- syncTreeNode($scope.content, data.path);
- $scope.page.buttonGroupState = 'success';
- deferred.resolve(data);
- }, function (err) {
- //error
- if (err) {
- editorState.set($scope.content);
- }
- $scope.page.buttonGroupState = 'error';
- 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 ($scope.page.isNew) {
- $scope.page.loading = true;
- //we are creating so get an empty content item
- $scope.getScaffoldMethod()().then(function (data) {
- $scope.content = data;
- init($scope.content);
- resetLastListPageNumber($scope.content);
- $scope.page.loading = false;
- });
- } else {
- $scope.page.loading = true;
- //we are editing so get the content item from the server
- $scope.getMethod()($scope.contentId).then(function (data) {
- $scope.content = data;
- if (data.isChildOfListView && data.trashed === false) {
- $scope.page.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.page.loading = false;
- });
- }
- $scope.unPublish = function () {
- if (formHelper.submitForm({
- scope: $scope,
- statusMessage: 'Unpublishing...',
- skipValidation: true
- })) {
- $scope.page.buttonGroupState = 'busy';
- 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.page.buttonGroupState = 'success';
- }, function (err) {
- $scope.page.buttonGroupState = 'error';
- });
- }
- };
- $scope.sendToPublish = function () {
- return performSave({
- saveMethod: contentResource.sendToPublish,
- statusMessage: 'Sending...',
- action: 'sendToPublish'
- });
- };
- $scope.saveAndPublish = function () {
- return performSave({
- saveMethod: contentResource.publish,
- statusMessage: 'Publishing...',
- action: 'publish'
- });
- };
- $scope.save = function () {
- return performSave({
- saveMethod: $scope.saveMethod(),
- statusMessage: 'Saving...',
- action: 'save'
- });
- };
- $scope.preview = function (content) {
- if (!$scope.busy) {
- // Chromes popup blocker will kick in if a window is opened
- // without the initial scoped request. This trick will fix that.
- //
- var previewWindow = $window.open('preview/?init=true&id=' + content.id, 'umbpreview');
- // Build the correct path so both /#/ and #/ work.
- var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?id=' + content.id;
- //The user cannot save if they don't have access to do that, in which case we just want to preview
- //and that's it otherwise they'll get an unauthorized access message
- if (!_.contains(content.allowedActions, 'A')) {
- previewWindow.location.href = redirect;
- } else {
- $scope.save().then(function (data) {
- previewWindow.location.href = redirect;
- });
- }
- }
- };
- }
- function createDirective() {
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/content/edit.html',
- controller: 'Umbraco.Editors.Content.EditorDirectiveController',
- scope: {
- contentId: '=',
- isNew: '=?',
- treeAlias: '@',
- page: '=?',
- saveMethod: '&',
- getMethod: '&',
- getScaffoldMethod: '&?'
- }
- };
- return directive;
- }
- angular.module('umbraco.directives').controller('Umbraco.Editors.Content.EditorDirectiveController', ContentEditController);
- angular.module('umbraco.directives').directive('contentEditor', createDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbEditorSubHeader
- @restrict E
- @description
- Use this directive to construct a sub header in the main editor window.
- The sub header is sticky and will follow along down the page when scrolling.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="MySection.Controller as vm">
- <form name="mySectionForm" novalidate>
- <umb-editor-view>
- <umb-editor-container>
- <umb-editor-sub-header>
- // sub header content here
- </umb-editor-sub-header>
- </umb-editor-container>
- </umb-editor-view>
- </form>
- </div>
- </pre>
- <h3>Use in combination with</h3>
- <ul>
- <li>{@link umbraco.directives.directive:umbEditorSubHeaderContentLeft umbEditorSubHeaderContentLeft}</li>
- <li>{@link umbraco.directives.directive:umbEditorSubHeaderContentRight umbEditorSubHeaderContentRight}</li>
- <li>{@link umbraco.directives.directive:umbEditorSubHeaderSection umbEditorSubHeaderSection}</li>
- </ul>
- **/
- (function () {
- 'use strict';
- function EditorSubHeaderDirective() {
- var directive = {
- transclude: true,
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/editor/subheader/umb-editor-sub-header.html'
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbEditorSubHeader', EditorSubHeaderDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbEditorSubHeaderContentLeft
- @restrict E
- @description
- Use this directive to left align content in a sub header in the main editor window.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="MySection.Controller as vm">
- <form name="mySectionForm" novalidate>
- <umb-editor-view>
- <umb-editor-container>
- <umb-editor-sub-header>
- <umb-editor-sub-header-content-left>
- // left content here
- </umb-editor-sub-header-content-left>
- <umb-editor-sub-header-content-right>
- // right content here
- </umb-editor-sub-header-content-right>
- </umb-editor-sub-header>
- </umb-editor-container>
- </umb-editor-view>
- </form>
- </div>
- </pre>
- <h3>Use in combination with</h3>
- <ul>
- <li>{@link umbraco.directives.directive:umbEditorSubHeader umbEditorSubHeader}</li>
- <li>{@link umbraco.directives.directive:umbEditorSubHeaderContentRight umbEditorSubHeaderContentRight}</li>
- <li>{@link umbraco.directives.directive:umbEditorSubHeaderSection umbEditorSubHeaderSection}</li>
- </ul>
- **/
- (function () {
- 'use strict';
- function EditorSubHeaderContentLeftDirective() {
- var directive = {
- transclude: true,
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/editor/subheader/umb-editor-sub-header-content-left.html'
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbEditorSubHeaderContentLeft', EditorSubHeaderContentLeftDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbEditorSubHeaderContentRight
- @restrict E
- @description
- Use this directive to rigt align content in a sub header in the main editor window.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="MySection.Controller as vm">
- <form name="mySectionForm" novalidate>
- <umb-editor-view>
- <umb-editor-container>
- <umb-editor-sub-header>
- <umb-editor-sub-header-content-left>
- // left content here
- </umb-editor-sub-header-content-left>
- <umb-editor-sub-header-content-right>
- // right content here
- </umb-editor-sub-header-content-right>
- </umb-editor-sub-header>
- </umb-editor-container>
- </umb-editor-view>
- </form>
- </div>
- </pre>
- <h3>Use in combination with</h3>
- <ul>
- <li>{@link umbraco.directives.directive:umbEditorSubHeader umbEditorSubHeader}</li>
- <li>{@link umbraco.directives.directive:umbEditorSubHeaderContentLeft umbEditorSubHeaderContentLeft}</li>
- <li>{@link umbraco.directives.directive:umbEditorSubHeaderSection umbEditorSubHeaderSection}</li>
- </ul>
- **/
- (function () {
- 'use strict';
- function EditorSubHeaderContentRightDirective() {
- var directive = {
- transclude: true,
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/editor/subheader/umb-editor-sub-header-content-right.html'
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbEditorSubHeaderContentRight', EditorSubHeaderContentRightDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbEditorSubHeaderSection
- @restrict E
- @description
- Use this directive to create sections, divided by borders, in a sub header in the main editor window.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="MySection.Controller as vm">
- <form name="mySectionForm" novalidate>
- <umb-editor-view>
- <umb-editor-container>
- <umb-editor-sub-header>
- <umb-editor-sub-header-content-right>
- <umb-editor-sub-header-section>
- // section content here
- </umb-editor-sub-header-section>
- <umb-editor-sub-header-section>
- // section content here
- </umb-editor-sub-header-section>
- <umb-editor-sub-header-section>
- // section content here
- </umb-editor-sub-header-section>
- </umb-editor-sub-header-content-right>
- </umb-editor-sub-header>
- </umb-editor-container>
- </umb-editor-view>
- </form>
- </div>
- </pre>
- <h3>Use in combination with</h3>
- <ul>
- <li>{@link umbraco.directives.directive:umbEditorSubHeader umbEditorSubHeader}</li>
- <li>{@link umbraco.directives.directive:umbEditorSubHeaderContentLeft umbEditorSubHeaderContentLeft}</li>
- <li>{@link umbraco.directives.directive:umbEditorSubHeaderContentRight umbEditorSubHeaderContentRight}</li>
- </ul>
- **/
- (function () {
- 'use strict';
- function EditorSubHeaderSectionDirective() {
- var directive = {
- transclude: true,
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/editor/subheader/umb-editor-sub-header-section.html'
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbEditorSubHeaderSection', EditorSubHeaderSectionDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbBreadcrumbs
- @restrict E
- @scope
- @description
- Use this directive to generate a list of breadcrumbs.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.Controller as vm">
- <umb-breadcrumbs
- ng-if="vm.ancestors && vm.ancestors.length > 0"
- ancestors="vm.ancestors"
- entity-type="content">
- </umb-breadcrumbs>
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
- function Controller(myService) {
- var vm = this;
- vm.ancestors = [];
- myService.getAncestors().then(function(ancestors){
- vm.ancestors = ancestors;
- });
- }
- angular.module("umbraco").controller("My.Controller", Controller);
- })();
- </pre>
- @param {array} ancestors Array of ancestors
- @param {string} entityType The content entity type (member, media, content).
- @param {callback} Callback when an ancestor is clicked. It will override the default link behaviour.
- **/
- (function () {
- 'use strict';
- function BreadcrumbsDirective() {
- function link(scope, el, attr, ctrl) {
- scope.allowOnOpen = false;
- scope.open = function (ancestor) {
- if (scope.onOpen && scope.allowOnOpen) {
- scope.onOpen({ 'ancestor': ancestor });
- }
- };
- function onInit() {
- if ('onOpen' in attr) {
- scope.allowOnOpen = true;
- }
- }
- onInit();
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/editor/umb-breadcrumbs.html',
- scope: {
- ancestors: '=',
- entityType: '@',
- onOpen: '&'
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbBreadcrumbs', BreadcrumbsDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbEditorContainer
- @restrict E
- @description
- Use this directive to construct a main content area inside the main editor window.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="Umbraco.Controller as vm">
- <umb-editor-view>
- <umb-editor-header
- // header configuration>
- </umb-editor-header>
- <umb-editor-container>
- // main content here
- </umb-editor-container>
- <umb-editor-footer>
- // footer content here
- </umb-editor-footer>
- </umb-editor-view>
- </div>
- </pre>
- <h3>Use in combination with</h3>
- <ul>
- <li>{@link umbraco.directives.directive:umbEditorView umbEditorView}</li>
- <li>{@link umbraco.directives.directive:umbEditorHeader umbEditorHeader}</li>
- <li>{@link umbraco.directives.directive:umbEditorFooter umbEditorFooter}</li>
- </ul>
- **/
- (function () {
- 'use strict';
- function EditorContainerDirective(overlayHelper) {
- function link(scope, el, attr, ctrl) {
- scope.numberOfOverlays = 0;
- scope.$watch(function () {
- return overlayHelper.getNumberOfOverlays();
- }, function (newValue) {
- scope.numberOfOverlays = newValue;
- });
- }
- var directive = {
- transclude: true,
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/editor/umb-editor-container.html',
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbEditorContainer', EditorContainerDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbEditorFooter
- @restrict E
- @description
- Use this directive to construct a footer inside the main editor window.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="MySection.Controller as vm">
- <form name="mySectionForm" novalidate>
- <umb-editor-view>
- <umb-editor-header
- // header configuration>
- </umb-editor-header>
- <umb-editor-container>
- // main content here
- </umb-editor-container>
- <umb-editor-footer>
- // footer content here
- </umb-editor-footer>
- </umb-editor-view>
- </form>
- </div>
- </pre>
- <h3>Use in combination with</h3>
- <ul>
- <li>{@link umbraco.directives.directive:umbEditorView umbEditorView}</li>
- <li>{@link umbraco.directives.directive:umbEditorHeader umbEditorHeader}</li>
- <li>{@link umbraco.directives.directive:umbEditorContainer umbEditorContainer}</li>
- <li>{@link umbraco.directives.directive:umbEditorFooterContentLeft umbEditorFooterContentLeft}</li>
- <li>{@link umbraco.directives.directive:umbEditorFooterContentRight umbEditorFooterContentRight}</li>
- </ul>
- **/
- (function () {
- 'use strict';
- function EditorFooterDirective() {
- var directive = {
- transclude: true,
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/editor/umb-editor-footer.html'
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbEditorFooter', EditorFooterDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbEditorFooterContentLeft
- @restrict E
- @description
- Use this directive to align content left inside the main editor footer.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="MySection.Controller as vm">
- <form name="mySectionForm" novalidate>
- <umb-editor-view>
- <umb-editor-footer>
- <umb-editor-footer-content-left>
- // align content left
- </umb-editor-footer-content-left>
- <umb-editor-footer-content-right>
- // align content right
- </umb-editor-footer-content-right>
- </umb-editor-footer>
- </umb-editor-view>
- </form>
- </div>
- </pre>
- <h3>Use in combination with</h3>
- <ul>
- <li>{@link umbraco.directives.directive:umbEditorView umbEditorView}</li>
- <li>{@link umbraco.directives.directive:umbEditorHeader umbEditorHeader}</li>
- <li>{@link umbraco.directives.directive:umbEditorContainer umbEditorContainer}</li>
- <li>{@link umbraco.directives.directive:umbEditorFooter umbEditorFooter}</li>
- <li>{@link umbraco.directives.directive:umbEditorFooterContentRight umbEditorFooterContentRight}</li>
- </ul>
- **/
- (function () {
- 'use strict';
- function EditorFooterContentLeftDirective() {
- var directive = {
- transclude: true,
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/editor/umb-editor-footer-content-left.html'
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbEditorFooterContentLeft', EditorFooterContentLeftDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbEditorFooterContentRight
- @restrict E
- @description
- Use this directive to align content right inside the main editor footer.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="MySection.Controller as vm">
- <form name="mySectionForm" novalidate>
- <umb-editor-view>
- <umb-editor-footer>
- <umb-editor-footer-content-left>
- // align content left
- </umb-editor-footer-content-left>
- <umb-editor-footer-content-right>
- // align content right
- </umb-editor-footer-content-right>
- </umb-editor-footer>
- </umb-editor-view>
- </form>
- </div>
- </pre>
- <h3>Use in combination with</h3>
- <ul>
- <li>{@link umbraco.directives.directive:umbEditorView umbEditorView}</li>
- <li>{@link umbraco.directives.directive:umbEditorHeader umbEditorHeader}</li>
- <li>{@link umbraco.directives.directive:umbEditorContainer umbEditorContainer}</li>
- <li>{@link umbraco.directives.directive:umbEditorFooter umbEditorFooter}</li>
- <li>{@link umbraco.directives.directive:umbEditorFooterContentLeft umbEditorFooterContentLeft}</li>
- </ul>
- **/
- (function () {
- 'use strict';
- function EditorFooterContentRightDirective() {
- var directive = {
- transclude: true,
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/editor/umb-editor-footer-content-right.html'
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbEditorFooterContentRight', EditorFooterContentRightDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbEditorHeader
- @restrict E
- @scope
- @description
- Use this directive to construct a header inside the main editor window.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="MySection.Controller as vm">
- <form name="mySectionForm" novalidate>
- <umb-editor-view>
- <umb-editor-header
- name="vm.content.name"
- hide-alias="true"
- hide-description="true"
- hide-icon="true">
- </umb-editor-header>
- <umb-editor-container>
- // main content here
- </umb-editor-container>
- <umb-editor-footer>
- // footer content here
- </umb-editor-footer>
- </umb-editor-view>
- </form>
- </div>
- </pre>
- <h3>Markup example - with tabs</h3>
- <pre>
- <div ng-controller="MySection.Controller as vm">
- <form name="mySectionForm" val-form-manager novalidate>
- <umb-editor-view umb-tabs>
- <umb-editor-header
- name="vm.content.name"
- tabs="vm.content.tabs"
- hide-alias="true"
- hide-description="true"
- hide-icon="true">
- </umb-editor-header>
- <umb-editor-container>
- <umb-tabs-content class="form-horizontal" view="true">
- <umb-tab id="tab{{tab.id}}" ng-repeat="tab in vm.content.tabs" rel="{{tab.id}}">
- <div ng-show="tab.alias==='tab1'">
- // tab 1 content
- </div>
- <div ng-show="tab.alias==='tab2'">
- // tab 2 content
- </div>
- </umb-tab>
- </umb-tabs-content>
- </umb-editor-container>
- <umb-editor-footer>
- // footer content here
- </umb-editor-footer>
- </umb-editor-view>
- </form>
- </div>
- </pre>
- <h3>Controller example - with tabs</h3>
- <pre>
- (function () {
- "use strict";
- function Controller() {
- var vm = this;
- vm.content = {
- name: "",
- tabs: [
- {
- id: 1,
- label: "Tab 1",
- alias: "tab1",
- active: true
- },
- {
- id: 2,
- label: "Tab 2",
- alias: "tab2",
- active: false
- }
- ]
- };
- }
- angular.module("umbraco").controller("MySection.Controller", Controller);
- })();
- </pre>
- <h3>Markup example - with sub views</h3>
- <pre>
- <div ng-controller="MySection.Controller as vm">
- <form name="mySectionForm" val-form-manager novalidate>
- <umb-editor-view>
- <umb-editor-header
- name="vm.content.name"
- navigation="vm.content.navigation"
- hide-alias="true"
- hide-description="true"
- hide-icon="true">
- </umb-editor-header>
- <umb-editor-container>
- <umb-editor-sub-views
- sub-views="vm.content.navigation"
- model="vm.content">
- </umb-editor-sub-views>
- </umb-editor-container>
- <umb-editor-footer>
- // footer content here
- </umb-editor-footer>
- </umb-editor-view>
- </form>
- </div>
- </pre>
- <h3>Controller example - with sub views</h3>
- <pre>
- (function () {
- "use strict";
- function Controller() {
- var vm = this;
- vm.content = {
- name: "",
- navigation: [
- {
- "name": "Section 1",
- "icon": "icon-document-dashed-line",
- "view": "/App_Plugins/path/to/html.html",
- "active": true
- },
- {
- "name": "Section 2",
- "icon": "icon-list",
- "view": "/App_Plugins/path/to/html.html",
- }
- ]
- };
- }
- angular.module("umbraco").controller("MySection.Controller", Controller);
- })();
- </pre>
- <h3>Use in combination with</h3>
- <ul>
- <li>{@link umbraco.directives.directive:umbEditorView umbEditorView}</li>
- <li>{@link umbraco.directives.directive:umbEditorContainer umbEditorContainer}</li>
- <li>{@link umbraco.directives.directive:umbEditorFooter umbEditorFooter}</li>
- </ul>
- @param {string} name The content name.
- @param {array=} tabs Array of tabs. See example above.
- @param {array=} navigation Array of sub views. See example above.
- @param {boolean=} nameLocked Set to <code>true</code> to lock the name.
- @param {object=} menu Add a context menu to the editor.
- @param {string=} icon Show and edit the content icon. Opens an overlay to change the icon.
- @param {boolean=} hideIcon Set to <code>true</code> to hide icon.
- @param {string=} alias show and edit the content alias.
- @param {boolean=} hideAlias Set to <code>true</code> to hide alias.
- @param {string=} description Add a description to the content.
- @param {boolean=} hideDescription Set to <code>true</code> to hide description.
- **/
- (function () {
- 'use strict';
- function EditorHeaderDirective(iconHelper) {
- function link(scope, el, attr, ctrl) {
- scope.openIconPicker = function () {
- scope.dialogModel = {
- view: 'iconpicker',
- show: true,
- submit: function (model) {
- /* ensure an icon is selected, because on focus on close button
- or an element in background no icon is submitted. So don't clear/update existing icon/preview.
- */
- if (model.icon) {
- if (model.color) {
- scope.icon = model.icon + ' ' + model.color;
- } else {
- scope.icon = model.icon;
- }
- // set the icon form to dirty
- scope.iconForm.$setDirty();
- }
- scope.dialogModel.show = false;
- scope.dialogModel = null;
- }
- };
- };
- }
- var directive = {
- transclude: true,
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/editor/umb-editor-header.html',
- scope: {
- tabs: '=',
- actions: '=',
- name: '=',
- nameLocked: '=',
- menu: '=',
- icon: '=',
- hideIcon: '@',
- alias: '=',
- hideAlias: '@',
- description: '=',
- hideDescription: '@',
- descriptionLocked: '@',
- navigation: '=',
- key: '='
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbEditorHeader', EditorHeaderDirective);
- }());
- (function () {
- 'use strict';
- function EditorMenuDirective($injector, treeService, navigationService, umbModelMapper, appState) {
- function link(scope, el, attr, ctrl) {
- //adds a handler to the context menu item click, we need to handle this differently
- //depending on what the menu item is supposed to do.
- scope.executeMenuItem = function (action) {
- navigationService.executeMenuAction(action, scope.currentNode, scope.currentSection);
- };
- //callback method to go and get the options async
- scope.getOptions = function () {
- if (!scope.currentNode) {
- return;
- }
- //when the options item is selected, we need to set the current menu item in appState (since this is synonymous with a menu)
- appState.setMenuState('currentNode', scope.currentNode);
- if (!scope.actions) {
- treeService.getMenu({ treeNode: scope.currentNode }).then(function (data) {
- scope.actions = data.menuItems;
- });
- }
- };
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/editor/umb-editor-menu.html',
- link: link,
- scope: {
- currentNode: '=',
- currentSection: '@'
- }
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbEditorMenu', EditorMenuDirective);
- }());
- (function () {
- 'use strict';
- function EditorNavigationDirective() {
- function link(scope, el, attr, ctrl) {
- scope.showNavigation = true;
- scope.clickNavigationItem = function (selectedItem) {
- setItemToActive(selectedItem);
- runItemAction(selectedItem);
- };
- function runItemAction(selectedItem) {
- if (selectedItem.action) {
- selectedItem.action(selectedItem);
- }
- }
- function setItemToActive(selectedItem) {
- // set all other views to inactive
- if (selectedItem.view) {
- for (var index = 0; index < scope.navigation.length; index++) {
- var item = scope.navigation[index];
- item.active = false;
- }
- // set view to active
- selectedItem.active = true;
- }
- }
- function activate() {
- // hide navigation if there is only 1 item
- if (scope.navigation.length <= 1) {
- scope.showNavigation = false;
- }
- }
- activate();
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/editor/umb-editor-navigation.html',
- scope: { navigation: '=' },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives.html').directive('umbEditorNavigation', EditorNavigationDirective);
- }());
- (function () {
- 'use strict';
- function EditorSubViewsDirective() {
- function link(scope, el, attr, ctrl) {
- scope.activeView = {};
- // set toolbar from selected navigation item
- function setActiveView(items) {
- for (var index = 0; index < items.length; index++) {
- var item = items[index];
- if (item.active && item.view) {
- scope.activeView = item;
- }
- }
- }
- // watch for navigation changes
- scope.$watch('subViews', function (newValue, oldValue) {
- if (newValue) {
- setActiveView(newValue);
- }
- }, true);
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/editor/umb-editor-sub-views.html',
- scope: {
- subViews: '=',
- model: '='
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbEditorSubViews', EditorSubViewsDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbEditorView
- @restrict E
- @scope
- @description
- Use this directive to construct the main editor window.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="MySection.Controller as vm">
- <form name="mySectionForm" novalidate>
- <umb-editor-view>
- <umb-editor-header
- name="vm.content.name"
- hide-alias="true"
- hide-description="true"
- hide-icon="true">
- </umb-editor-header>
- <umb-editor-container>
- // main content here
- </umb-editor-container>
- <umb-editor-footer>
- // footer content here
- </umb-editor-footer>
- </umb-editor-view>
- </form>
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
- function Controller() {
- var vm = this;
- }
- angular.module("umbraco").controller("MySection.Controller", Controller);
- })();
- </pre>
- <h3>Use in combination with</h3>
- <ul>
- <li>{@link umbraco.directives.directive:umbEditorHeader umbEditorHeader}</li>
- <li>{@link umbraco.directives.directive:umbEditorContainer umbEditorContainer}</li>
- <li>{@link umbraco.directives.directive:umbEditorFooter umbEditorFooter}</li>
- </ul>
- **/
- (function () {
- 'use strict';
- function EditorViewDirective() {
- function link(scope, el, attr) {
- if (attr.footer) {
- scope.footer = attr.footer;
- }
- }
- var directive = {
- transclude: true,
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/editor/umb-editor-view.html',
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbEditorView', EditorViewDirective);
- }());
- /**
- * @description Utillity directives for key and field events
- **/
- angular.module('umbraco.directives').directive('onKeyup', function () {
- return {
- link: function (scope, elm, attrs) {
- var f = function () {
- scope.$apply(attrs.onKeyup);
- };
- elm.on('keyup', f);
- scope.$on('$destroy', function () {
- elm.off('keyup', f);
- });
- }
- };
- }).directive('onKeydown', function () {
- return {
- link: function (scope, elm, attrs) {
- var f = function () {
- scope.$apply(attrs.onKeydown);
- };
- elm.on('keydown', f);
- scope.$on('$destroy', function () {
- elm.off('keydown', f);
- });
- }
- };
- }).directive('onBlur', function () {
- return {
- link: function (scope, elm, attrs) {
- var f = function () {
- scope.$apply(attrs.onBlur);
- };
- elm.on('blur', f);
- scope.$on('$destroy', function () {
- elm.off('blur', f);
- });
- }
- };
- }).directive('onFocus', function () {
- return {
- link: function (scope, elm, attrs) {
- var f = function () {
- scope.$apply(attrs.onFocus);
- };
- elm.on('focus', f);
- scope.$on('$destroy', function () {
- elm.off('focus', f);
- });
- }
- };
- }).directive('onDragEnter', function () {
- return {
- link: function (scope, elm, attrs) {
- var f = function () {
- scope.$apply(attrs.onDragEnter);
- };
- elm.on('dragenter', f);
- scope.$on('$destroy', function () {
- elm.off('dragenter', f);
- });
- }
- };
- }).directive('onDragLeave', function () {
- return function (scope, elm, attrs) {
- var f = function (event) {
- var rect = this.getBoundingClientRect();
- var getXY = function getCursorPosition(event) {
- var x, y;
- if (typeof event.clientX === 'undefined') {
- // try touch screen
- x = event.pageX + document.documentElement.scrollLeft;
- y = event.pageY + document.documentElement.scrollTop;
- } else {
- x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
- y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
- }
- return {
- x: x,
- y: y
- };
- };
- var e = getXY(event.originalEvent);
- // Check the mouseEvent coordinates are outside of the rectangle
- if (e.x > rect.left + rect.width - 1 || e.x < rect.left || e.y > rect.top + rect.height - 1 || e.y < rect.top) {
- scope.$apply(attrs.onDragLeave);
- }
- };
- elm.on('dragleave', f);
- scope.$on('$destroy', function () {
- elm.off('dragleave', f);
- });
- };
- }).directive('onDragOver', function () {
- return {
- link: function (scope, elm, attrs) {
- var f = function () {
- scope.$apply(attrs.onDragOver);
- };
- elm.on('dragover', f);
- scope.$on('$destroy', function () {
- elm.off('dragover', f);
- });
- }
- };
- }).directive('onDragStart', function () {
- return {
- link: function (scope, elm, attrs) {
- var f = function () {
- scope.$apply(attrs.onDragStart);
- };
- elm.on('dragstart', f);
- scope.$on('$destroy', function () {
- elm.off('dragstart', f);
- });
- }
- };
- }).directive('onDragEnd', function () {
- return {
- link: function (scope, elm, attrs) {
- var f = function () {
- scope.$apply(attrs.onDragEnd);
- };
- elm.on('dragend', f);
- scope.$on('$destroy', function () {
- elm.off('dragend', f);
- });
- }
- };
- }).directive('onDrop', function () {
- return {
- link: function (scope, elm, attrs) {
- var f = function () {
- scope.$apply(attrs.onDrop);
- };
- elm.on('drop', f);
- scope.$on('$destroy', function () {
- elm.off('drop', f);
- });
- }
- };
- }).directive('onOutsideClick', function ($timeout) {
- return function (scope, element, attrs) {
- var eventBindings = [];
- function oneTimeClick(event) {
- var el = event.target.nodeName;
- //ignore link and button clicks
- var els = [
- 'INPUT',
- 'A',
- 'BUTTON'
- ];
- if (els.indexOf(el) >= 0) {
- return;
- }
- // ignore clicks on new overlay
- var parents = $(event.target).parents('a,button,.umb-overlay');
- if (parents.length > 0) {
- return;
- }
- // ignore clicks on dialog from old dialog service
- var oldDialog = $(event.target).parents('#old-dialog-service');
- if (oldDialog.length === 1) {
- return;
- }
- // ignore clicks in tinyMCE dropdown(floatpanel)
- var floatpanel = $(event.target).closest('.mce-floatpanel');
- if (floatpanel.length === 1) {
- return;
- }
- //ignore clicks inside this element
- if ($(element).has($(event.target)).length > 0) {
- return;
- }
- scope.$apply(attrs.onOutsideClick);
- }
- $timeout(function () {
- if ('bindClickOn' in attrs) {
- eventBindings.push(scope.$watch(function () {
- return attrs.bindClickOn;
- }, function (newValue) {
- if (newValue === 'true') {
- $(document).on('click', oneTimeClick);
- } else {
- $(document).off('click', oneTimeClick);
- }
- }));
- } else {
- $(document).on('click', oneTimeClick);
- }
- scope.$on('$destroy', function () {
- $(document).off('click', oneTimeClick);
- // unbind watchers
- for (var e in eventBindings) {
- eventBindings[e]();
- }
- });
- }); // Temp removal of 1 sec timeout to prevent bug where overlay does not open. We need to find a better solution.
- };
- }).directive('onRightClick', function () {
- document.oncontextmenu = function (e) {
- if (e.target.hasAttribute('on-right-click')) {
- e.preventDefault();
- e.stopPropagation();
- return false;
- }
- };
- return function (scope, el, attrs) {
- el.on('contextmenu', function (e) {
- e.preventDefault();
- e.stopPropagation();
- scope.$apply(attrs.onRightClick);
- return false;
- });
- };
- }).directive('onDelayedMouseleave', function ($timeout, $parse) {
- return {
- restrict: 'A',
- link: function (scope, element, attrs, ctrl) {
- var active = false;
- var fn = $parse(attrs.onDelayedMouseleave);
- var leave_f = function (event) {
- var callback = function () {
- fn(scope, { $event: event });
- };
- active = false;
- $timeout(function () {
- if (active === false) {
- scope.$apply(callback);
- }
- }, 650);
- };
- var enter_f = function (event, args) {
- active = true;
- };
- element.on('mouseleave', leave_f);
- element.on('mouseenter', enter_f);
- //unsub events
- scope.$on('$destroy', function () {
- element.off('mouseleave', leave_f);
- element.off('mouseenter', enter_f);
- });
- }
- };
- });
- /*
-
- http://vitalets.github.io/checklist-model/
- <label ng-repeat="role in roles">
- <input type="checkbox" checklist-model="user.roles" checklist-value="role.id"> {{role.text}}
- </label>
- */
- angular.module('umbraco.directives').directive('checklistModel', [
- '$parse',
- '$compile',
- function ($parse, $compile) {
- // contains
- function contains(arr, item) {
- if (angular.isArray(arr)) {
- for (var i = 0; i < arr.length; i++) {
- if (angular.equals(arr[i], item)) {
- return true;
- }
- }
- }
- return false;
- }
- // add
- function add(arr, item) {
- arr = angular.isArray(arr) ? arr : [];
- for (var i = 0; i < arr.length; i++) {
- if (angular.equals(arr[i], item)) {
- return arr;
- }
- }
- arr.push(item);
- return arr;
- }
- // remove
- function remove(arr, item) {
- if (angular.isArray(arr)) {
- for (var i = 0; i < arr.length; i++) {
- if (angular.equals(arr[i], item)) {
- arr.splice(i, 1);
- break;
- }
- }
- }
- return arr;
- }
- // http://stackoverflow.com/a/19228302/1458162
- function postLinkFn(scope, elem, attrs) {
- // compile with `ng-model` pointing to `checked`
- $compile(elem)(scope);
- // getter / setter for original model
- var getter = $parse(attrs.checklistModel);
- var setter = getter.assign;
- // value added to list
- var value = $parse(attrs.checklistValue)(scope.$parent);
- // watch UI checked change
- scope.$watch('checked', function (newValue, oldValue) {
- if (newValue === oldValue) {
- return;
- }
- var current = getter(scope.$parent);
- if (newValue === true) {
- setter(scope.$parent, add(current, value));
- } else {
- setter(scope.$parent, remove(current, value));
- }
- });
- // watch original model change
- scope.$parent.$watch(attrs.checklistModel, function (newArr, oldArr) {
- scope.checked = contains(newArr, value);
- }, true);
- }
- return {
- restrict: 'A',
- priority: 1000,
- terminal: true,
- scope: true,
- compile: function (tElement, tAttrs) {
- if (tElement[0].tagName !== 'INPUT' || !tElement.attr('type', 'checkbox')) {
- throw 'checklist-model should be applied to `input[type="checkbox"]`.';
- }
- if (!tAttrs.checklistValue) {
- throw 'You should provide `checklist-value`.';
- }
- // exclude recursion
- tElement.removeAttr('checklist-model');
- // local scope var storing individual checkbox model
- tElement.attr('ng-model', 'checked');
- return postLinkFn;
- }
- };
- }
- ]);
- angular.module('umbraco.directives').directive('contenteditable', function () {
- return {
- require: 'ngModel',
- link: function (scope, element, attrs, ngModel) {
- function read() {
- ngModel.$setViewValue(element.html());
- }
- ngModel.$render = function () {
- element.html(ngModel.$viewValue || '');
- };
- element.bind('focus', function () {
- var range = document.createRange();
- range.selectNodeContents(element[0]);
- var sel = window.getSelection();
- sel.removeAllRanges();
- sel.addRange(range);
- });
- element.bind('blur keyup change', function () {
- scope.$apply(read);
- });
- }
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:fixNumber
- * @restrict A
- * @description Used in conjunction with type='number' input fields to ensure that the bound value is converted to a number when using ng-model
- * because normally it thinks it's a string and also validation doesn't work correctly due to an angular bug.
- **/
- function fixNumber($parse) {
- return {
- restrict: 'A',
- require: 'ngModel',
- link: function (scope, elem, attrs, ctrl) {
- //parse ngModel onload
- var modelVal = scope.$eval(attrs.ngModel);
- if (modelVal) {
- var asNum = parseFloat(modelVal, 10);
- if (!isNaN(asNum)) {
- $parse(attrs.ngModel).assign(scope, asNum);
- }
- }
- //always return an int to the model
- ctrl.$parsers.push(function (value) {
- if (value === 0) {
- return 0;
- }
- return parseFloat(value || '', 10);
- });
- //always try to format the model value as an int
- ctrl.$formatters.push(function (value) {
- if (angular.isString(value)) {
- return parseFloat(value, 10);
- }
- return value;
- });
- //This fixes this angular issue:
- //https://github.com/angular/angular.js/issues/2144
- // which doesn't actually validate the number input properly since the model only changes when a real number is entered
- // but the input box still allows non-numbers to be entered which do not validate (only via html5)
- if (typeof elem.prop('validity') === 'undefined') {
- return;
- }
- elem.bind('input', function (e) {
- var validity = elem.prop('validity');
- scope.$apply(function () {
- ctrl.$setValidity('number', !validity.badInput);
- });
- });
- }
- };
- }
- angular.module('umbraco.directives').directive('fixNumber', fixNumber);
- angular.module('umbraco.directives').directive('focusWhen', function ($timeout) {
- return {
- restrict: 'A',
- link: function (scope, elm, attrs, ctrl) {
- attrs.$observe('focusWhen', function (newValue) {
- if (newValue === 'true') {
- $timeout(function () {
- elm.focus();
- });
- }
- });
- }
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:hexBgColor
- * @restrict A
- * @description Used to set a hex background color on an element, this will detect valid hex and when it is valid it will set the color, otherwise
- * a color will not be set.
- **/
- function hexBgColor() {
- return {
- restrict: 'A',
- link: function (scope, element, attr, formCtrl) {
- var origColor = null;
- if (attr.hexBgOrig) {
- //set the orig based on the attribute if there is one
- origColor = attr.hexBgOrig;
- }
- attr.$observe('hexBgColor', function (newVal) {
- if (newVal) {
- if (!origColor) {
- //get the orig color before changing it
- origColor = element.css('border-color');
- }
- //validate it - test with and without the leading hash.
- if (/^([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) {
- element.css('background-color', '#' + newVal);
- return;
- }
- if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) {
- element.css('background-color', newVal);
- return;
- }
- }
- element.css('background-color', origColor);
- });
- }
- };
- }
- angular.module('umbraco.directives').directive('hexBgColor', hexBgColor);
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:hotkey
- **/
- angular.module('umbraco.directives').directive('hotkey', function ($window, keyboardService, $log) {
- return function (scope, el, attrs) {
- var options = {};
- var keyCombo = attrs.hotkey;
- if (!keyCombo) {
- //support data binding
- keyCombo = scope.$eval(attrs['hotkey']);
- }
- function activate() {
- if (keyCombo) {
- // disable shortcuts in input fields if keycombo is 1 character
- if (keyCombo.length === 1) {
- options = { inputDisabled: true };
- }
- keyboardService.bind(keyCombo, function () {
- var element = $(el);
- var activeElementType = document.activeElement.tagName;
- var clickableElements = [
- 'A',
- 'BUTTON'
- ];
- if (element.is('a,div,button,input[type=\'button\'],input[type=\'submit\'],input[type=\'checkbox\']') && !element.is(':disabled')) {
- if (element.is(':visible') || attrs.hotkeyWhenHidden) {
- if (attrs.hotkeyWhen && attrs.hotkeyWhen === 'false') {
- return;
- }
- // when keycombo is enter and a link or button has focus - click the link or button instead of using the hotkey
- if (keyCombo === 'enter' && clickableElements.indexOf(activeElementType) === 0) {
- document.activeElement.click();
- } else {
- element.click();
- }
- }
- } else {
- element.focus();
- }
- }, options);
- el.on('$destroy', function () {
- keyboardService.unbind(keyCombo);
- });
- }
- }
- activate();
- };
- });
- /**
- @ngdoc directive
- @name umbraco.directives.directive:preventDefault
- @description
- Use this directive to prevent default action of an element. Effectively implementing <a href="https://api.jquery.com/event.preventdefault/">jQuery's preventdefault</a>
- <h3>Markup example</h3>
- <pre>
- <a href="https://umbraco.com" prevent-default>Don't go to Umbraco.com</a>
- </pre>
- **/
- angular.module('umbraco.directives').directive('preventDefault', function () {
- return function (scope, element, attrs) {
- var enabled = true;
- //check if there's a value for the attribute, if there is and it's false then we conditionally don't
- //prevent default.
- if (attrs.preventDefault) {
- attrs.$observe('preventDefault', function (newVal) {
- enabled = newVal === 'false' || newVal === 0 || newVal === false ? false : true;
- });
- }
- $(element).click(function (event) {
- if (event.metaKey || event.ctrlKey) {
- return;
- } else {
- if (enabled === true) {
- event.preventDefault();
- }
- }
- });
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:preventEnterSubmit
- * @description prevents a form from submitting when the enter key is pressed on an input field
- **/
- angular.module('umbraco.directives').directive('preventEnterSubmit', function () {
- return function (scope, element, attrs) {
- var enabled = true;
- //check if there's a value for the attribute, if there is and it's false then we conditionally don't
- //prevent default.
- if (attrs.preventEnterSubmit) {
- attrs.$observe('preventEnterSubmit', function (newVal) {
- enabled = newVal === 'false' || newVal === 0 || newVal === false ? false : true;
- });
- }
- $(element).keypress(function (event) {
- if (event.which === 13) {
- event.preventDefault();
- }
- });
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:resizeToContent
- * @element div
- * @function
- *
- * @description
- * Resize iframe's automatically to fit to the content they contain
- *
- * @example
- <example module="umbraco.directives">
- <file name="index.html">
- <iframe resize-to-content src="meh.html"></iframe>
- </file>
- </example>
- */
- angular.module('umbraco.directives').directive('resizeToContent', function ($window, $timeout) {
- return function (scope, el, attrs) {
- var iframe = el[0];
- var iframeWin = iframe.contentWindow || iframe.contentDocument.parentWindow;
- if (iframeWin.document.body) {
- $timeout(function () {
- var height = iframeWin.document.documentElement.scrollHeight || iframeWin.document.body.scrollHeight;
- el.height(height);
- }, 3000);
- }
- };
- });
- angular.module('umbraco.directives').directive('selectOnFocus', function () {
- return function (scope, el, attrs) {
- $(el).bind('click', function () {
- var editmode = $(el).data('editmode');
- //If editmode is true a click is handled like a normal click
- if (!editmode) {
- //Initial click, select entire text
- this.select();
- //Set the edit mode so subsequent clicks work normally
- $(el).data('editmode', true);
- }
- }).bind('blur', function () {
- //Reset on focus lost
- $(el).data('editmode', false);
- });
- };
- });
- angular.module('umbraco.directives').directive('umbAutoFocus', function ($timeout) {
- return function (scope, element, attr) {
- var update = function () {
- //if it uses its default naming
- if (element.val() === '' || attr.focusOnFilled) {
- element.focus();
- }
- };
- $timeout(function () {
- update();
- });
- };
- });
- angular.module('umbraco.directives').directive('umbAutoResize', function ($timeout) {
- return {
- require: [
- '^?umbTabs',
- 'ngModel'
- ],
- link: function (scope, element, attr, controllersArr) {
- var domEl = element[0];
- var domElType = domEl.type;
- var umbTabsController = controllersArr[0];
- var ngModelController = controllersArr[1];
- // IE elements
- var isIEFlag = false;
- var wrapper = angular.element('#umb-ie-resize-input-wrapper');
- var mirror = angular.element('<span style="white-space:pre;"></span>');
- function isIE() {
- var ua = window.navigator.userAgent;
- var msie = ua.indexOf('MSIE ');
- if (msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./) || navigator.userAgent.match(/Edge\/\d+/)) {
- return true;
- } else {
- return false;
- }
- }
- function activate() {
- // check if browser is Internet Explorere
- isIEFlag = isIE();
- // scrollWidth on element does not work in IE on inputs
- // we have to do some dirty dom element copying.
- if (isIEFlag === true && domElType === 'text') {
- setupInternetExplorerElements();
- }
- }
- function setupInternetExplorerElements() {
- if (!wrapper.length) {
- wrapper = angular.element('<div id="umb-ie-resize-input-wrapper" style="position:fixed; top:-999px; left:0;"></div>');
- angular.element('body').append(wrapper);
- }
- angular.forEach([
- 'fontFamily',
- 'fontSize',
- 'fontWeight',
- 'fontStyle',
- 'letterSpacing',
- 'textTransform',
- 'wordSpacing',
- 'textIndent',
- 'boxSizing',
- 'borderRightWidth',
- 'borderLeftWidth',
- 'borderLeftStyle',
- 'borderRightStyle',
- 'paddingLeft',
- 'paddingRight',
- 'marginLeft',
- 'marginRight'
- ], function (value) {
- mirror.css(value, element.css(value));
- });
- wrapper.append(mirror);
- }
- function resizeInternetExplorerInput() {
- mirror.text(element.val() || attr.placeholder);
- element.css('width', mirror.outerWidth() + 1);
- }
- function resizeInput() {
- if (domEl.scrollWidth !== domEl.clientWidth) {
- if (ngModelController.$modelValue) {
- element.width(domEl.scrollWidth);
- }
- }
- if (!ngModelController.$modelValue && attr.placeholder) {
- attr.$set('size', attr.placeholder.length);
- element.width('auto');
- }
- }
- function resizeTextarea() {
- if (domEl.scrollHeight !== domEl.clientHeight) {
- element.height(domEl.scrollHeight);
- }
- }
- var update = function (force) {
- if (force === true) {
- if (domElType === 'textarea') {
- element.height(0);
- } else if (domElType === 'text') {
- element.width(0);
- }
- }
- if (isIEFlag === true && domElType === 'text') {
- resizeInternetExplorerInput();
- } else {
- if (domElType === 'textarea') {
- resizeTextarea();
- } else if (domElType === 'text') {
- resizeInput();
- }
- }
- };
- activate();
- //listen for tab changes
- if (umbTabsController != null) {
- umbTabsController.onTabShown(function (args) {
- update();
- });
- }
- // listen for ng-model changes
- var unbindModelWatcher = scope.$watch(function () {
- return ngModelController.$modelValue;
- }, function (newValue) {
- update(true);
- });
- scope.$on('$destroy', function () {
- element.unbind('keyup keydown keypress change', update);
- element.unbind('blur', update(true));
- unbindModelWatcher();
- // clean up IE dom element
- if (isIEFlag === true && domElType === 'text') {
- mirror.remove();
- }
- });
- }
- };
- });
- /*
- example usage: <textarea json-edit="myObject" rows="8" class="form-control"></textarea>
- jsonEditing is a string which we edit in a textarea. we try parsing to JSON with each change. when it is valid, propagate model changes via ngModelCtrl
- use isolate scope to prevent model propagation when invalid - will update manually. cannot replace with template, or will override ngModelCtrl, and not hide behind facade
- will override element type to textarea and add own attribute ngModel tied to jsonEditing
- */
- angular.module('umbraco.directives').directive('umbRawModel', function () {
- return {
- restrict: 'A',
- require: 'ngModel',
- template: '<textarea ng-model="jsonEditing"></textarea>',
- replace: true,
- scope: {
- model: '=umbRawModel',
- validateOn: '='
- },
- link: function (scope, element, attrs, ngModelCtrl) {
- function setEditing(value) {
- scope.jsonEditing = angular.copy(jsonToString(value));
- }
- function updateModel(value) {
- scope.model = stringToJson(value);
- }
- function setValid() {
- ngModelCtrl.$setValidity('json', true);
- }
- function setInvalid() {
- ngModelCtrl.$setValidity('json', false);
- }
- function stringToJson(text) {
- try {
- return angular.fromJson(text);
- } catch (err) {
- setInvalid();
- return text;
- }
- }
- function jsonToString(object) {
- // better than JSON.stringify(), because it formats + filters $$hashKey etc.
- // NOTE that this will remove all $-prefixed values
- return angular.toJson(object, true);
- }
- function isValidJson(model) {
- var flag = true;
- try {
- angular.fromJson(model);
- } catch (err) {
- flag = false;
- }
- return flag;
- }
- //init
- setEditing(scope.model);
- var onInputChange = function (newval, oldval) {
- if (newval !== oldval) {
- if (isValidJson(newval)) {
- setValid();
- updateModel(newval);
- } else {
- setInvalid();
- }
- }
- };
- if (scope.validateOn) {
- element.on(scope.validateOn, function () {
- scope.$apply(function () {
- onInputChange(scope.jsonEditing);
- });
- });
- } else {
- //check for changes going out
- scope.$watch('jsonEditing', onInputChange, true);
- }
- //check for changes coming in
- scope.$watch('model', function (newval, oldval) {
- if (newval !== oldval) {
- setEditing(newval);
- }
- }, true);
- }
- };
- });
- (function () {
- 'use strict';
- function SelectWhen($timeout) {
- function link(scope, el, attr, ctrl) {
- attr.$observe('umbSelectWhen', function (newValue) {
- if (newValue === 'true') {
- $timeout(function () {
- el.select();
- });
- }
- });
- }
- var directive = {
- restrict: 'A',
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbSelectWhen', SelectWhen);
- }());
- angular.module('umbraco.directives').directive('gridRte', function (tinyMceService, stylesheetResource, angularHelper, assetsService, $q, $timeout) {
- return {
- scope: {
- uniqueId: '=',
- value: '=',
- onClick: '&',
- onFocus: '&',
- onBlur: '&',
- configuration: '=',
- onMediaPickerClick: '=',
- onEmbedClick: '=',
- onMacroPickerClick: '=',
- onLinkPickerClick: '='
- },
- template: '<textarea ng-model="value" rows="10" class="mceNoEditor" style="overflow:hidden" id="{{uniqueId}}"></textarea>',
- replace: true,
- link: function (scope, element, attrs) {
- var initTiny = function () {
- //we always fetch the default one, and then override parts with our own
- tinyMceService.configuration().then(function (tinyMceConfig) {
- //config value from general tinymce.config file
- var validElements = tinyMceConfig.validElements;
- var fallbackStyles = [
- {
- title: 'Page header',
- block: 'h2'
- },
- {
- title: 'Section header',
- block: 'h3'
- },
- {
- title: 'Paragraph header',
- block: 'h4'
- },
- {
- title: 'Normal',
- block: 'p'
- },
- {
- title: 'Quote',
- block: 'blockquote'
- },
- {
- title: 'Code',
- block: 'code'
- }
- ];
- //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],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align],span[id|class|style]';
- var invalidElements = tinyMceConfig.inValidElements;
- var plugins = _.map(tinyMceConfig.plugins, function (plugin) {
- if (plugin.useOnFrontend) {
- return plugin.name;
- }
- }).join(' ') + ' autoresize';
- //config value on the data type
- var toolbar = [
- 'code',
- 'styleselect',
- 'bold',
- 'italic',
- 'alignleft',
- 'aligncenter',
- 'alignright',
- 'bullist',
- 'numlist',
- 'link',
- 'umbmediapicker',
- 'umbembeddialog'
- ].join(' | ');
- var stylesheets = [];
- var styleFormats = [];
- var await = [];
- //queue file loading
- if (typeof tinymce === 'undefined') {
- await.push(assetsService.loadJs('lib/tinymce/tinymce.min.js', scope));
- }
- if (scope.configuration && scope.configuration.toolbar) {
- toolbar = scope.configuration.toolbar.join(' | ');
- }
- if (scope.configuration && scope.configuration.stylesheets) {
- angular.forEach(scope.configuration.stylesheets, function (stylesheet, key) {
- stylesheets.push(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + '/' + stylesheet + '.css');
- await.push(stylesheetResource.getRulesByName(stylesheet).then(function (rules) {
- angular.forEach(rules, function (rule) {
- var r = {};
- var split = '';
- r.title = rule.name;
- if (rule.selector[0] === '.') {
- r.inline = 'span';
- r.classes = rule.selector.substring(1);
- } else if (rule.selector[0] === '#') {
- //Even though this will render in the style drop down, it will not actually be applied
- // to the elements, don't think TinyMCE even supports this and it doesn't really make much sense
- // since only one element can have one id.
- r.inline = 'span';
- r.attributes = { id: rule.selector.substring(1) };
- } else if (rule.selector[0] !== '.' && rule.selector.indexOf('.') > -1) {
- 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) {
- 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);
- });
- }));
- });
- } else {
- stylesheets.push('views/propertyeditors/grid/config/grid.default.rtestyles.css');
- styleFormats = fallbackStyles;
- }
- //stores a reference to the editor
- var tinyMceEditor = null;
- $q.all(await).then(function () {
- var uniqueId = scope.uniqueId;
- //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,
- relative_urls: false,
- toolbar: toolbar,
- content_css: stylesheets,
- style_formats: styleFormats,
- autoresize_bottom_margin: 0
- };
- 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) {
- }
- }
- }
- }
- angular.extend(baseLineConfigObj, tinyMceConfig.customConfig);
- }
- //set all the things that user configs should not be able to override
- baseLineConfigObj.elements = uniqueId;
- baseLineConfigObj.setup = function (editor) {
- //set the reference
- tinyMceEditor = editor;
- //enable browser based spell checking
- editor.on('init', function (e) {
- editor.getBody().setAttribute('spellcheck', true);
- //force overflow to hidden to prevent no needed scroll
- editor.getBody().style.overflow = 'hidden';
- $timeout(function () {
- if (scope.value === null) {
- editor.focus();
- }
- }, 400);
- });
- // pin toolbar to top of screen if we have focus and it scrolls off the screen
- var pinToolbar = function () {
- var _toolbar = $(editor.editorContainer).find('.mce-toolbar');
- var toolbarHeight = _toolbar.height();
- var _tinyMce = $(editor.editorContainer);
- var tinyMceRect = _tinyMce[0].getBoundingClientRect();
- var tinyMceTop = tinyMceRect.top;
- var tinyMceBottom = tinyMceRect.bottom;
- var tinyMceWidth = tinyMceRect.width;
- var _tinyMceEditArea = _tinyMce.find('.mce-edit-area');
- // set padding in top of mce so the content does not "jump" up
- _tinyMceEditArea.css('padding-top', toolbarHeight);
- if (tinyMceTop < 160 && 160 + toolbarHeight < tinyMceBottom) {
- _toolbar.css('visibility', 'visible').css('position', 'fixed').css('top', '160px').css('margin-top', '0').css('width', tinyMceWidth);
- } else {
- _toolbar.css('visibility', 'visible').css('position', 'absolute').css('top', 'auto').css('margin-top', '0').css('width', tinyMceWidth);
- }
- };
- // unpin toolbar to top of screen
- var unpinToolbar = function () {
- var _toolbar = $(editor.editorContainer).find('.mce-toolbar');
- var _tinyMce = $(editor.editorContainer);
- var _tinyMceEditArea = _tinyMce.find('.mce-edit-area');
- // reset padding in top of mce so the content does not "jump" up
- _tinyMceEditArea.css('padding-top', '0');
- _toolbar.css('position', 'static');
- };
- //when we leave the editor (maybe)
- editor.on('blur', function (e) {
- editor.save();
- angularHelper.safeApply(scope, function () {
- scope.value = editor.getContent();
- var _toolbar = $(editor.editorContainer).find('.mce-toolbar');
- if (scope.onBlur) {
- scope.onBlur();
- }
- unpinToolbar();
- $('.umb-panel-body').off('scroll', pinToolbar);
- });
- });
- // Focus on editor
- editor.on('focus', function (e) {
- angularHelper.safeApply(scope, function () {
- if (scope.onFocus) {
- scope.onFocus();
- }
- pinToolbar();
- $('.umb-panel-body').on('scroll', pinToolbar);
- });
- });
- // Click on editor
- editor.on('click', function (e) {
- angularHelper.safeApply(scope, function () {
- if (scope.onClick) {
- scope.onClick();
- }
- pinToolbar();
- $('.umb-panel-body').on('scroll', pinToolbar);
- });
- });
- //when buttons modify content
- editor.on('ExecCommand', function (e) {
- editor.save();
- angularHelper.safeApply(scope, function () {
- scope.value = editor.getContent();
- });
- });
- // Update model on keypress
- editor.on('KeyUp', function (e) {
- editor.save();
- angularHelper.safeApply(scope, function () {
- scope.value = editor.getContent();
- });
- });
- // Update model on change, i.e. copy/pasted text, plugins altering content
- editor.on('SetContent', function (e) {
- if (!e.initial) {
- editor.save();
- angularHelper.safeApply(scope, function () {
- scope.value = editor.getContent();
- });
- }
- });
- editor.on('ObjectResized', function (e) {
- var qs = '?width=' + e.width + '&height=' + e.height;
- var srcAttr = $(e.target).attr('src');
- var path = srcAttr.split('?')[0];
- $(e.target).attr('data-mce-src', path + qs);
- });
- //Create the insert link plugin
- tinyMceService.createLinkPicker(editor, scope, function (currentTarget, anchorElement) {
- if (scope.onLinkPickerClick) {
- scope.onLinkPickerClick(editor, currentTarget, anchorElement);
- }
- });
- //Create the insert media plugin
- tinyMceService.createMediaPicker(editor, scope, function (currentTarget, userData) {
- if (scope.onMediaPickerClick) {
- scope.onMediaPickerClick(editor, currentTarget, userData);
- }
- });
- //Create the embedded plugin
- tinyMceService.createInsertEmbeddedMedia(editor, scope, function () {
- if (scope.onEmbedClick) {
- scope.onEmbedClick(editor);
- }
- });
- //Create the insert macro plugin
- tinyMceService.createInsertMacro(editor, scope, function (dialogData) {
- if (scope.onMacroPickerClick) {
- scope.onMacroPickerClick(editor, dialogData);
- }
- });
- };
- /** 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);
- }, 150, 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.value = tinyMceEditor ? tinyMceEditor.getContent() : null;
- });
- //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();
- if (tinyMceEditor !== undefined && tinyMceEditor != null) {
- tinyMceEditor.destroy();
- }
- });
- });
- });
- };
- initTiny();
- }
- };
- });
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbBox
- @restrict E
- @description
- Use this directive to render an already styled empty div tag.
- <h3>Markup example</h3>
- <pre>
- <umb-box>
- <umb-box-header title="this is a title"></umb-box-header>
- <umb-box-content>
- // Content here
- </umb-box-content>
- </umb-box>
- </pre>
- <h3>Use in combination with:</h3>
- <ul>
- <li>{@link umbraco.directives.directive:umbBoxHeader umbBoxHeader}</li>
- <li>{@link umbraco.directives.directive:umbBoxContent umbBoxContent}</li>
- </ul>
- **/
- (function () {
- 'use strict';
- function BoxDirective() {
- var directive = {
- restrict: 'E',
- replace: true,
- transclude: true,
- templateUrl: 'views/components/html/umb-box/umb-box.html'
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbBox', BoxDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbBoxContent
- @restrict E
- @description
- Use this directive to render an empty container. Recommended to use it inside an {@link umbraco.directives.directive:umbBox umbBox} directive. See documentation for {@link umbraco.directives.directive:umbBox umbBox}.
- <h3>Markup example</h3>
- <pre>
- <umb-box>
- <umb-box-header title="this is a title"></umb-box-header>
- <umb-box-content>
- // Content here
- </umb-box-content>
- </umb-box>
- </pre>
- <h3>Use in combination with:</h3>
- <ul>
- <li>{@link umbraco.directives.directive:umbBox umbBox}</li>
- <li>{@link umbraco.directives.directive:umbBoxHeader umbBoxHeader}</li>
- </ul>
- **/
- (function () {
- 'use strict';
- function BoxContentDirective() {
- var directive = {
- restrict: 'E',
- replace: true,
- transclude: true,
- templateUrl: 'views/components/html/umb-box/umb-box-content.html'
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbBoxContent', BoxContentDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbBoxHeader
- @restrict E
- @scope
- @description
- Use this directive to construct a title. Recommended to use it inside an {@link umbraco.directives.directive:umbBox umbBox} directive. See documentation for {@link umbraco.directives.directive:umbBox umbBox}.
- <h3>Markup example</h3>
- <pre>
- <umb-box>
- <umb-box-header title="This is a title" description="I can enter a description right here"></umb-box-header>
- <umb-box-content>
- // Content here
- </umb-box-content>
- </umb-box>
- </pre>
- <h3>Markup example with using titleKey</h3>
- <pre>
- <umb-box>
- // the title-key property needs an areaAlias_keyAlias from the language files
- <umb-box-header title-key="areaAlias_keyAlias" description-key="areaAlias_keyAlias"></umb-box-header>
- <umb-box-content>
- // Content here
- </umb-box-content>
- </umb-box>
- </pre>
- {@link https://our.umbraco.org/documentation/extending/language-files/ Here you can see more about the language files}
- <h3>Use in combination with:</h3>
- <ul>
- <li>{@link umbraco.directives.directive:umbBox umbBox}</li>
- <li>{@link umbraco.directives.directive:umbBoxContent umbBoxContent}</li>
- </ul>
- @param {string=} title (<code>attrbute</code>): Custom title text.
- @param {string=} titleKey (<code>attrbute</code>): The translation key from the language xml files.
- @param {string=} description (<code>attrbute</code>): Custom description text.
- @param {string=} descriptionKey (<code>attrbute</code>): The translation key from the language xml files.
- **/
- (function () {
- 'use strict';
- function BoxHeaderDirective() {
- var directive = {
- restrict: 'E',
- replace: true,
- transclude: true,
- templateUrl: 'views/components/html/umb-box/umb-box-header.html',
- scope: {
- titleKey: '@?',
- title: '@?',
- descriptionKey: '@?',
- description: '@?'
- }
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbBoxHeader', BoxHeaderDirective);
- }());
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbControlGroup
- * @restrict E
- **/
- angular.module('umbraco.directives.html').directive('umbControlGroup', function (localizationService) {
- return {
- scope: {
- label: '@label',
- description: '@',
- hideLabel: '@',
- alias: '@',
- labelFor: '@',
- required: '@?'
- },
- require: '?^form',
- transclude: true,
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/html/umb-control-group.html',
- link: function (scope, element, attr, formCtrl) {
- scope.formValid = function () {
- if (formCtrl && scope.labelFor) {
- //if a label-for has been set, use that for the validation
- return formCtrl[scope.labelFor].$valid;
- }
- //there is no form.
- return true;
- };
- if (scope.label && scope.label[0] === '@') {
- scope.labelstring = localizationService.localize(scope.label.substring(1));
- } else {
- scope.labelstring = scope.label;
- }
- if (scope.description && scope.description[0] === '@') {
- scope.descriptionstring = localizationService.localize(scope.description.substring(1));
- } else {
- scope.descriptionstring = scope.description;
- }
- }
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbPane
- * @restrict E
- **/
- angular.module('umbraco.directives.html').directive('umbPane', function () {
- return {
- transclude: true,
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/html/umb-pane.html'
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbPanel
- * @restrict E
- **/
- angular.module('umbraco.directives.html').directive('umbPanel', function ($timeout, $log) {
- return {
- restrict: 'E',
- replace: true,
- transclude: 'true',
- templateUrl: 'views/components/html/umb-panel.html'
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbImageCrop
- * @restrict E
- * @function
- **/
- angular.module('umbraco.directives').directive('umbImageCrop', function ($timeout, localizationService, cropperHelper, $log) {
- return {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/imaging/umb-image-crop.html',
- scope: {
- src: '=',
- width: '@',
- height: '@',
- crop: '=',
- center: '=',
- maxSize: '@'
- },
- link: function (scope, element, attrs) {
- scope.width = 400;
- scope.height = 320;
- scope.dimensions = {
- image: {},
- cropper: {},
- viewport: {},
- margin: 20,
- scale: {
- min: 0.3,
- max: 3,
- current: 1
- }
- };
- //live rendering of viewport and image styles
- scope.style = function () {
- return {
- 'height': parseInt(scope.dimensions.viewport.height, 10) + 'px',
- 'width': parseInt(scope.dimensions.viewport.width, 10) + 'px'
- };
- };
- //elements
- var $viewport = element.find('.viewport');
- var $image = element.find('img');
- var $overlay = element.find('.overlay');
- var $container = element.find('.crop-container');
- //default constraints for drag n drop
- var constraints = {
- left: {
- max: scope.dimensions.margin,
- min: scope.dimensions.margin
- },
- top: {
- max: scope.dimensions.margin,
- min: scope.dimensions.margin
- }
- };
- scope.constraints = constraints;
- //set constaints for cropping drag and drop
- var setConstraints = function () {
- constraints.left.min = scope.dimensions.margin + scope.dimensions.cropper.width - scope.dimensions.image.width;
- constraints.top.min = scope.dimensions.margin + scope.dimensions.cropper.height - scope.dimensions.image.height;
- };
- var setDimensions = function (originalImage) {
- originalImage.width('auto');
- originalImage.height('auto');
- var image = {};
- image.originalWidth = originalImage.width();
- image.originalHeight = originalImage.height();
- image.width = image.originalWidth;
- image.height = image.originalHeight;
- image.left = originalImage[0].offsetLeft;
- image.top = originalImage[0].offsetTop;
- scope.dimensions.image = image;
- //unscaled editor size
- //var viewPortW = $viewport.width();
- //var viewPortH = $viewport.height();
- var _viewPortW = parseInt(scope.width, 10);
- var _viewPortH = parseInt(scope.height, 10);
- //if we set a constraint we will scale it down if needed
- if (scope.maxSize) {
- var ratioCalculation = cropperHelper.scaleToMaxSize(_viewPortW, _viewPortH, scope.maxSize);
- //so if we have a max size, override the thumb sizes
- _viewPortW = ratioCalculation.width;
- _viewPortH = ratioCalculation.height;
- }
- scope.dimensions.viewport.width = _viewPortW + 2 * scope.dimensions.margin;
- scope.dimensions.viewport.height = _viewPortH + 2 * scope.dimensions.margin;
- scope.dimensions.cropper.width = _viewPortW;
- // scope.dimensions.viewport.width - 2 * scope.dimensions.margin;
- scope.dimensions.cropper.height = _viewPortH; // scope.dimensions.viewport.height - 2 * scope.dimensions.margin;
- };
- //when loading an image without any crop info, we center and fit it
- var resizeImageToEditor = function () {
- //returns size fitting the cropper
- var size = cropperHelper.calculateAspectRatioFit(scope.dimensions.image.width, scope.dimensions.image.height, scope.dimensions.cropper.width, scope.dimensions.cropper.height, true);
- //sets the image size and updates the scope
- scope.dimensions.image.width = size.width;
- scope.dimensions.image.height = size.height;
- //calculate the best suited ratios
- scope.dimensions.scale.min = size.ratio;
- scope.dimensions.scale.max = 2;
- scope.dimensions.scale.current = size.ratio;
- //center the image
- var position = cropperHelper.centerInsideViewPort(scope.dimensions.image, scope.dimensions.cropper);
- scope.dimensions.top = position.top;
- scope.dimensions.left = position.left;
- setConstraints();
- };
- //resize to a given ratio
- var resizeImageToScale = function (ratio) {
- //do stuff
- var size = cropperHelper.calculateSizeToRatio(scope.dimensions.image.originalWidth, scope.dimensions.image.originalHeight, ratio);
- scope.dimensions.image.width = size.width;
- scope.dimensions.image.height = size.height;
- setConstraints();
- validatePosition(scope.dimensions.image.left, scope.dimensions.image.top);
- };
- //resize the image to a predefined crop coordinate
- var resizeImageToCrop = function () {
- scope.dimensions.image = cropperHelper.convertToStyle(scope.crop, {
- width: scope.dimensions.image.originalWidth,
- height: scope.dimensions.image.originalHeight
- }, scope.dimensions.cropper, scope.dimensions.margin);
- var ratioCalculation = cropperHelper.calculateAspectRatioFit(scope.dimensions.image.originalWidth, scope.dimensions.image.originalHeight, scope.dimensions.cropper.width, scope.dimensions.cropper.height, true);
- scope.dimensions.scale.current = scope.dimensions.image.ratio;
- //min max based on original width/height
- scope.dimensions.scale.min = ratioCalculation.ratio;
- scope.dimensions.scale.max = 2;
- };
- var validatePosition = function (left, top) {
- if (left > constraints.left.max) {
- left = constraints.left.max;
- }
- if (left <= constraints.left.min) {
- left = constraints.left.min;
- }
- if (top > constraints.top.max) {
- top = constraints.top.max;
- }
- if (top <= constraints.top.min) {
- top = constraints.top.min;
- }
- if (scope.dimensions.image.left !== left) {
- scope.dimensions.image.left = left;
- }
- if (scope.dimensions.image.top !== top) {
- scope.dimensions.image.top = top;
- }
- };
- //sets scope.crop to the recalculated % based crop
- var calculateCropBox = function () {
- scope.crop = cropperHelper.pixelsToCoordinates(scope.dimensions.image, scope.dimensions.cropper.width, scope.dimensions.cropper.height, scope.dimensions.margin);
- };
- //Drag and drop positioning, using jquery ui draggable
- var onStartDragPosition, top, left;
- $overlay.draggable({
- drag: function (event, ui) {
- scope.$apply(function () {
- validatePosition(ui.position.left, ui.position.top);
- });
- },
- stop: function (event, ui) {
- scope.$apply(function () {
- //make sure that every validates one more time...
- validatePosition(ui.position.left, ui.position.top);
- calculateCropBox();
- scope.dimensions.image.rnd = Math.random();
- });
- }
- });
- var init = function (image) {
- scope.loaded = false;
- //set dimensions on image, viewport, cropper etc
- setDimensions(image);
- //if we have a crop already position the image
- if (scope.crop) {
- resizeImageToCrop();
- } else {
- resizeImageToEditor();
- }
- //sets constaints for the cropper
- setConstraints();
- scope.loaded = true;
- };
- /// WATCHERS ////
- scope.$watchCollection('[width, height]', function (newValues, oldValues) {
- //we have to reinit the whole thing if
- //one of the external params changes
- if (newValues !== oldValues) {
- setDimensions($image);
- setConstraints();
- }
- });
- var throttledResizing = _.throttle(function () {
- resizeImageToScale(scope.dimensions.scale.current);
- calculateCropBox();
- }, 100);
- //happens when we change the scale
- scope.$watch('dimensions.scale.current', function () {
- if (scope.loaded) {
- throttledResizing();
- }
- });
- //ie hack
- if (window.navigator.userAgent.indexOf('MSIE ')) {
- var ranger = element.find('input');
- ranger.bind('change', function () {
- scope.$apply(function () {
- scope.dimensions.scale.current = ranger.val();
- });
- });
- }
- //// INIT /////
- $image.load(function () {
- $timeout(function () {
- init($image);
- });
- });
- }
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbImageGravity
- * @restrict E
- * @function
- * @description
- **/
- angular.module('umbraco.directives').directive('umbImageGravity', function ($timeout, localizationService, $log) {
- return {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/imaging/umb-image-gravity.html',
- scope: {
- src: '=',
- center: '=',
- onImageLoaded: '='
- },
- link: function (scope, element, attrs) {
- //Internal values for keeping track of the dot and the size of the editor
- scope.dimensions = {
- width: 0,
- height: 0,
- left: 0,
- top: 0
- };
- scope.loaded = false;
- //elements
- var $viewport = element.find('.viewport');
- var $image = element.find('img');
- var $overlay = element.find('.overlay');
- scope.style = function () {
- if (scope.dimensions.width <= 0) {
- setDimensions();
- }
- return {
- 'top': scope.dimensions.top + 'px',
- 'left': scope.dimensions.left + 'px'
- };
- };
- scope.setFocalPoint = function (event) {
- scope.$emit('imageFocalPointStart');
- var offsetX = event.offsetX - 10;
- var offsetY = event.offsetY - 10;
- calculateGravity(offsetX, offsetY);
- lazyEndEvent();
- };
- var setDimensions = function () {
- scope.dimensions.width = $image.width();
- scope.dimensions.height = $image.height();
- if (scope.center) {
- scope.dimensions.left = scope.center.left * scope.dimensions.width - 10;
- scope.dimensions.top = scope.center.top * scope.dimensions.height - 10;
- } else {
- scope.center = {
- left: 0.5,
- top: 0.5
- };
- }
- };
- var calculateGravity = function (offsetX, offsetY) {
- scope.dimensions.left = offsetX;
- scope.dimensions.top = offsetY;
- scope.center.left = (scope.dimensions.left + 10) / scope.dimensions.width;
- scope.center.top = (scope.dimensions.top + 10) / scope.dimensions.height;
- };
- var lazyEndEvent = _.debounce(function () {
- scope.$apply(function () {
- scope.$emit('imageFocalPointStop');
- });
- }, 2000);
- //Drag and drop positioning, using jquery ui draggable
- //TODO ensure that the point doesnt go outside the box
- $overlay.draggable({
- containment: 'parent',
- start: function () {
- scope.$apply(function () {
- scope.$emit('imageFocalPointStart');
- });
- },
- stop: function () {
- scope.$apply(function () {
- var offsetX = $overlay[0].offsetLeft;
- var offsetY = $overlay[0].offsetTop;
- calculateGravity(offsetX, offsetY);
- });
- lazyEndEvent();
- }
- });
- //// INIT /////
- $image.load(function () {
- $timeout(function () {
- setDimensions();
- scope.loaded = true;
- if (angular.isFunction(scope.onImageLoaded)) {
- scope.onImageLoaded();
- }
- });
- });
- $(window).on('resize.umbImageGravity', function () {
- scope.$apply(function () {
- $timeout(function () {
- setDimensions();
- });
- // Make sure we can find the offset values for the overlay(dot) before calculating
- // fixes issue with resize event when printing the page (ex. hitting ctrl+p inside the rte)
- if ($overlay.is(':visible')) {
- var offsetX = $overlay[0].offsetLeft;
- var offsetY = $overlay[0].offsetTop;
- calculateGravity(offsetX, offsetY);
- }
- });
- });
- scope.$on('$destroy', function () {
- $(window).off('.umbImageGravity');
- });
- }
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbImageThumbnail
- * @restrict E
- * @function
- * @description
- **/
- angular.module('umbraco.directives').directive('umbImageThumbnail', function ($timeout, localizationService, cropperHelper, $log) {
- return {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/imaging/umb-image-thumbnail.html',
- scope: {
- src: '=',
- width: '@',
- height: '@',
- center: '=',
- crop: '=',
- maxSize: '@'
- },
- link: function (scope, element, attrs) {
- //// INIT /////
- var $image = element.find('img');
- scope.loaded = false;
- $image.load(function () {
- $timeout(function () {
- $image.width('auto');
- $image.height('auto');
- scope.image = {};
- scope.image.width = $image[0].width;
- scope.image.height = $image[0].height;
- //we force a lower thumbnail size to fit the max size
- //we do not compare to the image dimensions, but the thumbs
- if (scope.maxSize) {
- var ratioCalculation = cropperHelper.calculateAspectRatioFit(scope.width, scope.height, scope.maxSize, scope.maxSize, false);
- //so if we have a max size, override the thumb sizes
- scope.width = ratioCalculation.width;
- scope.height = ratioCalculation.height;
- }
- setPreviewStyle();
- scope.loaded = true;
- });
- });
- /// WATCHERS ////
- scope.$watchCollection('[crop, center]', function (newValues, oldValues) {
- //we have to reinit the whole thing if
- //one of the external params changes
- setPreviewStyle();
- });
- scope.$watch('center', function () {
- setPreviewStyle();
- }, true);
- function setPreviewStyle() {
- if (scope.crop && scope.image) {
- scope.preview = cropperHelper.convertToStyle(scope.crop, scope.image, {
- width: scope.width,
- height: scope.height
- }, 0);
- } else if (scope.image) {
- //returns size fitting the cropper
- var p = cropperHelper.calculateAspectRatioFit(scope.image.width, scope.image.height, scope.width, scope.height, true);
- if (scope.center) {
- var xy = cropperHelper.alignToCoordinates(p, scope.center, {
- width: scope.width,
- height: scope.height
- });
- p.top = xy.top;
- p.left = xy.left;
- } else {
- }
- p.position = 'absolute';
- scope.preview = p;
- }
- }
- }
- };
- });
- angular.module('umbraco.directives') /**
- * @ngdoc directive
- * @name umbraco.directives.directive:localize
- * @restrict EA
- * @function
- * @description
- * <div>
- * <strong>Component</strong><br />
- * Localize a specific token to put into the HTML as an item
- * </div>
- * <div>
- * <strong>Attribute</strong><br />
- * Add a HTML attribute to an element containing the HTML attribute name you wish to localise
- * Using the format of '@section_key' or 'section_key'
- * </div>
- * ##Usage
- * <pre>
- * <!-- Component -->
- * <localize key="general_close">Close</localize>
- * <localize key="section_key">Fallback value</localize>
- *
- * <!-- Attribute -->
- * <input type="text" localize="placeholder" placeholder="@placeholders_entername" />
- * <input type="text" localize="placeholder,title" title="@section_key" placeholder="@placeholders_entername" />
- * <div localize="title" title="@section_key"></div>
- * </pre>
- **/.directive('localize', function ($log, localizationService) {
- return {
- restrict: 'E',
- scope: { key: '@' },
- replace: true,
- link: function (scope, element, attrs) {
- var key = scope.key;
- localizationService.localize(key).then(function (value) {
- element.html(value);
- });
- }
- };
- }).directive('localize', function ($log, localizationService) {
- return {
- restrict: 'A',
- link: function (scope, element, attrs) {
- //Support one or more attribute properties to update
- var keys = attrs.localize.split(',');
- angular.forEach(keys, function (value, key) {
- var attr = element.attr(value);
- if (attr) {
- if (attr[0] === '@') {
- //If the translation key starts with @ then remove it
- attr = attr.substring(1);
- }
- var t = localizationService.tokenize(attr, scope);
- localizationService.localize(t.key, t.tokens).then(function (val) {
- element.attr(value, val);
- });
- }
- });
- }
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbNotifications
- */
- (function () {
- 'use strict';
- function NotificationDirective(notificationsService) {
- function link(scope, el, attr, ctrl) {
- //subscribes to notifications in the notification service
- scope.notifications = notificationsService.current;
- scope.$watch('notificationsService.current', function (newVal, oldVal, scope) {
- if (newVal) {
- scope.notifications = newVal;
- }
- });
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/notifications/umb-notifications.html',
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbNotifications', NotificationDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbOverlay
- @restrict E
- @scope
- @description
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.Controller as vm">
- <button type="button" ng-click="vm.openOverlay()"></button>
- <umb-overlay
- ng-if="vm.overlay.show"
- model="vm.overlay"
- view="vm.overlay.view"
- position="right">
- </umb-overlay>
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
- function Controller() {
- var vm = this;
- vm.openOverlay = openOverlay;
- function openOverlay() {
- vm.overlay = {
- view: "mediapicker",
- show: true,
- submit: function(model) {
- vm.overlay.show = false;
- vm.overlay = null;
- },
- close: function(oldModel) {
- vm.overlay.show = false;
- vm.overlay = null;
- }
- }
- };
- }
- angular.module("umbraco").controller("My.Controller", Controller);
- })();
- </pre>
- <h1>General Options</h1>
- Lorem ipsum dolor sit amet..
- <table>
- <thead>
- <tr>
- <th>Param</th>
- <th>Type</th>
- <th>Details</th>
- </tr>
- </thead>
- <tr>
- <td>model.title</td>
- <td>String</td>
- <td>Set the title of the overlay.</td>
- </tr>
- <tr>
- <td>model.subTitle</td>
- <td>String</td>
- <td>Set the subtitle of the overlay.</td>
- </tr>
- <tr>
- <td>model.submitButtonLabel</td>
- <td>String</td>
- <td>Set an alternate submit button text</td>
- </tr>
- <tr>
- <td>model.submitButtonLabelKey</td>
- <td>String</td>
- <td>Set an alternate submit button label key for localized texts</td>
- </tr>
- <tr>
- <td>model.hideSubmitButton</td>
- <td>Boolean</td>
- <td>Hides the submit button</td>
- </tr>
- <tr>
- <td>model.closeButtonLabel</td>
- <td>String</td>
- <td>Set an alternate close button text</td>
- </tr>
- <tr>
- <td>model.closeButtonLabelKey</td>
- <td>String</td>
- <td>Set an alternate close button label key for localized texts</td>
- </tr>
- <tr>
- <td>model.show</td>
- <td>Boolean</td>
- <td>Show/hide the overlay</td>
- </tr>
- <tr>
- <td>model.submit</td>
- <td>Function</td>
- <td>Callback function when the overlay submits. Returns the overlay model object</td>
- </tr>
- <tr>
- <td>model.close</td>
- <td>Function</td>
- <td>Callback function when the overlay closes. Returns a copy of the overlay model object before being modified</td>
- </tr>
- </table>
- <h1>Content Picker</h1>
- Opens a content picker.</br>
- <strong>view: </strong>contentpicker
- <table>
- <thead>
- <tr>
- <th>Param</th>
- <th>Type</th>
- <th>Details</th>
- </tr>
- </thead>
- <tr>
- <td>model.multiPicker</td>
- <td>Boolean</td>
- <td>Pick one or multiple items</td>
- </tr>
- </table>
- <table>
- <thead>
- <tr>
- <th>Returns</th>
- <th>Type</th>
- <th>Details</th>
- </tr>
- </thead>
- <tr>
- <td>model.selection</td>
- <td>Array</td>
- <td>Array of content objects</td>
- </tr>
- </table>
- <h1>Icon Picker</h1>
- Opens an icon picker.</br>
- <strong>view: </strong>iconpicker
- <table>
- <thead>
- <tr>
- <th>Returns</th>
- <th>Type</th>
- <th>Details</th>
- </tr>
- </thead>
- <tr>
- <td>model.icon</td>
- <td>String</td>
- <td>The icon class</td>
- </tr>
- </table>
- <h1>Item Picker</h1>
- Opens an item picker.</br>
- <strong>view: </strong>itempicker
- <table>
- <thead>
- <tr>
- <th>Param</th>
- <th>Type</th>
- <th>Details</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>model.availableItems</td>
- <td>Array</td>
- <td>Array of available items</td>
- </tr>
- <tr>
- <td>model.selectedItems</td>
- <td>Array</td>
- <td>Array of selected items. When passed in the selected items will be filtered from the available items.</td>
- </tr>
- <tr>
- <td>model.filter</td>
- <td>Boolean</td>
- <td>Set to false to hide the filter</td>
- </tr>
- </tbody>
- </table>
- <table>
- <thead>
- <tr>
- <th>Returns</th>
- <th>Type</th>
- <th>Details</th>
- </tr>
- </thead>
- <tr>
- <td>model.selectedItem</td>
- <td>Object</td>
- <td>The selected item</td>
- </tr>
- </table>
- <h1>Macro Picker</h1>
- Opens a media picker.</br>
- <strong>view: </strong>macropicker
- <table>
- <thead>
- <tr>
- <th>Param</th>
- <th>Type</th>
- <th>Details</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>model.dialogData</td>
- <td>Object</td>
- <td>Object which contains array of allowedMacros. Set to <code>null</code> to allow all.</td>
- </tr>
- </tbody>
- </table>
- <table>
- <thead>
- <tr>
- <th>Returns</th>
- <th>Type</th>
- <th>Details</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>model.macroParams</td>
- <td>Array</td>
- <td>Array of macro params</td>
- </tr>
- <tr>
- <td>model.selectedMacro</td>
- <td>Object</td>
- <td>The selected macro</td>
- </tr>
- </tbody>
- </table>
- <h1>Media Picker</h1>
- Opens a media picker.</br>
- <strong>view: </strong>mediapicker
- <table>
- <thead>
- <tr>
- <th>Param</th>
- <th>Type</th>
- <th>Details</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>model.multiPicker</td>
- <td>Boolean</td>
- <td>Pick one or multiple items</td>
- </tr>
- <tr>
- <td>model.onlyImages</td>
- <td>Boolean</td>
- <td>Only display files that have an image file-extension</td>
- </tr>
- <tr>
- <td>model.disableFolderSelect</td>
- <td>Boolean</td>
- <td>Disable folder selection</td>
- </tr>
- </tbody>
- </table>
- <table>
- <thead>
- <tr>
- <th>Returns</th>
- <th>Type</th>
- <th>Details</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>model.selectedImages</td>
- <td>Array</td>
- <td>Array of selected images</td>
- </tr>
- </tbody>
- </table>
- <h1>Member Group Picker</h1>
- Opens a member group picker.</br>
- <strong>view: </strong>membergrouppicker
- <table>
- <thead>
- <tr>
- <th>Param</th>
- <th>Type</th>
- <th>Details</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>model.multiPicker</td>
- <td>Boolean</td>
- <td>Pick one or multiple items</td>
- </tr>
- </tbody>
- </table>
- <table>
- <thead>
- <tr>
- <th>Returns</th>
- <th>Type</th>
- <th>Details</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>model.selectedMemberGroup</td>
- <td>String</td>
- <td>The selected member group</td>
- </tr>
- <tr>
- <td>model.selectedMemberGroups (multiPicker)</td>
- <td>Array</td>
- <td>The selected member groups</td>
- </tr>
- </tbody>
- </table>
- <h1>Member Picker</h1>
- Opens a member picker. </br>
- <strong>view: </strong>memberpicker
- <table>
- <thead>
- <tr>
- <th>Param</th>
- <th>Type</th>
- <th>Details</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>model.multiPicker</td>
- <td>Boolean</td>
- <td>Pick one or multiple items</td>
- </tr>
- </tbody>
- </table>
- <table>
- <thead>
- <tr>
- <th>Returns</th>
- <th>Type</th>
- <th>Details</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>model.selection</td>
- <td>Array</td>
- <td>Array of selected members/td>
- </tr>
- </tbody>
- </table>
- <h1>YSOD</h1>
- Opens an overlay to show a custom YSOD. </br>
- <strong>view: </strong>ysod
- <table>
- <thead>
- <tr>
- <th>Param</th>
- <th>Type</th>
- <th>Details</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>model.error</td>
- <td>Object</td>
- <td>Error object</td>
- </tr>
- </tbody>
- </table>
- @param {object} model Overlay options.
- @param {string} view Path to view or one of the default view names.
- @param {string} position The overlay position ("left", "right", "center": "target").
- **/
- (function () {
- 'use strict';
- function OverlayDirective($timeout, formHelper, overlayHelper, localizationService) {
- function link(scope, el, attr, ctrl) {
- scope.directive = { enableConfirmButton: false };
- var overlayNumber = 0;
- var numberOfOverlays = 0;
- var isRegistered = false;
- var modelCopy = {};
- function activate() {
- setView();
- setButtonText();
- modelCopy = makeModelCopy(scope.model);
- $timeout(function () {
- if (scope.position === 'target') {
- setTargetPosition();
- }
- // this has to be done inside a timeout to ensure the destroy
- // event on other overlays is run before registering a new one
- registerOverlay();
- setOverlayIndent();
- });
- }
- function setView() {
- if (scope.view) {
- if (scope.view.indexOf('.html') === -1) {
- var viewAlias = scope.view.toLowerCase();
- scope.view = 'views/common/overlays/' + viewAlias + '/' + viewAlias + '.html';
- }
- }
- }
- function setButtonText() {
- if (!scope.model.closeButtonLabelKey && !scope.model.closeButtonLabel) {
- scope.model.closeButtonLabel = localizationService.localize('general_close');
- }
- if (!scope.model.submitButtonLabelKey && !scope.model.submitButtonLabel) {
- scope.model.submitButtonLabel = localizationService.localize('general_submit');
- }
- }
- function registerOverlay() {
- overlayNumber = overlayHelper.registerOverlay();
- $(document).bind('keydown.overlay-' + overlayNumber, function (event) {
- if (event.which === 27) {
- numberOfOverlays = overlayHelper.getNumberOfOverlays();
- if (numberOfOverlays === overlayNumber) {
- scope.$apply(function () {
- scope.closeOverLay();
- });
- }
- event.preventDefault();
- }
- if (event.which === 13) {
- numberOfOverlays = overlayHelper.getNumberOfOverlays();
- if (numberOfOverlays === overlayNumber) {
- var activeElementType = document.activeElement.tagName;
- var clickableElements = [
- 'A',
- 'BUTTON'
- ];
- var submitOnEnter = document.activeElement.hasAttribute('overlay-submit-on-enter');
- if (clickableElements.indexOf(activeElementType) === 0) {
- document.activeElement.click();
- event.preventDefault();
- } else if (activeElementType === 'TEXTAREA' && !submitOnEnter) {
- } else {
- scope.$apply(function () {
- scope.submitForm(scope.model);
- });
- event.preventDefault();
- }
- }
- }
- });
- isRegistered = true;
- }
- function unregisterOverlay() {
- if (isRegistered) {
- overlayHelper.unregisterOverlay();
- $(document).unbind('keydown.overlay-' + overlayNumber);
- isRegistered = false;
- }
- }
- function makeModelCopy(object) {
- var newObject = {};
- for (var key in object) {
- if (key !== 'event') {
- newObject[key] = angular.copy(object[key]);
- }
- }
- return newObject;
- }
- function setOverlayIndent() {
- var overlayIndex = overlayNumber - 1;
- var indentSize = overlayIndex * 20;
- var overlayWidth = el.context.clientWidth;
- el.css('width', overlayWidth - indentSize);
- if (scope.position === 'center' || scope.position === 'target') {
- var overlayTopPosition = el.context.offsetTop;
- el.css('top', overlayTopPosition + indentSize);
- }
- }
- function setTargetPosition() {
- var container = $('#contentwrapper');
- var containerLeft = container[0].offsetLeft;
- var containerRight = containerLeft + container[0].offsetWidth;
- var containerTop = container[0].offsetTop;
- var containerBottom = containerTop + container[0].offsetHeight;
- var mousePositionClickX = null;
- var mousePositionClickY = null;
- var elementHeight = null;
- var elementWidth = null;
- var position = {
- right: 'inherit',
- left: 'inherit',
- top: 'inherit',
- bottom: 'inherit'
- };
- // if mouse click position is know place element with mouse in center
- if (scope.model.event && scope.model.event) {
- // click position
- mousePositionClickX = scope.model.event.pageX;
- mousePositionClickY = scope.model.event.pageY;
- // element size
- elementHeight = el.context.clientHeight;
- elementWidth = el.context.clientWidth;
- // move element to this position
- position.left = mousePositionClickX - elementWidth / 2;
- position.top = mousePositionClickY - elementHeight / 2;
- // check to see if element is outside screen
- // outside right
- if (position.left + elementWidth > containerRight) {
- position.right = 10;
- position.left = 'inherit';
- }
- // outside bottom
- if (position.top + elementHeight > containerBottom) {
- position.bottom = 10;
- position.top = 'inherit';
- }
- // outside left
- if (position.left < containerLeft) {
- position.left = containerLeft + 10;
- position.right = 'inherit';
- }
- // outside top
- if (position.top < containerTop) {
- position.top = 10;
- position.bottom = 'inherit';
- }
- el.css(position);
- }
- }
- scope.submitForm = function (model) {
- if (scope.model.submit) {
- if (formHelper.submitForm({ scope: scope })) {
- formHelper.resetForm({ scope: scope });
- if (scope.model.confirmSubmit && scope.model.confirmSubmit.enable && !scope.directive.enableConfirmButton) {
- scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton);
- } else {
- unregisterOverlay();
- scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton);
- }
- }
- }
- };
- scope.cancelConfirmSubmit = function () {
- scope.model.confirmSubmit.show = false;
- };
- scope.closeOverLay = function () {
- unregisterOverlay();
- if (scope.model.close) {
- scope.model = modelCopy;
- scope.model.close(scope.model);
- } else {
- scope.model.show = false;
- scope.model = null;
- }
- };
- // angular does not support ng-show on custom directives
- // width isolated scopes. So we have to make our own.
- if (attr.hasOwnProperty('ngShow')) {
- scope.$watch('ngShow', function (value) {
- if (value) {
- el.show();
- activate();
- } else {
- unregisterOverlay();
- el.hide();
- }
- });
- } else {
- activate();
- }
- scope.$on('$destroy', function () {
- unregisterOverlay();
- });
- }
- var directive = {
- transclude: true,
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/overlays/umb-overlay.html',
- scope: {
- ngShow: '=',
- model: '=',
- view: '=',
- position: '@'
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbOverlay', OverlayDirective);
- }());
- (function () {
- 'use strict';
- function OverlayBackdropDirective(overlayHelper) {
- function link(scope, el, attr, ctrl) {
- scope.numberOfOverlays = 0;
- scope.$watch(function () {
- return overlayHelper.getNumberOfOverlays();
- }, function (newValue) {
- scope.numberOfOverlays = newValue;
- });
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/overlays/umb-overlay-backdrop.html',
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbOverlayBackdrop', OverlayBackdropDirective);
- }());
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbProperty
- * @restrict E
- **/
- angular.module('umbraco.directives').directive('umbProperty', function (umbPropEditorHelper) {
- return {
- scope: { property: '=' },
- transclude: true,
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/property/umb-property.html',
- link: function (scope) {
- scope.propertyAlias = Umbraco.Sys.ServerVariables.isDebuggingEnabled === true ? scope.property.alias : null;
- },
- //Define a controller for this directive to expose APIs to other directives
- controller: function ($scope, $timeout) {
- var self = this;
- //set the API properties/methods
- self.property = $scope.property;
- self.setPropertyError = function (errorMsg) {
- $scope.property.propertyErrorMessage = errorMsg;
- };
- }
- };
- });
- /**
- * @ngdoc directive
- * @function
- * @name umbraco.directives.directive:umbPropertyEditor
- * @requires formController
- * @restrict E
- **/
- //share property editor directive function
- var _umbPropertyEditor = function (umbPropEditorHelper) {
- return {
- scope: {
- model: '=',
- isPreValue: '@',
- preview: '@'
- },
- require: '^form',
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/property/umb-property-editor.html',
- link: function (scope, element, attrs, ctrl) {
- //we need to copy the form controller val to our isolated scope so that
- //it get's carried down to the child scopes of this!
- //we'll also maintain the current form name.
- scope[ctrl.$name] = ctrl;
- if (!scope.model.alias) {
- scope.model.alias = Math.random().toString(36).slice(2);
- }
- scope.$watch('model.view', function (val) {
- scope.propertyEditorView = umbPropEditorHelper.getViewPath(scope.model.view, scope.isPreValue);
- });
- }
- };
- };
- //Preffered is the umb-property-editor as its more explicit - but we keep umb-editor for backwards compat
- angular.module('umbraco.directives').directive('umbPropertyEditor', _umbPropertyEditor);
- angular.module('umbraco.directives').directive('umbEditor', _umbPropertyEditor);
- angular.module('umbraco.directives.html').directive('umbPropertyGroup', function () {
- return {
- transclude: true,
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/property/umb-property-group.html'
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbTab
- * @restrict E
- **/
- angular.module('umbraco.directives').directive('umbTab', function ($parse, $timeout) {
- return {
- restrict: 'E',
- replace: true,
- transclude: 'true',
- templateUrl: 'views/components/tabs/umb-tab.html'
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbTabs
- * @restrict A
- * @description Used to bind to bootstrap tab events so that sub directives can use this API to listen to tab changes
- **/
- angular.module('umbraco.directives').directive('umbTabs', function () {
- return {
- restrict: 'A',
- controller: function ($scope, $element, $attrs) {
- var callbacks = [];
- this.onTabShown = function (cb) {
- callbacks.push(cb);
- };
- function tabShown(event) {
- var curr = $(event.target);
- // active tab
- var prev = $(event.relatedTarget);
- // previous tab
- $scope.$apply();
- for (var c in callbacks) {
- callbacks[c].apply(this, [{
- current: curr,
- previous: prev
- }]);
- }
- }
- //NOTE: it MUST be done this way - binding to an ancestor element that exists
- // in the DOM to bind to the dynamic elements that will be created.
- // It would be nicer to create this event handler as a directive for which child
- // directives can attach to.
- $element.on('shown', '.nav-tabs a', tabShown);
- //ensure to unregister
- $scope.$on('$destroy', function () {
- $element.off('shown', '.nav-tabs a', tabShown);
- for (var c in callbacks) {
- delete callbacks[c];
- }
- callbacks = null;
- });
- }
- };
- });
- (function () {
- 'use strict';
- function UmbTabsContentDirective() {
- function link(scope, el, attr, ctrl) {
- scope.view = attr.view;
- }
- var directive = {
- restrict: 'E',
- replace: true,
- transclude: 'true',
- templateUrl: 'views/components/tabs/umb-tabs-content.html',
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbTabsContent', UmbTabsContentDirective);
- }());
- (function () {
- 'use strict';
- function UmbTabsNavDirective($timeout) {
- function link(scope, el, attr) {
- function activate() {
- $timeout(function () {
- //use bootstrap tabs API to show the first one
- el.find('a:first').tab('show');
- //enable the tab drop
- el.tabdrop();
- });
- }
- var unbindModelWatch = scope.$watch('model', function (newValue, oldValue) {
- activate();
- });
- scope.$on('$destroy', function () {
- //ensure to destroy tabdrop (unbinds window resize listeners)
- el.tabdrop('destroy');
- unbindModelWatch();
- });
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/tabs/umb-tabs-nav.html',
- scope: {
- model: '=',
- tabdrop: '=',
- idSuffix: '@'
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbTabsNav', UmbTabsNavDirective);
- }());
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbTree
- * @restrict E
- **/
- function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificationsService, $timeout, userService) {
- return {
- restrict: 'E',
- replace: true,
- terminal: false,
- scope: {
- section: '@',
- treealias: '@',
- hideoptions: '@',
- hideheader: '@',
- cachekey: '@',
- isdialog: '@',
- onlyinitialized: '@',
- //Custom query string arguments to pass in to the tree as a string, example: "startnodeid=123&something=value"
- customtreeparams: '@',
- eventhandler: '=',
- enablecheckboxes: '@',
- enablelistviewsearch: '@',
- enablelistviewexpand: '@'
- },
- compile: function (element, attrs) {
- //config
- //var showheader = (attrs.showheader !== 'false');
- var hideoptions = attrs.hideoptions === 'true' ? 'hide-options' : '';
- var template = '<ul class="umb-tree ' + hideoptions + '"><li class="root">';
- template += '<div ng-class="getNodeCssClass(tree.root)" ng-hide="hideheader" on-right-click="altSelect(tree.root, $event)">' + '<h5>' + '<a href="#/{{section}}" ng-click="select(tree.root, $event)" class="root-link"><i ng-if="enablecheckboxes == \'true\'" ng-class="selectEnabledNodeClass(tree.root)"></i> {{tree.name}}</a></h5>' + '<a class="umb-options" ng-hide="tree.root.isContainer || !tree.root.menuUrl" ng-click="options(tree.root, $event)" ng-swipe-right="options(tree.root, $event)"><i></i><i></i><i></i></a>' + '</div>';
- template += '<ul>' + '<umb-tree-item ng-repeat="child in tree.root.children" enablelistviewexpand="{{enablelistviewexpand}}" eventhandler="eventhandler" node="child" current-node="currentNode" tree="this" section="{{section}}" ng-animate="animation()"></umb-tree-item>' + '</ul>' + '</li>' + '</ul>';
- element.replaceWith(template);
- return function (scope, elem, attr, controller) {
- //flag to track the last loaded section when the tree 'un-loads'. We use this to determine if we should
- // re-load the tree again. For example, if we hover over 'content' the content tree is shown. Then we hover
- // outside of the tree and the tree 'un-loads'. When we re-hover over 'content', we don't want to re-load the
- // entire tree again since we already still have it in memory. Of course if the section is different we will
- // reload it. This saves a lot on processing if someone is navigating in and out of the same section many times
- // since it saves on data retreival and DOM processing.
- var lastSection = '';
- //setup a default internal handler
- if (!scope.eventhandler) {
- scope.eventhandler = $({});
- }
- //flag to enable/disable delete animations
- var deleteAnimations = false;
- /** Helper function to emit tree events */
- function emitEvent(eventName, args) {
- if (scope.eventhandler) {
- $(scope.eventhandler).trigger(eventName, args);
- }
- }
- /** This will deleteAnimations to true after the current digest */
- function enableDeleteAnimations() {
- //do timeout so that it re-enables them after this digest
- $timeout(function () {
- //enable delete animations
- deleteAnimations = true;
- }, 0, false);
- }
- /*this is the only external interface a tree has */
- function setupExternalEvents() {
- if (scope.eventhandler) {
- scope.eventhandler.clearCache = function (section) {
- treeService.clearCache({ section: section });
- };
- scope.eventhandler.load = function (section) {
- scope.section = section;
- loadTree();
- };
- scope.eventhandler.reloadNode = function (node) {
- if (!node) {
- node = scope.currentNode;
- }
- if (node) {
- scope.loadChildren(node, true);
- }
- };
- /**
- Used to do the tree syncing. If the args.tree is not specified we are assuming it has been
- specified previously using the _setActiveTreeType
- */
- scope.eventhandler.syncTree = function (args) {
- if (!args) {
- throw 'args cannot be null';
- }
- if (!args.path) {
- throw 'args.path cannot be null';
- }
- var deferred = $q.defer();
- //this is super complex but seems to be working in other places, here we're listening for our
- // own events, once the tree is sycned we'll resolve our promise.
- scope.eventhandler.one('treeSynced', function (e, syncArgs) {
- deferred.resolve(syncArgs);
- });
- //this should normally be set unless it is being called from legacy
- // code, so set the active tree type before proceeding.
- if (args.tree) {
- loadActiveTree(args.tree);
- }
- if (angular.isString(args.path)) {
- args.path = args.path.replace('"', '').split(',');
- }
- //reset current node selection
- //scope.currentNode = null;
- //Filter the path for root node ids (we don't want to pass in -1 or 'init')
- args.path = _.filter(args.path, function (item) {
- return item !== 'init' && item !== '-1';
- });
- //Once those are filtered we need to check if the current user has a special start node id,
- // if they do, then we're going to trim the start of the array for anything found from that start node
- // and previous so that the tree syncs properly. The tree syncs from the top down and if there are parts
- // of the tree's path in there that don't actually exist in the dom/model then syncing will not work.
- userService.getCurrentUser().then(function (userData) {
- var startNodes = [];
- for (var i = 0; i < userData.startContentIds; i++) {
- startNodes.push(userData.startContentIds[i]);
- }
- for (var j = 0; j < userData.startMediaIds; j++) {
- startNodes.push(userData.startMediaIds[j]);
- }
- _.each(startNodes, function (i) {
- var found = _.find(args.path, function (p) {
- return String(p) === String(i);
- });
- if (found) {
- args.path = args.path.splice(_.indexOf(args.path, found));
- }
- });
- loadPath(args.path, args.forceReload, args.activate);
- });
- return deferred.promise;
- };
- /**
- Internal method that should ONLY be used by the legacy API wrapper, the legacy API used to
- have to set an active tree and then sync, the new API does this in one method by using syncTree.
- loadChildren is optional but if it is set, it will set the current active tree and load the root
- node's children - this is synonymous with the legacy refreshTree method - again should not be used
- and should only be used for the legacy code to work.
- */
- scope.eventhandler._setActiveTreeType = function (treeAlias, loadChildren) {
- loadActiveTree(treeAlias, loadChildren);
- };
- }
- }
- //helper to load a specific path on the active tree as soon as its ready
- function loadPath(path, forceReload, activate) {
- if (scope.activeTree) {
- syncTree(scope.activeTree, path, forceReload, activate);
- } else {
- scope.eventhandler.one('activeTreeLoaded', function (e, args) {
- syncTree(args.tree, path, forceReload, activate);
- });
- }
- }
- //given a tree alias, this will search the current section tree for the specified tree alias and
- //set that to the activeTree
- //NOTE: loadChildren is ONLY used for legacy purposes, do not use this when syncing the tree as it will cause problems
- // since there will be double request and event handling operations.
- function loadActiveTree(treeAlias, loadChildren) {
- if (!treeAlias) {
- return;
- }
- scope.activeTree = undefined;
- function doLoad(tree) {
- var childrenAndSelf = [tree].concat(tree.children);
- scope.activeTree = _.find(childrenAndSelf, function (node) {
- if (node && node.metaData && node.metaData.treeAlias) {
- return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase();
- }
- return false;
- });
- if (!scope.activeTree) {
- throw 'Could not find the tree ' + treeAlias + ', activeTree has not been set';
- }
- //This is only used for the legacy tree method refreshTree!
- if (loadChildren) {
- scope.activeTree.expanded = true;
- scope.loadChildren(scope.activeTree, false).then(function () {
- emitEvent('activeTreeLoaded', { tree: scope.activeTree });
- });
- } else {
- emitEvent('activeTreeLoaded', { tree: scope.activeTree });
- }
- }
- if (scope.tree) {
- doLoad(scope.tree.root);
- } else {
- scope.eventhandler.one('treeLoaded', function (e, args) {
- doLoad(args.tree.root);
- });
- }
- }
- /** Method to load in the tree data */
- function loadTree() {
- if (!scope.loading && scope.section) {
- scope.loading = true;
- //anytime we want to load the tree we need to disable the delete animations
- deleteAnimations = false;
- //default args
- var args = {
- section: scope.section,
- tree: scope.treealias,
- cacheKey: scope.cachekey,
- isDialog: scope.isdialog ? scope.isdialog : false,
- onlyinitialized: scope.onlyinitialized
- };
- //add the extra query string params if specified
- if (scope.customtreeparams) {
- args['queryString'] = scope.customtreeparams;
- }
- treeService.getTree(args).then(function (data) {
- //set the data once we have it
- scope.tree = data;
- enableDeleteAnimations();
- scope.loading = false;
- //set the root as the current active tree
- scope.activeTree = scope.tree.root;
- emitEvent('treeLoaded', { tree: scope.tree });
- emitEvent('treeNodeExpanded', {
- tree: scope.tree,
- node: scope.tree.root,
- children: scope.tree.root.children
- });
- }, function (reason) {
- scope.loading = false;
- notificationsService.error('Tree Error', reason);
- });
- }
- }
- /** syncs the tree, the treeNode can be ANY tree node in the tree that requires syncing */
- function syncTree(treeNode, path, forceReload, activate) {
- deleteAnimations = false;
- treeService.syncTree({
- node: treeNode,
- path: path,
- forceReload: forceReload
- }).then(function (data) {
- if (activate === undefined || activate === true) {
- scope.currentNode = data;
- }
- emitEvent('treeSynced', {
- node: data,
- activate: activate
- });
- enableDeleteAnimations();
- });
- }
- /** Returns the css classses assigned to the node (div element) */
- scope.getNodeCssClass = function (node) {
- if (!node) {
- return '';
- }
- //TODO: This is called constantly because as a method in a template it's re-evaluated pretty much all the time
- // it would be better if we could cache the processing. The problem is that some of these things are dynamic.
- var css = [];
- if (node.cssClasses) {
- _.each(node.cssClasses, function (c) {
- css.push(c);
- });
- }
- return css.join(' ');
- };
- scope.selectEnabledNodeClass = function (node) {
- return node ? node.selected ? 'icon umb-tree-icon sprTree icon-check green temporary' : '' : '';
- };
- /** method to set the current animation for the node.
- * This changes dynamically based on if we are changing sections or just loading normal tree data.
- * When changing sections we don't want all of the tree-ndoes to do their 'leave' animations.
- */
- scope.animation = function () {
- if (deleteAnimations && scope.tree && scope.tree.root && scope.tree.root.expanded) {
- return { leave: 'tree-node-delete-leave' };
- } else {
- return {};
- }
- };
- /* helper to force reloading children of a tree node */
- scope.loadChildren = function (node, forceReload) {
- var deferred = $q.defer();
- //emit treeNodeExpanding event, if a callback object is set on the tree
- emitEvent('treeNodeExpanding', {
- tree: scope.tree,
- node: node
- });
- //standardising
- if (!node.children) {
- node.children = [];
- }
- if (forceReload || node.hasChildren && node.children.length === 0) {
- //get the children from the tree service
- treeService.loadNodeChildren({
- node: node,
- section: scope.section
- }).then(function (data) {
- //emit expanded event
- emitEvent('treeNodeExpanded', {
- tree: scope.tree,
- node: node,
- children: data
- });
- enableDeleteAnimations();
- deferred.resolve(data);
- });
- } else {
- emitEvent('treeNodeExpanded', {
- tree: scope.tree,
- node: node,
- children: node.children
- });
- node.expanded = true;
- enableDeleteAnimations();
- deferred.resolve(node.children);
- }
- return deferred.promise;
- };
- /**
- Method called when the options button next to the root node is called.
- The tree doesnt know about this, so it raises an event to tell the parent controller
- about it.
- */
- scope.options = function (n, ev) {
- emitEvent('treeOptionsClick', {
- element: elem,
- node: n,
- event: ev
- });
- };
- /**
- Method called when an item is clicked in the tree, this passes the
- DOM element, the tree node object and the original click
- and emits it as a treeNodeSelect element if there is a callback object
- defined on the tree
- */
- scope.select = function (n, ev) {
- if (n.metaData && n.metaData.noAccess === true) {
- ev.preventDefault();
- return;
- }
- //on tree select we need to remove the current node -
- // whoever handles this will need to make sure the correct node is selected
- //reset current node selection
- scope.currentNode = null;
- emitEvent('treeNodeSelect', {
- element: elem,
- node: n,
- event: ev
- });
- };
- scope.altSelect = function (n, ev) {
- emitEvent('treeNodeAltSelect', {
- element: elem,
- tree: scope.tree,
- node: n,
- event: ev
- });
- };
- //watch for section changes
- scope.$watch('section', function (newVal, oldVal) {
- if (!scope.tree) {
- loadTree();
- }
- if (!newVal) {
- //store the last section loaded
- lastSection = oldVal;
- } else if (newVal !== oldVal && newVal !== lastSection) {
- //only reload the tree data and Dom if the newval is different from the old one
- // and if the last section loaded is different from the requested one.
- loadTree();
- //store the new section to be loaded as the last section
- //clear any active trees to reset lookups
- lastSection = newVal;
- }
- });
- setupExternalEvents();
- loadTree();
- };
- }
- };
- }
- angular.module('umbraco.directives').directive('umbTree', umbTreeDirective);
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbTreeItem
- * @element li
- * @function
- *
- * @description
- * Renders a list item, representing a single node in the tree.
- * Includes element to toggle children, and a menu toggling button
- *
- * **note:** This directive is only used internally in the umbTree directive
- *
- * @example
- <example module="umbraco">
- <file name="index.html">
- <umb-tree-item ng-repeat="child in tree.children" node="child" callback="callback" section="content"></umb-tree-item>
- </file>
- </example>
- */
- angular.module('umbraco.directives').directive('umbTreeItem', function ($compile, $http, $templateCache, $interpolate, $log, $location, $rootScope, $window, treeService, $timeout, localizationService) {
- return {
- restrict: 'E',
- replace: true,
- scope: {
- section: '@',
- eventhandler: '=',
- currentNode: '=',
- enablelistviewexpand: '@',
- node: '=',
- tree: '='
- },
- //TODO: Remove more of the binding from this template and move the DOM manipulation to be manually done in the link function,
- // this will greatly improve performance since there's potentially a lot of nodes being rendered = a LOT of watches!
- template: '<li ng-class="{\'current\': (node == currentNode), \'has-children\': node.hasChildren}" on-right-click="altSelect(node, $event)">' + '<div ng-class="getNodeCssClass(node)" ng-swipe-right="options(node, $event)" ng-dblclick="load(node)" >' + //NOTE: This ins element is used to display the search icon if the node is a container/listview and the tree is currently in dialog
- //'<ins ng-if="tree.enablelistviewsearch && node.metaData.isContainer" class="umb-tree-node-search icon-search" ng-click="searchNode(node, $event)" alt="searchAltText"></ins>' +
- '<ins ng-class="{\'icon-navigation-right\': !node.expanded || node.metaData.isContainer, \'icon-navigation-down\': node.expanded && !node.metaData.isContainer}" ng-click="load(node)"> </ins>' + '<i class="icon umb-tree-icon sprTree" ng-click="select(node, $event)"></i>' + '<a class="umb-tree-item__label" href="#/{{node.routePath}}" ng-click="select(node, $event)"></a>' + //NOTE: These are the 'option' elipses
- '<a class="umb-options" ng-click="options(node, $event)"><i></i><i></i><i></i></a>' + '<div ng-show="node.loading" class="l"><div></div></div>' + '</div>' + '</li>',
- link: function (scope, element, attrs) {
- localizationService.localize('general_search').then(function (value) {
- scope.searchAltText = value;
- });
- //flag to enable/disable delete animations, default for an item is true
- var deleteAnimations = true;
- // Helper function to emit tree events
- function emitEvent(eventName, args) {
- if (scope.eventhandler) {
- $(scope.eventhandler).trigger(eventName, args);
- }
- }
- // updates the node's DOM/styles
- function setupNodeDom(node, tree) {
- //get the first div element
- element.children(':first') //set the padding
- .css('padding-left', node.level * 20 + 'px');
- //toggle visibility of last 'ins' depending on children
- //visibility still ensure the space is "reserved", so both nodes with and without children are aligned.
- if (node.hasChildren || node.metaData.isContainer && scope.enablelistviewexpand === 'true') {
- element.find('ins').last().css('visibility', 'visible');
- } else {
- element.find('ins').last().css('visibility', 'hidden');
- }
- var icon = element.find('i:first');
- icon.addClass(node.cssClass);
- icon.attr('title', node.routePath);
- element.find('a:first').text(node.name);
- if (!node.menuUrl) {
- element.find('a.umb-options').remove();
- }
- if (node.style) {
- element.find('i:first').attr('style', node.style);
- }
- }
- //This will deleteAnimations to true after the current digest
- function enableDeleteAnimations() {
- //do timeout so that it re-enables them after this digest
- $timeout(function () {
- //enable delete animations
- deleteAnimations = true;
- }, 0, false);
- }
- /** Returns the css classses assigned to the node (div element) */
- scope.getNodeCssClass = function (node) {
- if (!node) {
- return '';
- }
- //TODO: This is called constantly because as a method in a template it's re-evaluated pretty much all the time
- // it would be better if we could cache the processing. The problem is that some of these things are dynamic.
- var css = [];
- if (node.cssClasses) {
- _.each(node.cssClasses, function (c) {
- css.push(c);
- });
- }
- if (node.selected) {
- css.push('umb-tree-node-checked');
- }
- return css.join(' ');
- };
- //add a method to the node which we can use to call to update the node data if we need to ,
- // this is done by sync tree, we don't want to add a $watch for each node as that would be crazy insane slow
- // so we have to do this
- scope.node.updateNodeData = function (newNode) {
- _.extend(scope.node, newNode);
- //now update the styles
- setupNodeDom(scope.node, scope.tree);
- };
- /**
- Method called when the options button next to a node is called
- In the main tree this opens the menu, but internally the tree doesnt
- know about this, so it simply raises an event to tell the parent controller
- about it.
- */
- scope.options = function (n, ev) {
- emitEvent('treeOptionsClick', {
- element: element,
- tree: scope.tree,
- node: n,
- event: ev
- });
- };
- /**
- Method called when an item is clicked in the tree, this passes the
- DOM element, the tree node object and the original click
- and emits it as a treeNodeSelect element if there is a callback object
- defined on the tree
- */
- scope.select = function (n, ev) {
- if (ev.ctrlKey || ev.shiftKey || ev.metaKey || ev.button && ev.button === 1 // middle click, >IE9 + everyone else
- ) {
- return;
- }
- if (n.metaData && n.metaData.noAccess === true) {
- ev.preventDefault();
- return;
- }
- emitEvent('treeNodeSelect', {
- element: element,
- tree: scope.tree,
- node: n,
- event: ev
- });
- ev.preventDefault();
- };
- /**
- Method called when an item is right-clicked in the tree, this passes the
- DOM element, the tree node object and the original click
- and emits it as a treeNodeSelect element if there is a callback object
- defined on the tree
- */
- scope.altSelect = function (n, ev) {
- emitEvent('treeNodeAltSelect', {
- element: element,
- tree: scope.tree,
- node: n,
- event: ev
- });
- };
- /** method to set the current animation for the node.
- * This changes dynamically based on if we are changing sections or just loading normal tree data.
- * When changing sections we don't want all of the tree-ndoes to do their 'leave' animations.
- */
- scope.animation = function () {
- if (scope.node.showHideAnimation) {
- return scope.node.showHideAnimation;
- }
- if (deleteAnimations && scope.node.expanded) {
- return { leave: 'tree-node-delete-leave' };
- } else {
- return {};
- }
- };
- /**
- Method called when a node in the tree is expanded, when clicking the arrow
- takes the arrow DOM element and node data as parameters
- emits treeNodeCollapsing event if already expanded and treeNodeExpanding if collapsed
- */
- scope.load = function (node) {
- if (node.expanded && !node.metaData.isContainer) {
- deleteAnimations = false;
- emitEvent('treeNodeCollapsing', {
- tree: scope.tree,
- node: node,
- element: element
- });
- node.expanded = false;
- } else {
- scope.loadChildren(node, false);
- }
- };
- /* helper to force reloading children of a tree node */
- scope.loadChildren = function (node, forceReload) {
- //emit treeNodeExpanding event, if a callback object is set on the tree
- emitEvent('treeNodeExpanding', {
- tree: scope.tree,
- node: node
- });
- if (node.hasChildren && (forceReload || !node.children || angular.isArray(node.children) && node.children.length === 0)) {
- //get the children from the tree service
- treeService.loadNodeChildren({
- node: node,
- section: scope.section
- }).then(function (data) {
- //emit expanded event
- emitEvent('treeNodeExpanded', {
- tree: scope.tree,
- node: node,
- children: data
- });
- enableDeleteAnimations();
- });
- } else {
- emitEvent('treeNodeExpanded', {
- tree: scope.tree,
- node: node,
- children: node.children
- });
- node.expanded = true;
- enableDeleteAnimations();
- }
- };
- //if the current path contains the node id, we will auto-expand the tree item children
- setupNodeDom(scope.node, scope.tree);
- // load the children if the current user don't have access to the node
- // it is used to auto expand the tree to the start nodes the user has access to
- if (scope.node.hasChildren && scope.node.metaData.noAccess) {
- scope.loadChildren(scope.node);
- }
- var template = '<ul ng-class="{collapsed: !node.expanded}"><umb-tree-item ng-repeat="child in node.children" enablelistviewexpand="{{enablelistviewexpand}}" eventhandler="eventhandler" tree="tree" current-node="currentNode" node="child" section="{{section}}" ng-animate="animation()"></umb-tree-item></ul>';
- var newElement = angular.element(template);
- $compile(newElement)(scope);
- element.append(newElement);
- }
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbTreeSearchBox
- * @function
- * @element ANY
- * @restrict E
- **/
- function treeSearchBox(localizationService, searchService, $q) {
- return {
- scope: {
- searchFromId: '@',
- searchFromName: '@',
- showSearch: '@',
- section: '@',
- hideSearchCallback: '=',
- searchCallback: '='
- },
- restrict: 'E',
- // restrict to an element
- replace: true,
- // replace the html element with the template
- templateUrl: 'views/components/tree/umb-tree-search-box.html',
- link: function (scope, element, attrs, ctrl) {
- scope.term = '';
- scope.hideSearch = function () {
- scope.term = '';
- scope.hideSearchCallback();
- };
- localizationService.localize('general_typeToSearch').then(function (value) {
- scope.searchPlaceholderText = value;
- });
- if (!scope.showSearch) {
- scope.showSearch = 'false';
- }
- //used to cancel any request in progress if another one needs to take it's place
- var canceler = null;
- function performSearch() {
- if (scope.term) {
- scope.results = [];
- //a canceler exists, so perform the cancelation operation and reset
- if (canceler) {
- canceler.resolve();
- canceler = $q.defer();
- } else {
- canceler = $q.defer();
- }
- var searchArgs = {
- term: scope.term,
- canceler: canceler
- };
- //append a start node context if there is one
- if (scope.searchFromId) {
- searchArgs['searchFrom'] = scope.searchFromId;
- }
- searcher(searchArgs).then(function (data) {
- scope.searchCallback(data);
- //set back to null so it can be re-created
- canceler = null;
- });
- }
- }
- scope.$watch('term', _.debounce(function (newVal, oldVal) {
- scope.$apply(function () {
- if (newVal !== null && newVal !== undefined && newVal !== oldVal) {
- performSearch();
- }
- });
- }, 200));
- var searcher = searchService.searchContent;
- //search
- if (scope.section === 'member') {
- searcher = searchService.searchMembers;
- } else if (scope.section === 'media') {
- searcher = searchService.searchMedia;
- }
- }
- };
- }
- angular.module('umbraco.directives').directive('umbTreeSearchBox', treeSearchBox);
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbTreeSearchResults
- * @function
- * @element ANY
- * @restrict E
- **/
- function treeSearchResults() {
- return {
- scope: {
- results: '=',
- selectResultCallback: '='
- },
- restrict: 'E',
- // restrict to an element
- replace: true,
- // replace the html element with the template
- templateUrl: 'views/components/tree/umb-tree-search-results.html',
- link: function (scope, element, attrs, ctrl) {
- }
- };
- }
- angular.module('umbraco.directives').directive('umbTreeSearchResults', treeSearchResults);
- (function () {
- 'use strict';
- function AceEditorDirective(umbAceEditorConfig, assetsService, angularHelper) {
- /**
- * Sets editor options such as the wrapping mode or the syntax checker.
- *
- * The supported options are:
- *
- * <ul>
- * <li>showGutter</li>
- * <li>useWrapMode</li>
- * <li>onLoad</li>
- * <li>theme</li>
- * <li>mode</li>
- * </ul>
- *
- * @param acee
- * @param session ACE editor session
- * @param {object} opts Options to be set
- */
- var setOptions = function (acee, session, opts) {
- // sets the ace worker path, if running from concatenated
- // or minified source
- if (angular.isDefined(opts.workerPath)) {
- var config = window.ace.require('ace/config');
- config.set('workerPath', opts.workerPath);
- }
- // ace requires loading
- if (angular.isDefined(opts.require)) {
- opts.require.forEach(function (n) {
- window.ace.require(n);
- });
- }
- // Boolean options
- if (angular.isDefined(opts.showGutter)) {
- acee.renderer.setShowGutter(opts.showGutter);
- }
- if (angular.isDefined(opts.useWrapMode)) {
- session.setUseWrapMode(opts.useWrapMode);
- }
- if (angular.isDefined(opts.showInvisibles)) {
- acee.renderer.setShowInvisibles(opts.showInvisibles);
- }
- if (angular.isDefined(opts.showIndentGuides)) {
- acee.renderer.setDisplayIndentGuides(opts.showIndentGuides);
- }
- if (angular.isDefined(opts.useSoftTabs)) {
- session.setUseSoftTabs(opts.useSoftTabs);
- }
- if (angular.isDefined(opts.showPrintMargin)) {
- acee.setShowPrintMargin(opts.showPrintMargin);
- }
- // commands
- if (angular.isDefined(opts.disableSearch) && opts.disableSearch) {
- acee.commands.addCommands([{
- name: 'unfind',
- bindKey: {
- win: 'Ctrl-F',
- mac: 'Command-F'
- },
- exec: function () {
- return false;
- },
- readOnly: true
- }]);
- }
- // Basic options
- if (angular.isString(opts.theme)) {
- acee.setTheme('ace/theme/' + opts.theme);
- }
- if (angular.isString(opts.mode)) {
- session.setMode('ace/mode/' + opts.mode);
- }
- // Advanced options
- if (angular.isDefined(opts.firstLineNumber)) {
- if (angular.isNumber(opts.firstLineNumber)) {
- session.setOption('firstLineNumber', opts.firstLineNumber);
- } else if (angular.isFunction(opts.firstLineNumber)) {
- session.setOption('firstLineNumber', opts.firstLineNumber());
- }
- }
- // advanced options
- var key, obj;
- if (angular.isDefined(opts.advanced)) {
- for (key in opts.advanced) {
- // create a javascript object with the key and value
- obj = {
- name: key,
- value: opts.advanced[key]
- };
- // try to assign the option to the ace editor
- acee.setOption(obj.name, obj.value);
- }
- }
- // advanced options for the renderer
- if (angular.isDefined(opts.rendererOptions)) {
- for (key in opts.rendererOptions) {
- // create a javascript object with the key and value
- obj = {
- name: key,
- value: opts.rendererOptions[key]
- };
- // try to assign the option to the ace editor
- acee.renderer.setOption(obj.name, obj.value);
- }
- }
- // onLoad callbacks
- angular.forEach(opts.callbacks, function (cb) {
- if (angular.isFunction(cb)) {
- cb(acee);
- }
- });
- };
- function link(scope, el, attr, ngModel) {
- // Load in ace library
- assetsService.load([
- 'lib/ace-builds/src-min-noconflict/ace.js',
- 'lib/ace-builds/src-min-noconflict/ext-language_tools.js'
- ]).then(function () {
- if (angular.isUndefined(window.ace)) {
- throw new Error('ui-ace need ace to work... (o rly?)');
- } else {
- // init editor
- init();
- }
- });
- function init() {
- /**
- * Corresponds the umbAceEditorConfig ACE configuration.
- * @type object
- */
- var options = umbAceEditorConfig.ace || {};
- /**
- * umbAceEditorConfig merged with user options via json in attribute or data binding
- * @type object
- */
- var opts = angular.extend({}, options, scope.umbAceEditor);
- //load ace libraries here...
- /**
- * ACE editor
- * @type object
- */
- var acee = window.ace.edit(el[0]);
- acee.$blockScrolling = Infinity;
- /**
- * ACE editor session.
- * @type object
- * @see [EditSession]{@link http://ace.c9.io/#nav=api&api=edit_session}
- */
- var session = acee.getSession();
- /**
- * Reference to a change listener created by the listener factory.
- * @function
- * @see listenerFactory.onChange
- */
- var onChangeListener;
- /**
- * Reference to a blur listener created by the listener factory.
- * @function
- * @see listenerFactory.onBlur
- */
- var onBlurListener;
- /**
- * Calls a callback by checking its existing. The argument list
- * is variable and thus this function is relying on the arguments
- * object.
- * @throws {Error} If the callback isn't a function
- */
- var executeUserCallback = function () {
- /**
- * The callback function grabbed from the array-like arguments
- * object. The first argument should always be the callback.
- *
- * @see [arguments]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments}
- * @type {*}
- */
- var callback = arguments[0];
- /**
- * Arguments to be passed to the callback. These are taken
- * from the array-like arguments object. The first argument
- * is stripped because that should be the callback function.
- *
- * @see [arguments]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments}
- * @type {Array}
- */
- var args = Array.prototype.slice.call(arguments, 1);
- if (angular.isDefined(callback)) {
- scope.$evalAsync(function () {
- if (angular.isFunction(callback)) {
- callback(args);
- } else {
- throw new Error('ui-ace use a function as callback.');
- }
- });
- }
- };
- /**
- * Listener factory. Until now only change listeners can be created.
- * @type object
- */
- var listenerFactory = {
- /**
- * Creates a change listener which propagates the change event
- * and the editor session to the callback from the user option
- * onChange. It might be exchanged during runtime, if this
- * happens the old listener will be unbound.
- *
- * @param callback callback function defined in the user options
- * @see onChangeListener
- */
- onChange: function (callback) {
- return function (e) {
- var newValue = session.getValue();
- angularHelper.safeApply(scope, function () {
- scope.model = newValue;
- });
- executeUserCallback(callback, e, acee);
- };
- },
- /**
- * Creates a blur listener which propagates the editor session
- * to the callback from the user option onBlur. It might be
- * exchanged during runtime, if this happens the old listener
- * will be unbound.
- *
- * @param callback callback function defined in the user options
- * @see onBlurListener
- */
- onBlur: function (callback) {
- return function () {
- executeUserCallback(callback, acee);
- };
- }
- };
- attr.$observe('readonly', function (value) {
- acee.setReadOnly(!!value || value === '');
- });
- // Value Blind
- if (scope.model) {
- session.setValue(scope.model);
- }
- // Listen for option updates
- var updateOptions = function (current, previous) {
- if (current === previous) {
- return;
- }
- opts = angular.extend({}, options, scope.umbAceEditor);
- opts.callbacks = [opts.onLoad];
- if (opts.onLoad !== options.onLoad) {
- // also call the global onLoad handler
- opts.callbacks.unshift(options.onLoad);
- }
- // EVENTS
- // unbind old change listener
- session.removeListener('change', onChangeListener);
- // bind new change listener
- onChangeListener = listenerFactory.onChange(opts.onChange);
- session.on('change', onChangeListener);
- // unbind old blur listener
- //session.removeListener('blur', onBlurListener);
- acee.removeListener('blur', onBlurListener);
- // bind new blur listener
- onBlurListener = listenerFactory.onBlur(opts.onBlur);
- acee.on('blur', onBlurListener);
- setOptions(acee, session, opts);
- };
- scope.$watch(scope.umbAceEditor, updateOptions, /* deep watch */
- true);
- // set the options here, even if we try to watch later, if this
- // line is missing things go wrong (and the tests will also fail)
- updateOptions(options);
- el.on('$destroy', function () {
- acee.session.$stopWorker();
- acee.destroy();
- });
- scope.$watch(function () {
- return [
- el[0].offsetWidth,
- el[0].offsetHeight
- ];
- }, function () {
- acee.resize();
- acee.renderer.updateFull();
- }, true);
- }
- }
- var directive = {
- restrict: 'EA',
- scope: {
- 'umbAceEditor': '=',
- 'model': '='
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').constant('umbAceEditorConfig', {}).directive('umbAceEditor', AceEditorDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbAvatar
- @restrict E
- @scope
- @description
- Use this directive to render an avatar.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.Controller as vm">
- <umb-avatar
- size="xs"
- img-src="{{vm.avatar[0].value}}"
- img-srcset="{{vm.avatar[1].value}} 2x, {{vm.avatar[2].value}} 3x">
- </umb-avatar>
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
- function Controller() {
- var vm = this;
- vm.avatar = [
- { value: "assets/logo.png" },
- { value: "assets/logo@2x.png" },
- { value: "assets/logo@3x.png" }
- ];
- }
- angular.module("umbraco").controller("My.Controller", Controller);
- })();
- </pre>
- @param {string} size (<code>attribute</code>): The size of the avatar (xs, s, m, l, xl).
- @param {string} img-src (<code>attribute</code>): The image source to the avatar.
- @param {string} img-srcset (<code>atribute</code>): Reponsive support for the image source.
- **/
- (function () {
- 'use strict';
- function AvatarDirective() {
- function link(scope, element, attrs, ctrl) {
- var eventBindings = [];
- scope.initials = '';
- function onInit() {
- if (!scope.unknownChar) {
- scope.unknownChar = '?';
- }
- scope.initials = getNameInitials(scope.name);
- }
- function getNameInitials(name) {
- if (name) {
- var names = name.split(' '), initials = names[0].substring(0, 1);
- if (names.length > 1) {
- initials += names[names.length - 1].substring(0, 1);
- }
- return initials.toUpperCase();
- }
- return null;
- }
- eventBindings.push(scope.$watch('name', function (newValue, oldValue) {
- if (newValue === oldValue) {
- return;
- }
- if (oldValue === undefined || newValue === undefined) {
- return;
- }
- scope.initials = getNameInitials(newValue);
- }));
- onInit();
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/umb-avatar.html',
- scope: {
- size: '@',
- name: '@',
- color: '@',
- imgSrc: '@',
- imgSrcset: '@',
- unknownChar: '@'
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbAvatar', AvatarDirective);
- }());
- (function () {
- 'use strict';
- function BadgeDirective() {
- var directive = {
- restrict: 'E',
- replace: true,
- transclude: true,
- templateUrl: 'views/components/umb-badge.html',
- scope: {
- size: '@?',
- color: '@?'
- }
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbBadge', BadgeDirective);
- }());
- (function () {
- 'use strict';
- function CheckmarkDirective() {
- var directive = {
- restrict: 'E',
- replace: true,
- transclude: true,
- templateUrl: 'views/components/umb-checkmark.html',
- scope: {
- size: '@?',
- checked: '='
- }
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbCheckmark', CheckmarkDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbChildSelector
- @restrict E
- @scope
- @description
- Use this directive to render a ui component for selecting child items to a parent node.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.Controller as vm">
- <umb-child-selector
- selected-children="vm.selectedChildren"
- available-children="vm.availableChildren"
- parent-name="vm.name"
- parent-icon="vm.icon"
- parent-id="vm.id"
- on-add="vm.addChild"
- on-remove="vm.removeChild">
- </umb-child-selector>
- <!-- use overlay to select children from -->
- <umb-overlay
- ng-if="vm.overlay.show"
- model="vm.overlay"
- position="target"
- view="vm.overlay.view">
- </umb-overlay>
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
- function Controller() {
- var vm = this;
- vm.id = 1;
- vm.name = "My Parent element";
- vm.icon = "icon-document";
- vm.selectedChildren = [];
- vm.availableChildren = [
- {
- id: 1,
- alias: "item1",
- name: "Item 1",
- icon: "icon-document"
- },
- {
- id: 2,
- alias: "item2",
- name: "Item 2",
- icon: "icon-document"
- }
- ];
- vm.addChild = addChild;
- vm.removeChild = removeChild;
- function addChild($event) {
- vm.overlay = {
- view: "itempicker",
- title: "Choose child",
- availableItems: vm.availableChildren,
- selectedItems: vm.selectedChildren,
- event: $event,
- show: true,
- submit: function(model) {
- // add selected child
- vm.selectedChildren.push(model.selectedItem);
- // close overlay
- vm.overlay.show = false;
- vm.overlay = null;
- }
- };
- }
- function removeChild($index) {
- vm.selectedChildren.splice($index, 1);
- }
- }
- angular.module("umbraco").controller("My.Controller", Controller);
- })();
- </pre>
- @param {array} selectedChildren (<code>binding</code>): Array of selected children.
- @param {array} availableChildren (<code>binding</code>: Array of items available for selection.
- @param {string} parentName (<code>binding</code>): The parent name.
- @param {string} parentIcon (<code>binding</code>): The parent icon.
- @param {number} parentId (<code>binding</code>): The parent id.
- @param {callback} onRemove (<code>binding</code>): Callback when the remove button is clicked on an item.
- <h3>The callback returns:</h3>
- <ul>
- <li><code>child</code>: The selected item.</li>
- <li><code>$index</code>: The selected item index.</li>
- </ul>
- @param {callback} onAdd (<code>binding</code>): Callback when the add button is clicked.
- <h3>The callback returns:</h3>
- <ul>
- <li><code>$event</code>: The select event.</li>
- </ul>
- **/
- (function () {
- 'use strict';
- function ChildSelectorDirective() {
- function link(scope, el, attr, ctrl) {
- var eventBindings = [];
- scope.dialogModel = {};
- scope.showDialog = false;
- scope.removeChild = function (selectedChild, $index) {
- if (scope.onRemove) {
- scope.onRemove(selectedChild, $index);
- }
- };
- scope.addChild = function ($event) {
- if (scope.onAdd) {
- scope.onAdd($event);
- }
- };
- function syncParentName() {
- // update name on available item
- angular.forEach(scope.availableChildren, function (availableChild) {
- if (availableChild.id === scope.parentId) {
- availableChild.name = scope.parentName;
- }
- });
- // update name on selected child
- angular.forEach(scope.selectedChildren, function (selectedChild) {
- if (selectedChild.id === scope.parentId) {
- selectedChild.name = scope.parentName;
- }
- });
- }
- function syncParentIcon() {
- // update icon on available item
- angular.forEach(scope.availableChildren, function (availableChild) {
- if (availableChild.id === scope.parentId) {
- availableChild.icon = scope.parentIcon;
- }
- });
- // update icon on selected child
- angular.forEach(scope.selectedChildren, function (selectedChild) {
- if (selectedChild.id === scope.parentId) {
- selectedChild.icon = scope.parentIcon;
- }
- });
- }
- eventBindings.push(scope.$watch('parentName', function (newValue, oldValue) {
- if (newValue === oldValue) {
- return;
- }
- if (oldValue === undefined || newValue === undefined) {
- return;
- }
- syncParentName();
- }));
- eventBindings.push(scope.$watch('parentIcon', function (newValue, oldValue) {
- if (newValue === oldValue) {
- return;
- }
- if (oldValue === undefined || newValue === undefined) {
- return;
- }
- syncParentIcon();
- }));
- // clean up
- scope.$on('$destroy', function () {
- // unbind watchers
- for (var e in eventBindings) {
- eventBindings[e]();
- }
- });
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/umb-child-selector.html',
- scope: {
- selectedChildren: '=',
- availableChildren: '=',
- parentName: '=',
- parentIcon: '=',
- parentId: '=',
- onRemove: '=',
- onAdd: '='
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbChildSelector', ChildSelectorDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbClipboard
- @restrict E
- @scope
- @description
- <strong>Added in Umbraco v. 7.7:</strong> Use this directive to copy content to the clipboard
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.ClipBoardController as vm">
-
- <!-- Copy text from an element -->
- <div id="copy-text">Copy me!</div>
-
- <umb-button
- umb-clipboard
- umb-clipboard-success="vm.copySuccess()"
- umb-clipboard-error="vm.copyError()"
- umb-clipboard-target="#copy-text"
- state="vm.clipboardButtonState"
- type="button"
- label="Copy">
- </umb-button>
- <!-- Cut text from a textarea -->
- <textarea id="cut-text" ng-model="vm.cutText"></textarea>
- <umb-button
- umb-clipboard
- umb-clipboard-success="vm.copySuccess()"
- umb-clipboard-error="vm.copyError()"
- umb-clipboard-target="#cut-text"
- umb-clipboard-action="cut"
- state="vm.clipboardButtonState"
- type="button"
- label="Copy">
- </umb-button>
- <!-- Copy text without an element -->
- <umb-button
- ng-if="vm.copyText"
- umb-clipboard
- umb-clipboard-success="vm.copySuccess()"
- umb-clipboard-error="vm.copyError()"
- umb-clipboard-text="vm.copyText"
- state="vm.clipboardButtonState"
- type="button"
- label="Copy">
- </umb-button>
-
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
- function Controller() {
- var vm = this;
- vm.copyText = "Copy text without element";
- vm.cutText = "Text to cut";
- vm.copySuccess = copySuccess;
- vm.copyError = copyError;
- function copySuccess() {
- vm.clipboardButtonState = "success";
- }
-
- function copyError() {
- vm.clipboardButtonState = "error";
- }
- }
- angular.module("umbraco").controller("My.ClipBoardController", Controller);
- })();
- </pre>
- @param {callback} umbClipboardSuccess (<code>expression</code>): Callback function when the content is copied.
- @param {callback} umbClipboardError (<code>expression</code>): Callback function if the copy fails.
- @param {string} umbClipboardTarget (<code>attribute</code>): The target element to copy.
- @param {string} umbClipboardAction (<code>attribute</code>): Specify if you want to copy or cut content ("copy", "cut"). Cut only works on <code>input</code> and <code>textarea</code> elements.
- @param {string} umbClipboardText (<code>attribute</code>): Use this attribute if you don't have an element to copy from.
- **/
- (function () {
- 'use strict';
- function umbClipboardDirective($timeout, assetsService) {
- function link(scope, element, attrs, ctrl) {
- var clipboard;
- var target = element[0];
- assetsService.loadJs('lib/clipboard/clipboard.min.js').then(function () {
- if (scope.umbClipboardTarget) {
- target.setAttribute('data-clipboard-target', scope.umbClipboardTarget);
- }
- if (scope.umbClipboardAction) {
- target.setAttribute('data-clipboard-action', scope.umbClipboardAction);
- }
- if (scope.umbClipboardText) {
- target.setAttribute('data-clipboard-text', scope.umbClipboardText);
- }
- clipboard = new Clipboard(target);
- clipboard.on('success', function (e) {
- e.clearSelection();
- if (scope.umbClipboardSuccess) {
- scope.$apply(function () {
- scope.umbClipboardSuccess({ e: e });
- });
- }
- });
- clipboard.on('error', function (e) {
- if (scope.umbClipboardError) {
- scope.$apply(function () {
- scope.umbClipboardError({ e: e });
- });
- }
- });
- });
- // clean up
- scope.$on('$destroy', function () {
- clipboard.destroy();
- });
- }
- ////////////
- var directive = {
- restrict: 'A',
- scope: {
- umbClipboardSuccess: '&?',
- umbClipboardError: '&?',
- umbClipboardTarget: '@?',
- umbClipboardAction: '@?',
- umbClipboardText: '=?'
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbClipboard', umbClipboardDirective);
- }());
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbConfirm
- * @function
- * @description
- * A confirmation dialog
- *
- * @restrict E
- */
- function confirmDirective() {
- return {
- restrict: 'E',
- // restrict to an element
- replace: true,
- // replace the html element with the template
- templateUrl: 'views/components/umb-confirm.html',
- scope: {
- onConfirm: '=',
- onCancel: '=',
- caption: '@'
- },
- link: function (scope, element, attr, ctrl) {
- }
- };
- }
- angular.module('umbraco.directives').directive('umbConfirm', confirmDirective);
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbConfirmAction
- @restrict E
- @scope
- @description
- <p>Use this directive to toggle a confirmation prompt for an action.
- The prompt consists of a checkmark and a cross to confirm or cancel the action.
- The prompt can be opened in four direction up, down, left or right.</p>
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.Controller as vm">
- <div class="my-action" style="position:relative;">
- <i class="icon-trash" ng-click="vm.showPrompt()"></i>
- <umb-confirm-action
- ng-if="vm.promptIsVisible"
- direction="left"
- on-confirm="vm.confirmAction()"
- on-cancel="vm.hidePrompt()">
- </umb-confirm-action>
- </div>
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
- function Controller() {
- var vm = this;
- vm.promptIsVisible = false;
- vm.confirmAction = confirmAction;
- vm.showPrompt = showPrompt;
- vm.hidePrompt = hidePrompt;
- function confirmAction() {
- // confirm logic here
- }
- function showPrompt() {
- vm.promptIsVisible = true;
- }
- function hidePrompt() {
- vm.promptIsVisible = false;
- }
- }
- angular.module("umbraco").controller("My.Controller", Controller);
- })();
- </pre>
- @param {string} direction The direction the prompt opens ("up", "down", "left", "right").
- @param {callback} onConfirm Callback when the checkmark is clicked.
- @param {callback} onCancel Callback when the cross is clicked.
- **/
- (function () {
- 'use strict';
- function ConfirmAction() {
- function link(scope, el, attr, ctrl) {
- scope.clickConfirm = function () {
- if (scope.onConfirm) {
- scope.onConfirm();
- }
- };
- scope.clickCancel = function () {
- if (scope.onCancel) {
- scope.onCancel();
- }
- };
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/umb-confirm-action.html',
- scope: {
- direction: '@',
- onConfirm: '&',
- onCancel: '&'
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbConfirmAction', ConfirmAction);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbContentGrid
- @restrict E
- @scope
- @description
- Use this directive to generate a list of content items presented as a flexbox grid.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.Controller as vm">
- <umb-content-grid
- content="vm.contentItems"
- content-properties="vm.includeProperties"
- on-click="vm.selectItem"
- on-click-name="vm.clickItem">
- </umb-content-grid>
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
- function Controller() {
- var vm = this;
- vm.contentItems = [
- {
- "name": "Cape",
- "published": true,
- "icon": "icon-document",
- "updateDate": "15-02-2016",
- "owner": "Mr. Batman",
- "selected": false
- },
- {
- "name": "Utility Belt",
- "published": true,
- "icon": "icon-document",
- "updateDate": "15-02-2016",
- "owner": "Mr. Batman",
- "selected": false
- },
- {
- "name": "Cave",
- "published": true,
- "icon": "icon-document",
- "updateDate": "15-02-2016",
- "owner": "Mr. Batman",
- "selected": false
- }
- ];
- vm.includeProperties = [
- {
- "alias": "updateDate",
- "header": "Last edited"
- },
- {
- "alias": "owner",
- "header": "Created by"
- }
- ];
- vm.clickItem = clickItem;
- vm.selectItem = selectItem;
- function clickItem(item, $event, $index){
- // do magic here
- }
- function selectItem(item, $event, $index) {
- // set item.selected = true; to select the item
- // do magic here
- }
- }
- angular.module("umbraco").controller("My.Controller", Controller);
- })();
- </pre>
- @param {array} content (<code>binding</code>): Array of content items.
- @param {array=} contentProperties (<code>binding</code>): Array of content item properties to include in the item. If left empty the item will only show the item icon and name.
- @param {callback=} onClick (<code>binding</code>): Callback method to handle click events on the content item.
- <h3>The callback returns:</h3>
- <ul>
- <li><code>item</code>: The clicked item</li>
- <li><code>$event</code>: The select event</li>
- <li><code>$index</code>: The item index</li>
- </ul>
- @param {callback=} onClickName (<code>binding</code>): Callback method to handle click events on the checkmark icon.
- <h3>The callback returns:</h3>
- <ul>
- <li><code>item</code>: The selected item</li>
- <li><code>$event</code>: The select event</li>
- <li><code>$index</code>: The item index</li>
- </ul>
- **/
- (function () {
- 'use strict';
- function ContentGridDirective() {
- function link(scope, el, attr, ctrl) {
- scope.clickItem = function (item, $event, $index) {
- if (scope.onClick) {
- scope.onClick(item, $event, $index);
- }
- };
- scope.clickItemName = function (item, $event, $index) {
- if (scope.onClickName) {
- scope.onClickName(item, $event, $index);
- }
- };
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/umb-content-grid.html',
- scope: {
- content: '=',
- contentProperties: '=',
- onClick: '=',
- onClickName: '='
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbContentGrid', ContentGridDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbDateTimePicker
- @restrict E
- @scope
- @description
- <b>Added in Umbraco version 7.6</b>
- This directive is a wrapper of the bootstrap datetime picker version 3.1.3. Use it to render a date time picker.
- For extra details about options and events take a look here: http://eonasdan.github.io/bootstrap-datetimepicker/
- Use this directive to render a date time picker
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.Controller as vm">
- <umb-date-time-picker
- options="vm.config"
- on-change="vm.datePickerChange(event)"
- on-error="vm.datePickerError(event)">
- </umb-date-time-picker>
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
- function Controller() {
- var vm = this;
- vm.date = "";
- vm.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"
- }
- };
- vm.datePickerChange = datePickerChange;
- vm.datePickerError = datePickerError;
- function datePickerChange(event) {
- // handle change
- if(event.date && event.date.isValid()) {
- var date = event.date.format(vm.datePickerConfig.format);
- }
- }
- function datePickerError(event) {
- // handle error
- }
- }
- angular.module("umbraco").controller("My.Controller", Controller);
- })();
- </pre>
- @param {object} options (<code>binding</code>): Config object for the date picker.
- @param {callback} onHide (<code>callback</code>): Hide callback.
- @param {callback} onShow (<code>callback</code>): Show callback.
- @param {callback} onChange (<code>callback</code>): Change callback.
- @param {callback} onError (<code>callback</code>): Error callback.
- @param {callback} onUpdate (<code>callback</code>): Update callback.
- **/
- (function () {
- 'use strict';
- function DateTimePickerDirective(assetsService) {
- function link(scope, element, attrs, ctrl) {
- function onInit() {
- // load css file for the date picker
- assetsService.loadCss('lib/datetimepicker/bootstrap-datetimepicker.min.css');
- // load the js file for the date picker
- assetsService.loadJs('lib/datetimepicker/bootstrap-datetimepicker.js').then(function () {
- // init date picker
- initDatePicker();
- });
- }
- function onHide(event) {
- if (scope.onHide) {
- scope.$apply(function () {
- // callback
- scope.onHide({ event: event });
- });
- }
- }
- function onShow() {
- if (scope.onShow) {
- scope.$apply(function () {
- // callback
- scope.onShow();
- });
- }
- }
- function onChange(event) {
- if (scope.onChange && event.date && event.date.isValid()) {
- scope.$apply(function () {
- // callback
- scope.onChange({ event: event });
- });
- }
- }
- function onError(event) {
- if (scope.onError) {
- scope.$apply(function () {
- // callback
- scope.onError({ event: event });
- });
- }
- }
- function onUpdate(event) {
- if (scope.onUpdate) {
- scope.$apply(function () {
- // callback
- scope.onUpdate({ event: event });
- });
- }
- }
- function initDatePicker() {
- // Open the datepicker and add a changeDate eventlistener
- element.datetimepicker(scope.options).on('dp.hide', onHide).on('dp.show', onShow).on('dp.change', onChange).on('dp.error', onError).on('dp.update', onUpdate);
- }
- onInit();
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/umb-date-time-picker.html',
- scope: {
- options: '=',
- onHide: '&',
- onShow: '&',
- onChange: '&',
- onError: '&',
- onUpdate: '&'
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbDateTimePicker', DateTimePickerDirective);
- }());
- (function () {
- 'use strict';
- function UmbDisableFormValidation() {
- var directive = {
- restrict: 'A',
- require: '?form',
- link: function (scope, elm, attrs, ctrl) {
- //override the $setValidity function of the form to disable validation
- ctrl.$setValidity = function () {
- };
- }
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbDisableFormValidation', UmbDisableFormValidation);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbDropdown
- @restrict E
- @scope
- @description
- <b>Added in versions 7.7.0</b>: Use this component to render a dropdown menu.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="MyDropdown.Controller as vm">
- <div style="position: relative;">
- <umb-button
- type="button"
- label="Toggle dropdown"
- action="vm.toggle()">
- </umb-button>
- <umb-dropdown ng-if="vm.dropdownOpen" on-close="vm.close()" umb-keyboard-list>
- <umb-dropdown-item
- ng-repeat="item in vm.items">
- <a href="" ng-click="vm.select(item)">{{ item.name }}</a>
- </umb-dropdown-item>
- </umb-dropdown>
- </div>
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
- function Controller() {
- var vm = this;
- vm.dropdownOpen = false;
- vm.items = [
- { "name": "Item 1" },
- { "name": "Item 2" },
- { "name": "Item 3" }
- ];
- vm.toggle = toggle;
- vm.close = close;
- vm.select = select;
- function toggle() {
- vm.dropdownOpen = true;
- }
- function close() {
- vm.dropdownOpen = false;
- }
- function select(item) {
- // Do your magic here
- }
- }
- angular.module("umbraco").controller("MyDropdown.Controller", Controller);
- })();
- </pre>
- <h3>Use in combination with</h3>
- <ul>
- <li>{@link umbraco.directives.directive:umbDropdownItem umbDropdownItem}</li>
- <li>{@link umbraco.directives.directive:umbKeyboardList umbKeyboardList}</li>
- </ul>
- @param {callback} onClose Callback when the dropdown menu closes. When you click outside or press esc.
- **/
- (function () {
- 'use strict';
- function umbDropdown($document) {
- function link(scope, element, attr, ctrl) {
- scope.close = function () {
- if (scope.onClose) {
- scope.onClose();
- }
- };
- // Handle keydown events
- function keydown(event) {
- // press escape
- if (event.keyCode === 27) {
- scope.onClose();
- }
- }
- // Stop to listen typing.
- function stopListening() {
- $document.off('keydown', keydown);
- }
- // Start listening to key typing.
- $document.on('keydown', keydown);
- // Stop listening when scope is destroyed.
- scope.$on('$destroy', stopListening);
- }
- var directive = {
- restrict: 'E',
- replace: true,
- transclude: true,
- templateUrl: 'views/components/umb-dropdown.html',
- scope: { onClose: '&' },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbDropdown', umbDropdown);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbDropdownItem
- @restrict E
- @description
- <b>Added in versions 7.7.0</b>: Use this directive to construct a dropdown item. See documentation for {@link umbraco.directives.directive:umbDropdown umbDropdown}.
- **/
- (function () {
- 'use strict';
- function umbDropdownItem() {
- var directive = {
- restrict: 'E',
- replace: true,
- transclude: true,
- templateUrl: 'views/components/umb-dropdown-item.html'
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbDropdownItem', umbDropdownItem);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbEmptyState
- @restrict E
- @scope
- @description
- Use this directive to show an empty state message.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.Controller as vm">
- <umb-empty-state
- ng-if="!vm.items"
- position="center">
- // Empty state content
- </umb-empty-state>
- </div>
- </pre>
- @param {string=} size Set the size of the text ("small", "large").
- @param {string=} position Set the position of the text ("center").
- **/
- (function () {
- 'use strict';
- function EmptyStateDirective() {
- var directive = {
- restrict: 'E',
- replace: true,
- transclude: true,
- templateUrl: 'views/components/umb-empty-state.html',
- scope: {
- size: '@',
- position: '@'
- }
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbEmptyState', EmptyStateDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbFolderGrid
- @restrict E
- @scope
- @description
- Use this directive to generate a list of folders presented as a flexbox grid.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.Controller as vm">
- <umb-folder-grid
- ng-if="vm.folders.length > 0"
- folders="vm.folders"
- on-click="vm.clickFolder"
- on-select="vm.selectFolder">
- </umb-folder-grid>
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
- function Controller(myService) {
- var vm = this;
- vm.folders = [
- {
- "name": "Folder 1",
- "icon": "icon-folder",
- "selected": false
- },
- {
- "name": "Folder 2",
- "icon": "icon-folder",
- "selected": false
- }
- ];
- vm.clickFolder = clickFolder;
- vm.selectFolder = selectFolder;
- myService.getFolders().then(function(folders){
- vm.folders = folders;
- });
- function clickFolder(folder){
- // Execute when clicking folder name/link
- }
- function selectFolder(folder, event, index) {
- // Execute when clicking folder
- // set folder.selected = true; to show checkmark icon
- }
- }
- angular.module("umbraco").controller("My.Controller", Controller);
- })();
- </pre>
- @param {array} folders (<code>binding</code>): Array of folders
- @param {callback=} onClick (<code>binding</code>): Callback method to handle click events on the folder.
- <h3>The callback returns:</h3>
- <ul>
- <li><code>folder</code>: The selected folder</li>
- </ul>
- @param {callback=} onSelect (<code>binding</code>): Callback method to handle click events on the checkmark icon.
- <h3>The callback returns:</h3>
- <ul>
- <li><code>folder</code>: The selected folder</li>
- <li><code>$event</code>: The select event</li>
- <li><code>$index</code>: The folder index</li>
- </ul>
- **/
- (function () {
- 'use strict';
- function FolderGridDirective() {
- function link(scope, el, attr, ctrl) {
- scope.clickFolder = function (folder, $event, $index) {
- if (scope.onClick) {
- scope.onClick(folder, $event, $index);
- $event.stopPropagation();
- }
- };
- scope.clickFolderName = function (folder, $event, $index) {
- if (scope.onClickName) {
- scope.onClickName(folder, $event, $index);
- $event.stopPropagation();
- }
- };
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/umb-folder-grid.html',
- scope: {
- folders: '=',
- onClick: '=',
- onClickName: '='
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbFolderGrid', FolderGridDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbGenerateAlias
- @restrict E
- @scope
- @description
- Use this directive to generate a camelCased umbraco alias.
- When the aliasFrom value is changed the directive will get a formatted alias from the server and update the alias model. If "enableLock" is set to <code>true</code>
- the directive will use {@link umbraco.directives.directive:umbLockedField umbLockedField} to lock and unlock the alias.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.Controller as vm">
- <input type="text" ng-model="vm.name" />
- <umb-generate-alias
- enable-lock="true"
- alias-from="vm.name"
- alias="vm.alias">
- </umb-generate-alias>
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
- function Controller() {
- var vm = this;
- vm.name = "";
- vm.alias = "";
- }
- angular.module("umbraco").controller("My.Controller", Controller);
- })();
- </pre>
- @param {string} alias (<code>binding</code>): The model where the alias is bound.
- @param {string} aliasFrom (<code>binding</code>): The model to generate the alias from.
- @param {boolean=} enableLock (<code>binding</code>): Set to <code>true</code> to add a lock next to the alias from where it can be unlocked and changed.
- **/
- angular.module('umbraco.directives').directive('umbGenerateAlias', function ($timeout, entityResource) {
- return {
- restrict: 'E',
- templateUrl: 'views/components/umb-generate-alias.html',
- replace: true,
- scope: {
- alias: '=',
- aliasFrom: '=',
- enableLock: '=?',
- serverValidationField: '@'
- },
- link: function (scope, element, attrs, ctrl) {
- var eventBindings = [];
- var bindWatcher = true;
- var generateAliasTimeout = '';
- var updateAlias = false;
- scope.locked = true;
- scope.placeholderText = 'Enter alias...';
- function generateAlias(value) {
- if (generateAliasTimeout) {
- $timeout.cancel(generateAliasTimeout);
- }
- if (value !== undefined && value !== '' && value !== null) {
- scope.alias = '';
- scope.placeholderText = 'Generating Alias...';
- generateAliasTimeout = $timeout(function () {
- updateAlias = true;
- entityResource.getSafeAlias(value, true).then(function (safeAlias) {
- if (updateAlias) {
- scope.alias = safeAlias.alias;
- }
- });
- }, 500);
- } else {
- updateAlias = true;
- scope.alias = '';
- scope.placeholderText = 'Enter alias...';
- }
- }
- // if alias gets unlocked - stop watching alias
- eventBindings.push(scope.$watch('locked', function (newValue, oldValue) {
- if (newValue === false) {
- bindWatcher = false;
- }
- }));
- // validate custom entered alias
- eventBindings.push(scope.$watch('alias', function (newValue, oldValue) {
- if (scope.alias === '' && bindWatcher === true || scope.alias === null && bindWatcher === true) {
- // add watcher
- eventBindings.push(scope.$watch('aliasFrom', function (newValue, oldValue) {
- if (bindWatcher) {
- generateAlias(newValue);
- }
- }));
- }
- }));
- // clean up
- scope.$on('$destroy', function () {
- // unbind watchers
- for (var e in eventBindings) {
- eventBindings[e]();
- }
- });
- }
- };
- });
- (function () {
- 'use strict';
- function GridSelector() {
- function link(scope, el, attr, ctrl) {
- var eventBindings = [];
- scope.dialogModel = {};
- scope.showDialog = false;
- scope.itemLabel = '';
- // set default item name
- if (!scope.itemName) {
- scope.itemLabel = 'item';
- } else {
- scope.itemLabel = scope.itemName;
- }
- scope.removeItem = function (selectedItem) {
- var selectedItemIndex = scope.selectedItems.indexOf(selectedItem);
- scope.selectedItems.splice(selectedItemIndex, 1);
- };
- scope.removeDefaultItem = function () {
- // it will be the last item so we can clear the array
- scope.selectedItems = [];
- // remove as default item
- scope.defaultItem = null;
- };
- scope.openItemPicker = function ($event) {
- scope.dialogModel = {
- view: 'itempicker',
- title: 'Choose ' + scope.itemLabel,
- availableItems: scope.availableItems,
- selectedItems: scope.selectedItems,
- event: $event,
- show: true,
- submit: function (model) {
- scope.selectedItems.push(model.selectedItem);
- // if no default item - set item as default
- if (scope.defaultItem === null) {
- scope.setAsDefaultItem(model.selectedItem);
- }
- scope.dialogModel.show = false;
- scope.dialogModel = null;
- }
- };
- };
- scope.setAsDefaultItem = function (selectedItem) {
- // clear default item
- scope.defaultItem = {};
- // set as default item
- scope.defaultItem = selectedItem;
- };
- function updatePlaceholders() {
- // update default item
- if (scope.defaultItem !== null && scope.defaultItem.placeholder) {
- scope.defaultItem.name = scope.name;
- if (scope.alias !== null && scope.alias !== undefined) {
- scope.defaultItem.alias = scope.alias;
- }
- }
- // update selected items
- angular.forEach(scope.selectedItems, function (selectedItem) {
- if (selectedItem.placeholder) {
- selectedItem.name = scope.name;
- if (scope.alias !== null && scope.alias !== undefined) {
- selectedItem.alias = scope.alias;
- }
- }
- });
- // update availableItems
- angular.forEach(scope.availableItems, function (availableItem) {
- if (availableItem.placeholder) {
- availableItem.name = scope.name;
- if (scope.alias !== null && scope.alias !== undefined) {
- availableItem.alias = scope.alias;
- }
- }
- });
- }
- function activate() {
- // add watchers for updating placeholde name and alias
- if (scope.updatePlaceholder) {
- eventBindings.push(scope.$watch('name', function (newValue, oldValue) {
- updatePlaceholders();
- }));
- eventBindings.push(scope.$watch('alias', function (newValue, oldValue) {
- updatePlaceholders();
- }));
- }
- }
- activate();
- // clean up
- scope.$on('$destroy', function () {
- // clear watchers
- for (var e in eventBindings) {
- eventBindings[e]();
- }
- });
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/umb-grid-selector.html',
- scope: {
- name: '=',
- alias: '=',
- selectedItems: '=',
- availableItems: '=',
- defaultItem: '=',
- itemName: '@',
- updatePlaceholder: '='
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbGridSelector', GridSelector);
- }());
- (function () {
- 'use strict';
- function GroupsBuilderDirective(contentTypeHelper, contentTypeResource, mediaTypeResource, dataTypeHelper, dataTypeResource, $filter, iconHelper, $q, $timeout, notificationsService, localizationService) {
- function link(scope, el, attr, ctrl) {
- var validationTranslated = '';
- var tabNoSortOrderTranslated = '';
- scope.sortingMode = false;
- scope.toolbar = [];
- scope.sortableOptionsGroup = {};
- scope.sortableOptionsProperty = {};
- scope.sortingButtonKey = 'general_reorder';
- function activate() {
- setSortingOptions();
- // set placeholder property on each group
- if (scope.model.groups.length !== 0) {
- angular.forEach(scope.model.groups, function (group) {
- addInitProperty(group);
- });
- }
- // add init tab
- addInitGroup(scope.model.groups);
- activateFirstGroup(scope.model.groups);
- // localize texts
- localizationService.localize('validation_validation').then(function (value) {
- validationTranslated = value;
- });
- localizationService.localize('contentTypeEditor_tabHasNoSortOrder').then(function (value) {
- tabNoSortOrderTranslated = value;
- });
- }
- function setSortingOptions() {
- scope.sortableOptionsGroup = {
- distance: 10,
- tolerance: 'pointer',
- opacity: 0.7,
- scroll: true,
- cursor: 'move',
- placeholder: 'umb-group-builder__group-sortable-placeholder',
- zIndex: 6000,
- handle: '.umb-group-builder__group-handle',
- items: '.umb-group-builder__group-sortable',
- start: function (e, ui) {
- ui.placeholder.height(ui.item.height());
- },
- stop: function (e, ui) {
- updateTabsSortOrder();
- }
- };
- scope.sortableOptionsProperty = {
- distance: 10,
- tolerance: 'pointer',
- connectWith: '.umb-group-builder__properties',
- opacity: 0.7,
- scroll: true,
- cursor: 'move',
- placeholder: 'umb-group-builder__property_sortable-placeholder',
- zIndex: 6000,
- handle: '.umb-group-builder__property-handle',
- items: '.umb-group-builder__property-sortable',
- start: function (e, ui) {
- ui.placeholder.height(ui.item.height());
- },
- stop: function (e, ui) {
- updatePropertiesSortOrder();
- }
- };
- }
- function updateTabsSortOrder() {
- var first = true;
- var prevSortOrder = 0;
- scope.model.groups.map(function (group) {
- var index = scope.model.groups.indexOf(group);
- if (group.tabState !== 'init') {
- // set the first not inherited tab to sort order 0
- if (!group.inherited && first) {
- // set the first tab sort order to 0 if prev is 0
- if (prevSortOrder === 0) {
- group.sortOrder = 0; // when the first tab is inherited and sort order is not 0
- } else {
- group.sortOrder = prevSortOrder + 1;
- }
- first = false;
- } else if (!group.inherited && !first) {
- // find next group
- var nextGroup = scope.model.groups[index + 1];
- // if a groups is dropped in the middle of to groups with
- // same sort order. Give it the dropped group same sort order
- if (prevSortOrder === nextGroup.sortOrder) {
- group.sortOrder = prevSortOrder;
- } else {
- group.sortOrder = prevSortOrder + 1;
- }
- }
- // store this tabs sort order as reference for the next
- prevSortOrder = group.sortOrder;
- }
- });
- }
- function filterAvailableCompositions(selectedContentType, selecting) {
- //selecting = true if the user has check the item, false if the user has unchecked the item
- var selectedContentTypeAliases = selecting ? //the user has selected the item so add to the current list
- _.union(scope.compositionsDialogModel.compositeContentTypes, [selectedContentType.alias]) : //the user has unselected the item so remove from the current list
- _.reject(scope.compositionsDialogModel.compositeContentTypes, function (i) {
- return i === selectedContentType.alias;
- });
- //get the currently assigned property type aliases - ensure we pass these to the server side filer
- var propAliasesExisting = _.filter(_.flatten(_.map(scope.model.groups, function (g) {
- return _.map(g.properties, function (p) {
- return p.alias;
- });
- })), function (f) {
- return f !== null && f !== undefined;
- });
- //use a different resource lookup depending on the content type type
- var resourceLookup = scope.contentType === 'documentType' ? contentTypeResource.getAvailableCompositeContentTypes : mediaTypeResource.getAvailableCompositeContentTypes;
- return resourceLookup(scope.model.id, selectedContentTypeAliases, propAliasesExisting).then(function (filteredAvailableCompositeTypes) {
- _.each(scope.compositionsDialogModel.availableCompositeContentTypes, function (current) {
- //reset first
- current.allowed = true;
- //see if this list item is found in the response (allowed) list
- var found = _.find(filteredAvailableCompositeTypes, function (f) {
- return current.contentType.alias === f.contentType.alias;
- });
- //allow if the item was found in the response (allowed) list -
- // and ensure its set to allowed if it is currently checked,
- // DO not allow if it's a locked content type.
- current.allowed = scope.model.lockedCompositeContentTypes.indexOf(current.contentType.alias) === -1 && selectedContentTypeAliases.indexOf(current.contentType.alias) !== -1 || (found !== null && found !== undefined ? found.allowed : false);
- });
- });
- }
- function updatePropertiesSortOrder() {
- angular.forEach(scope.model.groups, function (group) {
- if (group.tabState !== 'init') {
- group.properties = contentTypeHelper.updatePropertiesSortOrder(group.properties);
- }
- });
- }
- function setupAvailableContentTypesModel(result) {
- scope.compositionsDialogModel.availableCompositeContentTypes = result;
- //iterate each one and set it up
- _.each(scope.compositionsDialogModel.availableCompositeContentTypes, function (c) {
- //enable it if it's part of the selected model
- if (scope.compositionsDialogModel.compositeContentTypes.indexOf(c.contentType.alias) !== -1) {
- c.allowed = true;
- }
- //set the inherited flags
- c.inherited = false;
- if (scope.model.lockedCompositeContentTypes.indexOf(c.contentType.alias) > -1) {
- c.inherited = true;
- }
- // convert icons for composite content types
- iconHelper.formatContentTypeIcons([c.contentType]);
- });
- }
- /* ---------- DELETE PROMT ---------- */
- scope.togglePrompt = function (object) {
- object.deletePrompt = !object.deletePrompt;
- };
- scope.hidePrompt = function (object) {
- object.deletePrompt = false;
- };
- /* ---------- TOOLBAR ---------- */
- scope.toggleSortingMode = function (tool) {
- if (scope.sortingMode === true) {
- var sortOrderMissing = false;
- for (var i = 0; i < scope.model.groups.length; i++) {
- var group = scope.model.groups[i];
- if (group.tabState !== 'init' && group.sortOrder === undefined) {
- sortOrderMissing = true;
- group.showSortOrderMissing = true;
- notificationsService.error(validationTranslated + ': ' + group.name + ' ' + tabNoSortOrderTranslated);
- }
- }
- if (!sortOrderMissing) {
- scope.sortingMode = false;
- scope.sortingButtonKey = 'general_reorder';
- }
- } else {
- scope.sortingMode = true;
- scope.sortingButtonKey = 'general_reorderDone';
- }
- };
- scope.openCompositionsDialog = function () {
- scope.compositionsDialogModel = {
- title: 'Compositions',
- contentType: scope.model,
- compositeContentTypes: scope.model.compositeContentTypes,
- view: 'views/common/overlays/contenttypeeditor/compositions/compositions.html',
- confirmSubmit: {
- title: 'Warning',
- description: 'Removing a composition will delete all the associated property data. Once you save the document type there\'s no way back, are you sure?',
- checkboxLabel: 'I know what I\'m doing',
- enable: true
- },
- submit: function (model, oldModel, confirmed) {
- var compositionRemoved = false;
- // check if any compositions has been removed
- for (var i = 0; oldModel.compositeContentTypes.length > i; i++) {
- var oldComposition = oldModel.compositeContentTypes[i];
- if (_.contains(model.compositeContentTypes, oldComposition) === false) {
- compositionRemoved = true;
- }
- }
- // show overlay confirm box if compositions has been removed.
- if (compositionRemoved && confirmed === false) {
- scope.compositionsDialogModel.confirmSubmit.show = true; // submit overlay if no compositions has been removed
- // or the action has been confirmed
- } else {
- // make sure that all tabs has an init property
- if (scope.model.groups.length !== 0) {
- angular.forEach(scope.model.groups, function (group) {
- addInitProperty(group);
- });
- }
- // remove overlay
- scope.compositionsDialogModel.show = false;
- scope.compositionsDialogModel = null;
- }
- },
- close: function (oldModel) {
- // reset composition changes
- scope.model.groups = oldModel.contentType.groups;
- scope.model.compositeContentTypes = oldModel.contentType.compositeContentTypes;
- // remove overlay
- scope.compositionsDialogModel.show = false;
- scope.compositionsDialogModel = null;
- },
- selectCompositeContentType: function (selectedContentType) {
- //first check if this is a new selection - we need to store this value here before any further digests/async
- // because after that the scope.model.compositeContentTypes will be populated with the selected value.
- var newSelection = scope.model.compositeContentTypes.indexOf(selectedContentType.alias) === -1;
- if (newSelection) {
- //merge composition with content type
- //use a different resource lookup depending on the content type type
- var resourceLookup = scope.contentType === 'documentType' ? contentTypeResource.getById : mediaTypeResource.getById;
- resourceLookup(selectedContentType.id).then(function (composition) {
- //based on the above filtering we shouldn't be able to select an invalid one, but let's be safe and
- // double check here.
- var overlappingAliases = contentTypeHelper.validateAddingComposition(scope.model, composition);
- if (overlappingAliases.length > 0) {
- //this will create an invalid composition, need to uncheck it
- scope.compositionsDialogModel.compositeContentTypes.splice(scope.compositionsDialogModel.compositeContentTypes.indexOf(composition.alias), 1);
- //dissallow this until something else is unchecked
- selectedContentType.allowed = false;
- } else {
- contentTypeHelper.mergeCompositeContentType(scope.model, composition);
- }
- //based on the selection, we need to filter the available composite types list
- filterAvailableCompositions(selectedContentType, newSelection).then(function () {
- });
- });
- } else {
- // split composition from content type
- contentTypeHelper.splitCompositeContentType(scope.model, selectedContentType);
- //based on the selection, we need to filter the available composite types list
- filterAvailableCompositions(selectedContentType, newSelection).then(function () {
- });
- }
- }
- };
- var availableContentTypeResource = scope.contentType === 'documentType' ? contentTypeResource.getAvailableCompositeContentTypes : mediaTypeResource.getAvailableCompositeContentTypes;
- var countContentTypeResource = scope.contentType === 'documentType' ? contentTypeResource.getCount : mediaTypeResource.getCount;
- //get the currently assigned property type aliases - ensure we pass these to the server side filer
- var propAliasesExisting = _.filter(_.flatten(_.map(scope.model.groups, function (g) {
- return _.map(g.properties, function (p) {
- return p.alias;
- });
- })), function (f) {
- return f !== null && f !== undefined;
- });
- $q.all([
- //get available composite types
- availableContentTypeResource(scope.model.id, [], propAliasesExisting).then(function (result) {
- setupAvailableContentTypesModel(result);
- }),
- //get content type count
- countContentTypeResource().then(function (result) {
- scope.compositionsDialogModel.totalContentTypes = parseInt(result, 10);
- })
- ]).then(function () {
- //resolves when both other promises are done, now show it
- scope.compositionsDialogModel.show = true;
- });
- };
- /* ---------- GROUPS ---------- */
- scope.addGroup = function (group) {
- // set group sort order
- var index = scope.model.groups.indexOf(group);
- var prevGroup = scope.model.groups[index - 1];
- if (index > 0) {
- // set index to 1 higher than the previous groups sort order
- group.sortOrder = prevGroup.sortOrder + 1;
- } else {
- // first group - sort order will be 0
- group.sortOrder = 0;
- }
- // activate group
- scope.activateGroup(group);
- };
- scope.activateGroup = function (selectedGroup) {
- // set all other groups that are inactive to active
- angular.forEach(scope.model.groups, function (group) {
- // skip init tab
- if (group.tabState !== 'init') {
- group.tabState = 'inActive';
- }
- });
- selectedGroup.tabState = 'active';
- };
- scope.removeGroup = function (groupIndex) {
- scope.model.groups.splice(groupIndex, 1);
- addInitGroup(scope.model.groups);
- };
- scope.updateGroupTitle = function (group) {
- if (group.properties.length === 0) {
- addInitProperty(group);
- }
- };
- scope.changeSortOrderValue = function (group) {
- if (group.sortOrder !== undefined) {
- group.showSortOrderMissing = false;
- }
- scope.model.groups = $filter('orderBy')(scope.model.groups, 'sortOrder');
- };
- function addInitGroup(groups) {
- // check i init tab already exists
- var addGroup = true;
- angular.forEach(groups, function (group) {
- if (group.tabState === 'init') {
- addGroup = false;
- }
- });
- if (addGroup) {
- groups.push({
- properties: [],
- parentTabContentTypes: [],
- parentTabContentTypeNames: [],
- name: '',
- tabState: 'init'
- });
- }
- return groups;
- }
- function activateFirstGroup(groups) {
- if (groups && groups.length > 0) {
- var firstGroup = groups[0];
- if (!firstGroup.tabState || firstGroup.tabState === 'inActive') {
- firstGroup.tabState = 'active';
- }
- }
- }
- /* ---------- PROPERTIES ---------- */
- scope.addProperty = function (property, group) {
- // set property sort order
- var index = group.properties.indexOf(property);
- var prevProperty = group.properties[index - 1];
- if (index > 0) {
- // set index to 1 higher than the previous property sort order
- property.sortOrder = prevProperty.sortOrder + 1;
- } else {
- // first property - sort order will be 0
- property.sortOrder = 0;
- }
- // open property settings dialog
- scope.editPropertyTypeSettings(property, group);
- };
- scope.editPropertyTypeSettings = function (property, group) {
- if (!property.inherited && !property.locked) {
- scope.propertySettingsDialogModel = {};
- scope.propertySettingsDialogModel.title = 'Property settings';
- scope.propertySettingsDialogModel.property = property;
- scope.propertySettingsDialogModel.contentType = scope.contentType;
- scope.propertySettingsDialogModel.contentTypeName = scope.model.name;
- scope.propertySettingsDialogModel.view = 'views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html';
- scope.propertySettingsDialogModel.show = true;
- // set state to active to access the preview
- property.propertyState = 'active';
- // set property states
- property.dialogIsOpen = true;
- scope.propertySettingsDialogModel.submit = function (model) {
- property.inherited = false;
- property.dialogIsOpen = false;
- // update existing data types
- if (model.updateSameDataTypes) {
- updateSameDataTypes(property);
- }
- // remove dialog
- scope.propertySettingsDialogModel.show = false;
- scope.propertySettingsDialogModel = null;
- // push new init property to group
- addInitProperty(group);
- // set focus on init property
- var numberOfProperties = group.properties.length;
- group.properties[numberOfProperties - 1].focus = true;
- // push new init tab to the scope
- addInitGroup(scope.model.groups);
- };
- scope.propertySettingsDialogModel.close = function (oldModel) {
- // reset all property changes
- property.label = oldModel.property.label;
- property.alias = oldModel.property.alias;
- property.description = oldModel.property.description;
- property.config = oldModel.property.config;
- property.editor = oldModel.property.editor;
- property.view = oldModel.property.view;
- property.dataTypeId = oldModel.property.dataTypeId;
- property.dataTypeIcon = oldModel.property.dataTypeIcon;
- property.dataTypeName = oldModel.property.dataTypeName;
- property.validation.mandatory = oldModel.property.validation.mandatory;
- property.validation.pattern = oldModel.property.validation.pattern;
- property.showOnMemberProfile = oldModel.property.showOnMemberProfile;
- property.memberCanEdit = oldModel.property.memberCanEdit;
- // because we set state to active, to show a preview, we have to check if has been filled out
- // label is required so if it is not filled we know it is a placeholder
- if (oldModel.property.editor === undefined || oldModel.property.editor === null || oldModel.property.editor === '') {
- property.propertyState = 'init';
- } else {
- property.propertyState = oldModel.property.propertyState;
- }
- // remove dialog
- scope.propertySettingsDialogModel.show = false;
- scope.propertySettingsDialogModel = null;
- };
- }
- };
- scope.deleteProperty = function (tab, propertyIndex) {
- // remove property
- tab.properties.splice(propertyIndex, 1);
- // if the last property in group is an placeholder - remove add new tab placeholder
- if (tab.properties.length === 1 && tab.properties[0].propertyState === 'init') {
- angular.forEach(scope.model.groups, function (group, index, groups) {
- if (group.tabState === 'init') {
- groups.splice(index, 1);
- }
- });
- }
- };
- function addInitProperty(group) {
- var addInitPropertyBool = true;
- var initProperty = {
- label: null,
- alias: null,
- propertyState: 'init',
- validation: {
- mandatory: false,
- pattern: null
- }
- };
- // check if there already is an init property
- angular.forEach(group.properties, function (property) {
- if (property.propertyState === 'init') {
- addInitPropertyBool = false;
- }
- });
- if (addInitPropertyBool) {
- group.properties.push(initProperty);
- }
- return group;
- }
- function updateSameDataTypes(newProperty) {
- // find each property
- angular.forEach(scope.model.groups, function (group) {
- angular.forEach(group.properties, function (property) {
- if (property.dataTypeId === newProperty.dataTypeId) {
- // update property data
- property.config = newProperty.config;
- property.editor = newProperty.editor;
- property.view = newProperty.view;
- property.dataTypeId = newProperty.dataTypeId;
- property.dataTypeIcon = newProperty.dataTypeIcon;
- property.dataTypeName = newProperty.dataTypeName;
- }
- });
- });
- }
- var unbindModelWatcher = scope.$watch('model', function (newValue, oldValue) {
- if (newValue !== undefined && newValue.groups !== undefined) {
- activate();
- }
- });
- // clean up
- scope.$on('$destroy', function () {
- unbindModelWatcher();
- });
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/umb-groups-builder.html',
- scope: {
- model: '=',
- compositions: '=',
- sorting: '=',
- contentType: '@'
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbGroupsBuilder', GroupsBuilderDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbkeyboardShortcutsOverview
- @restrict E
- @scope
- @description
- <p>Use this directive to show an overview of keyboard shortcuts in an editor.
- The directive will render an overview trigger wich shows how the overview is opened.
- When this combination is hit an overview is opened with shortcuts based on the model sent to the directive.</p>
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.Controller as vm">
- <umb-keyboard-shortcuts-overview
- model="vm.keyboardShortcutsOverview">
- </umb-keyboard-shortcuts-overview>
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
- function Controller() {
- var vm = this;
- vm.keyboardShortcutsOverview = [
- {
- "name": "Sections",
- "shortcuts": [
- {
- "description": "Navigate sections",
- "keys": [
- {"key": "1"},
- {"key": "4"}
- ],
- "keyRange": true
- }
- ]
- },
- {
- "name": "Design",
- "shortcuts": [
- {
- "description": "Add tab",
- "keys": [
- {"key": "alt"},
- {"key": "shift"},
- {"key": "t"}
- ]
- }
- ]
- }
- ];
- }
- angular.module("umbraco").controller("My.Controller", Controller);
- })();
- </pre>
- <h3>Model description</h3>
- <ul>
- <li>
- <strong>name</strong>
- <small>(string)</small> -
- Sets the shortcut section name.
- </li>
- <li>
- <strong>shortcuts</strong>
- <small>(array)</small> -
- Array of available shortcuts in the section.
- </li>
- <ul>
- <li>
- <strong>description</strong>
- <small>(string)</small> -
- Short description of the shortcut.
- </li>
- <li>
- <strong>keys</strong>
- <small>(array)</small> -
- Array of keys in the shortcut.
- </li>
- <ul>
- <li>
- <strong>key</strong>
- <small>(string)</small> -
- The invidual key in the shortcut.
- </li>
- </ul>
- <li>
- <strong>keyRange</strong>
- <small>(boolean)</small> -
- Set to <code>true</code> to show a key range. It combines the shortcut keys with "-" instead of "+".
- </li>
- </ul>
- </ul>
- @param {object} model keyboard shortcut model. See description and example above.
- **/
- (function () {
- 'use strict';
- function KeyboardShortcutsOverviewDirective(platformService) {
- function link(scope, el, attr, ctrl) {
- var eventBindings = [];
- var isMac = platformService.isMac();
- scope.toggleShortcutsOverlay = function () {
- scope.showOverlay = !scope.showOverlay;
- scope.onToggle();
- };
- function onInit() {
- angular.forEach(scope.model, function (shortcutGroup) {
- angular.forEach(shortcutGroup.shortcuts, function (shortcut) {
- shortcut.platformKeys = [];
- // get shortcut keys for mac
- if (isMac && shortcut.keys && shortcut.keys.mac) {
- shortcut.platformKeys = shortcut.keys.mac; // get shortcut keys for windows
- } else if (!isMac && shortcut.keys && shortcut.keys.win) {
- shortcut.platformKeys = shortcut.keys.win; // get default shortcut keys
- } else if (shortcut.keys && shortcut && shortcut.keys.length > 0) {
- shortcut.platformKeys = shortcut.keys;
- }
- });
- });
- }
- onInit();
- eventBindings.push(scope.$watch('model', function (newValue, oldValue) {
- if (newValue !== oldValue) {
- onInit();
- }
- }));
- // clean up
- scope.$on('$destroy', function () {
- // unbind watchers
- for (var e in eventBindings) {
- eventBindings[e]();
- }
- });
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/umb-keyboard-shortcuts-overview.html',
- link: link,
- scope: {
- model: '=',
- onToggle: '&',
- showOverlay: '=?'
- }
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbKeyboardShortcutsOverview', KeyboardShortcutsOverviewDirective);
- }());
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbLaunchMiniEditor
- * @restrict E
- * @function
- * @description
- * Used on a button to launch a mini content editor editor dialog
- **/
- angular.module('umbraco.directives').directive('umbLaunchMiniEditor', function (miniEditorHelper) {
- return {
- restrict: 'A',
- replace: false,
- scope: { node: '=umbLaunchMiniEditor' },
- link: function (scope, element, attrs) {
- element.click(function () {
- miniEditorHelper.launchMiniEditor(scope.node);
- });
- }
- };
- });
- (function () {
- 'use strict';
- function LayoutSelectorDirective() {
- function link(scope, el, attr, ctrl) {
- scope.layoutDropDownIsOpen = false;
- scope.showLayoutSelector = true;
- function activate() {
- setVisibility();
- setActiveLayout(scope.layouts);
- }
- function setVisibility() {
- var numberOfAllowedLayouts = getNumberOfAllowedLayouts(scope.layouts);
- if (numberOfAllowedLayouts === 1) {
- scope.showLayoutSelector = false;
- }
- }
- function getNumberOfAllowedLayouts(layouts) {
- var allowedLayouts = 0;
- for (var i = 0; layouts.length > i; i++) {
- var layout = layouts[i];
- if (layout.selected === true) {
- allowedLayouts++;
- }
- }
- return allowedLayouts;
- }
- function setActiveLayout(layouts) {
- for (var i = 0; layouts.length > i; i++) {
- var layout = layouts[i];
- if (layout.path === scope.activeLayout.path) {
- layout.active = true;
- }
- }
- }
- scope.pickLayout = function (selectedLayout) {
- if (scope.onLayoutSelect) {
- scope.onLayoutSelect(selectedLayout);
- scope.layoutDropDownIsOpen = false;
- }
- };
- scope.toggleLayoutDropdown = function () {
- scope.layoutDropDownIsOpen = !scope.layoutDropDownIsOpen;
- };
- scope.closeLayoutDropdown = function () {
- scope.layoutDropDownIsOpen = false;
- };
- activate();
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/umb-layout-selector.html',
- scope: {
- layouts: '=',
- activeLayout: '=',
- onLayoutSelect: '='
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbLayoutSelector', LayoutSelectorDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbLightbox
- @restrict E
- @scope
- @description
- <p>Use this directive to open a gallery in a lightbox overlay.</p>
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.Controller as vm">
- <div class="my-gallery">
- <a href="" ng-repeat="image in images" ng-click="vm.openLightbox($index, images)">
- <img ng-src="image.source" />
- </a>
- </div>
- <umb-lightbox
- ng-if="vm.lightbox.show"
- items="vm.lightbox.items"
- active-item-index="vm.lightbox.activeIndex"
- on-close="vm.closeLightbox">
- </umb-lightbox>
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
- function Controller() {
- var vm = this;
- vm.images = [
- {
- "source": "linkToImage"
- },
- {
- "source": "linkToImage"
- }
- ]
- vm.openLightbox = openLightbox;
- vm.closeLightbox = closeLightbox;
- function openLightbox(itemIndex, items) {
- vm.lightbox = {
- show: true,
- items: items,
- activeIndex: itemIndex
- };
- }
- function closeLightbox() {
- vm.lightbox.show = false;
- vm.lightbox = null;
- }
- }
- angular.module("umbraco").controller("My.Controller", Controller);
- })();
- </pre>
- @param {array} items Array of gallery items.
- @param {callback} onClose Callback when the lightbox is closed.
- @param {number} activeItemIndex Index of active item.
- **/
- (function () {
- 'use strict';
- function LightboxDirective() {
- function link(scope, el, attr, ctrl) {
- function activate() {
- var eventBindings = [];
- el.appendTo('body');
- // clean up
- scope.$on('$destroy', function () {
- // unbind watchers
- for (var e in eventBindings) {
- eventBindings[e]();
- }
- });
- }
- scope.next = function () {
- var nextItemIndex = scope.activeItemIndex + 1;
- if (nextItemIndex < scope.items.length) {
- scope.items[scope.activeItemIndex].active = false;
- scope.items[nextItemIndex].active = true;
- scope.activeItemIndex = nextItemIndex;
- }
- };
- scope.prev = function () {
- var prevItemIndex = scope.activeItemIndex - 1;
- if (prevItemIndex >= 0) {
- scope.items[scope.activeItemIndex].active = false;
- scope.items[prevItemIndex].active = true;
- scope.activeItemIndex = prevItemIndex;
- }
- };
- scope.close = function () {
- if (scope.onClose) {
- scope.onClose();
- }
- };
- activate();
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/umb-lightbox.html',
- scope: {
- items: '=',
- onClose: '=',
- activeItemIndex: '='
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbLightbox', LightboxDirective);
- }());
- (function () {
- 'use strict';
- function ListViewLayoutDirective() {
- function link(scope, el, attr, ctrl) {
- scope.getContent = function (contentId) {
- if (scope.onGetContent) {
- scope.onGetContent(contentId);
- }
- };
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/umb-list-view-layout.html',
- scope: {
- contentId: '=',
- folders: '=',
- items: '=',
- selection: '=',
- options: '=',
- entityType: '@',
- onGetContent: '='
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbListViewLayout', ListViewLayoutDirective);
- }());
- (function () {
- 'use strict';
- function ListViewSettingsDirective(contentTypeResource, dataTypeResource, dataTypeHelper, listViewPrevalueHelper) {
- function link(scope, el, attr, ctrl) {
- scope.dataType = {};
- scope.editDataTypeSettings = false;
- scope.customListViewCreated = false;
- /* ---------- INIT ---------- */
- function activate() {
- if (scope.enableListView) {
- dataTypeResource.getByName(scope.listViewName).then(function (dataType) {
- scope.dataType = dataType;
- listViewPrevalueHelper.setPrevalues(dataType.preValues);
- scope.customListViewCreated = checkForCustomListView();
- });
- } else {
- scope.dataType = {};
- }
- }
- /* ----------- LIST VIEW SETTINGS --------- */
- scope.toggleEditListViewDataTypeSettings = function () {
- scope.editDataTypeSettings = !scope.editDataTypeSettings;
- };
- scope.saveListViewDataType = function () {
- var preValues = dataTypeHelper.createPreValueProps(scope.dataType.preValues);
- dataTypeResource.save(scope.dataType, preValues, false).then(function (dataType) {
- // store data type
- scope.dataType = dataType;
- // hide settings panel
- scope.editDataTypeSettings = false;
- });
- };
- /* ---------- CUSTOM LIST VIEW ---------- */
- scope.createCustomListViewDataType = function () {
- dataTypeResource.createCustomListView(scope.modelAlias).then(function (dataType) {
- // store data type
- scope.dataType = dataType;
- // set list view name on scope
- scope.listViewName = dataType.name;
- // change state to custom list view
- scope.customListViewCreated = true;
- // show settings panel
- scope.editDataTypeSettings = true;
- });
- };
- scope.removeCustomListDataType = function () {
- scope.editDataTypeSettings = false;
- // delete custom list view data type
- dataTypeResource.deleteById(scope.dataType.id).then(function (dataType) {
- // set list view name on scope
- if (scope.contentType === 'documentType') {
- scope.listViewName = 'List View - Content';
- } else if (scope.contentType === 'mediaType') {
- scope.listViewName = 'List View - Media';
- }
- // get default data type
- dataTypeResource.getByName(scope.listViewName).then(function (dataType) {
- // store data type
- scope.dataType = dataType;
- // change state to default list view
- scope.customListViewCreated = false;
- });
- });
- };
- /* ----------- SCOPE WATCHERS ----------- */
- var unbindEnableListViewWatcher = scope.$watch('enableListView', function (newValue, oldValue) {
- if (newValue !== undefined) {
- activate();
- }
- });
- // clean up
- scope.$on('$destroy', function () {
- unbindEnableListViewWatcher();
- });
- /* ----------- METHODS ---------- */
- function checkForCustomListView() {
- return scope.dataType.name === 'List View - ' + scope.modelAlias;
- }
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/umb-list-view-settings.html',
- scope: {
- enableListView: '=',
- listViewName: '=',
- modelAlias: '=',
- contentType: '@'
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbListViewSettings', ListViewSettingsDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbLoadIndicator
- @restrict E
- @description
- Use this directive to generate a loading indicator.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.Controller as vm">
- <umb-load-indicator
- ng-if="vm.loading">
- </umb-load-indicator>
- <div class="content" ng-if="!vm.loading">
- <p>{{content}}</p>
- </div>
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
- function Controller(myService) {
- var vm = this;
- vm.content = "";
- vm.loading = true;
- myService.getContent().then(function(content){
- vm.content = content;
- vm.loading = false;
- });
- }
- ½
- angular.module("umbraco").controller("My.Controller", Controller);
- })();
- </pre>
- **/
- (function () {
- 'use strict';
- function UmbLoadIndicatorDirective() {
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/umb-load-indicator.html'
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbLoadIndicator', UmbLoadIndicatorDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbLockedField
- @restrict E
- @scope
- @description
- Use this directive to render a value with a lock next to it. When the lock is clicked the value gets unlocked and can be edited.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.Controller as vm">
- <umb-locked-field
- ng-model="vm.value"
- placeholder-text="'Click to unlock...'">
- </umb-locked-field>
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
- function Controller() {
- var vm = this;
- vm.value = "My locked text";
- }
- angular.module("umbraco").controller("My.Controller", Controller);
- })();
- </pre>
- @param {string} ngModel (<code>binding</code>): The locked text.
- @param {boolean=} locked (<code>binding</code>): <Code>true</code> by default. Set to <code>false</code> to unlock the text.
- @param {string=} placeholderText (<code>binding</code>): If ngModel is empty this text will be shown.
- @param {string=} regexValidation (<code>binding</code>): Set a regex expression for validation of the field.
- @param {string=} serverValidationField (<code>attribute</code>): Set a server validation field.
- **/
- (function () {
- 'use strict';
- function LockedFieldDirective($timeout, localizationService) {
- function link(scope, el, attr, ngModelCtrl) {
- function activate() {
- // if locked state is not defined as an attr set default state
- if (scope.locked === undefined || scope.locked === null) {
- scope.locked = true;
- }
- // if regex validation is not defined as an attr set default state
- // if this is set to an empty string then regex validation can be ignored.
- if (scope.regexValidation === undefined || scope.regexValidation === null) {
- scope.regexValidation = '^[a-zA-Z]\\w.*$';
- }
- if (scope.serverValidationField === undefined || scope.serverValidationField === null) {
- scope.serverValidationField = '';
- }
- // if locked state is not defined as an attr set default state
- if (scope.placeholderText === undefined || scope.placeholderText === null) {
- scope.placeholderText = 'Enter value...';
- }
- }
- scope.lock = function () {
- scope.locked = true;
- };
- scope.unlock = function () {
- scope.locked = false;
- };
- activate();
- }
- var directive = {
- require: 'ngModel',
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/umb-locked-field.html',
- scope: {
- ngModel: '=',
- locked: '=?',
- placeholderText: '=?',
- regexValidation: '=?',
- serverValidationField: '@'
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbLockedField', LockedFieldDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbMediaGrid
- @restrict E
- @scope
- @description
- Use this directive to generate a thumbnail grid of media items.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.Controller as vm">
- <umb-media-grid
- items="vm.mediaItems"
- on-click="vm.clickItem"
- on-click-name="vm.clickItemName">
- </umb-media-grid>
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
- function Controller() {
- var vm = this;
- vm.mediaItems = [];
- vm.clickItem = clickItem;
- vm.clickItemName = clickItemName;
- myService.getMediaItems().then(function (mediaItems) {
- vm.mediaItems = mediaItems;
- });
- function clickItem(item, $event, $index){
- // do magic here
- }
- function clickItemName(item, $event, $index) {
- // set item.selected = true; to select the item
- // do magic here
- }
- }
- angular.module("umbraco").controller("My.Controller", Controller);
- })();
- </pre>
- @param {array} items (<code>binding</code>): Array of media items.
- @param {callback=} onDetailsHover (<code>binding</code>): Callback method when the details icon is hovered.
- <h3>The callback returns:</h3>
- <ul>
- <li><code>item</code>: The hovered item</li>
- <li><code>$event</code>: The hover event</li>
- <li><code>hover</code>: Boolean to tell if the item is hovered or not</li>
- </ul>
- @param {callback=} onClick (<code>binding</code>): Callback method to handle click events on the media item.
- <h3>The callback returns:</h3>
- <ul>
- <li><code>item</code>: The clicked item</li>
- <li><code>$event</code>: The click event</li>
- <li><code>$index</code>: The item index</li>
- </ul>
- @param {callback=} onClickName (<code>binding</code>): Callback method to handle click events on the media item name.
- <h3>The callback returns:</h3>
- <ul>
- <li><code>item</code>: The clicked item</li>
- <li><code>$event</code>: The click event</li>
- <li><code>$index</code>: The item index</li>
- </ul>
- @param {string=} filterBy (<code>binding</code>): String to filter media items by
- @param {string=} itemMaxWidth (<code>attribute</code>): Sets a max width on the media item thumbnails.
- @param {string=} itemMaxHeight (<code>attribute</code>): Sets a max height on the media item thumbnails.
- @param {string=} itemMinWidth (<code>attribute</code>): Sets a min width on the media item thumbnails.
- @param {string=} itemMinHeight (<code>attribute</code>): Sets a min height on the media item thumbnails.
- **/
- (function () {
- 'use strict';
- function MediaGridDirective($filter, mediaHelper) {
- function link(scope, el, attr, ctrl) {
- var itemDefaultHeight = 200;
- var itemDefaultWidth = 200;
- var itemMaxWidth = 200;
- var itemMaxHeight = 200;
- var itemMinWidth = 125;
- var itemMinHeight = 125;
- function activate() {
- if (scope.itemMaxWidth) {
- itemMaxWidth = scope.itemMaxWidth;
- }
- if (scope.itemMaxHeight) {
- itemMaxHeight = scope.itemMaxHeight;
- }
- if (scope.itemMinWidth) {
- itemMinWidth = scope.itemMinWidth;
- }
- if (scope.itemMinHeight) {
- itemMinHeight = scope.itemMinHeight;
- }
- for (var i = 0; scope.items.length > i; i++) {
- var item = scope.items[i];
- setItemData(item);
- setOriginalSize(item, itemMaxHeight);
- // remove non images when onlyImages is set to true
- if (scope.onlyImages === 'true' && !item.isFolder && !item.thumbnail) {
- scope.items.splice(i, 1);
- i--;
- }
- }
- if (scope.items.length > 0) {
- setFlexValues(scope.items);
- }
- }
- function setItemData(item) {
- // check if item is a folder
- if (item.image) {
- // if is has an image path, it is not a folder
- item.isFolder = false;
- } else {
- item.isFolder = !mediaHelper.hasFilePropertyType(item);
- }
- if (!item.isFolder) {
- // handle entity
- if (item.image) {
- item.thumbnail = mediaHelper.resolveFileFromEntity(item, true);
- item.extension = mediaHelper.getFileExtension(item.image); // handle full media object
- } else {
- item.thumbnail = mediaHelper.resolveFile(item, true);
- item.image = mediaHelper.resolveFile(item, false);
- var fileProp = _.find(item.properties, function (v) {
- return v.alias === 'umbracoFile';
- });
- if (fileProp && fileProp.value) {
- item.file = fileProp.value;
- }
- var extensionProp = _.find(item.properties, function (v) {
- return v.alias === 'umbracoExtension';
- });
- if (extensionProp && extensionProp.value) {
- item.extension = extensionProp.value;
- }
- }
- }
- }
- function setOriginalSize(item, maxHeight) {
- //set to a square by default
- item.width = itemDefaultWidth;
- item.height = itemDefaultHeight;
- item.aspectRatio = 1;
- var widthProp = _.find(item.properties, function (v) {
- return v.alias === 'umbracoWidth';
- });
- if (widthProp && widthProp.value) {
- item.width = parseInt(widthProp.value, 10);
- if (isNaN(item.width)) {
- item.width = itemDefaultWidth;
- }
- }
- var heightProp = _.find(item.properties, function (v) {
- return v.alias === 'umbracoHeight';
- });
- if (heightProp && heightProp.value) {
- item.height = parseInt(heightProp.value, 10);
- if (isNaN(item.height)) {
- item.height = itemDefaultWidth;
- }
- }
- item.aspectRatio = item.width / item.height;
- // set max width and height
- // landscape
- if (item.aspectRatio >= 1) {
- if (item.width > itemMaxWidth) {
- item.width = itemMaxWidth;
- item.height = itemMaxWidth / item.aspectRatio;
- } // portrait
- } else {
- if (item.height > itemMaxHeight) {
- item.height = itemMaxHeight;
- item.width = itemMaxHeight * item.aspectRatio;
- }
- }
- }
- function setFlexValues(mediaItems) {
- var flexSortArray = mediaItems;
- var smallestImageWidth = null;
- var widestImageAspectRatio = null;
- // sort array after image width with the widest image first
- flexSortArray = $filter('orderBy')(flexSortArray, 'width', true);
- // find widest image aspect ratio
- widestImageAspectRatio = flexSortArray[0].aspectRatio;
- // find smallest image width
- smallestImageWidth = flexSortArray[flexSortArray.length - 1].width;
- for (var i = 0; flexSortArray.length > i; i++) {
- var mediaItem = flexSortArray[i];
- var flex = 1 / (widestImageAspectRatio / mediaItem.aspectRatio);
- if (flex === 0) {
- flex = 1;
- }
- var imageMinFlexWidth = smallestImageWidth * flex;
- var flexStyle = {
- 'flex': flex + ' 1 ' + imageMinFlexWidth + 'px',
- 'max-width': mediaItem.width + 'px',
- 'min-width': itemMinWidth + 'px',
- 'min-height': itemMinHeight + 'px'
- };
- mediaItem.flexStyle = flexStyle;
- }
- }
- scope.clickItem = function (item, $event, $index) {
- if (scope.onClick) {
- scope.onClick(item, $event, $index);
- $event.stopPropagation();
- }
- };
- scope.clickItemName = function (item, $event, $index) {
- if (scope.onClickName) {
- scope.onClickName(item, $event, $index);
- $event.stopPropagation();
- }
- };
- scope.hoverItemDetails = function (item, $event, hover) {
- if (scope.onDetailsHover) {
- scope.onDetailsHover(item, $event, hover);
- }
- };
- var unbindItemsWatcher = scope.$watch('items', function (newValue, oldValue) {
- if (angular.isArray(newValue)) {
- activate();
- }
- });
- scope.$on('$destroy', function () {
- unbindItemsWatcher();
- });
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/umb-media-grid.html',
- scope: {
- items: '=',
- onDetailsHover: '=',
- onClick: '=',
- onClickName: '=',
- filterBy: '=',
- itemMaxWidth: '@',
- itemMaxHeight: '@',
- itemMinWidth: '@',
- itemMinHeight: '@',
- onlyImages: '@'
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbMediaGrid', MediaGridDirective);
- }());
- (function () {
- 'use strict';
- function MiniListViewDirective(entityResource, iconHelper) {
- function link(scope, el, attr, ctrl) {
- scope.search = '';
- scope.miniListViews = [];
- scope.breadcrumb = [];
- var miniListViewsHistory = [];
- var goingForward = true;
- var skipAnimation = true;
- function onInit() {
- open(scope.node);
- }
- function open(node) {
- // convert legacy icon for node
- if (node && node.icon) {
- node.icon = iconHelper.convertFromLegacyIcon(node.icon);
- }
- goingForward = true;
- var miniListView = {
- node: node,
- loading: true,
- pagination: {
- pageSize: 10,
- pageNumber: 1,
- filter: '',
- orderDirection: 'Ascending',
- orderBy: 'SortOrder',
- orderBySystemField: true
- }
- };
- // clear and push mini list view in dom so we only render 1 view
- scope.miniListViews = [];
- scope.miniListViews.push(miniListView);
- // store in history so we quickly can navigate back
- miniListViewsHistory.push(miniListView);
- // get children
- getChildrenForMiniListView(miniListView);
- makeBreadcrumb();
- }
- function getChildrenForMiniListView(miniListView) {
- // start loading animation list view
- miniListView.loading = true;
- entityResource.getPagedChildren(miniListView.node.id, scope.entityType, miniListView.pagination).then(function (data) {
- // update children
- miniListView.children = data.items;
- _.each(miniListView.children, function (c) {
- // convert legacy icon for node
- if (c.icon) {
- c.icon = iconHelper.convertFromLegacyIcon(c.icon);
- }
- // set published state for content
- if (c.metaData) {
- c.hasChildren = c.metaData.HasChildren;
- if (scope.entityType === 'Document') {
- c.published = c.metaData.IsPublished;
- }
- }
- });
- // update pagination
- miniListView.pagination.totalItems = data.totalItems;
- miniListView.pagination.totalPages = data.totalPages;
- // stop load indicator
- miniListView.loading = false;
- });
- }
- scope.openNode = function (event, node) {
- open(node);
- event.stopPropagation();
- };
- scope.selectNode = function (node) {
- if (scope.onSelect) {
- scope.onSelect({ 'node': node });
- }
- };
- /* Pagination */
- scope.goToPage = function (pageNumber, miniListView) {
- // set new page number
- miniListView.pagination.pageNumber = pageNumber;
- // get children
- getChildrenForMiniListView(miniListView);
- };
- /* Breadcrumb */
- scope.clickBreadcrumb = function (ancestor) {
- var found = false;
- goingForward = false;
- angular.forEach(miniListViewsHistory, function (historyItem, index) {
- // We need to make sure we can compare the two id's.
- // Some id's are integers and others are strings.
- // Members have string ids like "all-members".
- if (historyItem.node.id.toString() === ancestor.id.toString()) {
- // load the list view from history
- scope.miniListViews = [];
- scope.miniListViews.push(historyItem);
- // clean up history - remove all children after
- miniListViewsHistory.splice(index + 1, miniListViewsHistory.length);
- found = true;
- }
- });
- if (!found) {
- // if we can't find the view in the history - close the list view
- scope.exitMiniListView();
- }
- // update the breadcrumb
- makeBreadcrumb();
- };
- scope.showBackButton = function () {
- // don't show the back button if the start node is a list view
- if (scope.node.metaData && scope.node.metaData.IsContainer || scope.node.isContainer) {
- return false;
- } else {
- return true;
- }
- };
- scope.exitMiniListView = function () {
- miniListViewsHistory = [];
- scope.miniListViews = [];
- if (scope.onClose) {
- scope.onClose();
- }
- };
- function makeBreadcrumb() {
- scope.breadcrumb = [];
- angular.forEach(miniListViewsHistory, function (historyItem) {
- scope.breadcrumb.push(historyItem.node);
- });
- }
- /* Search */
- scope.searchMiniListView = function (search, miniListView) {
- // set search value
- miniListView.pagination.filter = search;
- // reset pagination
- miniListView.pagination.pageNumber = 1;
- // start loading animation list view
- miniListView.loading = true;
- searchMiniListView(miniListView);
- };
- var searchMiniListView = _.debounce(function (miniListView) {
- scope.$apply(function () {
- getChildrenForMiniListView(miniListView);
- });
- }, 500);
- /* Animation */
- scope.getMiniListViewAnimation = function () {
- // disable the first "slide-in-animation"" if the start node is a list view
- if (scope.node.metaData && scope.node.metaData.IsContainer && skipAnimation || scope.node.isContainer && skipAnimation) {
- skipAnimation = false;
- return;
- }
- if (goingForward) {
- return 'umb-mini-list-view--forward';
- } else {
- return 'umb-mini-list-view--backwards';
- }
- };
- onInit();
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/umb-mini-list-view.html',
- scope: {
- node: '=',
- entityType: '@',
- startNodeId: '=',
- onSelect: '&',
- onClose: '&'
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbMiniListView', MiniListViewDirective);
- }());
- angular.module('umbraco.directives').directive('umbNestedContentEditor', [function () {
- var link = function ($scope) {
- // Clone the model because some property editors
- // do weird things like updating and config values
- // so we want to ensure we start from a fresh every
- // time, we'll just sync the value back when we need to
- $scope.model = angular.copy($scope.ngModel);
- $scope.nodeContext = $scope.model;
- // Find the selected tab
- var selectedTab = $scope.model.tabs[0];
- if ($scope.tabAlias) {
- angular.forEach($scope.model.tabs, function (tab) {
- if (tab.alias.toLowerCase() === $scope.tabAlias.toLowerCase()) {
- selectedTab = tab;
- return;
- }
- });
- }
- $scope.tab = selectedTab;
- // Listen for sync request
- var unsubscribe = $scope.$on('ncSyncVal', function (ev, args) {
- if (args.key === $scope.model.key) {
- // Tell inner controls we are submitting
- $scope.$broadcast('formSubmitting', { scope: $scope });
- // Sync the values back
- angular.forEach($scope.ngModel.tabs, function (tab) {
- if (tab.alias.toLowerCase() === selectedTab.alias.toLowerCase()) {
- var localPropsMap = selectedTab.properties.reduce(function (map, obj) {
- map[obj.alias] = obj;
- return map;
- }, {});
- angular.forEach(tab.properties, function (prop) {
- if (localPropsMap.hasOwnProperty(prop.alias)) {
- prop.value = localPropsMap[prop.alias].value;
- }
- });
- }
- });
- }
- });
- $scope.$on('$destroy', function () {
- unsubscribe();
- });
- };
- return {
- restrict: 'E',
- replace: true,
- templateUrl: Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/views/propertyeditors/nestedcontent/nestedcontent.editor.html',
- scope: {
- ngModel: '=',
- tabAlias: '='
- },
- link: link
- };
- }]);
- //angular.module("umbraco.directives").directive('nestedContentSubmitWatcher', function () {
- // var link = function (scope) {
- // // call the load callback on scope to obtain the ID of this submit watcher
- // var id = scope.loadCallback();
- // scope.$on("formSubmitting", function (ev, args) {
- // // on the "formSubmitting" event, call the submit callback on scope to notify the nestedContent controller to do it's magic
- // if (id === scope.activeSubmitWatcher) {
- // scope.submitCallback();
- // }
- // });
- // }
- // return {
- // restrict: "E",
- // replace: true,
- // template: "",
- // scope: {
- // loadCallback: '=',
- // submitCallback: '=',
- // activeSubmitWatcher: '='
- // },
- // link: link
- // }
- //});
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbNodePreview
- @restrict E
- @scope
- @description
- <strong>Added in Umbraco v. 7.6:</strong> Use this directive to render a node preview.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.NodePreviewController as vm">
-
- <div ui-sortable ng-model="vm.nodes">
- <umb-node-preview
- ng-repeat="node in vm.nodes"
- icon="node.icon"
- name="node.name"
- published="node.published"
- description="node.description"
- sortable="vm.sortable"
- allow-remove="vm.allowRemove"
- allow-open="vm.allowOpen"
- on-remove="vm.remove($index, vm.nodes)"
- on-open="vm.open(node)">
- </umb-node-preview>
- </div>
-
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
-
- function Controller() {
-
- var vm = this;
-
- vm.allowRemove = true;
- vm.allowOpen = true;
- vm.sortable = true;
-
- vm.nodes = [
- {
- "icon": "icon-document",
- "name": "My node 1",
- "published": true,
- "description": "A short description of my node"
- },
- {
- "icon": "icon-document",
- "name": "My node 2",
- "published": true,
- "description": "A short description of my node"
- }
- ];
-
- vm.remove = remove;
- vm.open = open;
-
- function remove(index, nodes) {
- alert("remove node");
- }
-
- function open(node) {
- alert("open node");
- }
-
- }
-
- angular.module("umbraco").controller("My.NodePreviewController", Controller);
-
- })();
- </pre>
- @param {string} icon (<code>binding</code>): The node icon.
- @param {string} name (<code>binding</code>): The node name.
- @param {boolean} published (<code>binding</code>): The node published state.
- @param {string} description (<code>binding</code>): A short description.
- @param {boolean} sortable (<code>binding</code>): Will add a move cursor on the node preview. Can used in combination with ui-sortable.
- @param {boolean} allowRemove (<code>binding</code>): Show/Hide the remove button.
- @param {boolean} allowOpen (<code>binding</code>): Show/Hide the open button.
- @param {boolean} allowEdit (<code>binding</code>): Show/Hide the edit button (Added in version 7.7.0).
- @param {function} onRemove (<code>expression</code>): Callback function when the remove button is clicked.
- @param {function} onOpen (<code>expression</code>): Callback function when the open button is clicked.
- @param {function} onEdit (<code>expression</code>): Callback function when the edit button is clicked (Added in version 7.7.0).
- **/
- (function () {
- 'use strict';
- function NodePreviewDirective() {
- function link(scope, el, attr, ctrl) {
- if (!scope.editLabelKey) {
- scope.editLabelKey = 'general_edit';
- }
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/umb-node-preview.html',
- scope: {
- icon: '=?',
- name: '=',
- description: '=?',
- permissions: '=?',
- published: '=?',
- sortable: '=?',
- allowOpen: '=?',
- allowRemove: '=?',
- allowEdit: '=?',
- onOpen: '&?',
- onRemove: '&?',
- onEdit: '&?'
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbNodePreview', NodePreviewDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbPagination
- @restrict E
- @scope
- @description
- Use this directive to generate a pagination.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.Controller as vm">
- <umb-pagination
- page-number="vm.pagination.pageNumber"
- total-pages="vm.pagination.totalPages"
- on-next="vm.nextPage"
- on-prev="vm.prevPage"
- on-go-to-page="vm.goToPage">
- </umb-pagination>
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
- function Controller() {
- var vm = this;
- vm.pagination = {
- pageNumber: 1,
- totalPages: 10
- }
- vm.nextPage = nextPage;
- vm.prevPage = prevPage;
- vm.goToPage = goToPage;
- function nextPage(pageNumber) {
- // do magic here
- console.log(pageNumber);
- alert("nextpage");
- }
- function prevPage(pageNumber) {
- // do magic here
- console.log(pageNumber);
- alert("prevpage");
- }
- function goToPage(pageNumber) {
- // do magic here
- console.log(pageNumber);
- alert("go to");
- }
- }
- angular.module("umbraco").controller("My.Controller", Controller);
- })();
- </pre>
- @param {number} pageNumber (<code>binding</code>): Current page number.
- @param {number} totalPages (<code>binding</code>): The total number of pages.
- @param {callback} onNext (<code>binding</code>): Callback method to go to the next page.
- <h3>The callback returns:</h3>
- <ul>
- <li><code>pageNumber</code>: The page number</li>
- </ul>
- @param {callback=} onPrev (<code>binding</code>): Callback method to go to the previous page.
- <h3>The callback returns:</h3>
- <ul>
- <li><code>pageNumber</code>: The page number</li>
- </ul>
- @param {callback=} onGoToPage (<code>binding</code>): Callback method to go to a specific page.
- <h3>The callback returns:</h3>
- <ul>
- <li><code>pageNumber</code>: The page number</li>
- </ul>
- **/
- (function () {
- 'use strict';
- function PaginationDirective(localizationService) {
- function link(scope, el, attr, ctrl) {
- function activate() {
- scope.pagination = [];
- var i = 0;
- if (scope.totalPages <= 10) {
- for (i = 0; i < scope.totalPages; i++) {
- scope.pagination.push({
- val: i + 1,
- isActive: scope.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.totalPages - 10;
- //set the start, but it can't be below zero
- var start = Math.max(scope.pageNumber - 5, 0);
- //ensure that it's not too far either
- start = Math.min(maxIndex, start);
- for (i = start; i < 10 + start; i++) {
- scope.pagination.push({
- val: i + 1,
- isActive: scope.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: localizationService.localize('general_first'),
- val: 1,
- isActive: false
- }, {
- val: '...',
- isActive: false
- });
- }
- //same for the end
- if (start < maxIndex) {
- scope.pagination.push({
- val: '...',
- isActive: false
- }, {
- name: localizationService.localize('general_last'),
- val: scope.totalPages,
- isActive: false
- });
- }
- }
- }
- scope.next = function () {
- if (scope.pageNumber < scope.totalPages) {
- scope.pageNumber++;
- if (scope.onNext) {
- scope.onNext(scope.pageNumber);
- }
- if (scope.onChange) {
- scope.onChange({ 'pageNumber': scope.pageNumber });
- }
- }
- };
- scope.prev = function (pageNumber) {
- if (scope.pageNumber > 1) {
- scope.pageNumber--;
- if (scope.onPrev) {
- scope.onPrev(scope.pageNumber);
- }
- if (scope.onChange) {
- scope.onChange({ 'pageNumber': scope.pageNumber });
- }
- }
- };
- scope.goToPage = function (pageNumber) {
- scope.pageNumber = pageNumber + 1;
- if (scope.onGoToPage) {
- scope.onGoToPage(scope.pageNumber);
- }
- if (scope.onChange) {
- if (scope.onChange) {
- scope.onChange({ 'pageNumber': scope.pageNumber });
- }
- }
- };
- var unbindPageNumberWatcher = scope.$watch('pageNumber', function (newValue, oldValue) {
- activate();
- });
- scope.$on('$destroy', function () {
- unbindPageNumberWatcher();
- });
- activate();
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/umb-pagination.html',
- scope: {
- pageNumber: '=',
- totalPages: '=',
- onNext: '=',
- onPrev: '=',
- onGoToPage: '=',
- onChange: '&'
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbPagination', PaginationDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbPasswordToggle
- @restrict E
- @scope
- @description
- <strong>Added in Umbraco v. 7.7.4:</strong> Use this directive to render a password toggle.
- **/
- (function () {
- 'use strict';
- // comes from https://codepen.io/jakob-e/pen/eNBQaP
- // works fine with Angular 1.6.5 - alas not with 1.1.5 - binding issue
- function PasswordToggleDirective($compile) {
- var directive = {
- restrict: 'A',
- scope: {},
- link: function (scope, elem, attrs) {
- scope.tgl = function () {
- elem.attr('type', elem.attr('type') === 'text' ? 'password' : 'text');
- };
- var lnk = angular.element('<a data-ng-click="tgl()">Toggle</a>');
- $compile(lnk)(scope);
- elem.wrap('<div class="password-toggle"/>').after(lnk);
- }
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbPasswordToggle', PasswordToggleDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbProgressBar
- @restrict E
- @scope
- @description
- Use this directive to generate a progress bar.
- <h3>Markup example</h3>
- <pre>
- <umb-progress-bar
- percentage="60">
- </umb-progress-bar>
- </pre>
- @param {number} percentage (<code>attribute</code>): The progress in percentage.
- @param {string} size (<code>attribute</code>): The size (s, m).
- **/
- (function () {
- 'use strict';
- function ProgressBarDirective() {
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/umb-progress-bar.html',
- scope: {
- percentage: '@',
- size: '@?'
- }
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbProgressBar', ProgressBarDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbStickyBar
- @restrict A
- @description
- Use this directive make an element sticky and follow the page when scrolling.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.Controller as vm">
- <div
- class="my-sticky-bar"
- umb-sticky-bar
- scrollable-container=".container">
- </div>
- </div>
- </pre>
- <h3>CSS example</h3>
- <pre>
- .my-sticky-bar {
- padding: 15px 0;
- background: #000000;
- position: relative;
- top: 0;
- }
- .my-sticky-bar.-umb-sticky-bar {
- top: 100px;
- }
- </pre>
- @param {string} scrollableContainer Set the class (".element") or the id ("#element") of the scrollable container element.
- **/
- (function () {
- 'use strict';
- function StickyBarDirective($rootScope) {
- function link(scope, el, attr, ctrl) {
- var bar = $(el);
- var scrollableContainer = null;
- var clonedBar = null;
- var cloneIsMade = false;
- function activate() {
- if (attr.scrollableContainer) {
- scrollableContainer = $(attr.scrollableContainer);
- } else {
- scrollableContainer = $(window);
- }
- scrollableContainer.on('scroll.umbStickyBar', determineVisibility).trigger('scroll');
- $(window).on('resize.umbStickyBar', determineVisibility);
- scope.$on('$destroy', function () {
- scrollableContainer.off('.umbStickyBar');
- $(window).off('.umbStickyBar');
- });
- }
- function determineVisibility() {
- var barTop = bar[0].offsetTop;
- var scrollTop = scrollableContainer.scrollTop();
- if (scrollTop > barTop) {
- if (!cloneIsMade) {
- createClone();
- clonedBar.css({ 'visibility': 'visible' });
- } else {
- calculateSize();
- }
- } else {
- if (cloneIsMade) {
- //remove cloned element (switched places with original on creation)
- bar.remove();
- bar = clonedBar;
- clonedBar = null;
- bar.removeClass('-umb-sticky-bar');
- bar.css({
- position: 'relative',
- 'width': 'auto',
- 'height': 'auto',
- 'z-index': 'auto',
- 'visibility': 'visible'
- });
- cloneIsMade = false;
- }
- }
- }
- function calculateSize() {
- clonedBar.css({
- width: bar.outerWidth(),
- height: bar.height()
- });
- }
- function createClone() {
- //switch place with cloned element, to keep binding intact
- clonedBar = bar;
- bar = clonedBar.clone();
- clonedBar.after(bar);
- clonedBar.addClass('-umb-sticky-bar');
- clonedBar.css({
- 'position': 'fixed',
- 'z-index': 500,
- 'visibility': 'hidden'
- });
- cloneIsMade = true;
- calculateSize();
- }
- activate();
- }
- var directive = {
- restrict: 'A',
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbStickyBar', StickyBarDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbTable
- @restrict E
- @scope
- @description
- <strong>Added in Umbraco v. 7.4:</strong> Use this directive to render a data table.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.TableController as vm">
-
- <umb-table
- ng-if="items"
- items="vm.items"
- item-properties="vm.options.includeProperties"
- allow-select-all="vm.allowSelectAll"
- on-select="vm.selectItem"
- on-click="vm.clickItem"
- on-select-all="vm.selectAll"
- on-selected-all="vm.isSelectedAll"
- on-sorting-direction="vm.isSortDirection"
- on-sort="vm.sort">
- </umb-table>
-
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
-
- function Controller() {
-
- var vm = this;
-
- vm.items = [
- {
- "icon": "icon-document",
- "name": "My node 1",
- "published": true,
- "description": "A short description of my node",
- "author": "Author 1"
- },
- {
- "icon": "icon-document",
- "name": "My node 2",
- "published": true,
- "description": "A short description of my node",
- "author": "Author 2"
- }
- ];
- vm.options = {
- includeProperties: [
- { alias: "description", header: "Description" },
- { alias: "author", header: "Author" }
- ]
- };
-
- vm.selectItem = selectItem;
- vm.clickItem = clickItem;
- vm.selectAll = selectAll;
- vm.isSelectedAll = isSelectedAll;
- vm.isSortDirection = isSortDirection;
- vm.sort = sort;
- function selectAll($event) {
- alert("select all");
- }
- function isSelectedAll() {
-
- }
-
- function clickItem(item) {
- alert("click node");
- }
- function selectItem(selectedItem, $index, $event) {
- alert("select node");
- }
-
- function isSortDirection(col, direction) {
-
- }
-
- function sort(field, allow, isSystem) {
-
- }
-
- }
-
- angular.module("umbraco").controller("My.TableController", Controller);
-
- })();
- </pre>
- @param {string} icon (<code>binding</code>): The node icon.
- @param {string} name (<code>binding</code>): The node name.
- @param {string} published (<code>binding</code>): The node published state.
- @param {function} onSelect (<code>expression</code>): Callback function when the row is selected.
- @param {function} onClick (<code>expression</code>): Callback function when the "Name" column link is clicked.
- @param {function} onSelectAll (<code>expression</code>): Callback function when selecting all items.
- @param {function} onSelectedAll (<code>expression</code>): Callback function when all items are selected.
- @param {function} onSortingDirection (<code>expression</code>): Callback function when sorting direction is changed.
- @param {function} onSort (<code>expression</code>): Callback function when sorting items.
- **/
- (function () {
- 'use strict';
- function TableDirective(iconHelper) {
- function link(scope, el, attr, ctrl) {
- scope.clickItem = function (item, $event) {
- if (scope.onClick) {
- scope.onClick(item);
- $event.stopPropagation();
- }
- };
- scope.selectItem = function (item, $index, $event) {
- if (scope.onSelect) {
- scope.onSelect(item, $index, $event);
- $event.stopPropagation();
- }
- };
- scope.selectAll = function ($event) {
- if (scope.onSelectAll) {
- scope.onSelectAll($event);
- }
- };
- scope.isSelectedAll = function () {
- if (scope.onSelectedAll && scope.items && scope.items.length > 0) {
- return scope.onSelectedAll();
- }
- };
- scope.isSortDirection = function (col, direction) {
- if (scope.onSortingDirection) {
- return scope.onSortingDirection(col, direction);
- }
- };
- scope.sort = function (field, allow, isSystem) {
- if (scope.onSort) {
- scope.onSort(field, allow, isSystem);
- }
- };
- scope.getIcon = function (entry) {
- return iconHelper.convertFromLegacyIcon(entry.icon);
- };
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/umb-table.html',
- scope: {
- items: '=',
- itemProperties: '=',
- allowSelectAll: '=',
- onSelect: '=',
- onClick: '=',
- onSelectAll: '=',
- onSelectedAll: '=',
- onSortingDirection: '=',
- onSort: '='
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbTable', TableDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbTooltip
- @restrict E
- @scope
- @description
- Use this directive to render a tooltip.
- <h3>Markup example</h3>
- <pre>
- <div ng-controller="My.Controller as vm">
- <div
- ng-mouseover="vm.mouseOver($event)"
- ng-mouseleave="vm.mouseLeave()">
- Hover me
- </div>
- <umb-tooltip
- ng-if="vm.tooltip.show"
- event="vm.tooltip.event">
- // tooltip content here
- </umb-tooltip>
- </div>
- </pre>
- <h3>Controller example</h3>
- <pre>
- (function () {
- "use strict";
- function Controller() {
- var vm = this;
- vm.tooltip = {
- show: false,
- event: null
- };
- vm.mouseOver = mouseOver;
- vm.mouseLeave = mouseLeave;
- function mouseOver($event) {
- vm.tooltip = {
- show: true,
- event: $event
- };
- }
- function mouseLeave() {
- vm.tooltip = {
- show: false,
- event: null
- };
- }
- }
- angular.module("umbraco").controller("My.Controller", Controller);
- })();
- </pre>
- @param {string} event Set the $event from the target element to position the tooltip relative to the mouse cursor.
- **/
- (function () {
- 'use strict';
- function TooltipDirective($timeout) {
- function link(scope, el, attr, ctrl) {
- scope.tooltipStyles = {};
- scope.tooltipStyles.left = 0;
- scope.tooltipStyles.top = 0;
- function activate() {
- $timeout(function () {
- setTooltipPosition(scope.event);
- });
- }
- function setTooltipPosition(event) {
- var container = $('#contentwrapper');
- var containerLeft = container[0].offsetLeft;
- var containerRight = containerLeft + container[0].offsetWidth;
- var containerTop = container[0].offsetTop;
- var containerBottom = containerTop + container[0].offsetHeight;
- var elementHeight = null;
- var elementWidth = null;
- var position = {
- right: 'inherit',
- left: 'inherit',
- top: 'inherit',
- bottom: 'inherit'
- };
- // element size
- elementHeight = el.context.clientHeight;
- elementWidth = el.context.clientWidth;
- position.left = event.pageX - elementWidth / 2;
- position.top = event.pageY;
- // check to see if element is outside screen
- // outside right
- if (position.left + elementWidth > containerRight) {
- position.right = 10;
- position.left = 'inherit';
- }
- // outside bottom
- if (position.top + elementHeight > containerBottom) {
- position.bottom = 10;
- position.top = 'inherit';
- }
- // outside left
- if (position.left < containerLeft) {
- position.left = containerLeft + 10;
- position.right = 'inherit';
- }
- // outside top
- if (position.top < containerTop) {
- position.top = 10;
- position.bottom = 'inherit';
- }
- scope.tooltipStyles = position;
- el.css(position);
- }
- activate();
- }
- var directive = {
- restrict: 'E',
- transclude: true,
- replace: true,
- templateUrl: 'views/components/umb-tooltip.html',
- scope: { event: '=' },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbTooltip', TooltipDirective);
- }());
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbFileDropzone
- * @restrict E
- * @function
- * @description
- * Used by editors that require naming an entity. Shows a textbox/headline with a required validator within it's own form.
- **/
- /*
- TODO
- .directive("umbFileDrop", function ($timeout, $upload, localizationService, umbRequestHelper){
- return{
- restrict: "A",
- link: function(scope, element, attrs){
- //load in the options model
- }
- }
- })
- */
- angular.module('umbraco.directives').directive('umbFileDropzone', function ($timeout, Upload, localizationService, umbRequestHelper) {
- return {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/upload/umb-file-dropzone.html',
- scope: {
- parentId: '@',
- contentTypeAlias: '@',
- propertyAlias: '@',
- accept: '@',
- maxFileSize: '@',
- compact: '@',
- hideDropzone: '@',
- acceptedMediatypes: '=',
- filesQueued: '=',
- handleFile: '=',
- filesUploaded: '='
- },
- link: function (scope, element, attrs) {
- scope.queue = [];
- scope.done = [];
- scope.rejected = [];
- scope.currentFile = undefined;
- function _filterFile(file) {
- var ignoreFileNames = ['Thumbs.db'];
- var ignoreFileTypes = ['directory'];
- // ignore files with names from the list
- // ignore files with types from the list
- // ignore files which starts with "."
- if (ignoreFileNames.indexOf(file.name) === -1 && ignoreFileTypes.indexOf(file.type) === -1 && file.name.indexOf('.') !== 0) {
- return true;
- } else {
- return false;
- }
- }
- function _filesQueued(files, event) {
- //Push into the queue
- angular.forEach(files, function (file) {
- if (_filterFile(file) === true) {
- if (file.$error) {
- scope.rejected.push(file);
- } else {
- scope.queue.push(file);
- }
- }
- });
- //when queue is done, kick the uploader
- if (!scope.working) {
- // Upload not allowed
- if (!scope.acceptedMediatypes || !scope.acceptedMediatypes.length) {
- files.map(function (file) {
- file.uploadStatus = 'error';
- file.serverErrorMessage = 'File type is not allowed here';
- scope.rejected.push(file);
- });
- scope.queue = [];
- }
- // One allowed type
- if (scope.acceptedMediatypes && scope.acceptedMediatypes.length === 1) {
- // Standard setup - set alias to auto select to let the server best decide which media type to use
- if (scope.acceptedMediatypes[0].alias === 'Image') {
- scope.contentTypeAlias = 'umbracoAutoSelect';
- } else {
- scope.contentTypeAlias = scope.acceptedMediatypes[0].alias;
- }
- _processQueueItem();
- }
- // More than one, open dialog
- if (scope.acceptedMediatypes && scope.acceptedMediatypes.length > 1) {
- _chooseMediaType();
- }
- }
- }
- function _processQueueItem() {
- if (scope.queue.length > 0) {
- scope.currentFile = scope.queue.shift();
- _upload(scope.currentFile);
- } else if (scope.done.length > 0) {
- if (scope.filesUploaded) {
- //queue is empty, trigger the done action
- scope.filesUploaded(scope.done);
- }
- //auto-clear the done queue after 3 secs
- var currentLength = scope.done.length;
- $timeout(function () {
- scope.done.splice(0, currentLength);
- }, 3000);
- }
- }
- function _upload(file) {
- scope.propertyAlias = scope.propertyAlias ? scope.propertyAlias : 'umbracoFile';
- scope.contentTypeAlias = scope.contentTypeAlias ? scope.contentTypeAlias : 'Image';
- Upload.upload({
- url: umbRequestHelper.getApiUrl('mediaApiBaseUrl', 'PostAddFile'),
- fields: {
- 'currentFolder': scope.parentId,
- 'contentTypeAlias': scope.contentTypeAlias,
- 'propertyAlias': scope.propertyAlias,
- 'path': file.path
- },
- file: file
- }).progress(function (evt) {
- if (file.uploadStat !== 'done' && file.uploadStat !== 'error') {
- // calculate progress in percentage
- var progressPercentage = parseInt(100 * evt.loaded / evt.total, 10);
- // set percentage property on file
- file.uploadProgress = progressPercentage;
- // set uploading status on file
- file.uploadStatus = 'uploading';
- }
- }).success(function (data, status, headers, config) {
- if (data.notifications && data.notifications.length > 0) {
- // set error status on file
- file.uploadStatus = 'error';
- // Throw message back to user with the cause of the error
- file.serverErrorMessage = data.notifications[0].message;
- // Put the file in the rejected pool
- scope.rejected.push(file);
- } else {
- // set done status on file
- file.uploadStatus = 'done';
- file.uploadProgress = 100;
- // set date/time for when done - used for sorting
- file.doneDate = new Date();
- // Put the file in the done pool
- scope.done.push(file);
- }
- scope.currentFile = undefined;
- //after processing, test if everthing is done
- _processQueueItem();
- }).error(function (evt, status, headers, config) {
- // set status done
- file.uploadStatus = 'error';
- //if the service returns a detailed error
- if (evt.InnerException) {
- file.serverErrorMessage = evt.InnerException.ExceptionMessage;
- //Check if its the common "too large file" exception
- if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf('ValidateRequestEntityLength') > 0) {
- file.serverErrorMessage = 'File too large to upload';
- }
- } else if (evt.Message) {
- file.serverErrorMessage = evt.Message;
- }
- // If file not found, server will return a 404 and display this message
- if (status === 404) {
- file.serverErrorMessage = 'File not found';
- }
- //after processing, test if everthing is done
- scope.rejected.push(file);
- scope.currentFile = undefined;
- _processQueueItem();
- });
- }
- function _chooseMediaType() {
- scope.mediatypepickerOverlay = {
- view: 'mediatypepicker',
- title: 'Choose media type',
- acceptedMediatypes: scope.acceptedMediatypes,
- hideSubmitButton: true,
- show: true,
- submit: function (model) {
- scope.contentTypeAlias = model.selectedType.alias;
- scope.mediatypepickerOverlay.show = false;
- scope.mediatypepickerOverlay = null;
- _processQueueItem();
- },
- close: function (oldModel) {
- scope.queue.map(function (file) {
- file.uploadStatus = 'error';
- file.serverErrorMessage = 'Cannot upload this file, no mediatype selected';
- scope.rejected.push(file);
- });
- scope.queue = [];
- scope.mediatypepickerOverlay.show = false;
- scope.mediatypepickerOverlay = null;
- }
- };
- }
- scope.handleFiles = function (files, event) {
- if (scope.filesQueued) {
- scope.filesQueued(files, event);
- }
- _filesQueued(files, event);
- };
- }
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbFileUpload
- * @function
- * @restrict A
- * @scope
- * @description
- * Listens for file input control changes and emits events when files are selected for use in other controllers.
- **/
- function umbFileUpload() {
- return {
- restrict: 'A',
- scope: true,
- //create a new scope
- link: function (scope, el, attrs) {
- el.bind('change', function (event) {
- var files = event.target.files;
- //emit event upward
- scope.$emit('filesSelected', { files: files });
- });
- }
- };
- }
- angular.module('umbraco.directives').directive('umbFileUpload', umbFileUpload);
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:umbSingleFileUpload
- * @function
- * @restrict A
- * @scope
- * @description
- * A single file upload field that will reset itself based on the object passed in for the rebuild parameter. This
- * is required because the only way to reset an upload control is to replace it's html.
- **/
- function umbSingleFileUpload($compile) {
- return {
- restrict: 'E',
- scope: { rebuild: '=' },
- replace: true,
- template: '<div><input type=\'file\' umb-file-upload /></div>',
- link: function (scope, el, attrs) {
- scope.$watch('rebuild', function (newVal, oldVal) {
- if (newVal && newVal !== oldVal) {
- //recompile it!
- el.html('<input type=\'file\' umb-file-upload />');
- $compile(el.contents())(scope);
- }
- });
- }
- };
- }
- angular.module('umbraco.directives').directive('umbSingleFileUpload', umbSingleFileUpload);
- (function () {
- 'use strict';
- function ChangePasswordController($scope) {
- 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
- }
- */
- $scope.showReset = false;
- //set defaults if they are not available
- if ($scope.config.disableToggle === undefined) {
- $scope.config.disableToggle = false;
- }
- if ($scope.config.hasPassword === undefined) {
- $scope.config.hasPassword = false;
- }
- if ($scope.config.enablePasswordRetrieval === undefined) {
- $scope.config.enablePasswordRetrieval = true;
- }
- if ($scope.config.requiresQuestionAnswer === undefined) {
- $scope.config.requiresQuestionAnswer = false;
- }
- //don't enable reset if it is new - that doesn't make sense
- if (isNew === 'true') {
- $scope.config.enableReset = false;
- } else if ($scope.config.enableReset === undefined) {
- $scope.config.enableReset = true;
- }
- if ($scope.config.minPasswordLength === undefined) {
- $scope.config.minPasswordLength = 0;
- }
- //set the model defaults
- if (!angular.isObject($scope.passwordValues)) {
- //if it's not an object then just create a new one
- $scope.passwordValues = {
- 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.passwordValues.newPassword = null;
- $scope.passwordValues.oldPassword = null;
- }
- $scope.passwordValues.reset = null;
- $scope.passwordValues.answer = null;
- }
- //the value to compare to match passwords
- if (!isNew) {
- $scope.passwordValues.confirm = '';
- } else if ($scope.passwordValues.newPassword && $scope.passwordValues.newPassword.length > 0) {
- //if it is new and a new password has been set, then set the confirm password too
- $scope.passwordValues.confirm = $scope.passwordValues.newPassword;
- }
- }
- resetModel($scope.isNew);
- //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.config.disableToggle === true || !$scope.config.hasPassword;
- //we're not currently changing so set the model to null
- if (!$scope.changing) {
- $scope.passwordValues = null;
- }
- $scope.doChange = function () {
- resetModel();
- $scope.changing = true;
- //if there was a previously generated password displaying, clear it
- $scope.passwordValues.generatedPassword = null;
- $scope.passwordValues.confirm = null;
- };
- $scope.cancelChange = function () {
- $scope.changing = false;
- //set model to null
- $scope.passwordValues = 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.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.passwordValues) {
- $scope.passwordValues.generatedPassword = null;
- } else if (!$scope.changing) {
- //we are not changing, so the model needs to be null
- $scope.passwordValues = null;
- }
- }));
- //when the scope is destroyed we need to unsubscribe
- $scope.$on('$destroy', function () {
- for (var u in unsubscribe) {
- unsubscribe[u]();
- }
- });
- $scope.showOldPass = function () {
- return $scope.config.hasPassword && !$scope.config.allowManuallyChangingPassword && !$scope.config.enablePasswordRetrieval && !$scope.showReset;
- };
- //TODO: I don't think we need this or the cancel button, this can be up to the editor rendering this directive
- $scope.showCancelBtn = function () {
- return $scope.config.disableToggle !== true && $scope.config.hasPassword;
- };
- }
- function ChangePasswordDirective() {
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/users/change-password.html',
- controller: 'Umbraco.Editors.Users.ChangePasswordDirectiveController',
- scope: {
- isNew: '=?',
- passwordValues: '=',
- config: '='
- }
- };
- return directive;
- }
- angular.module('umbraco.directives').controller('Umbraco.Editors.Users.ChangePasswordDirectiveController', ChangePasswordController);
- angular.module('umbraco.directives').directive('changePassword', ChangePasswordDirective);
- }());
- (function () {
- 'use strict';
- function PermissionDirective() {
- function link(scope, el, attr, ctrl) {
- scope.change = function () {
- scope.selected = !scope.selected;
- if (scope.onChange) {
- scope.onChange({ 'selected': scope.selected });
- }
- };
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/users/umb-permission.html',
- scope: {
- name: '=',
- description: '=?',
- selected: '=',
- onChange: '&'
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbPermission', PermissionDirective);
- }());
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbUserGroupPreview
- @restrict E
- @scope
- @description
- Use this directive to render a user group preview, where you can see the permissions the user or group has in the back office.
- <h3>Markup example</h3>
- <pre>
- <div>
- <umb-user-group-preview
- ng-repeat="userGroup in vm.user.userGroups"
- icon="userGroup.icon"
- name="userGroup.name"
- sections="userGroup.sections"
- content-start-node="userGroup.contentStartNode"
- media-start-node="userGroup.mediaStartNode"
- allow-remove="!vm.user.isCurrentUser"
- on-remove="vm.removeSelectedItem($index, vm.user.userGroups)">
- </umb-user-group-preview>
- </div>
- </pre>
- @param {string} icon (<code>binding</code>): The user group icon.
- @param {string} name (<code>binding</code>): The user group name.
- @param {array} sections (<code>binding</code>) Lists out the sections where the user has authority to edit.
- @param {string} contentStartNode (<code>binding</code>)
- <ul>
- <li>The starting point in the tree of the content section.</li>
- <li>So the user has no authority to work on other branches, only on this branch in the content section.</li>
- </ul>
- @param {boolean} hideContentStartNode (<code>binding</code>) Hides the contentStartNode.
- @param {string} mediaStartNode (<code>binding</code>)
- <ul>
- <li> The starting point in the tree of the media section.</li>
- <li> So the user has no authority to work on other branches, only on this branch in the media section.</li>
- </ul>
- @param {boolean} hideMediaStartNode (<code>binding</code>) Hides the mediaStartNode.
- @param {array} permissions (<code>binding<code>) A list of permissions, the user can have.
- @param {boolean} allowRemove (<code>binding</code>): Shows or Hides the remove button.
- @param {function} onRemove (<code>expression</code>): Callback function when the remove button is clicked.
- @param {boolean} allowEdit (<code>binding</code>): Shows or Hides the edit button.
- @param {function} onEdit (<code>expression</code>): Callback function when the edit button is clicked.
- **/
- (function () {
- 'use strict';
- function UserGroupPreviewDirective() {
- function link(scope, el, attr, ctrl) {
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/users/umb-user-group-preview.html',
- scope: {
- icon: '=?',
- name: '=',
- sections: '=?',
- contentStartNode: '=?',
- hideContentStartNode: '@?',
- mediaStartNode: '=?',
- hideMediaStartNode: '@?',
- permissions: '=?',
- allowRemove: '=?',
- allowEdit: '=?',
- onRemove: '&?',
- onEdit: '&?'
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbUserGroupPreview', UserGroupPreviewDirective);
- }());
- (function () {
- 'use strict';
- function UserPreviewDirective() {
- function link(scope, el, attr, ctrl) {
- }
- var directive = {
- restrict: 'E',
- replace: true,
- templateUrl: 'views/components/users/umb-user-preview.html',
- scope: {
- avatars: '=?',
- name: '=',
- allowRemove: '=?',
- onRemove: '&?'
- },
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbUserPreview', UserPreviewDirective);
- }());
- /**
- * Konami Code directive for AngularJS
- * @version v0.0.1
- * @license MIT License, http://www.opensource.org/licenses/MIT
- */
- angular.module('umbraco.directives').directive('konamiCode', [
- '$document',
- function ($document) {
- var konamiKeysDefault = [
- 38,
- 38,
- 40,
- 40,
- 37,
- 39,
- 37,
- 39,
- 66,
- 65
- ];
- return {
- restrict: 'A',
- link: function (scope, element, attr) {
- if (!attr.konamiCode) {
- throw 'Konami directive must receive an expression as value.';
- }
- // Let user define a custom code.
- var konamiKeys = attr.konamiKeys || konamiKeysDefault;
- var keyIndex = 0;
- /**
- * Fired when konami code is type.
- */
- function activated() {
- if ('konamiOnce' in attr) {
- stopListening();
- }
- // Execute expression.
- scope.$eval(attr.konamiCode);
- }
- /**
- * Handle keydown events.
- */
- function keydown(e) {
- if (e.keyCode === konamiKeys[keyIndex++]) {
- if (keyIndex === konamiKeys.length) {
- keyIndex = 0;
- activated();
- }
- } else {
- keyIndex = 0;
- }
- }
- /**
- * Stop to listen typing.
- */
- function stopListening() {
- $document.off('keydown', keydown);
- }
- // Start listening to key typing.
- $document.on('keydown', keydown);
- // Stop listening when scope is destroyed.
- scope.$on('$destroy', stopListening);
- }
- };
- }
- ]);
- /**
- @ngdoc directive
- @name umbraco.directives.directive:umbKeyboardList
- @restrict E
- @description
- <b>Added in versions 7.7.0</b>: Use this directive to add arrow up and down keyboard shortcuts to a list. Use this together with the {@link umbraco.directives.directive:umbDropdown umbDropdown} component to make easy accessible dropdown menus.
- <h3>Markup example</h3>
- <pre>
- <div>
- <ul umb-keyboard-list>
- <li><a href="">Item 1</a></li>
- <li><a href="">Item 2</a></li>
- <li><a href="">Item 3</a></li>
- <li><a href="">Item 4</a></li>
- <li><a href="">Item 5</a></li>
- <li><a href="">Item 6</a></li>
- </ul>
- </div>
- </pre>
- <h3>Use in combination with</h3>
- <ul>
- <li>{@link umbraco.directives.directive:umbDropdown umbDropdown}</li>
- </ul>
- **/
- angular.module('umbraco.directives').directive('umbKeyboardList', [
- '$document',
- '$timeout',
- function ($document, $timeout) {
- return {
- restrict: 'A',
- link: function (scope, element, attr) {
- var listItems = [];
- var currentIndex = 0;
- var focusSet = false;
- $timeout(function () {
- // get list of all links in the list
- listItems = element.find('li a');
- });
- // Handle keydown events
- function keydown(event) {
- $timeout(function () {
- checkFocus();
- // arrow down
- if (event.keyCode === 40) {
- arrowDown();
- }
- // arrow up
- if (event.keyCode === 38) {
- arrowUp();
- }
- });
- }
- function checkFocus() {
- var found = false;
- // check if any element has focus
- angular.forEach(listItems, function (item, index) {
- if ($(item).is(':focus')) {
- // if an element already has focus set the
- // currentIndex so we navigate from that element
- currentIndex = index;
- focusSet = true;
- found = true;
- }
- });
- // If we don't find an element with focus we reset the currentIndex and the focusSet flag
- // we do this because you can have navigated away from the list with tab and we want to reset it if you navigate back
- if (!found) {
- currentIndex = 0;
- focusSet = false;
- }
- }
- function arrowDown() {
- if (currentIndex < listItems.length - 1) {
- // only bump the current index if the focus is already
- // set else we just want to focus the first element
- if (focusSet) {
- currentIndex++;
- }
- listItems[currentIndex].focus();
- focusSet = true;
- }
- }
- function arrowUp() {
- if (currentIndex > 0) {
- currentIndex--;
- listItems[currentIndex].focus();
- }
- }
- // Stop to listen typing.
- function stopListening() {
- $document.off('keydown', keydown);
- }
- // Start listening to key typing.
- $document.on('keydown', keydown);
- // Stop listening when scope is destroyed.
- scope.$on('$destroy', stopListening);
- }
- };
- }
- ]);
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:noDirtyCheck
- * @restrict A
- * @description Can be attached to form inputs to prevent them from setting the form as dirty (http://stackoverflow.com/questions/17089090/prevent-input-from-setting-form-dirty-angularjs)
- **/
- function noDirtyCheck() {
- return {
- restrict: 'A',
- require: 'ngModel',
- link: function (scope, elm, attrs, ctrl) {
- elm.focus(function () {
- scope.$watch(function () {
- ctrl.$pristine = false;
- });
- });
- }
- };
- }
- angular.module('umbraco.directives.validation').directive('noDirtyCheck', noDirtyCheck);
- (function () {
- 'use strict';
- function SetDirtyOnChange() {
- function link(scope, el, attr, ctrl) {
- if (attr.ngModel) {
- scope.$watch(attr.ngModel, function (newValue, oldValue) {
- if (!newValue) {
- return;
- }
- if (newValue === oldValue) {
- return;
- }
- ctrl.$setDirty();
- }, true);
- } else {
- var initValue = attr.umbSetDirtyOnChange;
- attr.$observe('umbSetDirtyOnChange', function (newValue) {
- if (newValue !== initValue) {
- ctrl.$setDirty();
- }
- });
- }
- }
- var directive = {
- require: '^form',
- restrict: 'A',
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('umbSetDirtyOnChange', SetDirtyOnChange);
- }());
- angular.module('umbraco.directives.validation').directive('valCompare', function () {
- return {
- require: [
- 'ngModel',
- '^form'
- ],
- link: function (scope, elem, attrs, ctrls) {
- var ctrl = ctrls[0];
- var formCtrl = ctrls[1];
- var otherInput = formCtrl[attrs.valCompare];
- ctrl.$parsers.push(function (value) {
- if (value === otherInput.$viewValue) {
- ctrl.$setValidity('valCompare', true);
- return value;
- }
- ctrl.$setValidity('valCompare', false);
- });
- otherInput.$parsers.push(function (value) {
- ctrl.$setValidity('valCompare', value === ctrl.$viewValue);
- return value;
- });
- }
- };
- });
- /**
- * General-purpose validator for ngModel.
- * angular.js comes with several built-in validation mechanism for input fields (ngRequired, ngPattern etc.) but using
- * an arbitrary validation function requires creation of a custom formatters and / or parsers.
- * The ui-validate directive makes it easy to use any function(s) defined in scope as a validator function(s).
- * A validator function will trigger validation on both model and input changes.
- *
- * @example <input val-custom=" 'myValidatorFunction($value)' ">
- * @example <input val-custom="{ foo : '$value > anotherModel', bar : 'validateFoo($value)' }">
- * @example <input val-custom="{ foo : '$value > anotherModel' }" val-custom-watch=" 'anotherModel' ">
- * @example <input val-custom="{ foo : '$value > anotherModel', bar : 'validateFoo($value)' }" val-custom-watch=" { foo : 'anotherModel' } ">
- *
- * @param val-custom {string|object literal} If strings is passed it should be a scope's function to be used as a validator.
- * If an object literal is passed a key denotes a validation error key while a value should be a validator function.
- * In both cases validator function should take a value to validate as its argument and should return true/false indicating a validation result.
- */
- /*
- This code comes from the angular UI project, we had to change the directive name and module
- but other then that its unmodified
- */
- angular.module('umbraco.directives.validation').directive('valCustom', function () {
- return {
- restrict: 'A',
- require: 'ngModel',
- link: function (scope, elm, attrs, ctrl) {
- var validateFn, watch, validators = {}, validateExpr = scope.$eval(attrs.valCustom);
- if (!validateExpr) {
- return;
- }
- if (angular.isString(validateExpr)) {
- validateExpr = { validator: validateExpr };
- }
- angular.forEach(validateExpr, function (exprssn, key) {
- validateFn = function (valueToValidate) {
- var expression = scope.$eval(exprssn, { '$value': valueToValidate });
- if (angular.isObject(expression) && angular.isFunction(expression.then)) {
- // expression is a promise
- expression.then(function () {
- ctrl.$setValidity(key, true);
- }, function () {
- ctrl.$setValidity(key, false);
- });
- return valueToValidate;
- } else if (expression) {
- // expression is true
- ctrl.$setValidity(key, true);
- return valueToValidate;
- } else {
- // expression is false
- ctrl.$setValidity(key, false);
- return undefined;
- }
- };
- validators[key] = validateFn;
- ctrl.$parsers.push(validateFn);
- });
- function apply_watch(watch) {
- //string - update all validators on expression change
- if (angular.isString(watch)) {
- scope.$watch(watch, function () {
- angular.forEach(validators, function (validatorFn) {
- validatorFn(ctrl.$modelValue);
- });
- });
- return;
- }
- //array - update all validators on change of any expression
- if (angular.isArray(watch)) {
- angular.forEach(watch, function (expression) {
- scope.$watch(expression, function () {
- angular.forEach(validators, function (validatorFn) {
- validatorFn(ctrl.$modelValue);
- });
- });
- });
- return;
- }
- //object - update appropriate validator
- if (angular.isObject(watch)) {
- angular.forEach(watch, function (expression, validatorKey) {
- //value is string - look after one expression
- if (angular.isString(expression)) {
- scope.$watch(expression, function () {
- validators[validatorKey](ctrl.$modelValue);
- });
- }
- //value is array - look after all expressions in array
- if (angular.isArray(expression)) {
- angular.forEach(expression, function (intExpression) {
- scope.$watch(intExpression, function () {
- validators[validatorKey](ctrl.$modelValue);
- });
- });
- }
- });
- }
- }
- // Support for val-custom-watch
- if (attrs.valCustomWatch) {
- apply_watch(scope.$eval(attrs.valCustomWatch));
- }
- }
- };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:valEmail
- * @restrict A
- * @description A custom directive to validate an email address string, this is required because angular's default validator is incorrect.
- **/
- function valEmail(valEmailExpression) {
- return {
- require: 'ngModel',
- restrict: 'A',
- link: function (scope, elm, attrs, ctrl) {
- var patternValidator = function (viewValue) {
- //NOTE: we don't validate on empty values, use required validator for that
- if (!viewValue || valEmailExpression.EMAIL_REGEXP.test(viewValue)) {
- // it is valid
- ctrl.$setValidity('valEmail', true);
- //assign a message to the validator
- ctrl.errorMsg = '';
- return viewValue;
- } else {
- // it is invalid, return undefined (no model update)
- ctrl.$setValidity('valEmail', false);
- //assign a message to the validator
- ctrl.errorMsg = 'Invalid email';
- return undefined;
- }
- };
- //if there is an attribute: type="email" then we need to remove those formatters and parsers
- if (attrs.type === 'email') {
- //we need to remove the existing parsers = the default angular one which is created by
- // type="email", but this has a regex issue, so we'll remove that and add our custom one
- ctrl.$parsers.pop();
- //we also need to remove the existing formatter - the default angular one will not render
- // what it thinks is an invalid email address, so it will just be blank
- ctrl.$formatters.pop();
- }
- ctrl.$parsers.push(patternValidator);
- }
- };
- }
- angular.module('umbraco.directives.validation').directive('valEmail', valEmail).factory('valEmailExpression', function () {
- var emailRegex = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
- return { EMAIL_REGEXP: emailRegex };
- });
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:valFormManager
- * @restrict A
- * @require formController
- * @description Used to broadcast an event to all elements inside this one to notify that form validation has
- * changed. If we don't use this that means you have to put a watch for each directive on a form's validation
- * changing which would result in much higher processing. We need to actually watch the whole $error collection of a form
- * because just watching $valid or $invalid doesn't acurrately trigger form validation changing.
- * This also sets the show-validation (or a custom) css class on the element when the form is invalid - this lets
- * us css target elements to be displayed when the form is submitting/submitted.
- * Another thing this directive does is to ensure that any .control-group that contains form elements that are invalid will
- * be marked with the 'error' css class. This ensures that labels included in that control group are styled correctly.
- **/
- function valFormManager(serverValidationManager, $rootScope, $log, $timeout, notificationsService, eventsService, $routeParams) {
- return {
- require: 'form',
- restrict: 'A',
- controller: function ($scope) {
- //This exposes an API for direct use with this directive
- var unsubscribe = [];
- var self = this;
- //This is basically the same as a directive subscribing to an event but maybe a little
- // nicer since the other directive can use this directive's API instead of a magical event
- this.onValidationStatusChanged = function (cb) {
- unsubscribe.push($scope.$on('valStatusChanged', function (evt, args) {
- cb.apply(self, [
- evt,
- args
- ]);
- }));
- };
- //Ensure to remove the event handlers when this instance is destroyted
- $scope.$on('$destroy', function () {
- for (var u in unsubscribe) {
- unsubscribe[u]();
- }
- });
- },
- link: function (scope, element, attr, formCtrl) {
- scope.$watch(function () {
- return formCtrl.$error;
- }, function (e) {
- scope.$broadcast('valStatusChanged', { form: formCtrl });
- //find all invalid elements' .control-group's and apply the error class
- var inError = element.find('.control-group .ng-invalid').closest('.control-group');
- inError.addClass('error');
- //find all control group's that have no error and ensure the class is removed
- var noInError = element.find('.control-group .ng-valid').closest('.control-group').not(inError);
- noInError.removeClass('error');
- }, true);
- var className = attr.valShowValidation ? attr.valShowValidation : 'show-validation';
- var savingEventName = attr.savingEvent ? attr.savingEvent : 'formSubmitting';
- var savedEvent = attr.savedEvent ? attr.savingEvent : 'formSubmitted';
- //This tracks if the user is currently saving a new item, we use this to determine
- // if we should display the warning dialog that they are leaving the page - if a new item
- // is being saved we never want to display that dialog, this will also cause problems when there
- // are server side validation issues.
- var isSavingNewItem = false;
- //we should show validation if there are any msgs in the server validation collection
- if (serverValidationManager.items.length > 0) {
- element.addClass(className);
- }
- var unsubscribe = [];
- //listen for the forms saving event
- unsubscribe.push(scope.$on(savingEventName, function (ev, args) {
- element.addClass(className);
- //set the flag so we can check to see if we should display the error.
- isSavingNewItem = $routeParams.create;
- }));
- //listen for the forms saved event
- unsubscribe.push(scope.$on(savedEvent, function (ev, args) {
- //remove validation class
- element.removeClass(className);
- //clear form state as at this point we retrieve new data from the server
- //and all validation will have cleared at this point
- formCtrl.$setPristine();
- }));
- //This handles the 'unsaved changes' dialog which is triggered when a route is attempting to be changed but
- // the form has pending changes
- var locationEvent = $rootScope.$on('$locationChangeStart', function (event, nextLocation, currentLocation) {
- if (!formCtrl.$dirty || isSavingNewItem) {
- return;
- }
- var path = nextLocation.split('#')[1];
- if (path) {
- if (path.indexOf('%253') || path.indexOf('%252')) {
- path = decodeURIComponent(path);
- }
- if (!notificationsService.hasView()) {
- var msg = {
- view: 'confirmroutechange',
- args: {
- path: path,
- listener: locationEvent
- }
- };
- notificationsService.add(msg);
- }
- //prevent the route!
- event.preventDefault();
- //raise an event
- eventsService.emit('valFormManager.pendingChanges', true);
- }
- });
- unsubscribe.push(locationEvent);
- //Ensure to remove the event handler when this instance is destroyted
- scope.$on('$destroy', function () {
- for (var u in unsubscribe) {
- unsubscribe[u]();
- }
- });
- $timeout(function () {
- formCtrl.$setPristine();
- }, 1000);
- }
- };
- }
- angular.module('umbraco.directives.validation').directive('valFormManager', valFormManager);
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:valHighlight
- * @restrict A
- * @description Used on input fields when you want to signal that they are in error, this will highlight the item for 1 second
- **/
- function valHighlight($timeout) {
- return {
- restrict: 'A',
- link: function (scope, element, attrs, ctrl) {
- attrs.$observe('valHighlight', function (newVal) {
- if (newVal === 'true') {
- element.addClass('highlight-error');
- $timeout(function () {
- //set the bound scope property to false
- scope[attrs.valHighlight] = false;
- }, 1000);
- } else {
- element.removeClass('highlight-error');
- }
- });
- }
- };
- }
- angular.module('umbraco.directives.validation').directive('valHighlight', valHighlight);
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:valPropertyMsg
- * @restrict A
- * @element textarea
- * @requires formController
- * @description This directive is used to control the display of the property level validation message.
- * We will listen for server side validation changes
- * and when an error is detected for this property we'll show the error message.
- * In order for this directive to work, the valStatusChanged directive must be placed on the containing form.
- **/
- function valPropertyMsg(serverValidationManager) {
- return {
- scope: { property: '=' },
- require: '^form',
- //require that this directive is contained within an ngForm
- replace: true,
- //replace the element with the template
- restrict: 'E',
- //restrict to element
- template: '<div ng-show="errorMsg != \'\'" class=\'alert alert-error property-error\' >{{errorMsg}}</div>',
- /**
- Our directive requries a reference to a form controller
- which gets passed in to this parameter
- */
- link: function (scope, element, attrs, formCtrl) {
- var watcher = null;
- // Gets the error message to display
- function getErrorMsg() {
- //this can be null if no property was assigned
- if (scope.property) {
- //first try to get the error msg from the server collection
- var err = serverValidationManager.getPropertyError(scope.property.alias, '');
- //if there's an error message use it
- if (err && err.errorMsg) {
- return err.errorMsg;
- } else {
- return scope.property.propertyErrorMessage ? scope.property.propertyErrorMessage : 'Property has errors';
- }
- }
- return 'Property has errors';
- }
- // We need to subscribe to any changes to our model (based on user input)
- // This is required because when we have a server error we actually invalidate
- // the form which means it cannot be resubmitted.
- // So once a field is changed that has a server error assigned to it
- // we need to re-validate it for the server side validator so the user can resubmit
- // the form. Of course normal client-side validators will continue to execute.
- function startWatch() {
- //if there's not already a watch
- if (!watcher) {
- watcher = scope.$watch('property.value', function (newValue, oldValue) {
- if (!newValue || angular.equals(newValue, oldValue)) {
- return;
- }
- var errCount = 0;
- for (var e in formCtrl.$error) {
- if (angular.isArray(formCtrl.$error[e])) {
- errCount++;
- }
- }
- //we are explicitly checking for valServer errors here, since we shouldn't auto clear
- // based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg
- // is the only one, then we'll clear.
- if (errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg) || formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer)) {
- scope.errorMsg = '';
- formCtrl.$setValidity('valPropertyMsg', true);
- stopWatch();
- }
- }, true);
- }
- }
- //clear the watch when the property validator is valid again
- function stopWatch() {
- if (watcher) {
- watcher();
- watcher = null;
- }
- }
- //if there's any remaining errors in the server validation service then we should show them.
- var showValidation = serverValidationManager.items.length > 0;
- var hasError = false;
- //create properties on our custom scope so we can use it in our template
- scope.errorMsg = '';
- var unsubscribe = [];
- //listen for form error changes
- unsubscribe.push(scope.$on('valStatusChanged', function (evt, args) {
- if (args.form.$invalid) {
- //first we need to check if the valPropertyMsg validity is invalid
- if (formCtrl.$error.valPropertyMsg && formCtrl.$error.valPropertyMsg.length > 0) {
- //since we already have an error we'll just return since this means we've already set the
- // hasError and errorMsg properties which occurs below in the serverValidationManager.subscribe
- return;
- } else if (element.closest('.umb-control-group').find('.ng-invalid').length > 0) {
- //check if it's one of the properties that is invalid in the current content property
- hasError = true;
- //update the validation message if we don't already have one assigned.
- if (showValidation && scope.errorMsg === '') {
- scope.errorMsg = getErrorMsg();
- }
- } else {
- hasError = false;
- scope.errorMsg = '';
- }
- } else {
- hasError = false;
- scope.errorMsg = '';
- }
- }, true));
- //listen for the forms saving event
- unsubscribe.push(scope.$on('formSubmitting', function (ev, args) {
- showValidation = true;
- if (hasError && scope.errorMsg === '') {
- scope.errorMsg = getErrorMsg();
- } else if (!hasError) {
- scope.errorMsg = '';
- stopWatch();
- }
- }));
- //listen for the forms saved event
- unsubscribe.push(scope.$on('formSubmitted', function (ev, args) {
- showValidation = false;
- scope.errorMsg = '';
- formCtrl.$setValidity('valPropertyMsg', true);
- stopWatch();
- }));
- //listen for server validation changes
- // NOTE: we pass in "" in order to listen for all validation changes to the content property, not for
- // validation changes to fields in the property this is because some server side validators may not
- // return the field name for which the error belongs too, just the property for which it belongs.
- // It's important to note that we need to subscribe to server validation changes here because we always must
- // indicate that a content property is invalid at the property level since developers may not actually implement
- // the correct field validation in their property editors.
- if (scope.property) {
- //this can be null if no property was assigned
- serverValidationManager.subscribe(scope.property.alias, '', function (isValid, propertyErrors, allErrors) {
- hasError = !isValid;
- if (hasError) {
- //set the error message to the server message
- scope.errorMsg = propertyErrors[0].errorMsg;
- //flag that the current validator is invalid
- formCtrl.$setValidity('valPropertyMsg', false);
- startWatch();
- } else {
- scope.errorMsg = '';
- //flag that the current validator is valid
- formCtrl.$setValidity('valPropertyMsg', true);
- stopWatch();
- }
- });
- //when the element is disposed we need to unsubscribe!
- // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain
- // but they are a different callback instance than the above.
- element.bind('$destroy', function () {
- stopWatch();
- serverValidationManager.unsubscribe(scope.property.alias, '');
- });
- }
- //when the scope is disposed we need to unsubscribe
- scope.$on('$destroy', function () {
- for (var u in unsubscribe) {
- unsubscribe[u]();
- }
- });
- }
- };
- }
- angular.module('umbraco.directives.validation').directive('valPropertyMsg', valPropertyMsg);
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:valPropertyValidator
- * @restrict A
- * @description Performs any custom property value validation checks on the client side. This allows property editors to be highly flexible when it comes to validation
- on the client side. Typically if a property editor stores a primitive value (i.e. string) then the client side validation can easily be taken care of
- with standard angular directives such as ng-required. However since some property editors store complex data such as JSON, a given property editor
- might require custom validation. This directive can be used to validate an Umbraco property in any way that a developer would like by specifying a
- callback method to perform the validation. The result of this method must return an object in the format of
- {isValid: true, errorKey: 'required', errorMsg: 'Something went wrong' }
- The error message returned will also be displayed for the property level validation message.
- This directive should only be used when dealing with complex models, if custom validation needs to be performed with primitive values, use the simpler
- angular validation directives instead since this will watch the entire model.
- **/
- function valPropertyValidator(serverValidationManager) {
- return {
- scope: { valPropertyValidator: '=' },
- // The element must have ng-model attribute and be inside an umbProperty directive
- require: [
- 'ngModel',
- '?^umbProperty'
- ],
- restrict: 'A',
- link: function (scope, element, attrs, ctrls) {
- var modelCtrl = ctrls[0];
- var propCtrl = ctrls.length > 1 ? ctrls[1] : null;
- // Check whether the scope has a valPropertyValidator method
- if (!scope.valPropertyValidator || !angular.isFunction(scope.valPropertyValidator)) {
- throw new Error('val-property-validator directive must specify a function to call');
- }
- var initResult = scope.valPropertyValidator();
- // Validation method
- var validate = function (viewValue) {
- // Calls the validition method
- var result = scope.valPropertyValidator();
- if (!result.errorKey || result.isValid === undefined || !result.errorMsg) {
- throw 'The result object from valPropertyValidator does not contain required properties: isValid, errorKey, errorMsg';
- }
- if (result.isValid === true) {
- // Tell the controller that the value is valid
- modelCtrl.$setValidity(result.errorKey, true);
- if (propCtrl) {
- propCtrl.setPropertyError(null);
- }
- } else {
- // Tell the controller that the value is invalid
- modelCtrl.$setValidity(result.errorKey, false);
- if (propCtrl) {
- propCtrl.setPropertyError(result.errorMsg);
- }
- }
- };
- // Parsers are called as soon as the value in the form input is modified
- modelCtrl.$parsers.push(validate);
- }
- };
- }
- angular.module('umbraco.directives.validation').directive('valPropertyValidator', valPropertyValidator);
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:valRegex
- * @restrict A
- * @description A custom directive to allow for matching a value against a regex string.
- * NOTE: there's already an ng-pattern but this requires that a regex expression is set, not a regex string
- **/
- function valRegex() {
- return {
- require: 'ngModel',
- restrict: 'A',
- link: function (scope, elm, attrs, ctrl) {
- var flags = '';
- var regex;
- var eventBindings = [];
- attrs.$observe('valRegexFlags', function (newVal) {
- if (newVal) {
- flags = newVal;
- }
- });
- attrs.$observe('valRegex', function (newVal) {
- if (newVal) {
- try {
- var resolved = newVal;
- if (resolved) {
- regex = new RegExp(resolved, flags);
- } else {
- regex = new RegExp(attrs.valRegex, flags);
- }
- } catch (e) {
- regex = new RegExp(attrs.valRegex, flags);
- }
- }
- });
- eventBindings.push(scope.$watch('ngModel', function (newValue, oldValue) {
- if (newValue && newValue !== oldValue) {
- patternValidator(newValue);
- }
- }));
- var patternValidator = function (viewValue) {
- if (regex) {
- //NOTE: we don't validate on empty values, use required validator for that
- if (!viewValue || regex.test(viewValue.toString())) {
- // it is valid
- ctrl.$setValidity('valRegex', true);
- //assign a message to the validator
- ctrl.errorMsg = '';
- return viewValue;
- } else {
- // it is invalid, return undefined (no model update)
- ctrl.$setValidity('valRegex', false);
- //assign a message to the validator
- ctrl.errorMsg = 'Value is invalid, it does not match the correct pattern';
- return undefined;
- }
- }
- };
- scope.$on('$destroy', function () {
- // unbind watchers
- for (var e in eventBindings) {
- eventBindings[e]();
- }
- });
- }
- };
- }
- angular.module('umbraco.directives.validation').directive('valRegex', valRegex);
- (function () {
- 'use strict';
- function ValRequireComponentDirective() {
- function link(scope, el, attr, ngModel) {
- var unbindModelWatcher = scope.$watch(function () {
- return ngModel.$modelValue;
- }, function (newValue) {
- if (newValue === undefined || newValue === null || newValue === '') {
- ngModel.$setValidity('valRequiredComponent', false);
- } else {
- ngModel.$setValidity('valRequiredComponent', true);
- }
- });
- // clean up
- scope.$on('$destroy', function () {
- unbindModelWatcher();
- });
- }
- var directive = {
- require: 'ngModel',
- restrict: 'A',
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('valRequireComponent', ValRequireComponentDirective);
- }());
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:valServer
- * @restrict A
- * @description This directive is used to associate a content property with a server-side validation response
- * so that the validators in angular are updated based on server-side feedback.
- **/
- function valServer(serverValidationManager) {
- return {
- require: [
- 'ngModel',
- '?^umbProperty'
- ],
- restrict: 'A',
- link: function (scope, element, attr, ctrls) {
- var modelCtrl = ctrls[0];
- var umbPropCtrl = ctrls.length > 1 ? ctrls[1] : null;
- if (!umbPropCtrl) {
- //we cannot proceed, this validator will be disabled
- return;
- }
- var watcher = null;
- //Need to watch the value model for it to change, previously we had subscribed to
- //modelCtrl.$viewChangeListeners but this is not good enough if you have an editor that
- // doesn't specifically have a 2 way ng binding. This is required because when we
- // have a server error we actually invalidate the form which means it cannot be
- // resubmitted. So once a field is changed that has a server error assigned to it
- // we need to re-validate it for the server side validator so the user can resubmit
- // the form. Of course normal client-side validators will continue to execute.
- function startWatch() {
- //if there's not already a watch
- if (!watcher) {
- watcher = scope.$watch(function () {
- return modelCtrl.$modelValue;
- }, function (newValue, oldValue) {
- if (!newValue || angular.equals(newValue, oldValue)) {
- return;
- }
- if (modelCtrl.$invalid) {
- modelCtrl.$setValidity('valServer', true);
- stopWatch();
- }
- }, true);
- }
- }
- function stopWatch() {
- if (watcher) {
- watcher();
- watcher = null;
- }
- }
- var currentProperty = umbPropCtrl.property;
- //default to 'value' if nothing is set
- var fieldName = 'value';
- if (attr.valServer) {
- fieldName = scope.$eval(attr.valServer);
- if (!fieldName) {
- //eval returned nothing so just use the string
- fieldName = attr.valServer;
- }
- }
- //subscribe to the server validation changes
- serverValidationManager.subscribe(currentProperty.alias, fieldName, function (isValid, propertyErrors, allErrors) {
- if (!isValid) {
- modelCtrl.$setValidity('valServer', false);
- //assign an error msg property to the current validator
- modelCtrl.errorMsg = propertyErrors[0].errorMsg;
- startWatch();
- } else {
- modelCtrl.$setValidity('valServer', true);
- //reset the error message
- modelCtrl.errorMsg = '';
- stopWatch();
- }
- });
- //when the element is disposed we need to unsubscribe!
- // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain
- // but they are a different callback instance than the above.
- element.bind('$destroy', function () {
- stopWatch();
- serverValidationManager.unsubscribe(currentProperty.alias, fieldName);
- });
- }
- };
- }
- angular.module('umbraco.directives.validation').directive('valServer', valServer);
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:valServerField
- * @restrict A
- * @description This directive is used to associate a content field (not user defined) with a server-side validation response
- * so that the validators in angular are updated based on server-side feedback.
- **/
- function valServerField(serverValidationManager) {
- return {
- require: 'ngModel',
- restrict: 'A',
- link: function (scope, element, attr, ngModel) {
- var fieldName = null;
- var eventBindings = [];
- attr.$observe('valServerField', function (newVal) {
- if (newVal && fieldName === null) {
- fieldName = newVal;
- //subscribe to the changed event of the view model. This is required because when we
- // have a server error we actually invalidate the form which means it cannot be
- // resubmitted. So once a field is changed that has a server error assigned to it
- // we need to re-validate it for the server side validator so the user can resubmit
- // the form. Of course normal client-side validators will continue to execute.
- eventBindings.push(scope.$watch(function () {
- return ngModel.$modelValue;
- }, function (newValue) {
- if (ngModel.$invalid) {
- ngModel.$setValidity('valServerField', true);
- }
- }));
- //subscribe to the server validation changes
- serverValidationManager.subscribe(null, fieldName, function (isValid, fieldErrors, allErrors) {
- if (!isValid) {
- ngModel.$setValidity('valServerField', false);
- //assign an error msg property to the current validator
- ngModel.errorMsg = fieldErrors[0].errorMsg;
- } else {
- ngModel.$setValidity('valServerField', true);
- //reset the error message
- ngModel.errorMsg = '';
- }
- });
- //when the element is disposed we need to unsubscribe!
- // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain
- // but they are a different callback instance than the above.
- element.bind('$destroy', function () {
- serverValidationManager.unsubscribe(null, fieldName);
- });
- }
- });
- scope.$on('$destroy', function () {
- // unbind watchers
- for (var e in eventBindings) {
- eventBindings[e]();
- }
- });
- }
- };
- }
- angular.module('umbraco.directives.validation').directive('valServerField', valServerField);
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:valSubView
- * @restrict A
- * @description Used to show validation warnings for a editor sub view to indicate that the section content has validation errors in its data.
- * In order for this directive to work, the valFormManager directive must be placed on the containing form.
- **/
- (function () {
- 'use strict';
- function valSubViewDirective() {
- function link(scope, el, attr, ctrl) {
- //if there are no containing form or valFormManager controllers, then we do nothing
- if (!ctrl || !angular.isArray(ctrl) || ctrl.length !== 2 || !ctrl[0] || !ctrl[1]) {
- return;
- }
- var valFormManager = ctrl[1];
- scope.subView.hasError = false;
- //listen for form validation changes
- valFormManager.onValidationStatusChanged(function (evt, args) {
- if (!args.form.$valid) {
- var subViewContent = el.find('.ng-invalid');
- if (subViewContent.length > 0) {
- scope.subView.hasError = true;
- } else {
- scope.subView.hasError = false;
- }
- } else {
- scope.subView.hasError = false;
- }
- });
- }
- var directive = {
- require: [
- '?^form',
- '?^valFormManager'
- ],
- restrict: 'A',
- link: link
- };
- return directive;
- }
- angular.module('umbraco.directives').directive('valSubView', valSubViewDirective);
- }());
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:valTab
- * @restrict A
- * @description Used to show validation warnings for a tab to indicate that the tab content has validations errors in its data.
- * In order for this directive to work, the valFormManager directive must be placed on the containing form.
- **/
- function valTab() {
- return {
- require: [
- '^form',
- '^valFormManager'
- ],
- restrict: 'A',
- link: function (scope, element, attr, ctrs) {
- var valFormManager = ctrs[1];
- var tabId = 'tab' + scope.tab.id;
- scope.tabHasError = false;
- //listen for form validation changes
- valFormManager.onValidationStatusChanged(function (evt, args) {
- if (!args.form.$valid) {
- var tabContent = element.closest('.umb-panel').find('#' + tabId);
- //check if the validation messages are contained inside of this tabs
- if (tabContent.find('.ng-invalid').length > 0) {
- scope.tabHasError = true;
- } else {
- scope.tabHasError = false;
- }
- } else {
- scope.tabHasError = false;
- }
- });
- }
- };
- }
- angular.module('umbraco.directives.validation').directive('valTab', valTab);
- function valToggleMsg(serverValidationManager) {
- return {
- require: '^form',
- restrict: 'A',
- /**
- Our directive requries a reference to a form controller which gets passed in to this parameter
- */
- link: function (scope, element, attr, formCtrl) {
- if (!attr.valToggleMsg) {
- throw 'valToggleMsg requires that a reference to a validator is specified';
- }
- if (!attr.valMsgFor) {
- throw 'valToggleMsg requires that the attribute valMsgFor exists on the element';
- }
- if (!formCtrl[attr.valMsgFor]) {
- throw 'valToggleMsg cannot find field ' + attr.valMsgFor + ' on form ' + formCtrl.$name;
- }
- //if there's any remaining errors in the server validation service then we should show them.
- var showValidation = serverValidationManager.items.length > 0;
- var hasCustomMsg = element.contents().length > 0;
- //add a watch to the validator for the value (i.e. myForm.value.$error.required )
- scope.$watch(function () {
- //sometimes if a dialog closes in the middle of digest we can get null references here
- return formCtrl && formCtrl[attr.valMsgFor] ? formCtrl[attr.valMsgFor].$error[attr.valToggleMsg] : null;
- }, function () {
- //sometimes if a dialog closes in the middle of digest we can get null references here
- if (formCtrl && formCtrl[attr.valMsgFor]) {
- if (formCtrl[attr.valMsgFor].$error[attr.valToggleMsg] && showValidation) {
- element.show();
- //display the error message if this element has no contents
- if (!hasCustomMsg) {
- element.html(formCtrl[attr.valMsgFor].errorMsg);
- }
- } else {
- element.hide();
- }
- }
- });
- var unsubscribe = [];
- //listen for the saving event (the result is a callback method which is called to unsubscribe)
- unsubscribe.push(scope.$on('formSubmitting', function (ev, args) {
- showValidation = true;
- if (formCtrl[attr.valMsgFor].$error[attr.valToggleMsg]) {
- element.show();
- //display the error message if this element has no contents
- if (!hasCustomMsg) {
- element.html(formCtrl[attr.valMsgFor].errorMsg);
- }
- } else {
- element.hide();
- }
- }));
- //listen for the saved event (the result is a callback method which is called to unsubscribe)
- unsubscribe.push(scope.$on('formSubmitted', function (ev, args) {
- showValidation = false;
- element.hide();
- }));
- //when the element is disposed we need to unsubscribe!
- // NOTE: this is very important otherwise if this directive is part of a modal, the listener still exists because the dom
- // element might still be there even after the modal has been hidden.
- element.bind('$destroy', function () {
- for (var u in unsubscribe) {
- unsubscribe[u]();
- }
- });
- }
- };
- }
- /**
- * @ngdoc directive
- * @name umbraco.directives.directive:valToggleMsg
- * @restrict A
- * @element input
- * @requires formController
- * @description This directive will show/hide an error based on: is the value + the given validator invalid? AND, has the form been submitted ?
- **/
- angular.module('umbraco.directives.validation').directive('valToggleMsg', valToggleMsg);
- angular.module('umbraco.directives.validation').directive('valTriggerChange', function ($sniffer) {
- return {
- link: function (scope, elem, attrs) {
- elem.bind('click', function () {
- $(attrs.valTriggerChange).trigger($sniffer.hasEvent('input') ? 'input' : 'change');
- });
- },
- priority: 1
- };
- });
- }());