diff --git a/src/Cofoundry.Core/Core/Settings/DebugSettings.cs b/src/Cofoundry.Core/Core/Settings/DebugSettings.cs
index fa62d0de4..bd64a33bc 100644
--- a/src/Cofoundry.Core/Core/Settings/DebugSettings.cs
+++ b/src/Cofoundry.Core/Core/Settings/DebugSettings.cs
@@ -43,7 +43,7 @@ public partial class DebugSettings : CofoundryConfigurationSettingsBase
public string EmbeddedContentPhysicalPathRootOverride { get; set; }
///
- /// USe to determine if we should show the developer exception page,
+ /// Use to determine if we should show the developer exception page,
/// taking the current environment into consideration.
///
/// The current hosting environment.
diff --git a/src/Cofoundry.Web.Admin/Admin/Modules/Shared/Content/js/shared.js b/src/Cofoundry.Web.Admin/Admin/Modules/Shared/Content/js/shared.js
index f2c8daf80..939fd4c3b 100644
--- a/src/Cofoundry.Web.Admin/Admin/Modules/Shared/Content/js/shared.js
+++ b/src/Cofoundry.Web.Admin/Admin/Modules/Shared/Content/js/shared.js
@@ -8092,7 +8092,8 @@ function (
});
headers.common['X-Requested-With'] = 'XMLHttpRequest';
-
+ headers.common['Accept'] = 'application/json, text/html, text/plain, */*'
+
}]);
/**
* Fix to angular 1.5 > 1.6 upgrade where the default hashPrefix has changed. Here
@@ -9229,63 +9230,160 @@ function (
function Controller() {
}
}]);
-angular.module('cms.shared').directive('cmsFormFieldDirectorySelector', [
- '_',
- 'shared.directiveUtilities',
- 'shared.internalModulePath',
- 'shared.directoryService',
+angular.module('cms.shared').controller('ImageAssetEditorDialogController', [
+ '$scope',
+ 'shared.LoadState',
+ 'shared.imageService',
+ 'shared.SearchQuery',
+ 'shared.urlLibrary',
+ 'options',
+ 'close',
function (
- _,
- directiveUtilities,
- modulePath,
- directoryService
- ) {
+ $scope,
+ LoadState,
+ imageService,
+ SearchQuery,
+ urlLibrary,
+ options,
+ close) {
+
+ var vm = $scope,
+ isAssetInitialized;
- return {
- restrict: 'E',
- templateUrl: modulePath + 'UIComponents/Directories/FormFieldDirectorySelector.html',
- scope: {
- model: '=cmsModel',
- title: '@cmsTitle',
- onLoaded: '&cmsOnLoaded',
- readonly: '=cmsReadonly'
- },
- link: {
- pre: preLink
- },
- controller: Controller,
- controllerAs: 'vm',
- bindToController: true
- };
+ init();
+
+ /* INIT */
+
+ function init() {
+ angular.extend($scope, options);
- /* COMPILE */
+ vm.formLoadState = new LoadState();
+ vm.saveLoadState = new LoadState();
- function preLink(scope, el, attrs) {
- var vm = scope.vm;
+ vm.onInsert = onInsert;
+ vm.onCancel = onCancel;
- if (angular.isDefined(attrs.required)) {
- vm.isRequired = true;
- } else {
- vm.isRequired = false;
- vm.defaultItemText = attrs.cmsDefaultItemText || 'None';
+ vm.onImageChanged = onImageChanged;
+ vm.command = {};
+
+ setCurrentImage();
+ }
+
+ /* ACTIONS */
+
+ function setCurrentImage() {
+ // If we have an existing image, we need to find the asset id to set the command image
+ if (vm.imageAssetHtml && vm.imageAssetHtml.length) {
+ vm.command.imageAssetId = vm.imageAssetHtml.attr('data-image-asset-id');
+ vm.command.altTag = vm.imageAssetHtml.attr('alt');
+ vm.command.style = vm.imageAssetHtml.attr('style');
+
+ // If the image had any styles (mainly dimensions), pass them to the command so they are retained
+ if (vm.command.style) {
+ var styles = parseStyles(vm.command.style);
+ vm.command.width = styles['width'];
+ vm.command.height = styles['height'];
+
+ // Else, look to see if the dimensions are stored as attibutes of the image
+ } else {
+ vm.command.width = vm.imageAssetHtml.attr('width');
+ vm.command.height = vm.imageAssetHtml.attr('height');
+ }
+
+ // If we cannot find the asset id (could have removed the data attribute that this relies on),
+ // we try to work this out based on the image path (this might change in future versions of cofoundry so less reliable)
+ if (!vm.command.imageAssetId) {
+ var src = vm.imageAssetHtml.attr('src');
+ var lastIndex = src.lastIndexOf('/');
+ var extractId = src.substr(lastIndex + 1, ((src.indexOf('_') - lastIndex) - 1));
+ vm.command.imageAssetId = extractId;
+ }
}
- vm.title = attrs.cmsTitle || 'Directory';
- vm.description = attrs.cmsDescription;
- directiveUtilities.setModelName(vm, attrs);
}
+
+ /* EVENTS */
- /* CONTROLLER */
+ function onCancel() {
+ close();
+ }
- function Controller() {
- var vm = this;
+ function onImageChanged() {
+ vm.command.altTag = vm.command.imageAsset.title || vm.command.imageAsset.fileName;
+ }
- directoryService.getAll().then(function (pageDirectories) {
- vm.pageDirectories = pageDirectories;
+ function onInsert() {
- if (vm.onLoaded) vm.onLoaded();
- });
+ // Parse and hold dimensions
+ var dimensions = {
+ width: parseUnits(vm.command.width),
+ height: parseUnits(vm.command.height)
+ };
+
+ // If we have no sizes set, default to percentage respecting ratio
+ if (!dimensions.width && !dimensions.height) {
+ dimensions.width = '100%';
+ dimensions.height = 'auto';
+ }
+
+ // Get the image path, including specific size options if nessessary
+ var path = urlLibrary.getImageUrl(vm.command.imageAsset, parseImageRequestSize(dimensions));
+
+ // Default the alt tag to an empty string if not specified
+ var alt = vm.command.altTag || '';
+
+ // Define an object thay holds formatted outputs, plus the model itself
+ var output = {
+ markdown: "![Alt " + alt + "](" + path + ")",
+ html: "
",
+ model: vm.command
+ };
+
+ // Add css styles to output html
+ output.html = insertCssStyles(output.html, dimensions);
+
+ // Call callback with output
+ vm.onSelected(output);
+
+ // Close dialog
+ close();
+ }
+
+ /* PUBLIC HELPERS */
+
+ function insertCssStyles(html, styles) {
+ return angular.element(html).css(styles)[0].outerHTML;
+ }
+
+ function parseImageRequestSize(dimensions) {
+ // If unit type is percent, use original image size
+ if ((dimensions.width || '').indexOf('%') > -1 || (dimensions.height || '').indexOf('%') > -1) return {};
+
+ // Else, return raw pixel sizes
+ return {
+ width: dimensions.width.replace('px', ''),
+ height: dimensions.height.replace('px', '')
+ };
+ }
+
+ function parseUnits(value) {
+ if (!value) return '';
+
+ // Default to pixels if not unit type specified
+ if (value.indexOf('px') == -1 && value.indexOf('%') == -1 && value.indexOf('auto') == -1) return value + 'px';
+
+ // Return original value if we get here
+ return value;
+ }
+
+ function parseStyles(cssText) {
+ var regex = /([\w-]*)\s*:\s*([^;]*)/g;
+ var match, properties = {};
+ while (match = regex.exec(cssText)) properties[match[1]] = match[2];
+ return properties;
}
+
}]);
+
angular.module('cms.shared').directive('cmsDocumentAsset', [
'shared.internalModulePath',
'shared.urlLibrary',
@@ -10030,171 +10128,74 @@ function (
}]);
-angular.module('cms.shared').controller('ImageAssetEditorDialogController', [
+angular.module('cms.shared').directive('cmsFormFieldDirectorySelector', [
+ '_',
+ 'shared.directiveUtilities',
+ 'shared.internalModulePath',
+ 'shared.directoryService',
+function (
+ _,
+ directiveUtilities,
+ modulePath,
+ directoryService
+ ) {
+
+ return {
+ restrict: 'E',
+ templateUrl: modulePath + 'UIComponents/Directories/FormFieldDirectorySelector.html',
+ scope: {
+ model: '=cmsModel',
+ title: '@cmsTitle',
+ onLoaded: '&cmsOnLoaded',
+ readonly: '=cmsReadonly'
+ },
+ link: {
+ pre: preLink
+ },
+ controller: Controller,
+ controllerAs: 'vm',
+ bindToController: true
+ };
+
+ /* COMPILE */
+
+ function preLink(scope, el, attrs) {
+ var vm = scope.vm;
+
+ if (angular.isDefined(attrs.required)) {
+ vm.isRequired = true;
+ } else {
+ vm.isRequired = false;
+ vm.defaultItemText = attrs.cmsDefaultItemText || 'None';
+ }
+ vm.title = attrs.cmsTitle || 'Directory';
+ vm.description = attrs.cmsDescription;
+ directiveUtilities.setModelName(vm, attrs);
+ }
+
+ /* CONTROLLER */
+
+ function Controller() {
+ var vm = this;
+
+ directoryService.getAll().then(function (pageDirectories) {
+ vm.pageDirectories = pageDirectories;
+
+ if (vm.onLoaded) vm.onLoaded();
+ });
+ }
+}]);
+angular.module('cms.shared').controller('AddEntityAccessRuleController', [
'$scope',
+ '$q',
'shared.LoadState',
- 'shared.imageService',
- 'shared.SearchQuery',
- 'shared.urlLibrary',
+ 'shared.roleService',
+ 'shared.userAreaService',
'options',
'close',
function (
$scope,
- LoadState,
- imageService,
- SearchQuery,
- urlLibrary,
- options,
- close) {
-
- var vm = $scope,
- isAssetInitialized;
-
- init();
-
- /* INIT */
-
- function init() {
- angular.extend($scope, options);
-
- vm.formLoadState = new LoadState();
- vm.saveLoadState = new LoadState();
-
- vm.onInsert = onInsert;
- vm.onCancel = onCancel;
-
- vm.onImageChanged = onImageChanged;
- vm.command = {};
-
- setCurrentImage();
- }
-
- /* ACTIONS */
-
- function setCurrentImage() {
- // If we have an existing image, we need to find the asset id to set the command image
- if (vm.imageAssetHtml && vm.imageAssetHtml.length) {
- vm.command.imageAssetId = vm.imageAssetHtml.attr('data-image-asset-id');
- vm.command.altTag = vm.imageAssetHtml.attr('alt');
- vm.command.style = vm.imageAssetHtml.attr('style');
-
- // If the image had any styles (mainly dimensions), pass them to the command so they are retained
- if (vm.command.style) {
- var styles = parseStyles(vm.command.style);
- vm.command.width = styles['width'];
- vm.command.height = styles['height'];
-
- // Else, look to see if the dimensions are stored as attibutes of the image
- } else {
- vm.command.width = vm.imageAssetHtml.attr('width');
- vm.command.height = vm.imageAssetHtml.attr('height');
- }
-
- // If we cannot find the asset id (could have removed the data attribute that this relies on),
- // we try to work this out based on the image path (this might change in future versions of cofoundry so less reliable)
- if (!vm.command.imageAssetId) {
- var src = vm.imageAssetHtml.attr('src');
- var lastIndex = src.lastIndexOf('/');
- var extractId = src.substr(lastIndex + 1, ((src.indexOf('_') - lastIndex) - 1));
- vm.command.imageAssetId = extractId;
- }
- }
- }
-
- /* EVENTS */
-
- function onCancel() {
- close();
- }
-
- function onImageChanged() {
- vm.command.altTag = vm.command.imageAsset.title || vm.command.imageAsset.fileName;
- }
-
- function onInsert() {
-
- // Parse and hold dimensions
- var dimensions = {
- width: parseUnits(vm.command.width),
- height: parseUnits(vm.command.height)
- };
-
- // If we have no sizes set, default to percentage respecting ratio
- if (!dimensions.width && !dimensions.height) {
- dimensions.width = '100%';
- dimensions.height = 'auto';
- }
-
- // Get the image path, including specific size options if nessessary
- var path = urlLibrary.getImageUrl(vm.command.imageAsset, parseImageRequestSize(dimensions));
-
- // Default the alt tag to an empty string if not specified
- var alt = vm.command.altTag || '';
-
- // Define an object thay holds formatted outputs, plus the model itself
- var output = {
- markdown: "![Alt " + alt + "](" + path + ")",
- html: "
",
- model: vm.command
- };
-
- // Add css styles to output html
- output.html = insertCssStyles(output.html, dimensions);
-
- // Call callback with output
- vm.onSelected(output);
-
- // Close dialog
- close();
- }
-
- /* PUBLIC HELPERS */
-
- function insertCssStyles(html, styles) {
- return angular.element(html).css(styles)[0].outerHTML;
- }
-
- function parseImageRequestSize(dimensions) {
- // If unit type is percent, use original image size
- if ((dimensions.width || '').indexOf('%') > -1 || (dimensions.height || '').indexOf('%') > -1) return {};
-
- // Else, return raw pixel sizes
- return {
- width: dimensions.width.replace('px', ''),
- height: dimensions.height.replace('px', '')
- };
- }
-
- function parseUnits(value) {
- if (!value) return '';
-
- // Default to pixels if not unit type specified
- if (value.indexOf('px') == -1 && value.indexOf('%') == -1 && value.indexOf('auto') == -1) return value + 'px';
-
- // Return original value if we get here
- return value;
- }
-
- function parseStyles(cssText) {
- var regex = /([\w-]*)\s*:\s*([^;]*)/g;
- var match, properties = {};
- while (match = regex.exec(cssText)) properties[match[1]] = match[2];
- return properties;
- }
-
-}]);
-
-angular.module('cms.shared').controller('AddEntityAccessRuleController', [
- '$scope',
- '$q',
- 'shared.LoadState',
- 'shared.roleService',
- 'shared.userAreaService',
- 'options',
- 'close',
-function (
- $scope,
- $q,
+ $q,
LoadState,
roleService,
userAreaService,
@@ -11027,1216 +11028,1223 @@ function (
}]);
/**
- * Base class for form fields that uses default conventions and includes integration with
- * server validation.
+ * Validates that a field matches the value of another field. Set the came of the field
+ * in the attribute definition e.g. cms-match="vm.command.password"
+ * Adapted from http://ericpanorel.net/2013/10/05/angularjs-password-match-form-validation/
*/
-angular.module('cms.shared').factory('baseFormFieldFactory', [
+angular.module('cms.shared').directive('cmsMatch', [
+ '$parse',
'$timeout',
- 'shared.stringUtilities',
+ 'shared.internalModulePath',
'shared.directiveUtilities',
- 'shared.validationErrorService',
function (
+ $parse,
$timeout,
- stringUtilities,
- directiveUtilities,
- validationErrorService
+ modulePath,
+ directiveUtilities
) {
- var service = {},
- /* Here we can validation messages that can apply to all FormField controls */
- globalDefaultValidationMessages = [
- {
- attr: 'required',
- msg: 'This field is required'
- },
- {
- attr: 'maxlength',
- msg: 'This field cannot be longer than {0} characters'
- },
- {
- attr: 'minlength',
- msg: 'This must be at least {0} characters long'
- }
- ];
-
- /* PUBLIC */
-
- service.create = function (config) {
- return angular.extend({}, service.defaultConfig, config);
- }
-
- /* CONFIG */
+ var DIRECTIVE_ID = 'cmsMatch';
+ var DIRECTIVE_ATTRIBUTE = 'cms-match';
- /**
- * Configuration defaults
- */
- service.defaultConfig = {
- restrict: 'E',
- replace: true,
- require: ['^^cmsForm'],
- scope: {
- title: '@cmsTitle',
- description: '@cmsDescription',
- change: '&cmsChange',
- model: '=cmsModel',
- disabled: '=cmsDisabled',
- readonly: '=cmsReadonly'
- },
- compile: compile,
+ return {
link: link,
- controller: function () { },
- controllerAs: 'vm',
- bindToController: true,
-
- /* Custom Properties */
-
- /**
- * Should return the main input element that is displayed in edit mode.
- * By default this returns the first child input element.
- */
- getInputEl: getInputEl,
-
- /**
- * a list of attributes that when defined on the directive are passed through to the element
- * returned from getInputEl.
- */
- passThroughAttributes: [],
-
- /**
- * Default validation messages to use when none are provided on the element. Saves specifying common messages
- * like 'This field is required' on every field. Each array element should be an object in the form of:
- * { attr: 'name', msg: 'string message or formatter function' }
- */
- defaultValidationMessages: []
+ restrict: 'A',
+ require: ['^^cmsForm', '?ngModel'],
};
+
+ function link(scope, el, attrs, controllers) {
+ // NB: ngModel may be null on an outer form control before it has been copied to the inner input.
+ if (!attrs[DIRECTIVE_ID] || !controllers[1]) return;
- /* COMPILE */
-
- function compile(el, attrs) {
+ var formController = controllers[0],
+ ngModelController = controllers[1],
+ form = formController.getFormScope().getForm(),
+ sourceField = directiveUtilities.parseModelName(attrs[DIRECTIVE_ID]);
- initPassThroughAttributes.call(this, el, attrs);
+ var validator = function (value, otherVal) {
+ var formField = form[sourceField];
+ if (!formField) return false;
- return this.link.bind(this);
- }
+ var sourceFieldValue = formField.$viewValue;
- function link(scope, el, attrs, controllers) {
- var vm = scope.vm,
- formController = controllers[0];
+ return value === sourceFieldValue;
+ }
- // Model Properties
- vm.formScope = formController.getFormScope();
- vm.form = vm.formScope.getForm();
-
- directiveUtilities.setModelName(vm, attrs);
- parseErrorMessages(vm, attrs);
+ ngModelController.$validators[DIRECTIVE_ID] = validator;
+ }
+}]);
+angular.module('cms.shared').directive('cmsFormFieldImageAnchorLocationSelector', [
+ '_',
+ 'shared.internalModulePath',
+ function (
+ _,
+ modulePath) {
+ return {
+ restrict: 'E',
+ templateUrl: modulePath + 'UIComponents/ImageAssets/FormFieldImageAnchorLocationSelector.html',
+ scope: {
+ model: '=cmsModel',
+ readonly: '=cmsReadonly'
+ },
+ controller: Controller,
+ controllerAs: 'vm',
+ bindToController: true
+ };
- // Model Funcs
- vm.onChange = onChange.bind(vm);
- vm.resetCustomErrors = resetCustomErrors.bind(vm);
- vm.addOrUpdateValidator = addOrUpdateValidator.bind(vm);
+ /* CONTROLLER */
- // Init Errors
- vm.resetCustomErrors();
+ function Controller() {
+ var vm = this;
+
+ vm.options = [
+ { name: 'Top Left', id: 'TopLeft' },
+ { name: 'Top Center', id: 'TopCenter' },
+ { name: 'Top Right', id: 'TopRight' },
+ { name: 'Middle Left', id: 'MiddleLeft' },
+ { name: 'Middle Center', id: 'MiddleCenter' },
+ { name: 'Middle Right', id: 'MiddleRight' },
+ { name: 'Bottom Left', id: 'BottomLeft' },
+ { name: 'Bottom Center', id: 'BottomCenter' },
+ { name: 'Bottom Right', id: 'BottomRight' }
+ ];
+ }
+}]);
+/**
+ * A form field control for an image asset that uses a search and pick dialog
+ * to allow the user to change the selected file.
+ */
+angular.module('cms.shared').directive('cmsFormFieldImageAsset', [
+ '_',
+ 'shared.internalModulePath',
+ 'shared.internalContentPath',
+ 'shared.modalDialogService',
+ 'shared.stringUtilities',
+ 'shared.imageService',
+ 'shared.urlLibrary',
+ 'baseFormFieldFactory',
+ function (
+ _,
+ modulePath,
+ contentPath,
+ modalDialogService,
+ stringUtilities,
+ imageService,
+ urlLibrary,
+ baseFormFieldFactory) {
- // Bind Validation Events
- bindValidationEvents(scope, el);
+ /* VARS */
- // watches
- scope.$watch('vm.model', function () {
- vm.resetCustomErrors();
- });
- }
+ var assetReplacementPath = contentPath + 'img/AssetReplacement/',
+ noImagePath = assetReplacementPath + 'image-replacement.png',
+ baseConfig = baseFormFieldFactory.defaultConfig;
- /* PUBLIC */
+ /* CONFIG */
- function onChange() {
- var vm = this;
+ var config = {
+ templateUrl: modulePath + 'UIComponents/ImageAssets/FormFieldImageAsset.html',
+ scope: _.extend(baseConfig.scope, {
+ asset: '=cmsAsset', // if we already have the full asset data we can set it here to save an api call
+ loadState: '=cmsLoadState',
+ updateAsset: '@cmsUpdateAsset' // update the asset property if it changes
+ }),
+ passThroughAttributes: [
+ 'required'
+ ],
+ link: link
+ };
- vm.resetCustomErrors();
- if (vm.change) {
- // run after digest cycle completes so the parent ngModel is updated
- $timeout(vm.change, 0);
- }
- }
+ return baseFormFieldFactory.create(config);
- function resetCustomErrors() {
- var model = this.form[this.modelName];
+ /* LINK */
- if (model) {
- model.$setValidity('server', true);
- }
- this.customErrors = [];
- }
+ function link(scope, el, attributes, controllers) {
+ var vm = scope.vm,
+ isRequired = _.has(attributes, 'required'),
+ isAssetInitialized;
- function addOrUpdateValidator(validator) {
- var vm = this;
- var validators = _.filter(vm.validators, function (v) {
- return v.name !== validator.name;
- });
- validators.push(validator);
- vm.validators = validators;
- }
+ init();
+ return baseConfig.link(scope, el, attributes, controllers);
- /* HELPERS */
+ /* INIT */
- /**
- * Loop through attributes specified in config.passThroughAttributes and copy
- * them onto the input control requrned by config.getInputEl
- */
- function initPassThroughAttributes(rootEl, attrs) {
- var config = this,
- el = config.getInputEl(rootEl);
+ function init() {
- (config.passThroughAttributes || []).forEach(function (passThroughAttribute) {
- var name = passThroughAttribute.name || passThroughAttribute;
- if (angular.isDefined(attrs[name])) {
- el[0].setAttribute(attrs.$attr[name], attrs[name]);
- }
- else if (passThroughAttribute.default) {
- el[0].setAttribute(name, passThroughAttribute.default);
- }
- });
- }
+ vm.urlLibrary = urlLibrary;
+ vm.showPicker = showPicker;
+ vm.remove = remove;
+ vm.isRemovable = _.isObject(vm.model) && !isRequired;
- function getInputEl(rootEl) {
- return rootEl.find('input');
- }
+ vm.filter = parseFilters(attributes);
+ vm.previewWidth = attributes['cmsPreviewWidth'] || 450;
+ vm.previewHeight = attributes['cmsPreviewHeight'];
+
+ scope.$watch("vm.asset", setAsset);
+ scope.$watch("vm.model", setAssetById);
+ }
- function bindValidationEvents(scope, el) {
- var fn = _.partial(addErrors, scope.vm, el);
+ /* EVENTS */
- validationErrorService.addHandler(scope.vm.modelName, fn);
+ function remove() {
+ setAsset(null);
+ }
- scope.$on('$destroy', function () {
- validationErrorService.removeHandler(fn);
- });
- }
-
- function parseErrorMessages(vm, attrs) {
- var config = this,
- postfix = 'ValMsg',
- attrPostfix = '-val-msg';
+ function showPicker() {
- vm.validators = [];
+ modalDialogService.show({
+ templateUrl: modulePath + 'UIComponents/ImageAssets/ImageAssetPickerDialog.html',
+ controller: 'ImageAssetPickerDialogController',
+ options: {
+ currentAsset: vm.previewAsset,
+ filter: vm.filter,
+ onSelected: onSelected
+ }
+ });
- _.each(attrs.$attr, function (value, key) {
- var attributeToValidate,
- msg;
+ function onSelected(newAsset) {
- if (stringUtilities.endsWith(key, postfix)) {
- // if the property is postfix '-val-msg' then pull in the message from the attribute
- attributeToValidate = value.substring(0, value.length - attrPostfix.length);
- msg = attrs[key];
- } else {
+ if (!newAsset && vm.previewAsset) {
+ setAsset(null);
- attributeToValidate = value;
+ vm.onChange();
+ } else if (!vm.previewAsset || (newAsset && vm.previewAsset.imageAssetId !== newAsset.imageAssetId)) {
+ setAsset(newAsset);
- // check to see if we have a default message for the property
- msg = getDefaultErrorMessage(config.defaultValidationMessages, key) || getDefaultErrorMessage(globalDefaultValidationMessages, key);
- }
+ vm.onChange();
+ }
+ }
+ }
- if (msg) {
- vm.validators.push({
- name: attrs.$normalize(attributeToValidate),
- message: stringUtilities.format(msg, attrs[key])
- });
- }
- });
+ /**
+ * When the model is set without a preview asset, we need to go get the full
+ * asset details. This query can be bypassed by setting the cms-asset attribute
+ */
+ function setAssetById(assetId) {
- function getDefaultErrorMessage(defaultsToCheck, attr) {
- var validator = _.find(defaultsToCheck, function (v) {
- return v.attr === attr;
- });
+ // Remove the id if it is 0 or invalid to make sure required validation works
+ if (!assetId) {
+ vm.model = assetId = undefined;
+ }
- if (validator) {
- if (_.isFunction(validator.msg)) {
- return validator.msg(vm.modelName, attrs);
+ if (assetId && (!vm.previewAsset || vm.previewAsset.imageAssetId != assetId)) {
+ imageService.getById(assetId).then(function (asset) {
+ if (asset) {
+ setAsset(asset);
+ }
+ });
+ }
}
- return validator.msg;
- }
- }
- }
- function addErrors(vm, el, errors) {
- var form = vm.formScope.getForm();
- var model = form[vm.modelName];
- vm.resetCustomErrors();
+ /**
+ * Initialise the state when the asset is changed
+ */
+ function setAsset(asset) {
- model.$setValidity('server', false);
+ if (asset) {
+ vm.previewAsset = asset;
+ vm.isRemovable = !isRequired;
+ vm.model = asset.imageAssetId;
- // make dirty to ensure css classes are applied
- getInputEl(el).removeClass('ng-pristine').addClass('ng-dirty');
+ if (vm.updateAsset) {
+ vm.asset = asset;
+ }
+ } else if (isAssetInitialized) {
+ // Ignore if we are running this first time to avoid overwriting the model with a null vlaue
+ vm.previewAsset = null;
+ vm.isRemovable = false;
- errors.forEach(function (error) {
- vm.customErrors.push(error);
- });
- }
+ if (vm.model) {
+ vm.model = null;
+ }
+ if (vm.updateAsset) {
+ vm.asset = null;
+ }
+ }
- /* DEFINITION */
+ setButtonText();
- return service;
-}]);
-angular.module('cms.shared').directive('cmsFormFieldChar', [
- '_',
- 'shared.internalModulePath',
- 'baseFormFieldFactory',
-function (
- _,
- modulePath,
- baseFormFieldFactory) {
+ isAssetInitialized = true;
+ }
- var config = {
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldChar.html',
- passThroughAttributes: [
- 'required',
- 'placeholder',
- 'pattern',
- 'disabled',
- 'cmsMatch'
- ]
- };
+ /* Helpers */
+
+ function parseFilters(attributes) {
+ var filter = {},
+ attributePrefix = 'cms';
+
+ setAttribute('Tags');
+ setAttribute('Width', true);
+ setAttribute('Height', true);
+ setAttribute('MinWidth', true);
+ setAttribute('MinHeight', true);
- return baseFormFieldFactory.create(config);
-}]);
-angular.module('cms.shared').directive('cmsFormFieldCheckbox', [
- 'shared.internalModulePath',
- 'baseFormFieldFactory',
-function (
- modulePath,
- baseFormFieldFactory) {
+ return filter;
- var config = {
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldCheckbox.html',
- passThroughAttributes: [
- 'disabled'
- ]
- };
+ function setAttribute(filterName, isInt) {
+ var value = attributes[attributePrefix + filterName];
- return baseFormFieldFactory.create(config);
-}]);
-angular.module('cms.shared').directive('cmsFormFieldCheckboxList', [
+ if (value) {
+ filterName = stringUtilities.lowerCaseFirstWord(filterName);
+ filter[filterName] = isInt ? parseInt(value) : value;
+ }
+ }
+ }
+
+ function setButtonText() {
+ vm.buttonText = vm.model ? 'Change' : 'Select';
+ }
+ }
+
+ }]);
+angular.module('cms.shared').directive('cmsFormFieldImageAssetCollection', [
'_',
- '$http',
'shared.internalModulePath',
- 'shared.optionSourceService',
+ 'shared.LoadState',
+ 'shared.imageService',
+ 'shared.modalDialogService',
+ 'shared.arrayUtilities',
+ 'shared.stringUtilities',
+ 'shared.urlLibrary',
'baseFormFieldFactory',
function (
_,
- $http,
modulePath,
- optionSourceService,
+ LoadState,
+ imageService,
+ modalDialogService,
+ arrayUtilities,
+ stringUtilities,
+ urlLibrary,
baseFormFieldFactory) {
+ /* VARS */
+
+ var IMAGE_ASSET_ID_PROP = 'imageAssetId',
+ baseConfig = baseFormFieldFactory.defaultConfig;
+
+ /* CONFIG */
+
var config = {
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldCheckboxList.html',
- scope: _.extend(baseFormFieldFactory.defaultConfig.scope, {
- options: '=cmsOptions',
- optionValue: '@cmsOptionValue',
- optionName: '@cmsOptionName',
- optionsApi: '@cmsOptionsApi',
- noValueText: '@cmsNoValueText',
- required: '=cmsRequired',
- disabled: '=cmsDisabled'
- }),
+ templateUrl: modulePath + 'UIComponents/ImageAssets/FormFieldImageAssetCollection.html',
passThroughAttributes: [
- 'placeholder',
- 'disabled',
- 'cmsMatch',
- 'required'
+ 'required',
+ 'ngRequired'
],
link: link
};
return baseFormFieldFactory.create(config);
- /* PRIVATE */
+ /* LINK */
- function link(scope, element, attrs, controllers) {
+ function link(scope, el, attributes, controllers) {
var vm = scope.vm;
- init();
- // call base
- baseFormFieldFactory.defaultConfig.link.apply(this, arguments);
+ init();
+ return baseConfig.link(scope, el, attributes, controllers);
- /* Init */
+ /* INIT */
function init() {
- vm.isRequiredAttributeDefined = angular.isDefined(attrs.required);
- vm.onCheckChanged = updateModel;
- if (vm.optionsApi) {
- // options bound by api call
- getOptions(vm.optionsApi);
-
- } else {
-
- // options provided as a model
- var optionsWatch = scope.$watch('vm.options', function () {
+ vm.gridLoadState = new LoadState();
+ vm.urlLibrary = urlLibrary;
- bindOptions(vm.options);
- updateDisplayValues();
- });
- }
+ vm.showPicker = showPicker;
+ vm.remove = remove;
+ vm.onDrop = onDrop;
- scope.$watch('vm.model', onModelChanged);
+ scope.$watch("vm.model", setGridItems);
}
- /* Events */
+ /* EVENTS */
- function onModelChanged() {
- updateOptionSelectionFromModel();
- updateDisplayValues();
- }
+ function remove(image) {
- /* Helpers */
+ removeItemFromArray(vm.gridData, image);
+ removeItemFromArray(vm.model, image.imageAssetId);
- function getOptions(apiPath) {
- return optionSourceService.getFromApi(apiPath).then(loadOptions);
+ function removeItemFromArray(arr, item) {
+ var index = arr.indexOf(item);
- function loadOptions(options) {
- bindOptions(options);
- updateDisplayValues();
+ if (index >= 0) {
+ return arr.splice(index, 1);
+ }
}
}
- /**
- * Copies over the option collection so the original
- * is not modified with selcted properties.
- */
- function bindOptions(options) {
- vm.listOptions = _.map(options, mapOption);
+ function showPicker() {
- updateOptionSelectionFromModel();
+ modalDialogService.show({
+ templateUrl: modulePath + 'UIComponents/ImageAssets/ImageAssetPickerDialog.html',
+ controller: 'ImageAssetPickerDialogController',
+ options: {
+ selectedIds: vm.model || [],
+ filter: getFilter(),
+ onSelected: onSelected
+ }
+ });
- function mapOption(option) {
- return {
- text: option[vm.optionName],
- value: option[vm.optionValue]
- };
+ function onSelected(newImageArr) {
+ vm.model = newImageArr;
+ setGridItems(newImageArr);
}
}
- /**
- * Updates the selected values in the listOptions
- * collection from the model,
- */
- function updateOptionSelectionFromModel() {
- var isModelEmpty = !vm.model || !vm.model.length;
- vm.displayValues = [];
-
- _.each(vm.listOptions, function (option) {
-
- option.selected = !isModelEmpty && !!_.find(vm.model, function (m) {
- return m === option.value;
- });
- });
- }
+ function onDrop($index, droppedEntity) {
- function updateDisplayValues() {
+ arrayUtilities.moveObject(vm.gridData, droppedEntity, $index, IMAGE_ASSET_ID_PROP);
- vm.displayValues = _.chain(vm.listOptions)
- .filter(function (o) { return o.selected })
- .pluck('text')
- .value();
+ // Update model with new orering
+ setModelFromGridData();
}
- function updateModel() {
-
- vm.model = _.chain(vm.listOptions)
- .filter(function (o) { return o.selected })
- .pluck('value')
- .value();
+ function setModelFromGridData() {
+ vm.model = _.pluck(vm.gridData, IMAGE_ASSET_ID_PROP);
}
- }
-}]);
-angular.module('cms.shared').directive('cmsFormFieldColor', [
- 'shared.internalModulePath',
- 'baseFormFieldFactory',
-function (
- modulePath,
- baseFormFieldFactory
- ) {
-
- var config = {
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldColor.html',
- passThroughAttributes: [
- 'required',
- 'disabled'
- ],
- link: link
- };
-
- return baseFormFieldFactory.create(config);
-
- function link(scope) {
- var vm = scope.vm;
-
- // call base
- baseFormFieldFactory.defaultConfig.link.apply(this, arguments);
-
- // add custom error for value since its not attribute based like other validation messages
- vm.validators.push({
- name: 'pattern',
- message: vm.title + " must be a hexadecimal colour value e.g. '#EFEFEF' or '#fff'"
- });
- }
-}]);
-angular.module('cms.shared').directive('cmsFormFieldContainer', [
- 'shared.internalModulePath',
-function (
- modulePath
- ) {
- return {
- restrict: 'E',
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldContainer.html',
- require: ['^^cmsForm'],
- replace: true,
- transclude: true,
- scope: {
- title: '@cmsTitle',
- description: '@cmsDescription'
- }
- };
-}]);
-angular.module('cms.shared').directive('cmsFormFieldDate', [
- 'shared.internalModulePath',
- 'baseFormFieldFactory',
-function (
- modulePath,
- baseFormFieldFactory
- ) {
+ /* HELPERS */
- var config = {
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldDate.html',
- passThroughAttributes: [
- 'required',
- 'min',
- 'max',
- 'step',
- 'disabled',
- 'cmsMatch'
- ],
- link: link
- };
+ function getFilter() {
+ var filter = {},
+ attributePrefix = 'cms';
- return baseFormFieldFactory.create(config);
+ setAttribute('Tags');
+ setAttribute('Width', true);
+ setAttribute('Height', true);
+ setAttribute('MinWidth', true);
+ setAttribute('MinHeight', true);
- function link(scope, element, attrs, controllers) {
- var vm = scope.vm;
+ return filter;
- // call base
- baseFormFieldFactory.defaultConfig.link.apply(this, arguments);
+ function setAttribute(filterName, isInt) {
+ var value = attributes[attributePrefix + filterName];
- init();
+ if (value) {
+ filterName = stringUtilities.lowerCaseFirstWord(filterName);
+ filter[filterName] = isInt ? parseInt(value) : value;
+ }
+ }
+ }
- /* Init */
+ /**
+ * Load the grid data if it is inconsistent with the Ids collection.
+ */
+ function setGridItems(ids) {
- function init() {
- if (attrs.min) {
- vm.addOrUpdateValidator({
- name: 'min',
- message: "This date cannot be before " + attrs.min
- });
+ if (!ids || !ids.length) {
+ vm.gridData = [];
}
- if (attrs.max) {
- vm.addOrUpdateValidator({
- name: 'max',
- message: "This date cannot be after " + attrs.max
+ else if (!vm.gridData || _.pluck(vm.gridData, IMAGE_ASSET_ID_PROP).join() != ids.join()) {
+
+ vm.gridLoadState.on();
+ imageService.getByIdRange(ids).then(function (items) {
+ vm.gridData = items;
+ vm.gridLoadState.off();
});
}
}
}
}]);
-angular.module('cms.shared').directive('cmsFormFieldDateLocal', [
+/**
+ * File upload control for images. Uses https://github.com/danialfarid/angular-file-upload
+ */
+angular.module('cms.shared').directive('cmsFormFieldImageUpload', [
+ '_',
+ '$timeout',
'shared.internalModulePath',
+ 'shared.internalContentPath',
+ 'shared.LoadState',
+ 'shared.stringUtilities',
+ 'shared.imageFileUtilities',
+ 'shared.imageService',
+ 'shared.urlLibrary',
+ 'shared.validationErrorService',
'baseFormFieldFactory',
function (
+ _,
+ $timeout,
modulePath,
- baseFormFieldFactory
- ) {
+ contentPath,
+ LoadState,
+ stringUtilities,
+ imageFileUtilities,
+ imageService,
+ urlLibrary,
+ validationErrorService,
+ baseFormFieldFactory) {
+
+ /* VARS */
+
+ var baseConfig = baseFormFieldFactory.defaultConfig,
+ assetReplacementPath = contentPath + 'img/AssetReplacement/',
+ noPreviewImagePath = assetReplacementPath + 'preview-not-supported.png',
+ noImagePath = assetReplacementPath + 'image-replacement.png';
+
+ /* CONFIG */
var config = {
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldDateLocal.html',
- passThroughAttributes: [
- 'required',
- 'min',
- 'max',
- 'disabled',
- 'cmsMatch'
- ],
+ templateUrl: modulePath + 'UIComponents/ImageAssets/FormFieldImageUpload.html',
+ scope: _.extend(baseFormFieldFactory.defaultConfig.scope, {
+ asset: '=cmsAsset',
+ loadState: '=cmsLoadState',
+ filter: '=cmsFilter',
+ onChange: '&cmsOnChange'
+ }),
link: link
};
return baseFormFieldFactory.create(config);
- function link(scope, element, attrs, controllers) {
- var vm = scope.vm;
+ /* LINK */
- // call base
- baseFormFieldFactory.defaultConfig.link.apply(this, arguments);
+ function link(scope, el, attributes, controllers) {
+ var vm = scope.vm;
init();
+ return baseConfig.link(scope, el, attributes, controllers);
- /* Init */
+ /* INIT */
function init() {
- if (attrs.min) {
- vm.addOrUpdateValidator({
- name: 'min',
- message: "This date cannot be before " + attrs.min
- });
- }
- if (attrs.max) {
- vm.addOrUpdateValidator({
- name: 'max',
- message: "This date cannot be after " + attrs.max
+
+ vm.isRequired = _.has(attributes, 'required');
+ vm.remove = remove;
+ vm.fileChanged = onFileChanged;
+ vm.isRemovable = _.isObject(vm.ngModel) && !vm.isRequired;
+ vm.mainLoadState = new LoadState(true);
+
+ scope.$watch("vm.asset", setAsset);
+
+ imageService
+ .getSettings()
+ .then(mapSettings)
+ .then(vm.mainLoadState.off);
+ }
+
+ function mapSettings(settings) {
+ vm.settings = settings;
+ }
+
+ /* EVENTS */
+
+ function remove() {
+ onFileChanged();
+ }
+
+ /**
+ * Initialise the state when the asset is changed
+ */
+ function setAsset() {
+ var asset = vm.asset;
+
+ if (asset) {
+ vm.previewUrl = urlLibrary.getImageUrl(asset, {
+ width: 450
});
+ vm.isRemovable = !vm.isRequired;
+
+ vm.model = {
+ name: asset.fileName + '.' + asset.extension,
+ size: asset.fileSizeInBytes,
+ isCurrentFile: true
+ };
+ } else {
+ vm.previewUrl = noImagePath;
+ vm.isRemovable = false;
+
+ if (vm.model) {
+ vm.model = undefined;
+ }
}
+
+ setButtonText();
}
- }
-}]);
-angular.module('cms.shared').directive('cmsFormFieldDateTime', [
- 'shared.internalModulePath',
- 'baseFormFieldFactory',
-function (
- modulePath,
- baseFormFieldFactory
- ) {
- var config = {
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldDateTime.html',
- passThroughAttributes: [
- 'required',
- 'min',
- 'max',
- 'disabled',
- 'cmsMatch'
- ],
- link: link
- };
+ function onFileChanged($files) {
- return baseFormFieldFactory.create(config);
+ if ($files && $files[0]) {
+ vm.mainLoadState.on();
- function link(scope, element, attrs, controllers) {
- var vm = scope.vm;
+ // set the file if one is selected
+ imageFileUtilities
+ .getFileInfoAndResizeIfRequired($files[0], vm.settings)
+ .then(onImageInfoLoaded)
+ .finally(vm.mainLoadState.off);
- // call base
- baseFormFieldFactory.defaultConfig.link.apply(this, arguments);
+ } else if (!vm.ngModel || _.isUndefined($files)) {
+ onNoFileSelected();
+ }
- init();
+ function onImageInfoLoaded(imgInfo) {
+ if (!imgInfo) {
+ onNoFileSelected();
+ }
- /* Init */
+ vm.model = imgInfo.file;
+ validateSize(imgInfo);
- function init() {
- if (attrs.min) {
- var minDate = attrs.min.replace('T', ' ');
- vm.addOrUpdateValidator({
- name: 'min',
- message: "This date cannot be before " + minDate
- });
- }
- if (attrs.max) {
- var maxDate = attrs.min.replace('T', ' ');
- vm.addOrUpdateValidator({
- name: 'max',
- message: "This date cannot be after " + maxDate
- });
+ setPreviewImage(imgInfo.file);
+ vm.isRemovable = !vm.isRequired;
+ vm.isResized = imgInfo.isResized;
+ vm.resizeSize = imgInfo.width + 'x' + imgInfo.height;
+ onComplete();
}
- }
- }
-}]);
-angular.module('cms.shared').directive('cmsFormFieldDateTimeLocal', [
- 'shared.internalModulePath',
- 'baseFormFieldFactory',
-function (
- modulePath,
- baseFormFieldFactory
- ) {
- var config = {
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldDateTimeLocal.html',
- scope: _.extend(baseFormFieldFactory.defaultConfig.scope, {
- minUtc: '@cmsMinUtc',
- maxUtc: '@cmsMaxUtc'
- }),
- passThroughAttributes: [
- 'required',
- 'min',
- 'max',
- 'disabled',
- 'cmsMatch'
- ],
- link: link
- };
+ function validateSize(imgInfo) {
+ var filter = vm.filter;
+ if (!filter) return;
- return baseFormFieldFactory.create(config);
+ if (!isMinSize(filter.minWidth, imgInfo.width)
+ || !isMinSize(filter.minHeight, imgInfo.height)
+ || !isSize(filter.width, imgInfo.width)
+ || !isSize(filter.height, imgInfo.height)) {
+ addError('The image is ' + imgInfo.width + 'x' + imgInfo.height + ' which does not meet the size requirements.');
+ }
- function link(scope, element, attrs, controllers) {
- var vm = scope.vm;
+ function isMinSize(size, value) {
+ return !(size > 1) || value >= size;
+ }
- // call base
- baseFormFieldFactory.defaultConfig.link.apply(this, arguments);
+ function isSize(size, value) {
+ return !(size > 1) || value == size;
+ }
+ }
- init();
+ function addError(message) {
+ // Run in next digest cycle otherwise it will
+ // be overwirtten in the model change
+ $timeout(function () {
+ validationErrorService.raise([{
+ message: message,
+ properties: [vm.modelName]
+ }]);
+ });
+ }
- /* Init */
+ function onNoFileSelected() {
- function init() {
- scope.$watch("vm.minUtc", function (value) {
- var date = parseDateAsUtc(value);
- vm.minLocal = formatDate(date);
- if (vm.minLocal) {
- vm.addOrUpdateValidator({
- name: 'min',
- message: "This date cannot be before " + date.toLocaleString()
- });
- }
- });
+ // if we don't have an image loaded already, remove the file.
+ vm.model = undefined;
+ vm.previewUrl = noImagePath;
+ vm.isRemovable = false;
+ vm.isResized = false;
+ onComplete();
+ }
- scope.$watch("vm.maxUtc", function (value) {
- var date = parseDateAsUtc(value);
- vm.maxLocal = formatDate(date);
- if (vm.maxLocal) {
- vm.addOrUpdateValidator({
- name: 'max',
- message: "This date cannot be after " + date.toLocaleString()
- });
- }
- });
+ function onComplete() {
+ setButtonText();
+
+ // base onChange event
+ if (vm.onChange) vm.onChange();
+ }
}
- function parseDateAsUtc(value) {
- if (!value) return;
+ /* Helpers */
- // Format is expected to be 'yyyy-MM-ddT00:00'
- return new Date(value + ':00.000Z');
+ function setPreviewImage(file) {
+ if (!isPreviewSupported(file)) {
+ vm.previewUrl = noPreviewImagePath;
+ return;
+ }
+
+ try {
+ vm.previewUrl = URL.createObjectURL(file);
+ }
+ catch (err) {
+ vm.previewUrl = noPreviewImagePath;
+ }
}
- function formatDate(date) {
- if (!date) return;
+ function isPreviewSupported(file) {
+ var unsupportedPreviewFormats = ['.tiff', '.tif'];
- return date.getFullYear()
- + '-' + padTwoDigitNumber(date.getMonth() + 1)
- + '-' + padTwoDigitNumber(date.getDate())
- + 'T' + padTwoDigitNumber(date.getHours())
- + ':' + padTwoDigitNumber(date.getMinutes());
+ return !_.find(unsupportedPreviewFormats, function (format) {
+ return stringUtilities.endsWith(file.name, format);
+ });
}
- function padTwoDigitNumber(number) {
- return ("00" + number).slice(-2);
+ function setButtonText() {
+ vm.buttonText = vm.model ? 'Change' : 'Upload';
}
}
}]);
-angular.module('cms.shared').directive('cmsFormFieldDropdown', [
- '_',
- '$http',
+angular.module('cms.shared').directive('cmsImageAsset', [
'shared.internalModulePath',
- 'baseFormFieldFactory',
+ 'shared.internalContentPath',
+ 'shared.urlLibrary',
function (
- _,
- $http,
modulePath,
- baseFormFieldFactory) {
+ contentPath,
+ urlLibrary) {
- var config = {
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldDropdown.html',
- scope: _.extend(baseFormFieldFactory.defaultConfig.scope, {
- options: '=cmsOptions',
- optionValue: '@cmsOptionValue',
- optionName: '@cmsOptionName',
- optionsApi: '@cmsOptionsApi',
- defaultItemText: '@cmsDefaultItemText',
- href: '@cmsHref',
- required: '=cmsRequired',
- disabled: '=cmsDisabled'
- }),
- passThroughAttributes: [
- 'placeholder',
- 'disabled',
- 'cmsMatch'
- ],
- getInputEl: getInputEl,
- link: link
+ return {
+ restrict: 'E',
+ scope: {
+ image: '=cmsImage',
+ width: '@cmsWidth',
+ height: '@cmsHeight',
+ cropMode: '@cmsCropMode'
+ },
+ templateUrl: modulePath + 'UIComponents/ImageAssets/ImageAsset.html',
+ link: function (scope, el, attributes) {
+
+ scope.$watch('image', function (newValue, oldValue) {
+ if (newValue && newValue.imageAssetId) {
+ scope.src = urlLibrary.getImageUrl(newValue, {
+ width: scope.width,
+ height: scope.height,
+ mode: scope.cropMode
+ });
+ } else {
+ scope.src = contentPath + 'img/AssetReplacement/image-replacement.png';
+ }
+ });
+
+
+ },
+ replace: true
};
+}]);
+angular.module('cms.shared').controller('ImageAssetPickerDialogController', [
+ '$scope',
+ 'shared.LoadState',
+ 'shared.imageService',
+ 'shared.SearchQuery',
+ 'shared.modalDialogService',
+ 'shared.internalModulePath',
+ 'shared.permissionValidationService',
+ 'options',
+ 'close',
+function (
+ $scope,
+ LoadState,
+ imageService,
+ SearchQuery,
+ modalDialogService,
+ modulePath,
+ permissionValidationService,
+ options,
+ close) {
+
+ var vm = $scope;
+ init();
+
+ /* INIT */
+
+ function init() {
+ angular.extend($scope, options);
- return baseFormFieldFactory.create(config);
+ vm.onOk = onOk;
+ vm.onCancel = onCancel;
+ vm.onSelect = onSelect;
+ vm.onUpload = onUpload;
+ vm.selectedAsset = vm.currentAsset; // currentAsset is null in single mode
+ vm.onSelectAndClose = onSelectAndClose;
+ vm.close = onCancel;
- /* PRIVATE */
+ vm.gridLoadState = new LoadState();
+ vm.query = new SearchQuery({
+ onChanged: onQueryChanged,
+ useHistory: false,
+ defaultParams: options.filter
+ });
+ vm.presetFilter = options.filter;
- function link(scope, element, attrs, controllers) {
- var vm = scope.vm;
- init();
+ vm.filter = vm.query.getFilters();
+ vm.toggleFilter = toggleFilter;
- // call base
- baseFormFieldFactory.defaultConfig.link.apply(this, arguments);
+ vm.isSelected = isSelected;
+ vm.multiMode = vm.selectedIds ? true : false;
+ vm.okText = vm.multiMode ? 'Ok' : 'Select';
- /* Init */
+ vm.canCreate = permissionValidationService.canCreate('COFIMG');
- function init() {
- vm.isRequiredAttributeDefined = angular.isDefined(attrs.required);
+ toggleFilter(false);
+ loadGrid();
+ }
- if (vm.optionsApi) {
- // options bound by api call
- getOptions(vm.optionsApi);
+ /* ACTIONS */
- } else {
+ function toggleFilter(show) {
+ vm.isFilterVisible = _.isUndefined(show) ? !vm.isFilterVisible : show;
+ }
- // options provided as a model
- var optionsWatch = scope.$watch('vm.options', function () {
+ function onQueryChanged() {
+ toggleFilter(false);
+ loadGrid();
+ }
- // need to copy b/c of assignment issue with bound attributes
- vm.listOptions = vm.options;
+ function loadGrid() {
+ vm.gridLoadState.on();
+
+ return imageService.getAll(vm.query.getParameters()).then(function (result) {
+ vm.result = result;
+ vm.gridLoadState.off();
+ });
+ }
+
+ /* EVENTS */
+
+ function onCancel() {
+ if (!vm.multiMode) {
+ // in single-mode reset the currentAsset
+ vm.onSelected(vm.currentAsset);
+ }
+ close();
+ }
+
+ function onSelect(image) {
+ if (!vm.multiMode) {
+ vm.selectedAsset = image;
+ return;
+ }
- bindDisplayValue();
- });
- }
+ addOrRemove(image);
+ }
- scope.$watch('vm.model', bindDisplayValue);
+ function onSelectAndClose(image) {
+ if (!vm.multiMode) {
+ vm.selectedAsset = image;
+ onOk();
+ return;
}
- /* Helpers */
-
- function getOptions(apiPath) {
- return $http.get(apiPath).then(loadOptions);
+ addOrRemove(image);
+ onOk();
+ }
- function loadOptions(options) {
- vm.listOptions = options;
- bindDisplayValue();
- }
+ function onOk() {
+ if (!vm.multiMode) {
+ vm.onSelected(vm.selectedAsset);
+ } else {
+ vm.onSelected(vm.selectedIds);
}
- function bindDisplayValue() {
+ close();
+ }
- var selectedOption = _.find(vm.listOptions, function (option) {
- return option[vm.optionValue] == vm.model;
- });
+ function onUpload() {
+ modalDialogService.show({
+ templateUrl: modulePath + 'UIComponents/ImageAssets/UploadImageAssetDialog.html',
+ controller: 'UploadImageAssetDialogController',
+ options: {
+ filter: options.filter,
+ onUploadComplete: onUploadComplete
+ }
+ });
- vm.displayValue = selectedOption ? selectedOption[vm.optionName] : vm.defaultItemText;
+ function onUploadComplete(imageAssetId) {
- // if the options and model are bound, and the option does not appear in the
- // list, remove the value from the model.
- if (!selectedOption && vm.model != undefined && vm.listOptions) {
- vm.model = undefined;
- }
+ imageService
+ .getById(imageAssetId)
+ .then(onSelectAndClose);
}
}
- function getInputEl(rootEl) {
- return rootEl.find('select');
- }
-
-}]);
-angular.module('cms.shared').directive('cmsFormFieldEmailAddress', [
- 'shared.internalModulePath',
- 'baseFormFieldFactory',
-function (
- modulePath,
- baseFormFieldFactory) {
-
- var config = {
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldEmailAddress.html',
- passThroughAttributes: [
- 'required',
- 'maxlength',
- 'placeholder',
- 'disabled',
- 'cmsMatch'
- ],
- link: link
- };
+ /* PUBLIC HELPERS */
- return baseFormFieldFactory.create(config);
+ function isSelected(image) {
+ if (vm.selectedIds && image && vm.selectedIds.indexOf(image.imageAssetId) > -1) return true;
- function link(scope) {
- var vm = scope.vm;
+ if (!image || !vm.selectedAsset) return false;
- // call base
- baseFormFieldFactory.defaultConfig.link.apply(this, arguments);
+ return image.imageAssetId === vm.selectedAsset.imageAssetId;
+ }
- // add custom error for email since its not attribute based like other validation messages
- vm.validators.push({
- name: 'email',
- message: 'Please enter a valid email address'
- });
+ function addOrRemove(image) {
+ if (!isSelected(image)) {
+ vm.selectedIds.push(image.imageAssetId);
+ } else {
+ var index = vm.selectedIds.indexOf(image.imageAssetId);
+ vm.selectedIds.splice(index, 1);
+ }
}
}]);
-angular.module('cms.shared').directive('cmsFormFieldFilteredDropdown', [
- '$q',
- '_',
- 'shared.internalModulePath',
+
+angular.module('cms.shared').controller('UploadImageAssetDialogController', [
+ '$scope',
+ 'shared.LoadState',
+ 'shared.imageService',
+ 'shared.SearchQuery',
+ 'shared.focusService',
'shared.stringUtilities',
- 'baseFormFieldFactory',
+ 'options',
+ 'close',
function (
- $q,
- _,
- modulePath,
+ $scope,
+ LoadState,
+ imageService,
+ SearchQuery,
+ focusService,
stringUtilities,
- baseFormFieldFactory) {
-
- var config = {
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldFilteredDropdown.html',
- passThroughAttributes: [
- 'required',
- 'disabled'
- ],
- scope: _.extend(baseFormFieldFactory.defaultConfig.scope, {
- defaultItemText: '@cmsDefaultItemText',
- href: '@cmsHref',
- searchFunction: '&cmsSearchFunction',
- initialItemFunction: '&cmsInitialItemFunction',
- optionName: '@cmsOptionName',
- optionValue: '@cmsOptionValue',
- required: '=cmsRequired'
- }),
- require: _.union(baseFormFieldFactory.defaultConfig.require, ['?^^cmsFormDynamicFieldSet']),
- link: link,
- transclude: true
- };
+ options,
+ close) {
+
+ var vm = $scope;
+ init();
+
+ /* INIT */
+ function init() {
+ angular.extend($scope, options);
- return baseFormFieldFactory.create(config);
+ initData();
- /* LINK */
+ vm.onUpload = onUpload;
+ vm.onCancel = onCancel;
+ vm.close = onCancel;
+ vm.filter = options.filter;
+ $scope.$watch("command.file", setFileName);
- function link(scope, element, attrs, controllers) {
- var vm = scope.vm,
- dynamicFormFieldController = _.last(controllers);
+ setFilter(options.filter);
- init();
+ vm.saveLoadState = new LoadState();
+ }
- // call base
- baseFormFieldFactory.defaultConfig.link.apply(this, arguments);
+ /* EVENTS */
+ function onUpload() {
+ vm.saveLoadState.on();
- /* Init */
+ imageService
+ .add(vm.command)
+ .progress(vm.saveLoadState.setProgress)
+ .then(uploadComplete);
+ }
- function init() {
- vm.refreshDataSource = refreshDataSource;
- vm.dataSource = [];
- vm.hasRequiredAttribute = _.has(attrs, 'required');
- vm.placeholder = attrs['placeholder'];
- vm.clearSelected = clearSelected;
+ function setFileName() {
+ var command = vm.command;
- scope.$watch("vm.model", setSelectedText);
+ if (command.file && command.file.name) {
+ command.title = stringUtilities.capitaliseFirstLetter(stringUtilities.getFileNameWithoutExtension(command.file.name));
+ focusService.focusById('title');
}
+ }
- function setSelectedText(id) {
- vm.selectedText = '';
-
- if (id && vm.dataSource && vm.dataSource.length) {
- var item = _.find(vm.dataSource, function (item) {
- return id == item[vm.optionValue];
- });
-
- if (item) vm.selectedText = item[vm.optionName];
- }
+ function onCancel() {
+ close();
+ }
- if (!vm.selectedText && id && vm.initialItemFunction) {
- $q.when(vm.initialItemFunction({ id: id })).then(setSelectedItem);
- }
+ /* PUBLIC HELPERS */
+ function initData() {
+ vm.command = {};
- function setSelectedItem(item) {
- if (item) {
- vm.selectedText = item[vm.optionName];
- refreshDataSource(vm.selectedText);
- }
- }
+ if (vm.filter.tags) {
+ vm.command.tags = vm.filter.tags.split(',');
}
+ }
- function clearSelected() {
- vm.selectedText = '';
+ function setFilter(filter) {
+ var parts = [];
- if (vm.model) {
- vm.model = null;
- }
+ if (filter) {
+ addSize(filter.width, filter.height);
+ addSize(filter.minWidth, filter.minHeight, 'min-');
}
- function refreshDataSource(search) {
- var query = {
- text: search,
- pageSize: 20
- }
+ vm.filterText = parts.join(', ');
+ vm.isFilterSet = parts.length > 0;
- if (vm.localeId) {
- query.localeId = vm.localeId;
- } else if (dynamicFormFieldController && dynamicFormFieldController.additionalParameters) {
- query.localeId = dynamicFormFieldController.additionalParameters.localeId;
+ function addSize(width, height, prefix) {
+ if (width && height) {
+ parts.push(prefix + 'size ' + width + 'x' + height);
}
+ else {
+ addIfSet(prefix + 'width', width);
+ addIfSet(prefix + 'height', height);
+ }
+ }
- return vm.searchFunction({ $query: query }).then(loadResults);
-
- function loadResults(results) {
- if (!results || !results.items || !results.items.length) {
- vm.dataSource = [];
- } else {
- vm.dataSource = results.items;
- }
+ function addIfSet(name, value) {
+ if (value) {
+ parts.push(name + ' ' + value);
}
}
}
+ function cancel() {
+ close();
+ }
+
+ function uploadComplete(imageAssetId) {
+ options.onUploadComplete(imageAssetId);
+ close();
+ }
+
}]);
+
/**
- * Allows editing of html. Note that in order to display html we have included ngSanitize in
- * the module dependencies (https://docs.angularjs.org/api/ng/directive/ngBindHtml)
+ * Base class for form fields that uses default conventions and includes integration with
+ * server validation.
*/
-angular.module('cms.shared').directive('cmsFormFieldHtml', [
- '$sce',
- '$q',
- '$http',
- '_',
- 'shared.internalModulePath',
- 'shared.internalContentPath',
+angular.module('cms.shared').factory('baseFormFieldFactory', [
+ '$timeout',
'shared.stringUtilities',
- 'shared.modalDialogService',
- 'baseFormFieldFactory',
+ 'shared.directiveUtilities',
+ 'shared.validationErrorService',
function (
- $sce,
- $q,
- $http,
- _,
- modulePath,
- contentPath,
+ $timeout,
stringUtilities,
- modalDialogService,
- baseFormFieldFactory
-) {
+ directiveUtilities,
+ validationErrorService
+ ) {
- var config = {
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldHtml.html',
- passThroughAttributes: [
- 'required',
- 'maxlength',
- 'disabled',
- 'rows'
- ],
+ var service = {},
+ /* Here we can validation messages that can apply to all FormField controls */
+ globalDefaultValidationMessages = [
+ {
+ attr: 'required',
+ msg: 'This field is required'
+ },
+ {
+ attr: 'maxlength',
+ msg: 'This field cannot be longer than {0} characters'
+ },
+ {
+ attr: 'minlength',
+ msg: 'This must be at least {0} characters long'
+ }
+ ];
+
+ /* PUBLIC */
+
+ service.create = function (config) {
+ return angular.extend({}, service.defaultConfig, config);
+ }
+
+ /* CONFIG */
+
+ /**
+ * Configuration defaults
+ */
+ service.defaultConfig = {
+ restrict: 'E',
+ replace: true,
+ require: ['^^cmsForm'],
+ scope: {
+ title: '@cmsTitle',
+ description: '@cmsDescription',
+ change: '&cmsChange',
+ model: '=cmsModel',
+ disabled: '=cmsDisabled',
+ readonly: '=cmsReadonly'
+ },
+ compile: compile,
+ link: link,
+ controller: function () { },
+ controllerAs: 'vm',
+ bindToController: true,
+
+ /* Custom Properties */
+
+ /**
+ * Should return the main input element that is displayed in edit mode.
+ * By default this returns the first child input element.
+ */
getInputEl: getInputEl,
- scope: _.extend(baseFormFieldFactory.defaultConfig.scope, {
- toolbarsConfig: '@cmsToolbars',
- toolbarCustomConfig: '@cmsCustomToolbar',
- options: '=cmsOptions',
- configPath: '@cmsConfigPath',
- }),
- link: link
+
+ /**
+ * a list of attributes that when defined on the directive are passed through to the element
+ * returned from getInputEl.
+ */
+ passThroughAttributes: [],
+
+ /**
+ * Default validation messages to use when none are provided on the element. Saves specifying common messages
+ * like 'This field is required' on every field. Each array element should be an object in the form of:
+ * { attr: 'name', msg: 'string message or formatter function' }
+ */
+ defaultValidationMessages: []
};
- return baseFormFieldFactory.create(config);
+ /* COMPILE */
- /* OVERRIDES */
+ function compile(el, attrs) {
- function link(scope, el, attributes) {
- var vm = scope.vm;
+ initPassThroughAttributes.call(this, el, attrs);
- // call base
- baseFormFieldFactory.defaultConfig.link.apply(this, arguments);
+ return this.link.bind(this);
+ }
- loadTinyMCEOptions(vm, attributes).then(function (tinymceOptions) {
- vm.tinymceOptions = tinymceOptions;
- });
+ function link(scope, el, attrs, controllers) {
+ var vm = scope.vm,
+ formController = controllers[0];
- scope.$watch("vm.model", setEditorModel);
- scope.$watch("vm.editorModel", setCmsModel);
+ // Model Properties
+ vm.formScope = formController.getFormScope();
+ vm.form = vm.formScope.getForm();
+
+ directiveUtilities.setModelName(vm, attrs);
+ parseErrorMessages(vm, attrs);
- function setEditorModel(value) {
- if (value !== vm.editorModel) {
- vm.editorModel = value;
- vm.rawHtml = $sce.trustAsHtml(value);
- }
- }
+ // Model Funcs
+ vm.onChange = onChange.bind(vm);
+ vm.resetCustomErrors = resetCustomErrors.bind(vm);
+ vm.addOrUpdateValidator = addOrUpdateValidator.bind(vm);
- function setCmsModel(value) {
- if (value !== vm.model) {
- vm.model = value;
- vm.rawHtml = $sce.trustAsHtml(value);
- }
- }
- }
+ // Init Errors
+ vm.resetCustomErrors();
- function getInputEl(rootEl) {
- return rootEl.find('textarea');
+ // Bind Validation Events
+ bindValidationEvents(scope, el);
+
+ // watches
+ scope.$watch('vm.model', function () {
+ vm.resetCustomErrors();
+ });
}
- /* HELPERS */
+ /* PUBLIC */
- function loadTinyMCEOptions(vm, attributes) {
- var rows = 20,
- def = $q.defer();
+ function onChange() {
+ var vm = this;
- if (attributes.rows) {
- rows = parseInt(attributes.rows);
+ vm.resetCustomErrors();
+ if (vm.change) {
+ // run after digest cycle completes so the parent ngModel is updated
+ $timeout(vm.change, 0);
}
+ }
- var defaultOptions = {
- toolbar: parseToolbarButtons(vm.toolbarsConfig, vm.toolbarCustomConfig),
- plugins: 'link image media fullscreen imagetools code',
- content_css: contentPath + "css/third-party/tinymce/content.min.css",
- menubar: false,
- min_height: rows * 16,
- setup: function (editor) {
- editor.addButton('cfimage', {
- icon: 'image',
- onclick: onEditorImageButtonClick.bind(null, editor)
- });
- },
- browser_spellcheck: true,
- convert_urls: false
- };
-
- if (vm.configPath) {
- $http.get(vm.configPath).then(bindFileOptions);
+ function resetCustomErrors() {
+ var model = this.form[this.modelName];
- } else {
- bindCodeOptionsAndResolve();
+ if (model) {
+ model.$setValidity('server', true);
}
+ this.customErrors = [];
+ }
- return def.promise;
+ function addOrUpdateValidator(validator) {
+ var vm = this;
+ var validators = _.filter(vm.validators, function (v) {
+ return v.name !== validator.name;
+ });
+ validators.push(validator);
+ vm.validators = validators;
+ }
- function bindFileOptions(result) {
- if (result && result.data) {
- _.extend(defaultOptions, result.data);
- }
- bindCodeOptionsAndResolve();
- }
+ /* HELPERS */
- function bindCodeOptionsAndResolve() {
- // Always apply last
- if (vm.options) {
- _.extend(defaultOptions, vm.options);
+ /**
+ * Loop through attributes specified in config.passThroughAttributes and copy
+ * them onto the input control requrned by config.getInputEl
+ */
+ function initPassThroughAttributes(rootEl, attrs) {
+ var config = this,
+ el = config.getInputEl(rootEl);
+
+ (config.passThroughAttributes || []).forEach(function (passThroughAttribute) {
+ var name = passThroughAttribute.name || passThroughAttribute;
+ if (angular.isDefined(attrs[name])) {
+ el[0].setAttribute(attrs.$attr[name], attrs[name]);
+ }
+ else if (passThroughAttribute.default) {
+ el[0].setAttribute(name, passThroughAttribute.default);
}
+ });
+ }
- def.resolve(defaultOptions);
- }
+ function getInputEl(rootEl) {
+ return rootEl.find('input');
}
- function onEditorImageButtonClick(editor) {
- var currentElement = editor.selection.getContent({ format: 'image' }),
- currentImage = currentElement.length ? angular.element(currentElement) : null;
+ function bindValidationEvents(scope, el) {
+ var fn = _.partial(addErrors, scope.vm, el);
- modalDialogService.show({
- templateUrl: modulePath + 'UIComponents/EditorDialogs/ImageAssetEditorDialog.html',
- controller: 'ImageAssetEditorDialogController',
- options: {
- imageAssetHtml: currentImage,
- onSelected: function (output) {
- editor.insertContent(output.html);
- }
- }
+ validationErrorService.addHandler(scope.vm.modelName, fn);
+
+ scope.$on('$destroy', function () {
+ validationErrorService.removeHandler(fn);
});
}
+
+ function parseErrorMessages(vm, attrs) {
+ var config = this,
+ postfix = 'ValMsg',
+ attrPostfix = '-val-msg';
- function parseToolbarButtons(toolbarsConfig, toolbarCustomConfig) {
- var DEFAULT_CONFIG = 'headings,basicFormatting',
- buttonConfig = {
- headings: 'formatselect',
- basicFormatting: 'fullscreen undo redo removeformat | bold italic underline | link unlink',
- advancedFormatting: 'bullist numlist blockquote | alignleft aligncenter alignright alignjustify | strikethrough superscript subscript',
- media: 'cfimage media',
- source: 'code',
- }, toolbars = [];
+ vm.validators = [];
- toolbarsConfig = toolbarsConfig || DEFAULT_CONFIG;
+ _.each(attrs.$attr, function (value, key) {
+ var attributeToValidate,
+ msg;
- toolbarsConfig.split(',').forEach(function (configItem) {
- configItem = stringUtilities.lowerCaseFirstWord(configItem.trim());
+ if (stringUtilities.endsWith(key, postfix)) {
+ // if the property is postfix '-val-msg' then pull in the message from the attribute
+ attributeToValidate = value.substring(0, value.length - attrPostfix.length);
+ msg = attrs[key];
+ } else {
- if (configItem === 'custom') {
- toolbars = _.union(toolbars, parseCustomConfig(toolbarCustomConfig));
+ attributeToValidate = value;
- } else if (buttonConfig[configItem]) {
- toolbars.push(buttonConfig[configItem]);
+ // check to see if we have a default message for the property
+ msg = getDefaultErrorMessage(config.defaultValidationMessages, key) || getDefaultErrorMessage(globalDefaultValidationMessages, key);
+ }
+
+ if (msg) {
+ vm.validators.push({
+ name: attrs.$normalize(attributeToValidate),
+ message: stringUtilities.format(msg, attrs[key])
+ });
}
});
- return toolbars.join(' | ');
+ function getDefaultErrorMessage(defaultsToCheck, attr) {
+ var validator = _.find(defaultsToCheck, function (v) {
+ return v.attr === attr;
+ });
- function parseCustomConfig(toolbarCustomConfig) {
- var customToolbars;
+ if (validator) {
+ if (_.isFunction(validator.msg)) {
+ return validator.msg(vm.modelName, attrs);
+ }
+ return validator.msg;
+ }
+ }
+ }
- if (toolbarCustomConfig) {
+ function addErrors(vm, el, errors) {
+ var form = vm.formScope.getForm();
+ var model = form[vm.modelName];
+ vm.resetCustomErrors();
- // old formatting allowed array parsing, but this is ambigous when mixed with other toolbars
- // and so will be removed eventually.
- if (!toolbarCustomConfig.startsWith("'") && !toolbarCustomConfig.startsWith("\"")) {
- // single toolbar
- return [toolbarCustomConfig];
- }
- try {
- // parse an array of toolbars, with each one surrounded by quotationmarks
- customToolbars = JSON.parse('{"j":[' + toolbarCustomConfig.replace(/'/g, '"') + ']}').j;
- }
- catch (e) { }
+ model.$setValidity('server', false);
- if (customToolbars && customToolbars.length) {
- return customToolbars;
- }
- }
+ // make dirty to ensure css classes are applied
+ getInputEl(el).removeClass('ng-pristine').addClass('ng-dirty');
- return [];
- }
+ errors.forEach(function (error) {
+ vm.customErrors.push(error);
+ });
}
+
+ /* DEFINITION */
+
+ return service;
}]);
-angular.module('cms.shared').directive('cmsFormFieldNumber', [
+angular.module('cms.shared').directive('cmsFormFieldChar', [
+ '_',
'shared.internalModulePath',
'baseFormFieldFactory',
function (
+ _,
modulePath,
- baseFormFieldFactory
- ) {
+ baseFormFieldFactory) {
var config = {
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldNumber.html',
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldChar.html',
passThroughAttributes: [
'required',
- 'maxlength',
- 'min',
- 'max',
- 'step',
- 'disabled',
'placeholder',
+ 'pattern',
+ 'disabled',
'cmsMatch'
]
};
return baseFormFieldFactory.create(config);
}]);
-var numericTypes = [
- { name: 'Byte', min: 0, max: 255, step: 1 },
- { name: 'SByte', min: -128, max: 127, step: 1 },
- { name: 'Int16', min: -32768, max: 32767, step: 1 },
- { name: 'UInt16', min: 0, max: 65535, step: 1 },
- { name: 'Int32', min: -2147483648, max: 2147483647, step: 1 },
- { name: 'UInt32', min: 0, max: 4294967295, step: 1 },
- { name: 'Int64', step: 1 },
- { name: 'UInt64', min: 0, step: 1 }
-];
-
-numericTypes.forEach(function (numericType) {
-
- angular.module('cms.shared').directive('cmsFormField' + numericType.name, [
- 'shared.internalModulePath',
- 'baseFormFieldFactory',
- function (
- modulePath,
- baseFormFieldFactory
- ) {
-
- var config = {
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldNumber.html',
- passThroughAttributes: [
- 'required',
- 'maxlength',
- { name: 'min', default: numericType.min },
- { name: 'max', default: numericType.max },
- { name: 'step', default: numericType.step },
- 'disabled',
- 'placeholder',
- 'cmsMatch'
- ]
- };
-
- return baseFormFieldFactory.create(config);
- }]);
-});
-angular.module('cms.shared').directive('cmsFormFieldPassword', [
+angular.module('cms.shared').directive('cmsFormFieldCheckbox', [
'shared.internalModulePath',
'baseFormFieldFactory',
function (
@@ -12244,113 +12252,43 @@ function (
baseFormFieldFactory) {
var config = {
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldPassword.html',
- scope: _.extend(baseFormFieldFactory.defaultConfig.scope, {
- passwordPolicy: '=cmsPasswordPolicy'
- }),
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldCheckbox.html',
passThroughAttributes: [
- 'required',
- 'minlength',
- 'maxlength',
- 'pattern',
- 'passwordrules',
- 'disabled',
- 'cmsMatch'
- ],
- link: link
+ 'disabled'
+ ]
};
return baseFormFieldFactory.create(config);
-
-
- function link(scope, element, attrs, controllers) {
- var vm = scope.vm;
-
- // call base
- baseFormFieldFactory.defaultConfig.link.apply(this, arguments);
-
- init();
-
- /* Init */
-
- function init() {
- scope.$watch('vm.passwordPolicy', onPasswordPolicyChange);
- }
-
- function onPasswordPolicyChange(policy) {
-
- vm.policyAttributes = {};
-
- processAttribute('minlength', true);
- processAttribute('maxlength', true);
- processAttribute('placeholder');
- processAttribute('pattern', true);
- processAttribute('passwordrules');
- processAttribute('title', false, policy ? policy.description : null);
-
- function processAttribute(attribute, addValidator, defaultValue) {
- var value;
-
- if (attrs[attribute]) {
- value = attrs[attribute]
- }
- else if (policy && policy.attributes[attribute]) {
- value = policy.attributes[attribute];
- }
-
- setAttributeValue(attribute, value, addValidator, defaultValue);
- }
-
- function setAttributeValue(attribute, value, addValidator, defaultValue) {
-
- vm.policyAttributes[attribute] = value || defaultValue || '';
- if (!addValidator) return;
-
- var validator = _.findWhere(vm.validators, { name: attribute });
-
- if (validator && (!value || !policy)) {
- vm.validators = _.without(vm.validators, validator);
- }
- else if (policy && value && validator) {
- validator.message = policy.description;
- }
- else if (policy && value && !validator)
- {
- vm.validators.push({
- name: attribute,
- message: policy.description
- });
- }
- }
- }
- }
}]);
-angular.module('cms.shared').directive('cmsFormFieldRadioList', [
+angular.module('cms.shared').directive('cmsFormFieldCheckboxList', [
'_',
'$http',
'shared.internalModulePath',
+ 'shared.optionSourceService',
'baseFormFieldFactory',
function (
_,
$http,
modulePath,
+ optionSourceService,
baseFormFieldFactory) {
var config = {
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldRadioList.html',
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldCheckboxList.html',
scope: _.extend(baseFormFieldFactory.defaultConfig.scope, {
options: '=cmsOptions',
optionValue: '@cmsOptionValue',
optionName: '@cmsOptionName',
optionsApi: '@cmsOptionsApi',
- defaultItemText: '@cmsDefaultItemText',
+ noValueText: '@cmsNoValueText',
required: '=cmsRequired',
disabled: '=cmsDisabled'
}),
passThroughAttributes: [
'placeholder',
'disabled',
- 'cmsMatch'
+ 'cmsMatch',
+ 'required'
],
link: link
};
@@ -12370,6 +12308,7 @@ function (
function init() {
vm.isRequiredAttributeDefined = angular.isDefined(attrs.required);
+ vm.onCheckChanged = updateModel;
if (vm.optionsApi) {
// options bound by api call
@@ -12380,66 +12319,115 @@ function (
// options provided as a model
var optionsWatch = scope.$watch('vm.options', function () {
- // need to copy b/c of assignment issue with bound attributes
- vm.listOptions = vm.options;
-
- bindDisplayValue();
+ bindOptions(vm.options);
+ updateDisplayValues();
});
}
- scope.$watch('vm.model', bindDisplayValue);
+ scope.$watch('vm.model', onModelChanged);
+ }
+
+ /* Events */
+
+ function onModelChanged() {
+ updateOptionSelectionFromModel();
+ updateDisplayValues();
}
/* Helpers */
function getOptions(apiPath) {
- return $http.get(apiPath).then(loadOptions);
+ return optionSourceService.getFromApi(apiPath).then(loadOptions);
function loadOptions(options) {
- vm.listOptions = options;
- bindDisplayValue();
+ bindOptions(options);
+ updateDisplayValues();
}
}
- function bindDisplayValue() {
+ /**
+ * Copies over the option collection so the original
+ * is not modified with selcted properties.
+ */
+ function bindOptions(options) {
+ vm.listOptions = _.map(options, mapOption);
- var selectedOption = _.find(vm.listOptions, function (option) {
- return option[vm.optionValue] == vm.model;
+ updateOptionSelectionFromModel();
+
+ function mapOption(option) {
+ return {
+ text: option[vm.optionName],
+ value: option[vm.optionValue]
+ };
+ }
+ }
+
+ /**
+ * Updates the selected values in the listOptions
+ * collection from the model,
+ */
+ function updateOptionSelectionFromModel() {
+ var isModelEmpty = !vm.model || !vm.model.length;
+ vm.displayValues = [];
+
+ _.each(vm.listOptions, function (option) {
+
+ option.selected = !isModelEmpty && !!_.find(vm.model, function (m) {
+ return m === option.value;
+ });
});
+ }
- vm.displayValue = selectedOption ? selectedOption[vm.optionName] : vm.defaultItemText;
+ function updateDisplayValues() {
- // if the options and model are bound, and the option does not appear in the
- // list, remove the value from the model.
- if (!selectedOption && vm.model != undefined && vm.listOptions) {
- vm.model = undefined;
- }
+ vm.displayValues = _.chain(vm.listOptions)
+ .filter(function (o) { return o.selected })
+ .pluck('text')
+ .value();
}
- }
+ function updateModel() {
+
+ vm.model = _.chain(vm.listOptions)
+ .filter(function (o) { return o.selected })
+ .pluck('value')
+ .value();
+ }
+ }
}]);
-angular.module('cms.shared').directive('cmsFormFieldReadonly', [
+angular.module('cms.shared').directive('cmsFormFieldColor', [
'shared.internalModulePath',
+ 'baseFormFieldFactory',
function (
- modulePath
+ modulePath,
+ baseFormFieldFactory
) {
- return {
- restrict: 'E',
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldReadonly.html',
- replace: true,
- require: '^^cmsForm',
- scope: {
- title: '@cmsTitle',
- description: '@cmsDescription',
- model: '=cmsModel'
- },
- controller: function () { },
- controllerAs: 'vm',
- bindToController: true
+ var config = {
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldColor.html',
+ passThroughAttributes: [
+ 'required',
+ 'disabled'
+ ],
+ link: link
};
+
+ return baseFormFieldFactory.create(config);
+
+ function link(scope) {
+ var vm = scope.vm;
+
+ // call base
+ baseFormFieldFactory.defaultConfig.link.apply(this, arguments);
+
+ // add custom error for value since its not attribute based like other validation messages
+ vm.validators.push({
+ name: 'pattern',
+ message: vm.title + " must be a hexadecimal colour value e.g. '#EFEFEF' or '#fff'"
+ });
+ }
}]);
-angular.module('cms.shared').directive('cmsFormFieldSubHeading', [
+angular.module('cms.shared').directive('cmsFormFieldContainer', [
'shared.internalModulePath',
function (
modulePath
@@ -12447,89 +12435,128 @@ function (
return {
restrict: 'E',
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldSubHeading.html',
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldContainer.html',
require: ['^^cmsForm'],
replace: true,
- transclude: true
+ transclude: true,
+ scope: {
+ title: '@cmsTitle',
+ description: '@cmsDescription'
+ }
};
}]);
-angular.module('cms.shared').directive('cmsFormFieldText', [
- '_',
+angular.module('cms.shared').directive('cmsFormFieldDate', [
'shared.internalModulePath',
'baseFormFieldFactory',
function (
- _,
modulePath,
- baseFormFieldFactory) {
+ baseFormFieldFactory
+ ) {
var config = {
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldText.html',
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldDate.html',
passThroughAttributes: [
'required',
- 'minlength',
- 'maxlength',
- 'placeholder',
- 'pattern',
+ 'min',
+ 'max',
+ 'step',
'disabled',
- 'readonly',
'cmsMatch'
- ]
+ ],
+ link: link
};
return baseFormFieldFactory.create(config);
+
+ function link(scope, element, attrs, controllers) {
+ var vm = scope.vm;
+
+ // call base
+ baseFormFieldFactory.defaultConfig.link.apply(this, arguments);
+
+ init();
+
+ /* Init */
+
+ function init() {
+ if (attrs.min) {
+ vm.addOrUpdateValidator({
+ name: 'min',
+ message: "This date cannot be before " + attrs.min
+ });
+ }
+ if (attrs.max) {
+ vm.addOrUpdateValidator({
+ name: 'max',
+ message: "This date cannot be after " + attrs.max
+ });
+ }
+ }
+ }
}]);
-angular.module('cms.shared').directive('cmsFormFieldTextArea', [
- 'shared.internalModulePath',
- 'shared.stringUtilities',
- 'baseFormFieldFactory',
+angular.module('cms.shared').directive('cmsFormFieldDateLocal', [
+ 'shared.internalModulePath',
+ 'baseFormFieldFactory',
function (
- modulePath,
- stringUtilities,
- baseFormFieldFactory) {
+ modulePath,
+ baseFormFieldFactory
+ ) {
var config = {
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldTextArea.html',
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldDateLocal.html',
passThroughAttributes: [
'required',
- 'maxlength',
- 'placeholder',
- 'ngMinlength',
- 'ngMaxlength',
- 'ngPattern',
+ 'min',
+ 'max',
'disabled',
- 'rows',
- 'cols',
- 'wrap'
+ 'cmsMatch'
],
- getInputEl: getInputEl
+ link: link
};
return baseFormFieldFactory.create(config);
- function getInputEl(rootEl) {
- return rootEl.find('textarea');
- }
+ function link(scope, element, attrs, controllers) {
+ var vm = scope.vm;
+ // call base
+ baseFormFieldFactory.defaultConfig.link.apply(this, arguments);
+
+ init();
+
+ /* Init */
+
+ function init() {
+ if (attrs.min) {
+ vm.addOrUpdateValidator({
+ name: 'min',
+ message: "This date cannot be before " + attrs.min
+ });
+ }
+ if (attrs.max) {
+ vm.addOrUpdateValidator({
+ name: 'max',
+ message: "This date cannot be after " + attrs.max
+ });
+ }
+ }
+ }
}]);
-angular.module('cms.shared').directive('cmsFormFieldTime', [
+angular.module('cms.shared').directive('cmsFormFieldDateTime', [
'shared.internalModulePath',
- 'shared.timeUtilities',
'baseFormFieldFactory',
function (
modulePath,
- timeUtilities,
baseFormFieldFactory
) {
var config = {
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldTime.html',
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldDateTime.html',
passThroughAttributes: [
'required',
'min',
- 'step',
'max',
'disabled',
- 'readonly',
'cmsMatch'
],
link: link
@@ -12548,1035 +12575,1009 @@ function (
/* Init */
function init() {
-
- // Use watch instead of formatters/parsers here due to issues
- // with utc dates
- scope.$watch("vm.model", setEditorModel);
- scope.$watch("vm.editorModel", setCmsModel);
-
- function setEditorModel(value) {
- if (value !== vm.editorModel) {
- vm.editorModel = timeUtilities.toDate(value);
- }
+ if (attrs.min) {
+ var minDate = attrs.min.replace('T', ' ');
+ vm.addOrUpdateValidator({
+ name: 'min',
+ message: "This date cannot be before " + minDate
+ });
}
-
- function setCmsModel(value) {
- if (value !== vm.model) {
- vm.model = timeUtilities.format(value);
- }
+ if (attrs.max) {
+ var maxDate = attrs.min.replace('T', ' ');
+ vm.addOrUpdateValidator({
+ name: 'max',
+ message: "This date cannot be after " + maxDate
+ });
}
}
}
}]);
-angular.module('cms.shared').directive('cmsFormFieldUrl', [
- '_',
+angular.module('cms.shared').directive('cmsFormFieldDateTimeLocal', [
'shared.internalModulePath',
'baseFormFieldFactory',
function (
- _,
modulePath,
- baseFormFieldFactory) {
+ baseFormFieldFactory
+ ) {
var config = {
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldUrl.html',
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldDateTimeLocal.html',
+ scope: _.extend(baseFormFieldFactory.defaultConfig.scope, {
+ minUtc: '@cmsMinUtc',
+ maxUtc: '@cmsMaxUtc'
+ }),
passThroughAttributes: [
'required',
- 'minlength',
- 'maxlength',
- 'placeholder',
- 'pattern',
+ 'min',
+ 'max',
'disabled',
'cmsMatch'
- ]
+ ],
+ link: link
};
return baseFormFieldFactory.create(config);
-}]);
-angular.module('cms.shared').directive('cmsFormFieldValidationSummary', [
- 'shared.internalModulePath',
-function (
- modulePath
- ) {
+ function link(scope, element, attrs, controllers) {
+ var vm = scope.vm;
- return {
- restrict: 'E',
- templateUrl: modulePath + 'UIComponents/FormFields/FormFieldValidationSummary.html',
- replace: true
- };
-}]);
-angular.module('cms.shared').directive('cmsHttpPrefix', function () {
- return {
- restrict: 'A',
- require: 'ngModel',
- link: function (scope, element, attrs, controller) {
- function ensureHttpPrefix(value) {
- var httpPrefix = 'http://';
+ // call base
+ baseFormFieldFactory.defaultConfig.link.apply(this, arguments);
- if (value
- && !/^(https?):\/\//i.test(value)
- && httpPrefix.indexOf(value) === -1
- && 'https://'.indexOf(value) === -1) {
+ init();
- controller.$setViewValue(httpPrefix + value);
- controller.$render();
+ /* Init */
- return httpPrefix + value;
+ function init() {
+ scope.$watch("vm.minUtc", function (value) {
+ var date = parseDateAsUtc(value);
+ vm.minLocal = formatDate(date);
+ if (vm.minLocal) {
+ vm.addOrUpdateValidator({
+ name: 'min',
+ message: "This date cannot be before " + date.toLocaleString()
+ });
}
- else {
- return value;
+ });
+
+ scope.$watch("vm.maxUtc", function (value) {
+ var date = parseDateAsUtc(value);
+ vm.maxLocal = formatDate(date);
+ if (vm.maxLocal) {
+ vm.addOrUpdateValidator({
+ name: 'max',
+ message: "This date cannot be after " + date.toLocaleString()
+ });
}
- }
- controller.$formatters.push(ensureHttpPrefix);
- controller.$parsers.splice(0, 0, ensureHttpPrefix);
+ });
}
- };
-});
-/**
- * Validates that a field matches the value of another field. Set the came of the field
- * in the attribute definition e.g. cms-match="vm.command.password"
- * Adapted from http://ericpanorel.net/2013/10/05/angularjs-password-match-form-validation/
- */
-angular.module('cms.shared').directive('cmsMatch', [
- '$parse',
- '$timeout',
- 'shared.internalModulePath',
- 'shared.directiveUtilities',
-function (
- $parse,
- $timeout,
- modulePath,
- directiveUtilities
- ) {
-
- var DIRECTIVE_ID = 'cmsMatch';
- var DIRECTIVE_ATTRIBUTE = 'cms-match';
-
- return {
- link: link,
- restrict: 'A',
- require: ['^^cmsForm', '?ngModel'],
- };
-
- function link(scope, el, attrs, controllers) {
- // NB: ngModel may be null on an outer form control before it has been copied to the inner input.
- if (!attrs[DIRECTIVE_ID] || !controllers[1]) return;
-
- var formController = controllers[0],
- ngModelController = controllers[1],
- form = formController.getFormScope().getForm(),
- sourceField = directiveUtilities.parseModelName(attrs[DIRECTIVE_ID]);
-
- var validator = function (value, otherVal) {
- var formField = form[sourceField];
- if (!formField) return false;
- var sourceFieldValue = formField.$viewValue;
+ function parseDateAsUtc(value) {
+ if (!value) return;
- return value === sourceFieldValue;
+ // Format is expected to be 'yyyy-MM-ddT00:00'
+ return new Date(value + ':00.000Z');
}
- ngModelController.$validators[DIRECTIVE_ID] = validator;
- }
-}]);
-angular.module('cms.shared').directive('cmsFormFieldImageAnchorLocationSelector', [
- '_',
- 'shared.internalModulePath',
- function (
- _,
- modulePath) {
- return {
- restrict: 'E',
- templateUrl: modulePath + 'UIComponents/ImageAssets/FormFieldImageAnchorLocationSelector.html',
- scope: {
- model: '=cmsModel',
- readonly: '=cmsReadonly'
- },
- controller: Controller,
- controllerAs: 'vm',
- bindToController: true
- };
+ function formatDate(date) {
+ if (!date) return;
- /* CONTROLLER */
+ return date.getFullYear()
+ + '-' + padTwoDigitNumber(date.getMonth() + 1)
+ + '-' + padTwoDigitNumber(date.getDate())
+ + 'T' + padTwoDigitNumber(date.getHours())
+ + ':' + padTwoDigitNumber(date.getMinutes());
+ }
- function Controller() {
- var vm = this;
-
- vm.options = [
- { name: 'Top Left', id: 'TopLeft' },
- { name: 'Top Center', id: 'TopCenter' },
- { name: 'Top Right', id: 'TopRight' },
- { name: 'Middle Left', id: 'MiddleLeft' },
- { name: 'Middle Center', id: 'MiddleCenter' },
- { name: 'Middle Right', id: 'MiddleRight' },
- { name: 'Bottom Left', id: 'BottomLeft' },
- { name: 'Bottom Center', id: 'BottomCenter' },
- { name: 'Bottom Right', id: 'BottomRight' }
- ];
+ function padTwoDigitNumber(number) {
+ return ("00" + number).slice(-2);
+ }
}
}]);
-/**
- * A form field control for an image asset that uses a search and pick dialog
- * to allow the user to change the selected file.
- */
-angular.module('cms.shared').directive('cmsFormFieldImageAsset', [
- '_',
- 'shared.internalModulePath',
- 'shared.internalContentPath',
- 'shared.modalDialogService',
- 'shared.stringUtilities',
- 'shared.imageService',
- 'shared.urlLibrary',
- 'baseFormFieldFactory',
- function (
- _,
- modulePath,
- contentPath,
- modalDialogService,
- stringUtilities,
- imageService,
- urlLibrary,
- baseFormFieldFactory) {
-
- /* VARS */
-
- var assetReplacementPath = contentPath + 'img/AssetReplacement/',
- noImagePath = assetReplacementPath + 'image-replacement.png',
- baseConfig = baseFormFieldFactory.defaultConfig;
-
- /* CONFIG */
-
- var config = {
- templateUrl: modulePath + 'UIComponents/ImageAssets/FormFieldImageAsset.html',
- scope: _.extend(baseConfig.scope, {
- asset: '=cmsAsset', // if we already have the full asset data we can set it here to save an api call
- loadState: '=cmsLoadState',
- updateAsset: '@cmsUpdateAsset' // update the asset property if it changes
- }),
- passThroughAttributes: [
- 'required'
- ],
- link: link
- };
-
- return baseFormFieldFactory.create(config);
-
- /* LINK */
-
- function link(scope, el, attributes, controllers) {
- var vm = scope.vm,
- isRequired = _.has(attributes, 'required'),
- isAssetInitialized;
-
- init();
- return baseConfig.link(scope, el, attributes, controllers);
-
- /* INIT */
-
- function init() {
+angular.module('cms.shared').directive('cmsFormFieldDropdown', [
+ '_',
+ '$http',
+ 'shared.internalModulePath',
+ 'baseFormFieldFactory',
+function (
+ _,
+ $http,
+ modulePath,
+ baseFormFieldFactory) {
- vm.urlLibrary = urlLibrary;
- vm.showPicker = showPicker;
- vm.remove = remove;
- vm.isRemovable = _.isObject(vm.model) && !isRequired;
+ var config = {
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldDropdown.html',
+ scope: _.extend(baseFormFieldFactory.defaultConfig.scope, {
+ options: '=cmsOptions',
+ optionValue: '@cmsOptionValue',
+ optionName: '@cmsOptionName',
+ optionsApi: '@cmsOptionsApi',
+ defaultItemText: '@cmsDefaultItemText',
+ href: '@cmsHref',
+ required: '=cmsRequired',
+ disabled: '=cmsDisabled'
+ }),
+ passThroughAttributes: [
+ 'placeholder',
+ 'disabled',
+ 'cmsMatch'
+ ],
+ getInputEl: getInputEl,
+ link: link
+ };
- vm.filter = parseFilters(attributes);
- vm.previewWidth = attributes['cmsPreviewWidth'] || 450;
- vm.previewHeight = attributes['cmsPreviewHeight'];
-
- scope.$watch("vm.asset", setAsset);
- scope.$watch("vm.model", setAssetById);
- }
+ return baseFormFieldFactory.create(config);
- /* EVENTS */
+ /* PRIVATE */
- function remove() {
- setAsset(null);
- }
+ function link(scope, element, attrs, controllers) {
+ var vm = scope.vm;
+ init();
- function showPicker() {
+ // call base
+ baseFormFieldFactory.defaultConfig.link.apply(this, arguments);
- modalDialogService.show({
- templateUrl: modulePath + 'UIComponents/ImageAssets/ImageAssetPickerDialog.html',
- controller: 'ImageAssetPickerDialogController',
- options: {
- currentAsset: vm.previewAsset,
- filter: vm.filter,
- onSelected: onSelected
- }
- });
+ /* Init */
- function onSelected(newAsset) {
+ function init() {
+ vm.isRequiredAttributeDefined = angular.isDefined(attrs.required);
- if (!newAsset && vm.previewAsset) {
- setAsset(null);
+ if (vm.optionsApi) {
+ // options bound by api call
+ getOptions(vm.optionsApi);
- vm.onChange();
- } else if (!vm.previewAsset || (newAsset && vm.previewAsset.imageAssetId !== newAsset.imageAssetId)) {
- setAsset(newAsset);
+ } else {
- vm.onChange();
- }
- }
- }
+ // options provided as a model
+ var optionsWatch = scope.$watch('vm.options', function () {
- /**
- * When the model is set without a preview asset, we need to go get the full
- * asset details. This query can be bypassed by setting the cms-asset attribute
- */
- function setAssetById(assetId) {
+ // need to copy b/c of assignment issue with bound attributes
+ vm.listOptions = vm.options;
- // Remove the id if it is 0 or invalid to make sure required validation works
- if (!assetId) {
- vm.model = assetId = undefined;
- }
+ bindDisplayValue();
+ });
+ }
- if (assetId && (!vm.previewAsset || vm.previewAsset.imageAssetId != assetId)) {
- imageService.getById(assetId).then(function (asset) {
- if (asset) {
- setAsset(asset);
- }
- });
- }
- }
+ scope.$watch('vm.model', bindDisplayValue);
+ }
- /**
- * Initialise the state when the asset is changed
- */
- function setAsset(asset) {
+ /* Helpers */
- if (asset) {
- vm.previewAsset = asset;
- vm.isRemovable = !isRequired;
- vm.model = asset.imageAssetId;
+ function getOptions(apiPath) {
+ return $http.get(apiPath).then(loadOptions);
- if (vm.updateAsset) {
- vm.asset = asset;
- }
- } else if (isAssetInitialized) {
- // Ignore if we are running this first time to avoid overwriting the model with a null vlaue
- vm.previewAsset = null;
- vm.isRemovable = false;
+ function loadOptions(options) {
+ vm.listOptions = options;
+ bindDisplayValue();
+ }
+ }
- if (vm.model) {
- vm.model = null;
- }
- if (vm.updateAsset) {
- vm.asset = null;
- }
- }
+ function bindDisplayValue() {
- setButtonText();
+ var selectedOption = _.find(vm.listOptions, function (option) {
+ return option[vm.optionValue] == vm.model;
+ });
- isAssetInitialized = true;
- }
+ vm.displayValue = selectedOption ? selectedOption[vm.optionName] : vm.defaultItemText;
- /* Helpers */
+ // if the options and model are bound, and the option does not appear in the
+ // list, remove the value from the model.
+ if (!selectedOption && vm.model != undefined && vm.listOptions) {
+ vm.model = undefined;
+ }
+ }
+ }
- function parseFilters(attributes) {
- var filter = {},
- attributePrefix = 'cms';
+ function getInputEl(rootEl) {
+ return rootEl.find('select');
+ }
- setAttribute('Tags');
- setAttribute('Width', true);
- setAttribute('Height', true);
- setAttribute('MinWidth', true);
- setAttribute('MinHeight', true);
+}]);
+angular.module('cms.shared').directive('cmsFormFieldEmailAddress', [
+ 'shared.internalModulePath',
+ 'baseFormFieldFactory',
+function (
+ modulePath,
+ baseFormFieldFactory) {
- return filter;
+ var config = {
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldEmailAddress.html',
+ passThroughAttributes: [
+ 'required',
+ 'maxlength',
+ 'placeholder',
+ 'disabled',
+ 'cmsMatch'
+ ],
+ link: link
+ };
- function setAttribute(filterName, isInt) {
- var value = attributes[attributePrefix + filterName];
+ return baseFormFieldFactory.create(config);
- if (value) {
- filterName = stringUtilities.lowerCaseFirstWord(filterName);
- filter[filterName] = isInt ? parseInt(value) : value;
- }
- }
- }
+ function link(scope) {
+ var vm = scope.vm;
- function setButtonText() {
- vm.buttonText = vm.model ? 'Change' : 'Select';
- }
- }
+ // call base
+ baseFormFieldFactory.defaultConfig.link.apply(this, arguments);
- }]);
-angular.module('cms.shared').directive('cmsFormFieldImageAssetCollection', [
+ // add custom error for email since its not attribute based like other validation messages
+ vm.validators.push({
+ name: 'email',
+ message: 'Please enter a valid email address'
+ });
+ }
+}]);
+angular.module('cms.shared').directive('cmsFormFieldFilteredDropdown', [
+ '$q',
'_',
'shared.internalModulePath',
- 'shared.LoadState',
- 'shared.imageService',
- 'shared.modalDialogService',
- 'shared.arrayUtilities',
'shared.stringUtilities',
- 'shared.urlLibrary',
'baseFormFieldFactory',
function (
+ $q,
_,
modulePath,
- LoadState,
- imageService,
- modalDialogService,
- arrayUtilities,
stringUtilities,
- urlLibrary,
baseFormFieldFactory) {
- /* VARS */
-
- var IMAGE_ASSET_ID_PROP = 'imageAssetId',
- baseConfig = baseFormFieldFactory.defaultConfig;
-
- /* CONFIG */
-
var config = {
- templateUrl: modulePath + 'UIComponents/ImageAssets/FormFieldImageAssetCollection.html',
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldFilteredDropdown.html',
passThroughAttributes: [
'required',
- 'ngRequired'
+ 'disabled'
],
- link: link
+ scope: _.extend(baseFormFieldFactory.defaultConfig.scope, {
+ defaultItemText: '@cmsDefaultItemText',
+ href: '@cmsHref',
+ searchFunction: '&cmsSearchFunction',
+ initialItemFunction: '&cmsInitialItemFunction',
+ optionName: '@cmsOptionName',
+ optionValue: '@cmsOptionValue',
+ required: '=cmsRequired'
+ }),
+ require: _.union(baseFormFieldFactory.defaultConfig.require, ['?^^cmsFormDynamicFieldSet']),
+ link: link,
+ transclude: true
};
return baseFormFieldFactory.create(config);
/* LINK */
- function link(scope, el, attributes, controllers) {
- var vm = scope.vm;
-
- init();
- return baseConfig.link(scope, el, attributes, controllers);
-
- /* INIT */
-
- function init() {
-
- vm.gridLoadState = new LoadState();
- vm.urlLibrary = urlLibrary;
-
- vm.showPicker = showPicker;
- vm.remove = remove;
- vm.onDrop = onDrop;
-
- scope.$watch("vm.model", setGridItems);
- }
-
- /* EVENTS */
-
- function remove(image) {
-
- removeItemFromArray(vm.gridData, image);
- removeItemFromArray(vm.model, image.imageAssetId);
-
- function removeItemFromArray(arr, item) {
- var index = arr.indexOf(item);
-
- if (index >= 0) {
- return arr.splice(index, 1);
- }
- }
- }
-
- function showPicker() {
+ function link(scope, element, attrs, controllers) {
+ var vm = scope.vm,
+ dynamicFormFieldController = _.last(controllers);
- modalDialogService.show({
- templateUrl: modulePath + 'UIComponents/ImageAssets/ImageAssetPickerDialog.html',
- controller: 'ImageAssetPickerDialogController',
- options: {
- selectedIds: vm.model || [],
- filter: getFilter(),
- onSelected: onSelected
- }
- });
+ init();
- function onSelected(newImageArr) {
- vm.model = newImageArr;
- setGridItems(newImageArr);
- }
- }
+ // call base
+ baseFormFieldFactory.defaultConfig.link.apply(this, arguments);
- function onDrop($index, droppedEntity) {
+ /* Init */
- arrayUtilities.moveObject(vm.gridData, droppedEntity, $index, IMAGE_ASSET_ID_PROP);
+ function init() {
+ vm.refreshDataSource = refreshDataSource;
+ vm.dataSource = [];
+ vm.hasRequiredAttribute = _.has(attrs, 'required');
+ vm.placeholder = attrs['placeholder'];
+ vm.clearSelected = clearSelected;
- // Update model with new orering
- setModelFromGridData();
+ scope.$watch("vm.model", setSelectedText);
}
- function setModelFromGridData() {
- vm.model = _.pluck(vm.gridData, IMAGE_ASSET_ID_PROP);
- }
+ function setSelectedText(id) {
+ vm.selectedText = '';
- /* HELPERS */
+ if (id && vm.dataSource && vm.dataSource.length) {
+ var item = _.find(vm.dataSource, function (item) {
+ return id == item[vm.optionValue];
+ });
- function getFilter() {
- var filter = {},
- attributePrefix = 'cms';
+ if (item) vm.selectedText = item[vm.optionName];
+ }
- setAttribute('Tags');
- setAttribute('Width', true);
- setAttribute('Height', true);
- setAttribute('MinWidth', true);
- setAttribute('MinHeight', true);
+ if (!vm.selectedText && id && vm.initialItemFunction) {
+ $q.when(vm.initialItemFunction({ id: id })).then(setSelectedItem);
+ }
- return filter;
+ function setSelectedItem(item) {
+ if (item) {
+ vm.selectedText = item[vm.optionName];
+ refreshDataSource(vm.selectedText);
+ }
+ }
+ }
- function setAttribute(filterName, isInt) {
- var value = attributes[attributePrefix + filterName];
+ function clearSelected() {
+ vm.selectedText = '';
- if (value) {
- filterName = stringUtilities.lowerCaseFirstWord(filterName);
- filter[filterName] = isInt ? parseInt(value) : value;
- }
+ if (vm.model) {
+ vm.model = null;
}
}
- /**
- * Load the grid data if it is inconsistent with the Ids collection.
- */
- function setGridItems(ids) {
+ function refreshDataSource(search) {
+ var query = {
+ text: search,
+ pageSize: 20
+ }
- if (!ids || !ids.length) {
- vm.gridData = [];
+ if (vm.localeId) {
+ query.localeId = vm.localeId;
+ } else if (dynamicFormFieldController && dynamicFormFieldController.additionalParameters) {
+ query.localeId = dynamicFormFieldController.additionalParameters.localeId;
}
- else if (!vm.gridData || _.pluck(vm.gridData, IMAGE_ASSET_ID_PROP).join() != ids.join()) {
- vm.gridLoadState.on();
- imageService.getByIdRange(ids).then(function (items) {
- vm.gridData = items;
- vm.gridLoadState.off();
- });
+ return vm.searchFunction({ $query: query }).then(loadResults);
+
+ function loadResults(results) {
+ if (!results || !results.items || !results.items.length) {
+ vm.dataSource = [];
+ } else {
+ vm.dataSource = results.items;
+ }
}
}
}
+
}]);
/**
- * File upload control for images. Uses https://github.com/danialfarid/angular-file-upload
+ * Allows editing of html. Note that in order to display html we have included ngSanitize in
+ * the module dependencies (https://docs.angularjs.org/api/ng/directive/ngBindHtml)
*/
-angular.module('cms.shared').directive('cmsFormFieldImageUpload', [
+angular.module('cms.shared').directive('cmsFormFieldHtml', [
+ '$sce',
+ '$q',
+ '$http',
'_',
- '$timeout',
- 'shared.internalModulePath',
+ 'shared.internalModulePath',
'shared.internalContentPath',
- 'shared.LoadState',
'shared.stringUtilities',
- 'shared.imageFileUtilities',
- 'shared.imageService',
- 'shared.urlLibrary',
- 'shared.validationErrorService',
+ 'shared.modalDialogService',
'baseFormFieldFactory',
function (
+ $sce,
+ $q,
+ $http,
_,
- $timeout,
- modulePath,
+ modulePath,
contentPath,
- LoadState,
stringUtilities,
- imageFileUtilities,
- imageService,
- urlLibrary,
- validationErrorService,
- baseFormFieldFactory) {
-
- /* VARS */
-
- var baseConfig = baseFormFieldFactory.defaultConfig,
- assetReplacementPath = contentPath + 'img/AssetReplacement/',
- noPreviewImagePath = assetReplacementPath + 'preview-not-supported.png',
- noImagePath = assetReplacementPath + 'image-replacement.png';
-
- /* CONFIG */
+ modalDialogService,
+ baseFormFieldFactory
+) {
var config = {
- templateUrl: modulePath + 'UIComponents/ImageAssets/FormFieldImageUpload.html',
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldHtml.html',
+ passThroughAttributes: [
+ 'required',
+ 'maxlength',
+ 'disabled',
+ 'rows'
+ ],
+ getInputEl: getInputEl,
scope: _.extend(baseFormFieldFactory.defaultConfig.scope, {
- asset: '=cmsAsset',
- loadState: '=cmsLoadState',
- filter: '=cmsFilter',
- onChange: '&cmsOnChange'
+ toolbarsConfig: '@cmsToolbars',
+ toolbarCustomConfig: '@cmsCustomToolbar',
+ options: '=cmsOptions',
+ configPath: '@cmsConfigPath',
}),
link: link
};
return baseFormFieldFactory.create(config);
- /* LINK */
+ /* OVERRIDES */
- function link(scope, el, attributes, controllers) {
+ function link(scope, el, attributes) {
var vm = scope.vm;
- init();
- return baseConfig.link(scope, el, attributes, controllers);
+ // call base
+ baseFormFieldFactory.defaultConfig.link.apply(this, arguments);
- /* INIT */
+ loadTinyMCEOptions(vm, attributes).then(function (tinymceOptions) {
+ vm.tinymceOptions = tinymceOptions;
+ });
- function init() {
+ scope.$watch("vm.model", setEditorModel);
+ scope.$watch("vm.editorModel", setCmsModel);
+
+ function setEditorModel(value) {
+ if (value !== vm.editorModel) {
+ vm.editorModel = value;
+ vm.rawHtml = $sce.trustAsHtml(value);
+ }
+ }
+
+ function setCmsModel(value) {
+ if (value !== vm.model) {
+ vm.model = value;
+ vm.rawHtml = $sce.trustAsHtml(value);
+ }
+ }
+ }
+
+ function getInputEl(rootEl) {
+ return rootEl.find('textarea');
+ }
+
+ /* HELPERS */
+
+ function loadTinyMCEOptions(vm, attributes) {
+ var rows = 20,
+ def = $q.defer();
+
+ if (attributes.rows) {
+ rows = parseInt(attributes.rows);
+ }
+
+ var defaultOptions = {
+ toolbar: parseToolbarButtons(vm.toolbarsConfig, vm.toolbarCustomConfig),
+ plugins: 'link image media fullscreen imagetools code',
+ content_css: contentPath + "css/third-party/tinymce/content.min.css",
+ menubar: false,
+ min_height: rows * 16,
+ setup: function (editor) {
+ editor.addButton('cfimage', {
+ icon: 'image',
+ onclick: onEditorImageButtonClick.bind(null, editor)
+ });
+ },
+ browser_spellcheck: true,
+ convert_urls: false
+ };
+
+ if (vm.configPath) {
+ $http.get(vm.configPath).then(bindFileOptions);
+
+ } else {
+ bindCodeOptionsAndResolve();
+ }
+
+ return def.promise;
+
+ function bindFileOptions(result) {
+ if (result && result.data) {
+ _.extend(defaultOptions, result.data);
+ }
+ bindCodeOptionsAndResolve();
+ }
+
+ function bindCodeOptionsAndResolve() {
+ // Always apply last
+ if (vm.options) {
+ _.extend(defaultOptions, vm.options);
+ }
+
+ def.resolve(defaultOptions);
+ }
+ }
+
+ function onEditorImageButtonClick(editor) {
+ var currentElement = editor.selection.getContent({ format: 'image' }),
+ currentImage = currentElement.length ? angular.element(currentElement) : null;
+
+ modalDialogService.show({
+ templateUrl: modulePath + 'UIComponents/EditorDialogs/ImageAssetEditorDialog.html',
+ controller: 'ImageAssetEditorDialogController',
+ options: {
+ imageAssetHtml: currentImage,
+ onSelected: function (output) {
+ editor.insertContent(output.html);
+ }
+ }
+ });
+ }
- vm.isRequired = _.has(attributes, 'required');
- vm.remove = remove;
- vm.fileChanged = onFileChanged;
- vm.isRemovable = _.isObject(vm.ngModel) && !vm.isRequired;
- vm.mainLoadState = new LoadState(true);
+ function parseToolbarButtons(toolbarsConfig, toolbarCustomConfig) {
+ var DEFAULT_CONFIG = 'headings,basicFormatting',
+ buttonConfig = {
+ headings: 'formatselect',
+ basicFormatting: 'fullscreen undo redo removeformat | bold italic underline | link unlink',
+ advancedFormatting: 'bullist numlist blockquote | alignleft aligncenter alignright alignjustify | strikethrough superscript subscript',
+ media: 'cfimage media',
+ source: 'code',
+ }, toolbars = [];
- scope.$watch("vm.asset", setAsset);
+ toolbarsConfig = toolbarsConfig || DEFAULT_CONFIG;
- imageService
- .getSettings()
- .then(mapSettings)
- .then(vm.mainLoadState.off);
- }
+ toolbarsConfig.split(',').forEach(function (configItem) {
+ configItem = stringUtilities.lowerCaseFirstWord(configItem.trim());
- function mapSettings(settings) {
- vm.settings = settings;
- }
+ if (configItem === 'custom') {
+ toolbars = _.union(toolbars, parseCustomConfig(toolbarCustomConfig));
- /* EVENTS */
+ } else if (buttonConfig[configItem]) {
+ toolbars.push(buttonConfig[configItem]);
+ }
+ });
- function remove() {
- onFileChanged();
- }
+ return toolbars.join(' | ');
- /**
- * Initialise the state when the asset is changed
- */
- function setAsset() {
- var asset = vm.asset;
+ function parseCustomConfig(toolbarCustomConfig) {
+ var customToolbars;
- if (asset) {
- vm.previewUrl = urlLibrary.getImageUrl(asset, {
- width: 450
- });
- vm.isRemovable = !vm.isRequired;
+ if (toolbarCustomConfig) {
- vm.model = {
- name: asset.fileName + '.' + asset.extension,
- size: asset.fileSizeInBytes,
- isCurrentFile: true
- };
- } else {
- vm.previewUrl = noImagePath;
- vm.isRemovable = false;
+ // old formatting allowed array parsing, but this is ambigous when mixed with other toolbars
+ // and so will be removed eventually.
+ if (!toolbarCustomConfig.startsWith("'") && !toolbarCustomConfig.startsWith("\"")) {
+ // single toolbar
+ return [toolbarCustomConfig];
+ }
+ try {
+ // parse an array of toolbars, with each one surrounded by quotationmarks
+ customToolbars = JSON.parse('{"j":[' + toolbarCustomConfig.replace(/'/g, '"') + ']}').j;
+ }
+ catch (e) { }
- if (vm.model) {
- vm.model = undefined;
+ if (customToolbars && customToolbars.length) {
+ return customToolbars;
}
}
- setButtonText();
+ return [];
}
+ }
+}]);
+angular.module('cms.shared').directive('cmsFormFieldNumber', [
+ 'shared.internalModulePath',
+ 'baseFormFieldFactory',
+function (
+ modulePath,
+ baseFormFieldFactory
+ ) {
- function onFileChanged($files) {
-
- if ($files && $files[0]) {
- vm.mainLoadState.on();
+ var config = {
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldNumber.html',
+ passThroughAttributes: [
+ 'required',
+ 'maxlength',
+ 'min',
+ 'max',
+ 'step',
+ 'disabled',
+ 'placeholder',
+ 'cmsMatch'
+ ]
+ };
- // set the file if one is selected
- imageFileUtilities
- .getFileInfoAndResizeIfRequired($files[0], vm.settings)
- .then(onImageInfoLoaded)
- .finally(vm.mainLoadState.off);
+ return baseFormFieldFactory.create(config);
+}]);
+var numericTypes = [
+ { name: 'Byte', min: 0, max: 255, step: 1 },
+ { name: 'SByte', min: -128, max: 127, step: 1 },
+ { name: 'Int16', min: -32768, max: 32767, step: 1 },
+ { name: 'UInt16', min: 0, max: 65535, step: 1 },
+ { name: 'Int32', min: -2147483648, max: 2147483647, step: 1 },
+ { name: 'UInt32', min: 0, max: 4294967295, step: 1 },
+ { name: 'Int64', step: 1 },
+ { name: 'UInt64', min: 0, step: 1 }
+];
- } else if (!vm.ngModel || _.isUndefined($files)) {
- onNoFileSelected();
- }
+numericTypes.forEach(function (numericType) {
- function onImageInfoLoaded(imgInfo) {
- if (!imgInfo) {
- onNoFileSelected();
- }
+ angular.module('cms.shared').directive('cmsFormField' + numericType.name, [
+ 'shared.internalModulePath',
+ 'baseFormFieldFactory',
+ function (
+ modulePath,
+ baseFormFieldFactory
+ ) {
- vm.model = imgInfo.file;
- validateSize(imgInfo);
+ var config = {
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldNumber.html',
+ passThroughAttributes: [
+ 'required',
+ 'maxlength',
+ { name: 'min', default: numericType.min },
+ { name: 'max', default: numericType.max },
+ { name: 'step', default: numericType.step },
+ 'disabled',
+ 'placeholder',
+ 'cmsMatch'
+ ]
+ };
- setPreviewImage(imgInfo.file);
- vm.isRemovable = !vm.isRequired;
- vm.isResized = imgInfo.isResized;
- vm.resizeSize = imgInfo.width + 'x' + imgInfo.height;
- onComplete();
- }
+ return baseFormFieldFactory.create(config);
+ }]);
+});
+angular.module('cms.shared').directive('cmsFormFieldPassword', [
+ 'shared.internalModulePath',
+ 'baseFormFieldFactory',
+function (
+ modulePath,
+ baseFormFieldFactory) {
- function validateSize(imgInfo) {
- var filter = vm.filter;
- if (!filter) return;
+ var config = {
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldPassword.html',
+ scope: _.extend(baseFormFieldFactory.defaultConfig.scope, {
+ passwordPolicy: '=cmsPasswordPolicy'
+ }),
+ passThroughAttributes: [
+ 'required',
+ 'minlength',
+ 'maxlength',
+ 'pattern',
+ 'passwordrules',
+ 'disabled',
+ 'cmsMatch'
+ ],
+ link: link
+ };
- if (!isMinSize(filter.minWidth, imgInfo.width)
- || !isMinSize(filter.minHeight, imgInfo.height)
- || !isSize(filter.width, imgInfo.width)
- || !isSize(filter.height, imgInfo.height)) {
- addError('The image is ' + imgInfo.width + 'x' + imgInfo.height + ' which does not meet the size requirements.');
- }
+ return baseFormFieldFactory.create(config);
- function isMinSize(size, value) {
- return !(size > 1) || value >= size;
- }
+
+ function link(scope, element, attrs, controllers) {
+ var vm = scope.vm;
- function isSize(size, value) {
- return !(size > 1) || value == size;
- }
- }
+ // call base
+ baseFormFieldFactory.defaultConfig.link.apply(this, arguments);
+
+ init();
- function addError(message) {
- // Run in next digest cycle otherwise it will
- // be overwirtten in the model change
- $timeout(function () {
- validationErrorService.raise([{
- message: message,
- properties: [vm.modelName]
- }]);
- });
- }
+ /* Init */
- function onNoFileSelected() {
+ function init() {
+ scope.$watch('vm.passwordPolicy', onPasswordPolicyChange);
+ }
- // if we don't have an image loaded already, remove the file.
- vm.model = undefined;
- vm.previewUrl = noImagePath;
- vm.isRemovable = false;
- vm.isResized = false;
- onComplete();
- }
+ function onPasswordPolicyChange(policy) {
+
+ vm.policyAttributes = {};
- function onComplete() {
- setButtonText();
+ processAttribute('minlength', true);
+ processAttribute('maxlength', true);
+ processAttribute('placeholder');
+ processAttribute('pattern', true);
+ processAttribute('passwordrules');
+ processAttribute('title', false, policy ? policy.description : null);
- // base onChange event
- if (vm.onChange) vm.onChange();
- }
- }
+ function processAttribute(attribute, addValidator, defaultValue) {
+ var value;
- /* Helpers */
+ if (attrs[attribute]) {
+ value = attrs[attribute]
+ }
+ else if (policy && policy.attributes[attribute]) {
+ value = policy.attributes[attribute];
+ }
- function setPreviewImage(file) {
- if (!isPreviewSupported(file)) {
- vm.previewUrl = noPreviewImagePath;
- return;
+ setAttributeValue(attribute, value, addValidator, defaultValue);
}
- try {
- vm.previewUrl = URL.createObjectURL(file);
- }
- catch (err) {
- vm.previewUrl = noPreviewImagePath;
- }
- }
+ function setAttributeValue(attribute, value, addValidator, defaultValue) {
- function isPreviewSupported(file) {
- var unsupportedPreviewFormats = ['.tiff', '.tif'];
+ vm.policyAttributes[attribute] = value || defaultValue || '';
+ if (!addValidator) return;
- return !_.find(unsupportedPreviewFormats, function (format) {
- return stringUtilities.endsWith(file.name, format);
- });
- }
+ var validator = _.findWhere(vm.validators, { name: attribute });
- function setButtonText() {
- vm.buttonText = vm.model ? 'Change' : 'Upload';
+ if (validator && (!value || !policy)) {
+ vm.validators = _.without(vm.validators, validator);
+ }
+ else if (policy && value && validator) {
+ validator.message = policy.description;
+ }
+ else if (policy && value && !validator)
+ {
+ vm.validators.push({
+ name: attribute,
+ message: policy.description
+ });
+ }
+ }
}
}
}]);
-angular.module('cms.shared').directive('cmsImageAsset', [
+angular.module('cms.shared').directive('cmsFormFieldRadioList', [
+ '_',
+ '$http',
'shared.internalModulePath',
- 'shared.internalContentPath',
- 'shared.urlLibrary',
+ 'baseFormFieldFactory',
function (
+ _,
+ $http,
modulePath,
- contentPath,
- urlLibrary) {
-
- return {
- restrict: 'E',
- scope: {
- image: '=cmsImage',
- width: '@cmsWidth',
- height: '@cmsHeight',
- cropMode: '@cmsCropMode'
- },
- templateUrl: modulePath + 'UIComponents/ImageAssets/ImageAsset.html',
- link: function (scope, el, attributes) {
-
- scope.$watch('image', function (newValue, oldValue) {
- if (newValue && newValue.imageAssetId) {
- scope.src = urlLibrary.getImageUrl(newValue, {
- width: scope.width,
- height: scope.height,
- mode: scope.cropMode
- });
- } else {
- scope.src = contentPath + 'img/AssetReplacement/image-replacement.png';
- }
- });
-
+ baseFormFieldFactory) {
- },
- replace: true
+ var config = {
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldRadioList.html',
+ scope: _.extend(baseFormFieldFactory.defaultConfig.scope, {
+ options: '=cmsOptions',
+ optionValue: '@cmsOptionValue',
+ optionName: '@cmsOptionName',
+ optionsApi: '@cmsOptionsApi',
+ defaultItemText: '@cmsDefaultItemText',
+ required: '=cmsRequired',
+ disabled: '=cmsDisabled'
+ }),
+ passThroughAttributes: [
+ 'placeholder',
+ 'disabled',
+ 'cmsMatch'
+ ],
+ link: link
};
-}]);
-angular.module('cms.shared').controller('ImageAssetPickerDialogController', [
- '$scope',
- 'shared.LoadState',
- 'shared.imageService',
- 'shared.SearchQuery',
- 'shared.modalDialogService',
- 'shared.internalModulePath',
- 'shared.permissionValidationService',
- 'options',
- 'close',
-function (
- $scope,
- LoadState,
- imageService,
- SearchQuery,
- modalDialogService,
- modulePath,
- permissionValidationService,
- options,
- close) {
-
- var vm = $scope;
- init();
-
- /* INIT */
-
- function init() {
- angular.extend($scope, options);
- vm.onOk = onOk;
- vm.onCancel = onCancel;
- vm.onSelect = onSelect;
- vm.onUpload = onUpload;
- vm.selectedAsset = vm.currentAsset; // currentAsset is null in single mode
- vm.onSelectAndClose = onSelectAndClose;
- vm.close = onCancel;
+ return baseFormFieldFactory.create(config);
- vm.gridLoadState = new LoadState();
- vm.query = new SearchQuery({
- onChanged: onQueryChanged,
- useHistory: false,
- defaultParams: options.filter
- });
- vm.presetFilter = options.filter;
+ /* PRIVATE */
- vm.filter = vm.query.getFilters();
- vm.toggleFilter = toggleFilter;
+ function link(scope, element, attrs, controllers) {
+ var vm = scope.vm;
+ init();
- vm.isSelected = isSelected;
- vm.multiMode = vm.selectedIds ? true : false;
- vm.okText = vm.multiMode ? 'Ok' : 'Select';
+ // call base
+ baseFormFieldFactory.defaultConfig.link.apply(this, arguments);
- vm.canCreate = permissionValidationService.canCreate('COFIMG');
+ /* Init */
- toggleFilter(false);
- loadGrid();
- }
+ function init() {
+ vm.isRequiredAttributeDefined = angular.isDefined(attrs.required);
- /* ACTIONS */
+ if (vm.optionsApi) {
+ // options bound by api call
+ getOptions(vm.optionsApi);
- function toggleFilter(show) {
- vm.isFilterVisible = _.isUndefined(show) ? !vm.isFilterVisible : show;
- }
+ } else {
- function onQueryChanged() {
- toggleFilter(false);
- loadGrid();
- }
+ // options provided as a model
+ var optionsWatch = scope.$watch('vm.options', function () {
- function loadGrid() {
- vm.gridLoadState.on();
+ // need to copy b/c of assignment issue with bound attributes
+ vm.listOptions = vm.options;
- return imageService.getAll(vm.query.getParameters()).then(function (result) {
- vm.result = result;
- vm.gridLoadState.off();
- });
- }
-
- /* EVENTS */
+ bindDisplayValue();
+ });
+ }
- function onCancel() {
- if (!vm.multiMode) {
- // in single-mode reset the currentAsset
- vm.onSelected(vm.currentAsset);
+ scope.$watch('vm.model', bindDisplayValue);
}
- close();
- }
- function onSelect(image) {
- if (!vm.multiMode) {
- vm.selectedAsset = image;
- return;
- }
+ /* Helpers */
- addOrRemove(image);
- }
+ function getOptions(apiPath) {
+ return $http.get(apiPath).then(loadOptions);
- function onSelectAndClose(image) {
- if (!vm.multiMode) {
- vm.selectedAsset = image;
- onOk();
- return;
+ function loadOptions(options) {
+ vm.listOptions = options;
+ bindDisplayValue();
+ }
}
- addOrRemove(image);
- onOk();
- }
+ function bindDisplayValue() {
- function onOk() {
- if (!vm.multiMode) {
- vm.onSelected(vm.selectedAsset);
- } else {
- vm.onSelected(vm.selectedIds);
- }
+ var selectedOption = _.find(vm.listOptions, function (option) {
+ return option[vm.optionValue] == vm.model;
+ });
- close();
- }
+ vm.displayValue = selectedOption ? selectedOption[vm.optionName] : vm.defaultItemText;
- function onUpload() {
- modalDialogService.show({
- templateUrl: modulePath + 'UIComponents/ImageAssets/UploadImageAssetDialog.html',
- controller: 'UploadImageAssetDialogController',
- options: {
- filter: options.filter,
- onUploadComplete: onUploadComplete
+ // if the options and model are bound, and the option does not appear in the
+ // list, remove the value from the model.
+ if (!selectedOption && vm.model != undefined && vm.listOptions) {
+ vm.model = undefined;
}
- });
-
- function onUploadComplete(imageAssetId) {
-
- imageService
- .getById(imageAssetId)
- .then(onSelectAndClose);
}
}
- /* PUBLIC HELPERS */
+}]);
+angular.module('cms.shared').directive('cmsFormFieldReadonly', [
+ 'shared.internalModulePath',
+function (
+ modulePath
+ ) {
- function isSelected(image) {
- if (vm.selectedIds && image && vm.selectedIds.indexOf(image.imageAssetId) > -1) return true;
+ return {
+ restrict: 'E',
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldReadonly.html',
+ replace: true,
+ require: '^^cmsForm',
+ scope: {
+ title: '@cmsTitle',
+ description: '@cmsDescription',
+ model: '=cmsModel'
+ },
+ controller: function () { },
+ controllerAs: 'vm',
+ bindToController: true
+ };
+}]);
+angular.module('cms.shared').directive('cmsFormFieldSubHeading', [
+ 'shared.internalModulePath',
+function (
+ modulePath
+ ) {
- if (!image || !vm.selectedAsset) return false;
+ return {
+ restrict: 'E',
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldSubHeading.html',
+ require: ['^^cmsForm'],
+ replace: true,
+ transclude: true
+ };
+}]);
+angular.module('cms.shared').directive('cmsFormFieldText', [
+ '_',
+ 'shared.internalModulePath',
+ 'baseFormFieldFactory',
+function (
+ _,
+ modulePath,
+ baseFormFieldFactory) {
- return image.imageAssetId === vm.selectedAsset.imageAssetId;
- }
+ var config = {
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldText.html',
+ passThroughAttributes: [
+ 'required',
+ 'minlength',
+ 'maxlength',
+ 'placeholder',
+ 'pattern',
+ 'disabled',
+ 'readonly',
+ 'cmsMatch'
+ ]
+ };
- function addOrRemove(image) {
- if (!isSelected(image)) {
- vm.selectedIds.push(image.imageAssetId);
- } else {
- var index = vm.selectedIds.indexOf(image.imageAssetId);
- vm.selectedIds.splice(index, 1);
- }
- }
+ return baseFormFieldFactory.create(config);
}]);
-
-angular.module('cms.shared').controller('UploadImageAssetDialogController', [
- '$scope',
- 'shared.LoadState',
- 'shared.imageService',
- 'shared.SearchQuery',
- 'shared.focusService',
+angular.module('cms.shared').directive('cmsFormFieldTextArea', [
+ 'shared.internalModulePath',
'shared.stringUtilities',
- 'options',
- 'close',
+ 'baseFormFieldFactory',
function (
- $scope,
- LoadState,
- imageService,
- SearchQuery,
- focusService,
+ modulePath,
stringUtilities,
- options,
- close) {
-
- var vm = $scope;
- init();
-
- /* INIT */
- function init() {
- angular.extend($scope, options);
-
- initData();
+ baseFormFieldFactory) {
- vm.onUpload = onUpload;
- vm.onCancel = onCancel;
- vm.close = onCancel;
- vm.filter = options.filter;
- $scope.$watch("command.file", setFileName);
+ var config = {
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldTextArea.html',
+ passThroughAttributes: [
+ 'required',
+ 'maxlength',
+ 'placeholder',
+ 'ngMinlength',
+ 'ngMaxlength',
+ 'ngPattern',
+ 'disabled',
+ 'rows',
+ 'cols',
+ 'wrap'
+ ],
+ getInputEl: getInputEl
+ };
- setFilter(options.filter);
+ return baseFormFieldFactory.create(config);
- vm.saveLoadState = new LoadState();
+ function getInputEl(rootEl) {
+ return rootEl.find('textarea');
}
- /* EVENTS */
- function onUpload() {
- vm.saveLoadState.on();
-
- imageService
- .add(vm.command)
- .progress(vm.saveLoadState.setProgress)
- .then(uploadComplete);
- }
+}]);
+angular.module('cms.shared').directive('cmsFormFieldTime', [
+ 'shared.internalModulePath',
+ 'shared.timeUtilities',
+ 'baseFormFieldFactory',
+function (
+ modulePath,
+ timeUtilities,
+ baseFormFieldFactory
+ ) {
- function setFileName() {
- var command = vm.command;
+ var config = {
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldTime.html',
+ passThroughAttributes: [
+ 'required',
+ 'min',
+ 'step',
+ 'max',
+ 'disabled',
+ 'readonly',
+ 'cmsMatch'
+ ],
+ link: link
+ };
- if (command.file && command.file.name) {
- command.title = stringUtilities.capitaliseFirstLetter(stringUtilities.getFileNameWithoutExtension(command.file.name));
- focusService.focusById('title');
- }
- }
+ return baseFormFieldFactory.create(config);
- function onCancel() {
- close();
- }
+ function link(scope, element, attrs, controllers) {
+ var vm = scope.vm;
- /* PUBLIC HELPERS */
- function initData() {
- vm.command = {};
+ // call base
+ baseFormFieldFactory.defaultConfig.link.apply(this, arguments);
- if (vm.filter.tags) {
- vm.command.tags = vm.filter.tags.split(',');
- }
- }
+ init();
- function setFilter(filter) {
- var parts = [];
+ /* Init */
- if (filter) {
- addSize(filter.width, filter.height);
- addSize(filter.minWidth, filter.minHeight, 'min-');
- }
+ function init() {
- vm.filterText = parts.join(', ');
- vm.isFilterSet = parts.length > 0;
+ // Use watch instead of formatters/parsers here due to issues
+ // with utc dates
+ scope.$watch("vm.model", setEditorModel);
+ scope.$watch("vm.editorModel", setCmsModel);
- function addSize(width, height, prefix) {
- if (width && height) {
- parts.push(prefix + 'size ' + width + 'x' + height);
- }
- else {
- addIfSet(prefix + 'width', width);
- addIfSet(prefix + 'height', height);
+ function setEditorModel(value) {
+ if (value !== vm.editorModel) {
+ vm.editorModel = timeUtilities.toDate(value);
+ }
}
- }
- function addIfSet(name, value) {
- if (value) {
- parts.push(name + ' ' + value);
+ function setCmsModel(value) {
+ if (value !== vm.model) {
+ vm.model = timeUtilities.format(value);
+ }
}
}
}
+}]);
+angular.module('cms.shared').directive('cmsFormFieldUrl', [
+ '_',
+ 'shared.internalModulePath',
+ 'baseFormFieldFactory',
+function (
+ _,
+ modulePath,
+ baseFormFieldFactory) {
- function cancel() {
- close();
- }
+ var config = {
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldUrl.html',
+ passThroughAttributes: [
+ 'required',
+ 'minlength',
+ 'maxlength',
+ 'placeholder',
+ 'pattern',
+ 'disabled',
+ 'cmsMatch'
+ ]
+ };
- function uploadComplete(imageAssetId) {
- options.onUploadComplete(imageAssetId);
- close();
- }
+ return baseFormFieldFactory.create(config);
+
+}]);
+angular.module('cms.shared').directive('cmsFormFieldValidationSummary', [
+ 'shared.internalModulePath',
+function (
+ modulePath
+ ) {
+ return {
+ restrict: 'E',
+ templateUrl: modulePath + 'UIComponents/FormFields/FormFieldValidationSummary.html',
+ replace: true
+ };
}]);
+angular.module('cms.shared').directive('cmsHttpPrefix', function () {
+ return {
+ restrict: 'A',
+ require: 'ngModel',
+ link: function (scope, element, attrs, controller) {
+ function ensureHttpPrefix(value) {
+ var httpPrefix = 'http://';
+
+ if (value
+ && !/^(https?):\/\//i.test(value)
+ && httpPrefix.indexOf(value) === -1
+ && 'https://'.indexOf(value) === -1) {
+
+ controller.$setViewValue(httpPrefix + value);
+ controller.$render();
+ return httpPrefix + value;
+ }
+ else {
+ return value;
+ }
+ }
+ controller.$formatters.push(ensureHttpPrefix);
+ controller.$parsers.splice(0, 0, ensureHttpPrefix);
+ }
+ };
+});
angular.module('cms.shared').directive('cmsField', [
'$timeout',
'shared.internalModulePath',
@@ -13834,259 +13835,31 @@ function (
localeService.getAll().then(function (locales) {
- vm.locales = _.map(locales, function (locale) {
- return {
- name: locale.name + ' (' + locale.ietfLanguageTag + ')',
- id: locale.localeId
- }
- });
-
- if (vm.onLoaded) vm.onLoaded();
- });
- }
-}]);
-angular.module('cms.shared').directive('cmsMenu', [
- 'shared.internalModulePath',
-function (
- modulePath) {
-
- return {
- restrict: 'E',
- replace: true,
- transclude: true,
- templateUrl: modulePath + 'UIComponents/Menus/Menu.html',
- scope: {
- icon: '@cmsIcon'
- }
- };
-}]);
-angular.module('cms.shared').controller('AlertController', [
- '$scope',
- 'options',
- 'close', function (
- $scope,
- options,
- close) {
-
- angular.extend($scope, options);
- $scope.close = close;
-}]);
-angular.module('cms.shared').controller('ConfirmDialogController', ['$scope', 'options', 'close', function ($scope, options, close) {
- angular.extend($scope, options);
- $scope.close = resolve;
-
- /* helpers */
-
- function resolve(result) {
- var resolver = result ? options.ok : options.cancel;
-
- if (resolver) {
- resolver()
- .then(closeIfRequired)
- .finally(options.onCancel);
- }
- }
-
- function closeIfRequired() {
- if (options.autoClose) {
- close();
- }
- }
-
-}]);
-angular.module('cms.shared').controller('DeveloperExceptionController', [
- '$scope',
- '$sce',
- 'shared.internalContentPath',
- 'options',
- 'close',
-function (
- $scope,
- $sce,
- internalContentPath,
- options,
- close) {
-
- var html = options.response.data;
-
- var iframe = document.createElement('iframe');
- iframe.setAttribute('srcdoc', html);
- iframe.setAttribute('src', internalContentPath + 'developer-exception-not-supported.html');
- iframe.setAttribute('sandbox', 'allow-scripts');
- $scope.messageHtml = $sce.trustAsHtml(iframe.outerHTML);
-
- angular.extend($scope, options);
- $scope.close = close;
-
-}]);
-angular.module('cms.shared').directive('cmsModalDialogActions', ['shared.internalModulePath', function (modulePath) {
- return {
- restrict: 'E',
- templateUrl: modulePath + 'UIComponents/Modals/ModalDialogActions.html',
- transclude: true
- };
-}]);
-angular.module('cms.shared').directive('cmsModalDialogBody', ['shared.internalModulePath', function (modulePath) {
- return {
- restrict: 'E',
- templateUrl: modulePath + 'UIComponents/Modals/ModalDialogBody.html',
- transclude: true,
- };
-}]);
-angular.module('cms.shared').directive('cmsModalDialogContainer', [
- 'shared.internalModulePath',
- '$timeout',
-function (
- modulePath,
- $timeout) {
-
- return {
- restrict: 'E',
- templateUrl: modulePath + 'UIComponents/Modals/ModalDialogContainer.html',
- transclude: true,
- link: link,
- controller: angular.noop
- };
-
- function link(scope, el, attributes) {
- var cls = attributes.cmsModalSize === 'large' ? 'modal-lg' : '';
- cls += (scope.isRootModal ? ' is-root-modal' : ' is-child-modal');
- if (attributes.cmsModalSize === 'large') {
- scope.sizeCls = cls;
- }
- $timeout(function () {
- scope.sizeCls = cls + ' modal--show';
- }, 1);
- }
-}]);
-angular.module('cms.shared').directive('cmsModalDialogHeader', ['shared.internalModulePath', function (modulePath) {
- return {
- restrict: 'E',
- templateUrl: modulePath + 'UIComponents/Modals/ModalDialogHeader.html',
- transclude: true,
- };
-}]);
-angular.module('cms.shared').factory('shared.modalDialogService', [
- '$q',
- '_',
- 'ModalService',
- 'shared.internalModulePath',
- 'shared.LoadState',
-function (
- $q,
- _,
- ModalService,
- modulePath,
- LoadState) {
-
- var service = {};
-
- /* PUBLIC */
-
- /**
- * Displays a modal message with a button to dismiss the message.
- */
- service.alert = function (optionsOrMessage) {
- var deferred = $q.defer(),
- options = optionsOrMessage || {};
-
- if (_.isString(optionsOrMessage)) {
- options = {
- message: optionsOrMessage
- }
- }
-
- ModalService.showModal({
- templateUrl: modulePath + "UIComponents/Modals/Alert.html",
- controller: "AlertController",
- inputs: {
- options: options
- }
- }).then(function (modal) {
-
- // Apres-creation stuff
- modal.close.then(deferred.resolve);
- });
-
- return deferred.promise;
- }
-
- /**
- * Displays a custom modal popup using a template at the specified url.
- */
- service.show = function (modalOptions) {
- return ModalService.showModal({
- templateUrl: modalOptions.templateUrl,
- controller: modalOptions.controller,
- inputs: {
- options: modalOptions.options
- }
- });
- }
-
- /**
- * Displays a modal message with a button options to ok/cancel an action.
- */
- service.confirm = function (optionsOrMessage) {
- var returnDeferred = $q.defer(),
- onOkLoadState = new LoadState(),
- options = initOptions(optionsOrMessage);
-
- ModalService.showModal({
- templateUrl: modulePath + "UIComponents/Modals/ConfirmDialog.html",
- controller: "ConfirmDialogController",
- inputs: {
- options: options
- }
- });
-
- return returnDeferred.promise;
-
- /* helpers */
-
- function initOptions(optionsOrMessage) {
- var options = optionsOrMessage || {},
- defaults = {
- okButtonTitle: 'OK',
- cancelButtonTitle: 'Cancel',
- autoClose: true,
- // onCancel: fn or promise
- // onOk: fn or promise
- },
- internalScope = {
- ok: resolve.bind(null, true),
- cancel: resolve.bind(null, false),
- onOkLoadState: onOkLoadState
- };
-
- if (_.isString(optionsOrMessage)) {
- options = {
- message: optionsOrMessage
- }
- }
-
- return _.defaults(internalScope, options, defaults);
- }
-
- function resolve(isSuccess) {
- var optionToExec = isSuccess ? options.onOk : options.onOk.onCancel,
- deferredAction = isSuccess ? returnDeferred.resolve : returnDeferred.reject,
- optionResult;
-
- // run the action
- if (_.isFunction(optionToExec)) {
- onOkLoadState.on();
- optionResult = optionToExec();
- }
+ vm.locales = _.map(locales, function (locale) {
+ return {
+ name: locale.name + ' (' + locale.ietfLanguageTag + ')',
+ id: locale.localeId
+ }
+ });
- // Wait for the result to resolve if its a promise
- // Then resolve/reject promise we returned to the callee
- return $q.when(optionResult)
- .then(deferredAction);
- }
+ if (vm.onLoaded) vm.onLoaded();
+ });
}
+}]);
+angular.module('cms.shared').directive('cmsMenu', [
+ 'shared.internalModulePath',
+function (
+ modulePath) {
- return service;
+ return {
+ restrict: 'E',
+ replace: true,
+ transclude: true,
+ templateUrl: modulePath + 'UIComponents/Menus/Menu.html',
+ scope: {
+ icon: '@cmsIcon'
+ }
+ };
}]);
/**
* Helper used for working with collections of dynamic model data that
@@ -14358,6 +14131,234 @@ function (
}
}]);
+angular.module('cms.shared').controller('AlertController', [
+ '$scope',
+ 'options',
+ 'close', function (
+ $scope,
+ options,
+ close) {
+
+ angular.extend($scope, options);
+ $scope.close = close;
+}]);
+angular.module('cms.shared').controller('ConfirmDialogController', ['$scope', 'options', 'close', function ($scope, options, close) {
+ angular.extend($scope, options);
+ $scope.close = resolve;
+
+ /* helpers */
+
+ function resolve(result) {
+ var resolver = result ? options.ok : options.cancel;
+
+ if (resolver) {
+ resolver()
+ .then(closeIfRequired)
+ .finally(options.onCancel);
+ }
+ }
+
+ function closeIfRequired() {
+ if (options.autoClose) {
+ close();
+ }
+ }
+
+}]);
+angular.module('cms.shared').controller('DeveloperExceptionController', [
+ '$scope',
+ '$sce',
+ 'shared.internalContentPath',
+ 'options',
+ 'close',
+function (
+ $scope,
+ $sce,
+ internalContentPath,
+ options,
+ close) {
+
+ var html = options.response.data;
+
+ var iframe = document.createElement('iframe');
+ iframe.setAttribute('srcdoc', html);
+ iframe.setAttribute('src', internalContentPath + 'developer-exception-not-supported.html');
+ iframe.setAttribute('sandbox', 'allow-scripts');
+ $scope.messageHtml = $sce.trustAsHtml(iframe.outerHTML);
+
+ angular.extend($scope, options);
+ $scope.close = close;
+
+}]);
+angular.module('cms.shared').directive('cmsModalDialogActions', ['shared.internalModulePath', function (modulePath) {
+ return {
+ restrict: 'E',
+ templateUrl: modulePath + 'UIComponents/Modals/ModalDialogActions.html',
+ transclude: true
+ };
+}]);
+angular.module('cms.shared').directive('cmsModalDialogBody', ['shared.internalModulePath', function (modulePath) {
+ return {
+ restrict: 'E',
+ templateUrl: modulePath + 'UIComponents/Modals/ModalDialogBody.html',
+ transclude: true,
+ };
+}]);
+angular.module('cms.shared').directive('cmsModalDialogContainer', [
+ 'shared.internalModulePath',
+ '$timeout',
+function (
+ modulePath,
+ $timeout) {
+
+ return {
+ restrict: 'E',
+ templateUrl: modulePath + 'UIComponents/Modals/ModalDialogContainer.html',
+ transclude: true,
+ link: link,
+ controller: angular.noop
+ };
+
+ function link(scope, el, attributes) {
+ var cls = attributes.cmsModalSize === 'large' ? 'modal-lg' : '';
+ cls += (scope.isRootModal ? ' is-root-modal' : ' is-child-modal');
+ if (attributes.cmsModalSize === 'large') {
+ scope.sizeCls = cls;
+ }
+ $timeout(function () {
+ scope.sizeCls = cls + ' modal--show';
+ }, 1);
+ }
+}]);
+angular.module('cms.shared').directive('cmsModalDialogHeader', ['shared.internalModulePath', function (modulePath) {
+ return {
+ restrict: 'E',
+ templateUrl: modulePath + 'UIComponents/Modals/ModalDialogHeader.html',
+ transclude: true,
+ };
+}]);
+angular.module('cms.shared').factory('shared.modalDialogService', [
+ '$q',
+ '_',
+ 'ModalService',
+ 'shared.internalModulePath',
+ 'shared.LoadState',
+function (
+ $q,
+ _,
+ ModalService,
+ modulePath,
+ LoadState) {
+
+ var service = {};
+
+ /* PUBLIC */
+
+ /**
+ * Displays a modal message with a button to dismiss the message.
+ */
+ service.alert = function (optionsOrMessage) {
+ var deferred = $q.defer(),
+ options = optionsOrMessage || {};
+
+ if (_.isString(optionsOrMessage)) {
+ options = {
+ message: optionsOrMessage
+ }
+ }
+
+ ModalService.showModal({
+ templateUrl: modulePath + "UIComponents/Modals/Alert.html",
+ controller: "AlertController",
+ inputs: {
+ options: options
+ }
+ }).then(function (modal) {
+
+ // Apres-creation stuff
+ modal.close.then(deferred.resolve);
+ });
+
+ return deferred.promise;
+ }
+
+ /**
+ * Displays a custom modal popup using a template at the specified url.
+ */
+ service.show = function (modalOptions) {
+ return ModalService.showModal({
+ templateUrl: modalOptions.templateUrl,
+ controller: modalOptions.controller,
+ inputs: {
+ options: modalOptions.options
+ }
+ });
+ }
+
+ /**
+ * Displays a modal message with a button options to ok/cancel an action.
+ */
+ service.confirm = function (optionsOrMessage) {
+ var returnDeferred = $q.defer(),
+ onOkLoadState = new LoadState(),
+ options = initOptions(optionsOrMessage);
+
+ ModalService.showModal({
+ templateUrl: modulePath + "UIComponents/Modals/ConfirmDialog.html",
+ controller: "ConfirmDialogController",
+ inputs: {
+ options: options
+ }
+ });
+
+ return returnDeferred.promise;
+
+ /* helpers */
+
+ function initOptions(optionsOrMessage) {
+ var options = optionsOrMessage || {},
+ defaults = {
+ okButtonTitle: 'OK',
+ cancelButtonTitle: 'Cancel',
+ autoClose: true,
+ // onCancel: fn or promise
+ // onOk: fn or promise
+ },
+ internalScope = {
+ ok: resolve.bind(null, true),
+ cancel: resolve.bind(null, false),
+ onOkLoadState: onOkLoadState
+ };
+
+ if (_.isString(optionsOrMessage)) {
+ options = {
+ message: optionsOrMessage
+ }
+ }
+
+ return _.defaults(internalScope, options, defaults);
+ }
+
+ function resolve(isSuccess) {
+ var optionToExec = isSuccess ? options.onOk : options.onOk.onCancel,
+ deferredAction = isSuccess ? returnDeferred.resolve : returnDeferred.reject,
+ optionResult;
+
+ // run the action
+ if (_.isFunction(optionToExec)) {
+ onOkLoadState.on();
+ optionResult = optionToExec();
+ }
+
+ // Wait for the result to resolve if its a promise
+ // Then resolve/reject promise we returned to the callee
+ return $q.when(optionResult)
+ .then(deferredAction);
+ }
+ }
+
+ return service;
+}]);
angular.module('cms.shared').controller('EditNestedDataModelDialogController', [
'$scope',
'shared.nestedDataModelSchemaService',
diff --git a/src/Cofoundry.Web.Admin/Admin/Modules/Shared/Content/js/shared_min.js b/src/Cofoundry.Web.Admin/Admin/Modules/Shared/Content/js/shared_min.js
index 51a434161..129f43c30 100644
--- a/src/Cofoundry.Web.Admin/Admin/Modules/Shared/Content/js/shared_min.js
+++ b/src/Cofoundry.Web.Admin/Admin/Modules/Shared/Content/js/shared_min.js
@@ -472,7 +472,7 @@ angular.module("cms.shared").factory("shared.errorService",function(){var e={},n
angular.module("cms.shared").factory("shared.focusService",["$document",function(e){var c={},u=e[0];return c.focusById=function(e){e=u.getElementById(e);e&&e.focus()},c}]);
angular.module("cms.shared").factory("httpInterceptor",["$q","$rootScope","_","shared.validationErrorService","authenticationService","shared.errorService","shared.stringUtilities",function(t,e,r,a,s,i,n){var o={response:function(e){return e=!r.isUndefined(e.data.data)?e.data.data:e},responseError:function(e){var r;return n.startsWith(e.config.url,"/")&&function(){switch(e.status){case 400:!1===e.data.isValid&&a.raise(e.data.errors);break;case 401:s.redirectToLogin();break;case 403:i.raise({title:"Permission Denied",message:"This action is not authorized"});break;default:404==e.status&&"GET"===e.config.method||(r={title:e.statusText,message:"An unexpected server error has occurred.",response:e},i.raise(r))}}(),t.reject(e)}};return o}]);
-angular.module("cms.shared").config(["$httpProvider","csrfToken","csrfHeaderName",function(e,t,r){var o=e.defaults.headers;e.interceptors.push("httpInterceptor"),["put","post","patch","delete"].forEach(function(e){o[e]=o[e]||{},o[e][r]=t}),o.common["X-Requested-With"]="XMLHttpRequest"}]);
+angular.module("cms.shared").config(["$httpProvider","csrfToken","csrfHeaderName",function(t,e,o){var c=t.defaults.headers;t.interceptors.push("httpInterceptor"),["put","post","patch","delete"].forEach(function(t){c[t]=c[t]||{},c[t][o]=e}),c.common["X-Requested-With"]="XMLHttpRequest",c.common.Accept="application/json, text/html, text/plain, */*"}]);
angular.module("cms.shared").config(["$locationProvider",function(o){o.hashPrefix("")}]);
angular.module("cms.shared").factory("shared.permissionValidationService",["_","shared.currentUser",function(e,r){var s={};return s.hasPermission=function(n){return e.contains(r.permissionCodes,n)},s.canRead=function(n){return s.hasPermission(n+"COMRED")},s.canViewModule=function(n){return s.hasPermission(n+"COMMOD")},s.canCreate=function(n){return s.hasPermission(n+"COMCRT")},s.canUpdate=function(n){return s.hasPermission(n+"COMUPD")},s.canDelete=function(n){return s.hasPermission(n+"COMDEL")},s}]);
angular.module("cms.shared").factory("shared.validationErrorService",["_",function(t){var n={},i=[];return n.raise=function(n){var r,o=[];n.forEach(function(e){var n=t.filter(i,function(r){return t.find(e.properties,function(n){return!(!n||!r.prop)&&r.prop.toLowerCase()===n.toLowerCase()})});n.length?n.forEach(function(n){n.fn([e])}):o.push(e)}),o.length&&((n=t.filter(i,function(n){return!n.prop})).length?(r=o,n.forEach(function(n){n.fn(r)})):function(){throw new Error("An unhandled validation exception has occurred")}())},n.addHandler=function(n,r){r={prop:n,fn:r};return i.push(r),r},n.removeHandler=function(n){n=t.isFunction(n)?t.where(i,{fn:n}):t.where(i,{prop:n});i=t.difference(i,n)},n}]);
@@ -486,7 +486,7 @@ angular.module("cms.shared").controller("CustomEntityPickerDialogController",["$
angular.module("cms.shared").directive("cmsFormFieldCustomEntityCollection",["_","shared.internalModulePath","shared.LoadState","shared.customEntityService","shared.modalDialogService","shared.arrayUtilities","shared.ModelPreviewFieldset","shared.ImagePreviewFieldCollection","baseFormFieldFactory",function(h,v,y,C,I,E,p,F,e){var w="customEntityId",S=e.defaultConfig,t={templateUrl:v+"UIComponents/CustomEntities/FormFieldCustomEntityCollection.html",scope:h.extend(S.scope,{customEntityDefinitionCode:"@cmsCustomEntityDefinitionCode",localeId:"=cmsLocaleId",orderable:"=cmsOrderable"}),require:h.union(S.require,["?^^cmsFormDynamicFieldSet"]),passThroughAttributes:["required"],link:function(e,t,i,o){var n,r,a,d=e.vm,l=(h.has(i,"required"),h.last(o));return function(){d.gridLoadState=new y,d.showPicker=s,d.remove=c,d.onDrop=m,d.onDropSuccess=u,n=C.getDefinition(d.customEntityDefinitionCode).then(function(e){d.customEntityDefinition=e}),r=C.getDataModelSchema(d.customEntityDefinitionCode).then(f),e.$watch("vm.model",D)}(),S.link(e,t,i,o);function c(e,t){E.removeObject(d.gridData,e),E.removeObject(d.model,e[w]),d.gridImages.remove(t)}function s(){I.show({templateUrl:v+"UIComponents/CustomEntities/CustomEntityPickerDialog.html",controller:"CustomEntityPickerDialogController",options:{selectedIds:d.model||[],customEntityDefinition:d.customEntityDefinition,filter:function(){var e,t={};d.localeId?e=d.localeId:l&&l.additionalParameters&&(e=l.additionalParameters.localeId);e&&(t.localeId=e);return t}(),onSelected:function(e){D(d.model=e)}}})}function m(e,t){a=e}function u(e){E.move(d.gridData,e,a),d.gridImages.move(e,a),g()}function g(){d.model=h.pluck(d.gridData,w)}function f(e){d.previewFields=new p(e)}function D(e){e&&e.length?d.gridData&&h.pluck(d.gridData,w).join()==e.join()||(d.gridLoadState.on(),e=C.getByIdRange(e).then(function(e){d.gridData=e,d.orderable||(d.gridData=h.sortBy(d.gridData,"title"),g()),r.then(function(){return d.gridImages=new F,d.gridImages.load(d.gridData,d.previewFields)})}),d.gridLoadState.offWhen(n,e)):d.gridData=[]}}};return e.create(t)}]);
angular.module("cms.shared").directive("cmsFormFieldCustomEntityMultiTypeCollection",["_","shared.internalModulePath","shared.LoadState","shared.customEntityService","shared.modalDialogService","shared.arrayUtilities","shared.ModelPreviewFieldset","shared.ImagePreviewFieldCollection","baseFormFieldFactory",function(p,h,y,D,v,E,I,w,e){var F="customEntityId",H="customEntityDefinitionCode",P=e.defaultConfig,t={templateUrl:h+"UIComponents/CustomEntities/FormFieldCustomEntityMultiTypeCollection.html",scope:p.extend(P.scope,{customEntityDefinitionCodes:"@cmsCustomEntityDefinitionCodes",localeId:"=cmsLocaleId",orderable:"=cmsOrderable",titleColumnHeader:"@cmsTitleColumnHeader",descriptionColumnHeader:"@cmsDescriptionColumnHeader",imageColumnHeader:"@cmsImageColumnHeader",typeColumnHeader:"@cmsTypeColumnHeader"}),require:p.union(P.require,["?^^cmsFormDynamicFieldSet"]),passThroughAttributes:["required"],link:function(t,e,i,o){var n,r,d,a=t.vm,l=(p.has(i,"required"),p.last(o));return function(){var e=a.customEntityDefinitionCodes?a.customEntityDefinitionCodes.split(","):[];a.gridLoadState=new y,a.showPicker=m,a.remove=s,a.onDrop=c,a.onDropSuccess=u,n=D.getDefinitionsByIdRange(e).then(function(e){a.customEntityDefinitions={},p.each(e,function(e){(a.customEntityDefinitions[e.customEntityDefinitionCode]=e).autoPublish||(a.showPublishColumn=!0)})}),r=D.getDataModelSchemasByCodeRange(e).then(g),t.$watch("vm.model",C),void 0===a.titleColumnHeader&&(a.titleColumnHeader="Title"),void 0===a.descriptionColumnHeader&&(a.descriptionColumnHeader="Description"),void(void 0===a.typeColumnHeader&&(a.typeColumnHeader="Type"))}(),P.link(t,e,i,o);function s(e,t){E.removeObject(a.gridData,e),E.remove(a.model,t),a.gridImages.remove(t)}function m(o){v.show({templateUrl:h+"UIComponents/CustomEntities/CustomEntityPickerDialog.html",controller:"CustomEntityPickerDialogController",options:{selectedIds:p.chain(a.model).where({customEntityDefinitionCode:o[H]}).map(function(e){return e[F]}).value(),customEntityDefinition:o,filter:function(){var e,t={};a.localeId?e=a.localeId:l&&l.additionalParameters&&(e=l.additionalParameters.localeId);e&&(t.localeId=e);return t}(),onSelected:function(e){a.model||(a.model=[]);if(0",model:s.command};t.html=(a=t.html,e=e,angular.element(a).css(e)[0].outerHTML),s.onSelected(t),d()}function h(e){return e?-1==e.indexOf("px")&&-1==e.indexOf("%")&&-1==e.indexOf("auto")?e+"px":e:""}angular.extend(e,n),s.formLoadState=new t,s.saveLoadState=new t,s.onInsert=g,s.onCancel=o,s.onImageChanged=r,s.command={},function(){var e,t;s.imageAssetHtml&&s.imageAssetHtml.length&&(s.command.imageAssetId=s.imageAssetHtml.attr("data-image-asset-id"),s.command.altTag=s.imageAssetHtml.attr("alt"),s.command.style=s.imageAssetHtml.attr("style"),s.command.style?(t=function(e){var t,a=/([\w-]*)\s*:\s*([^;]*)/g,m={};for(;t=a.exec(e);)m[t[1]]=t[2];return m}(s.command.style),s.command.width=t.width,s.command.height=t.height):(s.command.width=s.imageAssetHtml.attr("width"),s.command.height=s.imageAssetHtml.attr("height")),s.command.imageAssetId||(e=s.imageAssetHtml.attr("src"),t=e.lastIndexOf("/"),t=e.substr(t+1,e.indexOf("_")-t-1),s.command.imageAssetId=t))}()}]);
angular.module("cms.shared").directive("cmsDocumentAsset",["shared.internalModulePath","shared.urlLibrary",function(e,r){return{restrict:"E",scope:{document:"=cmsDocument"},templateUrl:e+"UIComponents/DocumentAssets/DocumentAsset.html",link:function(e,t,n){e.getDocumentUrl=r.getDocumentUrl}}}]);
angular.module("cms.shared").controller("DocumentAssetPickerDialogController",["$scope","shared.LoadState","shared.documentService","shared.SearchQuery","shared.modalDialogService","shared.internalModulePath","shared.permissionValidationService","shared.urlLibrary","options","close",function(e,t,s,o,n,l,d,r,c,i){var u=e;function a(e){u.isFilterVisible=_.isUndefined(e)?!u.isFilterVisible:e}function m(){a(!1),f()}function f(){return u.gridLoadState.on(),s.getAll(u.query.getParameters()).then(function(e){u.result=e,u.gridLoadState.off()})}function A(){u.multiMode||u.onSelected(u.currentAsset),i()}function g(e){u.multiMode?C(e):u.selectedAsset=e}function h(e){if(!u.multiMode)return u.selectedAsset=e,void S();C(e),S()}function S(){u.multiMode?u.onSelected(u.selectedIds):u.onSelected(u.selectedAsset),i()}function p(){n.show({templateUrl:l+"UIComponents/DocumentAssets/UploadDocumentAssetDialog.html",controller:"UploadDocumentAssetDialogController",options:{filter:c.filter,onUploadComplete:function(e){h({documentAssetId:e})}}})}function I(e){return!!(u.selectedIds&&e&&-1",model:s.command};t.html=(a=t.html,e=e,angular.element(a).css(e)[0].outerHTML),s.onSelected(t),d()}function h(e){return e?-1==e.indexOf("px")&&-1==e.indexOf("%")&&-1==e.indexOf("auto")?e+"px":e:""}angular.extend(e,n),s.formLoadState=new t,s.saveLoadState=new t,s.onInsert=g,s.onCancel=o,s.onImageChanged=r,s.command={},function(){var e,t;s.imageAssetHtml&&s.imageAssetHtml.length&&(s.command.imageAssetId=s.imageAssetHtml.attr("data-image-asset-id"),s.command.altTag=s.imageAssetHtml.attr("alt"),s.command.style=s.imageAssetHtml.attr("style"),s.command.style?(t=function(e){var t,a=/([\w-]*)\s*:\s*([^;]*)/g,m={};for(;t=a.exec(e);)m[t[1]]=t[2];return m}(s.command.style),s.command.width=t.width,s.command.height=t.height):(s.command.width=s.imageAssetHtml.attr("width"),s.command.height=s.imageAssetHtml.attr("height")),s.command.imageAssetId||(e=s.imageAssetHtml.attr("src"),t=e.lastIndexOf("/"),t=e.substr(t+1,e.indexOf("_")-t-1),s.command.imageAssetId=t))}()}]);
+angular.module("cms.shared").directive("cmsFormFieldDirectorySelector",["_","shared.directiveUtilities","shared.internalModulePath","shared.directoryService",function(e,i,r,t){return{restrict:"E",templateUrl:r+"UIComponents/Directories/FormFieldDirectorySelector.html",scope:{model:"=cmsModel",title:"@cmsTitle",onLoaded:"&cmsOnLoaded",readonly:"=cmsReadonly"},link:{pre:function(e,r,t){e=e.vm;angular.isDefined(t.required)?e.isRequired=!0:(e.isRequired=!1,e.defaultItemText=t.cmsDefaultItemText||"None");e.title=t.cmsTitle||"Directory",e.description=t.cmsDescription,i.setModelName(e,t)}},controller:function(){var r=this;t.getAll().then(function(e){r.pageDirectories=e,r.onLoaded&&r.onLoaded()})},controllerAs:"vm",bindToController:!0}}]);
angular.module("cms.shared").controller("AddEntityAccessRuleController",["$scope","$q","shared.LoadState","shared.roleService","shared.userAreaService","options","close",function(e,o,n,r,a,t,d){var s=e;function u(){s.command.roleId=null}function l(e){if(s.command.userAreaCode)return e.userAreaCode=s.command.userAreaCode,e.excludeAnonymous=!0,r.search(e);e=o.defer();return e.resolve(),e.promise}function c(){t.onSave(s.command),d()}function f(){d()}s.globalLoadState=new n,s.saveLoadState=new n,s.formLoadState=new n,function(e){s.globalLoadState.on(),e&&_.isFunction(e.on)&&e.on()}(s.formLoadState),s.onAdd=c,s.onCancel=f,s.onUserAreaChanged=u,s.searchRoles=l,s.command={},u(),a.getAll().then(function(e){s.userAreas=_.filter(e,function(e){return"COF"!==e.userAreaCode}),1==s.userAreas.length&&(s.command.userAreaCode=s.userAreas[0].userAreaCode)}).finally(function(e){s.globalLoadState.off(),e&&_.isFunction(e.off)&&e.off()}.bind(null,s.formLoadState))}]);
angular.module("cms.shared").controller("EntityAccessEditorController",["$scope","$q","shared.LoadState","shared.userAreaService","shared.roleService","shared.modalDialogService","shared.arrayUtilities","shared.internalModulePath","shared.permissionValidationService","shared.urlLibrary","options","close",function(e,o,n,t,i,r,s,a,c,u,d,l){var A=e;function f(){A.command.accessRules=_.map(A.accessRuleSet.accessRules,function(e){var n=d.entityIdPrefix+"AccessRuleId",r={userAreaCode:e.userArea.userAreaCode,roleId:e.role?e.role.roleId:null};return r[n]=e[n],r}),A.command.redirectToSignIn||(A.command.userAreaCodeForSignInRedirect=null),I(A.saveLoadState),d.saveAccess(A.command).then(function(e,n){return h(n).then(A.mainForm.formStatus.success.bind(null,e))}.bind(null,"Access rules updated successfully")).then(l).finally(C.bind(null,A.saveLoadState))}function R(){r.show({templateUrl:a+"UIComponents/EntityAccess/AddEntityAccessRule.html",controller:"AddEntityAccessRuleController",options:{onSave:S}})}function m(e,n){s.removeObject(A.accessRuleSet.accessRules,e),g()}function S(r){var n={};_.find(A.accessRuleSet.accessRules,function(e){var n=e.role?e.role.roleId:null;return e.userArea.userAreaCode===r.userAreaCode&&n==r.roleId})||(I(),o.all([r.roleId?i.getById(r.roleId).then(function(e){n.role=e}):o(function(e){e()}),t.getByCode(r.userAreaCode).then(function(e){n.userArea=e})]).then(function(){A.accessRuleSet.accessRules.push(n),A.accessRuleSet.accessRules=_(A.accessRuleSet.accessRules).chain().sortBy(function(e){return e.role?e.role.roleId:-1}).sortBy(function(e){return e.userArea.userAreaCode}).value(),g()}).finally(C))}function h(e){return A.entityDefinitionName=d.entityDefinitionName,A.entityDefinitionNameLower=d.entityDefinitionName.toLowerCase(),A.entityDescription=d.entityDescription,A.violationActions=[{id:"Error",name:"Error",description:"Error (403: Forbidden)"},{id:"NotFound",name:"Not Found",description:"Not Found (404: Not Found)"}],d.entityAccessLoader().then(function(e){A.accessRuleSet=e,A.command=function(e){var n=_.pick(e,d.entityIdPrefix+"Id","userAreaCodeForSignInRedirect","violationAction");e.userAreaForSignInRedirect&&(n.userAreaCodeForSignInRedirect=e.userAreaForSignInRedirect.userAreaCode,n.redirectToSignIn=!0);return n}(e),A.inheritedRules=[],_.each(A.accessRuleSet.inheritedAccessRules,function(n){n.violationAction=_.findWhere(A.violationActions,{id:n.violationAction}),n.userAreaForSignInRedirect?(n.signInRedirect="Yes",n.signInRedirectDescription="If the user is not signed in, then they will be redirected to the sign in page associated with the "+n.userAreaForSignInRedirect.name+" user area."):(n.signInRedirect="No",n.signInRedirectDescription="No sign in redirection, the default action will trigger instead."),_.each(n.accessRules,function(e){e.accessRuleSet=n,A.inheritedRules.push(e)})}),g()}).then(C.bind(null,e))}function g(){A.userAreasInRules=_(A.accessRuleSet.accessRules).chain().map(function(e){return e.userArea}).uniq(function(e){return e.userAreaCode}).sortBy("userAreaCode").value(),A.userAreasInRules.length&&_.find(A.userAreasInRules,function(e){return e.userAreaCode===A.command.userAreaCodeForSignInRedirect})||(A.command.redirectToSignIn=!1,A.command.userAreaCodeForSignInRedirect=null),!A.command.userAreaCodeForSignInRedirect&&A.userAreasInRules.length&&(A.command.userAreaCodeForSignInRedirect=A.userAreasInRules[0].userAreaCode,console.log("setting vm.command.userAreaCodeForSignInRedirect",A.command.userAreaCodeForSignInRedirect))}function I(e){A.globalLoadState.on(),e&&_.isFunction(e.on)&&e.on()}function C(e){A.globalLoadState.off(),e&&_.isFunction(e.off)&&e.off()}A.save=f,A.close=l,A.add=R,A.deleteRule=m,A.globalLoadState=new n,A.saveLoadState=new n,A.formLoadState=new n(!0),A.urlLibrary=u,A.canManage=c.hasPermission(d.entityDefinitionCode+"ACCRUL"),A.editMode=A.canManage,h(A.formLoadState)}]);
angular.module("cms.shared").factory("shared.entityVersionModalDialogService",["shared.entityVersionService","shared.modalDialogService",function(s,a){var t={},l={entityNameSingular:"Page"};return t.publish=function(t,e,i){var n=i||l,i={title:"Publish "+n.entityNameSingular,message:"Are you sure you want to publish this "+n.entityNameSingular.toLowerCase()+"?",okButtonTitle:"Yes, publish it",onOk:function(){return e(),s.publish(n.isCustomEntity,t)}};return a.confirm(i)},t.unpublish=function(t,e,i){var n=i||l,i={title:"Unpublish "+n.entityNameSingular,message:"Unpublishing this "+n.entityNameSingular.toLowerCase()+" will remove it from the live site and put it into draft status. Are you sure you want to continue?",okButtonTitle:"Yes, unpublish it",onOk:function(){return e(),s.unpublish(n.isCustomEntity,t)}};return a.confirm(i)},t.copyToDraft=function(t,e,i,n,r){var o=r||l,r={title:"Copy "+o.entityNameSingular+" Version",message:"A draft version of this "+o.entityNameSingular.toLowerCase()+" already exists. Copying this version will delete the current draft. Do you wish to continue?",okButtonTitle:"Yes, replace it",onOk:function(){return n(),s.removeDraft(o.isCustomEntity,t).then(u)}};return i?a.confirm(r):(n(),u());function u(){return s.duplicateDraft(o.isCustomEntity,t,e)}},t}]);
@@ -505,6 +505,14 @@ angular.module("cms.shared").directive("cmsFormSectionActions",["shared.internal
angular.module("cms.shared").directive("cmsFormSectionAuditData",["shared.internalModulePath",function(t){return{restrict:"E",templateUrl:t+"UIComponents/Form/FormSectionAuditData.html",scope:{auditData:"=cmsAuditData"}}}]);
angular.module("cms.shared").directive("cmsFormStatus",["_","shared.validationErrorService","shared.internalModulePath",function(t,s,r){return{restrict:"E",templateUrl:r+"UIComponents/Form/FormStatus.html",require:["^^cmsForm"],replace:!0,scope:!0,link:{post:function(r,e,n,o){(function(r,e){e=e.getFormScope().getForm(),r.success=function(r){i(this,r,"success")}.bind(r),r.error=function(r){i(this,r,"error")}.bind(r),r.errors=function(r,e){r=t.uniq(r,function(r){return r.message});i(this,e,"error",r)}.bind(r),r.clear=function(){i(this)}.bind(r),e.formStatus=r})(r,o[0]),function(r){s.addHandler("",r.errors),r.$on("$destroy",function(){s.removeHandler(r.errors)})}(r)}}};function i(r,e,n,o){r.message=e,r.errors=o,r.cls=n}}]);
angular.module("cms.shared").directive("cmsFormDynamicFieldSet",["$compile","_","shared.stringUtilities","shared.internalModulePath","shared.LoadState",function(i,c,d,e,t){return{restrict:"E",replace:!0,scope:{dataSource:"=cmsDataSource",additionalParameters:"=cmsAdditionalParameters"},link:function(t,a,e,r){t.vm;var n=new l;function o(e){var r="";a.empty(),e&&e.modelMetaData.dataModelProperties.length&&(e.modelMetaData.dataModelProperties.forEach(function(e,a){var t=function(e){var t="cms-form-field-";switch(e.dataTemplateName){case"Single":case"Double":case"Decimal":return t+"number";case"String":return t+"text";case"Boolean":return t+"checkbox";case"MultilineText":return t+"text-area"}return t+d.toSnakeCase(e.dataTemplateName)}(e);r+="<"+t,r+=n.map("model",d.lowerCaseFirstWord(e.name)),r+=n.map("title",e.displayName),r+=n.map("required",e.isRequired&&!e.additionalAttributes.readonly),r+=n.map("description",e.description),e.additionalAttributes&&c.each(e.additionalAttributes,function(e,t){r+=n.map(t,e,a)}),r+=">"+t+">"}),a.append(i(r)(t)))}t.$watch("vm.dataSource",function(e){o(e)})},controller:["$scope",function(e){}],bindToController:!0,controllerAs:"vm"};function l(){var a="cms-",o={maxlength:i,minlength:i,min:i,max:i,pattern:i,step:i,placeholder:i,match:e,model:e,options:function(e,t,a){return c(e,"vm.dataSource.modelMetaData.dataModelProperties["+a+"].additionalAttributes['"+e+"']")},required:function(e,t){if(t)return r(e.toLowerCase());return""},rows:i,cols:i};function e(e,t){return c(e,t="vm.dataSource.model['"+t+"']")}function i(e,t){return r(d.toSnakeCase(e),t)}function c(e,t){return r(e=a+d.toSnakeCase(e),t)}function r(e,t){return t?" "+e+'="'+t+'"':" "+e}this.map=function(e,t,a){var r="ValMsg",n=o[e];return!n&&d.endsWith(e,r)?(r=e.substring(0,e.length-r.length),t&&o[r]===i&&(n=i)):n=n||c,n?n(e,t,a):""}}}]);
+angular.module("cms.shared").directive("cmsMatch",["$parse","$timeout","shared.internalModulePath","shared.directiveUtilities",function(e,r,t,o){var s="cmsMatch";return{link:function(e,r,t,a){if(!t[s]||!a[1])return;var i=a[0],a=a[1],n=i.getFormScope().getForm(),c=o.parseModelName(t[s]);a.$validators[s]=function(e,r){var t=n[c];return!!t&&e===t.$viewValue}},restrict:"A",require:["^^cmsForm","?ngModel"]}}]);
+angular.module("cms.shared").directive("cmsFormFieldImageAnchorLocationSelector",["_","shared.internalModulePath",function(e,t){return{restrict:"E",templateUrl:t+"UIComponents/ImageAssets/FormFieldImageAnchorLocationSelector.html",scope:{model:"=cmsModel",readonly:"=cmsReadonly"},controller:function(){this.options=[{name:"Top Left",id:"TopLeft"},{name:"Top Center",id:"TopCenter"},{name:"Top Right",id:"TopRight"},{name:"Middle Left",id:"MiddleLeft"},{name:"Middle Center",id:"MiddleCenter"},{name:"Middle Right",id:"MiddleRight"},{name:"Bottom Left",id:"BottomLeft"},{name:"Bottom Center",id:"BottomCenter"},{name:"Bottom Right",id:"BottomRight"}]},controllerAs:"vm",bindToController:!0}}]);
+angular.module("cms.shared").directive("cmsFormFieldImageAsset",["_","shared.internalModulePath","shared.internalContentPath","shared.modalDialogService","shared.stringUtilities","shared.imageService","shared.urlLibrary","baseFormFieldFactory",function(c,u,e,h,g,v,A,t){var p=t.defaultConfig,e={templateUrl:u+"UIComponents/ImageAssets/FormFieldImageAsset.html",scope:c.extend(p.scope,{asset:"=cmsAsset",loadState:"=cmsLoadState",updateAsset:"@cmsUpdateAsset"}),passThroughAttributes:["required"],link:function(e,t,s,i){var r,a=e.vm,n=c.has(s,"required");return function(){a.urlLibrary=A,a.showPicker=l,a.remove=o,a.isRemovable=c.isObject(a.model)&&!n,a.filter=function(i){var r={};return e("Tags"),e("Width",!0),e("Height",!0),e("MinWidth",!0),e("MinHeight",!0),r;function e(e,t){var s=i["cms"+e];s&&(e=g.lowerCaseFirstWord(e),r[e]=t?parseInt(s):s)}}(s),a.previewWidth=s.cmsPreviewWidth||450,a.previewHeight=s.cmsPreviewHeight,e.$watch("vm.asset",d),e.$watch("vm.model",m)}(),p.link(e,t,s,i);function o(){d(null)}function l(){h.show({templateUrl:u+"UIComponents/ImageAssets/ImageAssetPickerDialog.html",controller:"ImageAssetPickerDialogController",options:{currentAsset:a.previewAsset,filter:a.filter,onSelected:function(e){!e&&a.previewAsset?(d(null),a.onChange()):(!a.previewAsset||e&&a.previewAsset.imageAssetId!==e.imageAssetId)&&(d(e),a.onChange())}}})}function m(e){e||(a.model=e=void 0),!e||a.previewAsset&&a.previewAsset.imageAssetId==e||v.getById(e).then(function(e){e&&d(e)})}function d(e){e?(a.previewAsset=e,a.isRemovable=!n,a.model=e.imageAssetId,a.updateAsset&&(a.asset=e)):r&&(a.previewAsset=null,a.isRemovable=!1,a.model&&(a.model=null),a.updateAsset&&(a.asset=null)),a.buttonText=a.model?"Change":"Select",r=!0}}};return t.create(e)}]);
+angular.module("cms.shared").directive("cmsFormFieldImageAssetCollection",["_","shared.internalModulePath","shared.LoadState","shared.imageService","shared.modalDialogService","shared.arrayUtilities","shared.stringUtilities","shared.urlLibrary","baseFormFieldFactory",function(l,g,m,c,u,h,f,I,e){var p="imageAssetId",v=e.defaultConfig,t={templateUrl:g+"UIComponents/ImageAssets/FormFieldImageAssetCollection.html",passThroughAttributes:["required","ngRequired"],link:function(e,t,i,r){var o=e.vm;return o.gridLoadState=new m,o.urlLibrary=I,o.showPicker=n,o.remove=a,o.onDrop=s,e.$watch("vm.model",d),v.link(e,t,i,r);function a(e){function t(e,t){t=e.indexOf(t);return 0<=t&&e.splice(t,1)}t(o.gridData,e),t(o.model,e.imageAssetId)}function n(){function e(e,t){var r=i["cms"+e];r&&(e=f.lowerCaseFirstWord(e),a[e]=t?parseInt(r):r)}var a;u.show({templateUrl:g+"UIComponents/ImageAssets/ImageAssetPickerDialog.html",controller:"ImageAssetPickerDialogController",options:{selectedIds:o.model||[],filter:(a={},e("Tags"),e("Width",!0),e("Height",!0),e("MinWidth",!0),e("MinHeight",!0),a),onSelected:function(e){d(o.model=e)}}})}function s(e,t){h.moveObject(o.gridData,t,e,p),o.model=l.pluck(o.gridData,p)}function d(e){e&&e.length?o.gridData&&l.pluck(o.gridData,p).join()==e.join()||(o.gridLoadState.on(),c.getByIdRange(e).then(function(e){o.gridData=e,o.gridLoadState.off()})):o.gridData=[]}}};return e.create(t)}]);
+angular.module("cms.shared").directive("cmsFormFieldImageUpload",["_","$timeout","shared.internalModulePath","shared.internalContentPath","shared.LoadState","shared.stringUtilities","shared.imageFileUtilities","shared.imageService","shared.urlLibrary","shared.validationErrorService","baseFormFieldFactory",function(m,h,e,i,f,u,c,g,v,p,t){var R=t.defaultConfig,i=i+"img/AssetReplacement/",U=i+"preview-not-supported.png",w=i+"image-replacement.png",e={templateUrl:e+"UIComponents/ImageAssets/FormFieldImageUpload.html",scope:m.extend(t.defaultConfig.scope,{asset:"=cmsAsset",loadState:"=cmsLoadState",filter:"=cmsFilter",onChange:"&cmsOnChange"}),link:function(e,i,t,n){var a=e.vm;return a.isRequired=m.has(t,"required"),a.remove=o,a.fileChanged=d,a.isRemovable=m.isObject(a.ngModel)&&!a.isRequired,a.mainLoadState=new f(!0),e.$watch("vm.asset",s),g.getSettings().then(r).then(a.mainLoadState.off),R.link(e,i,t,n);function r(e){a.settings=e}function o(){d()}function s(){var e=a.asset;e?(a.previewUrl=v.getImageUrl(e,{width:450}),a.isRemovable=!a.isRequired,a.model={name:e.fileName+"."+e.extension,size:e.fileSizeInBytes,isCurrentFile:!0}):(a.previewUrl=w,a.isRemovable=!1,a.model&&(a.model=void 0)),l()}function d(e){function i(){a.model=void 0,a.previewUrl=w,a.isRemovable=!1,a.isResized=!1,t()}function t(){l(),a.onChange&&a.onChange()}e&&e[0]?(a.mainLoadState.on(),c.getFileInfoAndResizeIfRequired(e[0],a.settings).then(function(e){e||i();a.model=e.file,function(e){var i=a.filter;function t(e,i){return!(1',replace:!0,transclude:!0}});
@@ -552,6 +552,8 @@ angular.module("cms.shared").factory("shared.LoadState",["$q","$rootScope","_",f
angular.module("cms.shared").directive("cmsProgressBar",["shared.internalModulePath",function(e){return{restrict:"E",scope:{loadState:"="},templateUrl:e+"UIComponents/Loader/ProgressBar.html"}}]);
angular.module("cms.shared").directive("cmsFormFieldLocaleSelector",["_","shared.internalModulePath","shared.localeService","shared.directiveUtilities",function(l,e,n,t){return{restrict:"E",templateUrl:e+"UIComponents/Locales/FormFieldLocaleSelector.html",scope:{model:"=cmsModel",onLoaded:"&cmsOnLoaded",readonly:"=cmsReadonly"},link:{pre:function(e,o,l){e=e.vm;angular.isDefined(l.required)?e.isRequired=!0:(e.isRequired=!1,e.defaultItemText=l.cmsDefaultItemText||"None");t.setModelName(e,l)}},controller:function(){var o=this;n.getAll().then(function(e){o.locales=l.map(e,function(e){return{name:e.name+" ("+e.ietfLanguageTag+")",id:e.localeId}}),o.onLoaded&&o.onLoaded()})},controllerAs:"vm",bindToController:!0}}]);
angular.module("cms.shared").directive("cmsMenu",["shared.internalModulePath",function(e){return{restrict:"E",replace:!0,transclude:!0,templateUrl:e+"UIComponents/Menus/Menu.html",scope:{icon:"@cmsIcon"}}}]);
+angular.module("cms.shared").factory("shared.ImagePreviewFieldCollection",["$q","_","shared.arrayUtilities","shared.imageService",function(s,m,d,c){return function(n){var i,r,a=this,t="previewImage";function u(e){if(n){e=e[n],e=r[e].fields[t];return e?e.lowerName:void 0}return i=!i&&r.fields[t]?r.fields[t].lowerName:i}function o(e,n,i){var r=u(e);if(r){r=f(e,r);if(!i){var t,i=a.images[n];if(r==(t=i?i.imageAssetId:t))return;if(!r)return void(a.images[n]=void 0)}return c.getById(r).then(function(e){a.images[n]=e})}}function f(e,n){if(n)return(e.model||e)[n]}a.load=function(e,n){if(r=n,!e||!e.length||!n)return i();n=m.chain(e).map(function(e){return f(e,u(e))}).filter(function(e){return!!e}).uniq().value();return n.length?c.getByIdRange(n).then(function(i){a.images=[],m.each(e,function(e){var n,e=f(e,u(e));e&&(n=m.find(i,{imageAssetId:e})),a.images.push(n)})}):i();function i(){var e=s.defer();return e.resolve(),a.images=[],e.promise}},a.move=function(e,n){d.move(a.images,e,n)},a.add=function(e,n){return o(e,n,!0)},a.update=function(e,n){return o(e,n)},a.remove=function(e){d.remove(a.images,e)}}}]);
+angular.module("cms.shared").factory("shared.ModelPreviewFieldset",["$q","_","shared.stringUtilities","shared.imageService",function(e,o,d,i){return function(e){var i=this,n="previewTitle",s="previewDescription",a="previewImage";i.modelMetaData=e,i.fields=function(r){var t={};return e(n),e(s),e(a),t;function e(i){var e=o.find(r.dataModelProperties,function(e){return e.additionalAttributes[i]});e&&(e.lowerName=d.lowerCaseFirstWord(e.name),t[i]=e,t.hasFields=!0)}}(e),i.showTitle=function(e){return e[n]||!e.hasFields}(i.fields),i.titleTerm=function(e){return e[n]?e[n].displayName:"Title"}(i.fields),i.on=function(){i.isLoading=!0,100===i.progress&&(i.progress=0)}}}]);
angular.module("cms.shared").controller("AlertController",["$scope","options","close",function(o,e,l){angular.extend(o,e),o.close=l}]);
angular.module("cms.shared").controller("ConfirmDialogController",["$scope","options","close",function(o,n,l){function e(){n.autoClose&&l()}angular.extend(o,n),o.close=function(o){o=o?n.ok:n.cancel;o&&o().then(e).finally(n.onCancel)}}]);
angular.module("cms.shared").controller("DeveloperExceptionController",["$scope","$sce","shared.internalContentPath","options","close",function(e,t,o,r,s){var n=r.response.data,l=document.createElement("iframe");l.setAttribute("srcdoc",n),l.setAttribute("src",o+"developer-exception-not-supported.html"),l.setAttribute("sandbox","allow-scripts"),e.messageHtml=t.trustAsHtml(l.outerHTML),angular.extend(e,r),e.close=s}]);
@@ -560,8 +562,6 @@ angular.module("cms.shared").directive("cmsModalDialogBody",["shared.internalMod
angular.module("cms.shared").directive("cmsModalDialogContainer",["shared.internalModulePath","$timeout",function(o,i){return{restrict:"E",templateUrl:o+"UIComponents/Modals/ModalDialogContainer.html",transclude:!0,link:function(o,l,a){var e="large"===a.cmsModalSize?"modal-lg":"";e+=o.isRootModal?" is-root-modal":" is-child-modal","large"===a.cmsModalSize&&(o.sizeCls=e);i(function(){o.sizeCls=e+" modal--show"},1)},controller:angular.noop}}]);
angular.module("cms.shared").directive("cmsModalDialogHeader",["shared.internalModulePath",function(e){return{restrict:"E",templateUrl:e+"UIComponents/Modals/ModalDialogHeader.html",transclude:!0}}]);
angular.module("cms.shared").factory("shared.modalDialogService",["$q","_","ModalService","shared.internalModulePath","shared.LoadState",function(i,s,u,c,e){var o={alert:function(o){var e=i.defer(),n=o||{};return s.isString(o)&&(n={message:o}),u.showModal({templateUrl:c+"UIComponents/Modals/Alert.html",controller:"AlertController",inputs:{options:n}}).then(function(o){o.close.then(e.resolve)}),e.promise},show:function(o){return u.showModal({templateUrl:o.templateUrl,controller:o.controller,inputs:{options:o.options}})}};return o.confirm=function(o){var t=i.defer(),r=new e,l=function(o){var e=o||{},n={ok:a.bind(null,!0),cancel:a.bind(null,!1),onOkLoadState:r};s.isString(o)&&(e={message:o});return s.defaults(n,e,{okButtonTitle:"OK",cancelButtonTitle:"Cancel",autoClose:!0})}(o);return u.showModal({templateUrl:c+"UIComponents/Modals/ConfirmDialog.html",controller:"ConfirmDialogController",inputs:{options:l}}),t.promise;function a(o){var e,n=o?l.onOk:l.onOk.onCancel,o=o?t.resolve:t.reject;return s.isFunction(n)&&(r.on(),e=n()),i.when(e).then(o)}},o}]);
-angular.module("cms.shared").factory("shared.ImagePreviewFieldCollection",["$q","_","shared.arrayUtilities","shared.imageService",function(s,m,d,c){return function(n){var i,r,a=this,t="previewImage";function u(e){if(n){e=e[n],e=r[e].fields[t];return e?e.lowerName:void 0}return i=!i&&r.fields[t]?r.fields[t].lowerName:i}function o(e,n,i){var r=u(e);if(r){r=f(e,r);if(!i){var t,i=a.images[n];if(r==(t=i?i.imageAssetId:t))return;if(!r)return void(a.images[n]=void 0)}return c.getById(r).then(function(e){a.images[n]=e})}}function f(e,n){if(n)return(e.model||e)[n]}a.load=function(e,n){if(r=n,!e||!e.length||!n)return i();n=m.chain(e).map(function(e){return f(e,u(e))}).filter(function(e){return!!e}).uniq().value();return n.length?c.getByIdRange(n).then(function(i){a.images=[],m.each(e,function(e){var n,e=f(e,u(e));e&&(n=m.find(i,{imageAssetId:e})),a.images.push(n)})}):i();function i(){var e=s.defer();return e.resolve(),a.images=[],e.promise}},a.move=function(e,n){d.move(a.images,e,n)},a.add=function(e,n){return o(e,n,!0)},a.update=function(e,n){return o(e,n)},a.remove=function(e){d.remove(a.images,e)}}}]);
-angular.module("cms.shared").factory("shared.ModelPreviewFieldset",["$q","_","shared.stringUtilities","shared.imageService",function(e,o,d,i){return function(e){var i=this,n="previewTitle",s="previewDescription",a="previewImage";i.modelMetaData=e,i.fields=function(r){var t={};return e(n),e(s),e(a),t;function e(i){var e=o.find(r.dataModelProperties,function(e){return e.additionalAttributes[i]});e&&(e.lowerName=d.lowerCaseFirstWord(e.name),t[i]=e,t.hasFields=!0)}}(e),i.showTitle=function(e){return e[n]||!e.hasFields}(i.fields),i.titleTerm=function(e){return e[n]?e[n].displayName:"Title"}(i.fields),i.on=function(){i.isLoading=!0,100===i.progress&&(i.progress=0)}}}]);
angular.module("cms.shared").controller("EditNestedDataModelDialogController",["$scope","shared.nestedDataModelSchemaService","shared.LoadState","options","close",function(e,a,o,t,l){var d=e;function n(){d.saveLoadState.on(),a.validate(d.formDataSource.modelMetaData.typeName,d.formDataSource.model).then(function(){t.onSave&&t.onSave(d.formDataSource.model);l()}).finally(d.saveLoadState.off)}function r(){l()}angular.extend(e,t),d.save=n,d.onCancel=r,d.close=r,d.isNew=!t.model,d.title=d.isNew?"Add Item":"Edit Item",d.saveLoadState=new o,function(e){var a=e.model||{},e=e.modelMetaData;d.isNew&&e.defaultValue&&e.defaultValue.value&&(a=angular.copy(e.defaultValue.value)),d.formDataSource={model:a,modelMetaData:e}}(t)}]);
angular.module("cms.shared").directive("cmsFormFieldNestedDataModelCollection",["_","shared.internalModulePath","shared.LoadState","shared.nestedDataModelSchemaService","shared.modalDialogService","shared.arrayUtilities","shared.ModelPreviewFieldset","shared.ImagePreviewFieldCollection","baseFormFieldFactory",function(p,h,e,f,D,M,I,F,t){var w=t.defaultConfig,o={templateUrl:h+"UIComponents/NestedDataModels/FormFieldNestedDataModelCollection.html",scope:p.extend(w.scope,{minItems:"@cmsMinItems",maxItems:"@cmsMaxItems",modelType:"@cmsModelType",orderable:"=cmsOrderable"}),passThroughAttributes:["required"],link:function(e,t,o,a){var d,l=e.vm,i=p.last(a);return function(){l.add=s,l.edit=m,l.remove=n,l.onDrop=u,l.onDropSuccess=g,l.getTitle=v,f.getByName(l.modelType).then(function(e){l.modelMetaData=e,l.previewFields=new I(e),l.gridImages=new F,l.gridImages.load(l.model,l.previewFields)})}(),w.link(e,t,o,a);function r(){l.model=l.model.slice(0)}function n(e,t){M.removeObject(l.model,e),l.gridImages.remove(t)}function m(e,t){c({model:e,onSave:function(){l.gridImages.update(e,t),r()}})}function s(){c({onSave:function(e,t){l.model=l.model||[],l.model.push(e),l.gridImages.add(e,l.model.length-1),r()}})}function c(e){e.modelMetaData=l.modelMetaData,i&&(e.additionalParameters=i.additionalParameters),D.show({templateUrl:h+"UIComponents/NestedDataModels/EditNestedDataModelDialog.html",controller:"EditNestedDataModelDialogController",options:e})}function u(e,t){d=e}function g(e){M.move(l.model,e,d),l.gridImages.move(e,d)}function v(e,t){var o=l.previewFields.fields.previewTitle;return o?e[o.lowerName]:e.title||"Item "+(t+1)}}};return t.create(o)}]);
angular.module("cms.shared").directive("cmsFormFieldNestedDataModelMultiTypeCollection",["_","shared.internalModulePath","shared.LoadState","shared.nestedDataModelSchemaService","shared.modalDialogService","shared.arrayUtilities","shared.ModelPreviewFieldset","shared.ImagePreviewFieldCollection","baseFormFieldFactory",function(C,D,e,w,M,I,F,N,o){var H=o.defaultConfig,t={templateUrl:D+"UIComponents/NestedDataModels/FormFieldNestedDataModelMultiTypeCollection.html",scope:C.extend(H.scope,{minItems:"@cmsMinItems",maxItems:"@cmsMaxItems",modelTypes:"@cmsModelTypes",orderable:"=cmsOrderable",titleColumnHeader:"@cmsTitleColumnHeader",descriptionColumnHeader:"@cmsDescriptionColumnHeader",imageColumnHeader:"@cmsImageColumnHeader",typeColumnHeader:"@cmsTypeColumnHeader"}),passThroughAttributes:["required"],link:function(e,o,t,a){var d,i=e.vm,l=C.last(a),r="previewTitle",m="previewDescription",n="previewImage";return function(){var e=i.modelTypes?i.modelTypes.split(","):[];i.add=v,i.edit=u,i.remove=c,i.onDrop=h,i.onDropSuccess=y,i.getTitle=f,w.getByNames(e).then(p),void 0===i.titleColumnHeader&&(i.titleColumnHeader="Title"),void 0===i.descriptionColumnHeader&&(i.descriptionColumnHeader="Description"),void(void 0===i.typeColumnHeader&&(i.typeColumnHeader="Type"))}(),H.link(e,o,t,a);function s(){i.model=i.model.slice(0)}function p(e){var t=!1,a=!1;i.modelMetaDataLookup={},i.previewFields={},C.chain(e).sortBy("displayName").each(function(e){i.modelMetaDataLookup[e.typeName]=e;var o=new F(e);o.fields[r]&&(t=!0),o.fields[m]&&(i.previewFields.showDescription=!0,a=!0),o.fields[n]&&(i.previewFields.showImage=!0,a=!0),i.previewFields[e.typeName]=o}),!t&&a||(i.previewFields.showTitle=!0),i.gridImages=new N("typeName"),i.gridImages.load(i.model,i.previewFields)}function c(e,o){I.removeObject(i.model,e),i.gridImages.remove(o)}function u(e,o){g({model:e.model,onSave:function(){i.gridImages.update(e,o),s()},modelMetaData:i.modelMetaDataLookup[e.typeName]})}function v(o){g({onSave:function(e){i.model=i.model||[];e={model:e,typeName:o.typeName};i.model.push(e),i.gridImages.add(e,i.model.length-1,!0),s()},modelMetaData:o})}function g(e){l&&(e.additionalParameters=l.additionalParameters),M.show({templateUrl:D+"UIComponents/NestedDataModels/EditNestedDataModelDialog.html",controller:"EditNestedDataModelDialogController",options:e})}function h(e,o){d=e}function y(e){I.move(i.model,e,d),i.gridImages.move(e,d)}function f(e,o){var t=i.previewFields[e.typeName].fields[r];return t?e.model[t.lowerName]:e.model.title||"Item "+(o+1)}}};return o.create(t)}]);
diff --git a/src/Cofoundry.Web.Admin/Admin/Modules/Shared/Js/Framework/HttpProviderConfig.js b/src/Cofoundry.Web.Admin/Admin/Modules/Shared/Js/Framework/HttpProviderConfig.js
index a4ea694d6..a238f0aca 100644
--- a/src/Cofoundry.Web.Admin/Admin/Modules/Shared/Js/Framework/HttpProviderConfig.js
+++ b/src/Cofoundry.Web.Admin/Admin/Modules/Shared/Js/Framework/HttpProviderConfig.js
@@ -27,5 +27,6 @@ function (
});
headers.common['X-Requested-With'] = 'XMLHttpRequest';
-
+ headers.common['Accept'] = 'application/json, text/html, text/plain, */*'
+
}]);
\ No newline at end of file
diff --git a/src/Cofoundry.Web/App_Start/StartupTasks/ConfigurationTasks/ErrorHandlingMiddlewareConfigurationTask.cs b/src/Cofoundry.Web/App_Start/StartupTasks/ConfigurationTasks/ErrorHandlingMiddlewareConfigurationTask.cs
index 987ec8de0..1e72650ad 100644
--- a/src/Cofoundry.Web/App_Start/StartupTasks/ConfigurationTasks/ErrorHandlingMiddlewareConfigurationTask.cs
+++ b/src/Cofoundry.Web/App_Start/StartupTasks/ConfigurationTasks/ErrorHandlingMiddlewareConfigurationTask.cs
@@ -29,7 +29,6 @@ public void Configure(IApplicationBuilder app)
if (_debugSettings.CanShowDeveloperExceptionPage(env))
{
app.UseDeveloperExceptionPage();
- app.UseDatabaseErrorPage();
}
else
{