581 lines
19 KiB
JavaScript
581 lines
19 KiB
JavaScript
angular.module('umbraco.services').factory('archetypeLabelService', function (archetypeCacheService, $q, $injector, $timeout) {
|
|
//private
|
|
|
|
/**
|
|
* This will repeatedly wait for all promises in an array of promises to resolve, and it allows
|
|
* for promises to be added to the array (i.e., if you add more promises, those added promises
|
|
* will also be resolved too).
|
|
* @param promises The array of promises.
|
|
* @returns {*} The promise that will resolve once all promises in the array resolve.
|
|
*/
|
|
function repeatedlyWaitForPromises(promises) {
|
|
|
|
// Remember the original number of promises being resolved.
|
|
var originalLength = promises.length;
|
|
|
|
return $q.all(promises).then(function () {
|
|
|
|
// If there are new promises, resolve those too.
|
|
if (promises.length > originalLength) {
|
|
promises = promises.slice(originalLength);
|
|
return repeatedlyWaitForPromises(promises);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Processes a value to be used in a fieldset label. Since it might be a promise or a function, the
|
|
* value is repeatedly processed until it becomes a string.
|
|
* @param labelValue The value to be used in the fieldset label.
|
|
* @param promises The collection of promises to add any promises to.
|
|
* @param match The current matched substring object in a label template.
|
|
*/
|
|
function processLabelValue(labelValue, promises, match) {
|
|
|
|
// Normalize null/undefined values to an empty string.
|
|
if(!labelValue) {
|
|
labelValue = "";
|
|
}
|
|
|
|
// Check the type of value (may be a string, promise, function, or other).
|
|
if (isString(labelValue)) {
|
|
|
|
// handle collapsing dollar signs in labels (#387)
|
|
if (labelValue.indexOf("$$") >= 0) {
|
|
labelValue = labelValue.replace(/\$\$/g, "$$$$$$$$");
|
|
}
|
|
|
|
// Set a new value now that it has been processed.
|
|
//console.log("Processing string..." + labelValue);
|
|
match.value = labelValue;
|
|
} else if (isPromise(labelValue)) {
|
|
// Remember the promise so we can wait for it to be completed before constructing the
|
|
// fieldset label.
|
|
promises.push(labelValue);
|
|
labelValue.then(function (value) {
|
|
//console.log("Processing final value..." + value);
|
|
// The value will probably be a string, but recursively process it in case it's
|
|
// something else.
|
|
processLabelValue(value, promises, match);
|
|
});
|
|
} else if (_.isFunction(labelValue)) {
|
|
|
|
// Allow for the function to accept injected parameters, and invoke it.
|
|
labelValue = $injector.invoke(labelValue);
|
|
|
|
// Recursively check result (may be a string, promise, or another function (another
|
|
// function would be pretty strange, though I see no reason to disallow it).
|
|
processLabelValue(labelValue, promises, match);
|
|
} else {
|
|
// Some other data type (e.g., number, date, object).
|
|
match.value = labelValue;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if the specified value is of type string.
|
|
* @param value The value to check the type of.
|
|
* @returns {boolean} True, if the value is a string; otherwise, false.
|
|
*/
|
|
function isString(value) {
|
|
if (value === null) {
|
|
return false;
|
|
} else if (typeof value === 'string') {
|
|
return true;
|
|
} else if (value instanceof String) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if the specified value is a JavaScript promise.
|
|
* @param value The value that may be a promise.
|
|
* @returns {*} True, if the value appears to be a promise; otherwise, false.
|
|
*/
|
|
function isPromise(value) {
|
|
return value && _.isFunction(value.then);
|
|
}
|
|
|
|
/**
|
|
* Splits a string value into a collection based on a regular expression.
|
|
* @param rgx The regular expression to use to find matches in the string value.
|
|
* @param value The string value to split.
|
|
* @returns {Array} A collection of objects, each representing a portion of the supplied
|
|
* string. Each object will contain the substring value, as well as a property
|
|
* indicating whether or not that substring was matched the regular expression.
|
|
*/
|
|
function splitByRegex(rgx, value) {
|
|
// Validate input.
|
|
if (!rgx || !value) {
|
|
return [];
|
|
}
|
|
|
|
// Variables.
|
|
var substring,
|
|
splitParts = [],
|
|
nextIndex = 0,
|
|
index;
|
|
|
|
// Reset regex so we get all the matches.
|
|
rgx.lastIndex = 0;
|
|
|
|
// Loop through each match until there are no more matches.
|
|
var match = rgx.exec(value);
|
|
while (match) {
|
|
|
|
// Extract match index.
|
|
index = match.index;
|
|
|
|
// Is there text between the prior match and this one?
|
|
if (nextIndex < index) {
|
|
substring = value.substring(nextIndex, index);
|
|
splitParts.push({
|
|
isMatch: false,
|
|
value: substring
|
|
});
|
|
}
|
|
|
|
// Remember the end of this match for the next loop iteration.
|
|
nextIndex = rgx.lastIndex;
|
|
|
|
// Store info about this match.
|
|
substring = value.substring(index, nextIndex);
|
|
splitParts.push({
|
|
isMatch: true,
|
|
value: substring
|
|
});
|
|
|
|
// Get next match.
|
|
match = rgx.exec(value);
|
|
}
|
|
|
|
// The text after the last match.
|
|
if (nextIndex < value.length) {
|
|
substring = value.substring(nextIndex);
|
|
splitParts.push({
|
|
isMatch: false,
|
|
value: substring
|
|
});
|
|
}
|
|
|
|
// Reset regex in case somebody else wants to use it.
|
|
rgx.lastIndex = 0;
|
|
|
|
// Return information about the matches.
|
|
return splitParts;
|
|
}
|
|
|
|
function executeFunctionByName(functionName, context) {
|
|
var args = Array.prototype.slice.call(arguments).splice(2);
|
|
|
|
var namespaces = functionName.split(".");
|
|
var func = namespaces.pop();
|
|
|
|
for(var i = 0; i < namespaces.length; i++) {
|
|
context = context[namespaces[i]];
|
|
}
|
|
|
|
if(context && context[func]) {
|
|
return context[func].apply(this, args);
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
function getNativeLabel(datatype, value, scope) {
|
|
switch (datatype.selectedEditor) {
|
|
case "Imulus.UrlPicker":
|
|
return imulusUrlPicker(value, scope, {});
|
|
case "Umbraco.TinyMCEv3":
|
|
return coreTinyMce(value, scope, {});
|
|
case "Umbraco.MultiNodeTreePicker":
|
|
return coreMntp(value, scope, datatype);
|
|
case "Umbraco.MultiNodeTreePicker2":
|
|
return coreMntpV2(value, scope, datatype);
|
|
case "Umbraco.MultipleMediaPicker":
|
|
case "Umbraco.MediaPicker":
|
|
return coreMediaPicker(value, scope, datatype);
|
|
case "Umbraco.MediaPicker2":
|
|
return coreMediaPickerV2(value, scope, datatype);
|
|
case "Umbraco.DropDown":
|
|
return coreDropdown(value, scope, datatype);
|
|
case "RJP.MultiUrlPicker":
|
|
return rjpMultiUrlPicker(value, scope, {});
|
|
case "Umbraco.ContentPickerAlias":
|
|
return coreContentPicker(value, scope, datatype);
|
|
case "Umbraco.ContentPicker2":
|
|
return coreContentPickerV2(value, scope, datatype);
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function coreDropdown(value, scope, args) {
|
|
|
|
if(!value)
|
|
return "";
|
|
|
|
var prevalue = args.preValues[0].value[value];
|
|
|
|
if(prevalue) {
|
|
return prevalue.value;
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
function coreMntp(value, scope, args) {
|
|
var ids = value.split(',');
|
|
var type = "Document";
|
|
var deferred = $q.defer();
|
|
|
|
switch(args.preValues[0].value.type) {
|
|
case 'content':
|
|
type = 'Document';
|
|
break;
|
|
case 'media':
|
|
type = 'media';
|
|
break;
|
|
case 'member':
|
|
type = 'member';
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
var entityArray = [];
|
|
var promises = [];
|
|
|
|
_.each(ids, function(id){
|
|
if(id) {
|
|
promises.push(archetypeCacheService.getEntityById(id, type).then(function(entity){
|
|
if(entity) {
|
|
entityArray.push(entity.name);
|
|
}
|
|
}).promise);
|
|
}
|
|
});
|
|
|
|
$q.all(promises).then(function() {
|
|
deferred.resolve(entityArray.join(', '));
|
|
});
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
function coreMntpV2(value, scope, args) {
|
|
var deferred = $q.defer();
|
|
|
|
var ids = value.split(',');
|
|
|
|
if (ids.length == 0) {
|
|
return "";
|
|
}
|
|
|
|
var type = "document";
|
|
|
|
switch(args.preValues[0].value.type) {
|
|
case 'content':
|
|
type = 'document';
|
|
break;
|
|
case 'media':
|
|
type = 'media';
|
|
break;
|
|
case 'member':
|
|
type = 'member';
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
var entityArray = [];
|
|
var promises = [];
|
|
|
|
_.each(ids, function (id) {
|
|
if(id) {
|
|
promises.push(archetypeCacheService.getEntityByUmbracoId(id, type).then(function(entity) {
|
|
if(entity) {
|
|
entityArray.push(entity.name);
|
|
}
|
|
}).promise);
|
|
}
|
|
});
|
|
|
|
$q.all(promises).then(function() {
|
|
if(entityArray.length == 0) {
|
|
deferred.resolve("");
|
|
}
|
|
|
|
var firstEntityName = entityArray[0];
|
|
|
|
var value = firstEntityName + (ids.length > 1 ? ", ..." : "");
|
|
|
|
deferred.resolve(value);
|
|
});
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
function coreMediaPicker(value, scope, args) {
|
|
var deferred = $q.defer();
|
|
|
|
if(value) {
|
|
archetypeCacheService.getEntityById(value, "media").then(function(entity) {
|
|
deferred.resolve(entity.name);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
deferred.resolve("");
|
|
}
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
function coreMediaPickerV2(value, scope, args) {
|
|
var deferred = $q.defer();
|
|
|
|
//console.log("value=");
|
|
//console.log(value);
|
|
|
|
if(value) {
|
|
archetypeCacheService.getEntityByUmbracoId(value, "media").then(function(entity) {
|
|
deferred.resolve(entity.name);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
deferred.resolve("");
|
|
}
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
function coreContentPicker(value, scope, args) {
|
|
var deferred = $q.defer();
|
|
|
|
if(value) {
|
|
archetypeCacheService.getEntityById(value, "document").then(function(entity) {
|
|
deferred.resolve(entity.name);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
deferred.resolve("");
|
|
}
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
function coreContentPickerV2(value, scope, args) {
|
|
var deferred = $q.defer();
|
|
|
|
if (value) {
|
|
archetypeCacheService.getEntityByUmbracoId(value, "document").then(function(entity) {
|
|
deferred.resolve(entity.name);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
deferred.resolve("");
|
|
}
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
function imulusUrlPicker(value, scope, args) {
|
|
var deferred = $q.defer();
|
|
|
|
if(!value) {
|
|
deferred.resolve("");
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
if(!args.propertyName) {
|
|
args = {propertyName: "name"}
|
|
}
|
|
|
|
if(typeof(value) != "object") {
|
|
value = JSON.parse(value);
|
|
}
|
|
|
|
//console.log("urlpicker value...");
|
|
//console.log(value);
|
|
|
|
if(value.length) {
|
|
value = value[0];
|
|
}
|
|
|
|
switch (value.type) {
|
|
case "content":
|
|
if(value.typeData.contentId) {
|
|
archetypeCacheService.getEntityById(value.typeData.contentId, "Document").then(function(entity) {
|
|
//console.log("Retrived entity from cache!");
|
|
//console.log("Resolving the entity name with " + entity[args.propertyName]);
|
|
deferred.resolve(entity[args.propertyName]);
|
|
});
|
|
}
|
|
break;
|
|
|
|
case "media":
|
|
if(value.typeData.mediaId) {
|
|
archetypeCacheService.getEntityById(value.typeData.mediaId, "Media").then(function(entity) {
|
|
deferred.resolve(entity[args.propertyName]);
|
|
});
|
|
}
|
|
break;
|
|
|
|
case "url":
|
|
deferred.resolve(value.typeData.url);
|
|
|
|
return deferred.promise;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
function coreTinyMce(value, scope, args) {
|
|
if(!args.contentLength) {
|
|
args = {contentLength: 50}
|
|
}
|
|
|
|
var suffix = "";
|
|
var strippedText = $("<div/>").html(value).text();
|
|
|
|
if(strippedText.length > args.contentLength) {
|
|
suffix = "…";
|
|
}
|
|
|
|
return strippedText.substring(0, args.contentLength) + suffix;
|
|
}
|
|
|
|
function rjpMultiUrlPicker(values, scope, args) {
|
|
var names = [];
|
|
|
|
_.each(values, function (value) {
|
|
if (value.name) {
|
|
names.push(value.name);
|
|
}
|
|
});
|
|
|
|
return names.join(", ");
|
|
}
|
|
|
|
return {
|
|
getFieldsetTitle: function(scope, fieldsetConfigModel, fieldsetIndex) {
|
|
|
|
if(!fieldsetConfigModel)
|
|
return $q.when("");
|
|
|
|
var fieldset = scope.model.value.fieldsets[fieldsetIndex];
|
|
var fieldsetConfig = scope.getConfigFieldsetByAlias(fieldset.alias);
|
|
var template = fieldsetConfigModel.labelTemplate;
|
|
var promises = [];
|
|
|
|
if (template.length < 1)
|
|
return $q.when(fieldsetConfig.label);
|
|
|
|
var rgx = /{{.*?}}/g;
|
|
var matches = splitByRegex(rgx, template);
|
|
|
|
_.each(matches, function (match) {
|
|
|
|
// Skip over substrings that didn't match the regex (they do not require a transformation).
|
|
if (!match.isMatch) {
|
|
return;
|
|
}
|
|
|
|
// split the template in case it consists of multiple property aliases and/or functions
|
|
var templates = match.value.replace("{{", '').replace("}}", '').split("|");
|
|
var templateLabelValue = "";
|
|
|
|
for(var i = 0; i < templates.length; i++) {
|
|
// stop looking for a template label value if a previous template part already yielded a value
|
|
if(templateLabelValue !== "") {
|
|
break;
|
|
}
|
|
|
|
var template = templates[i];
|
|
|
|
//test for function
|
|
var beginParamsIndexOf = template.indexOf("(");
|
|
var endParamsIndexOf = template.indexOf(")");
|
|
|
|
//if passed a function
|
|
if(beginParamsIndexOf !== -1 && endParamsIndexOf !== -1)
|
|
{
|
|
var functionName = template.substring(0, beginParamsIndexOf);
|
|
var propertyAlias = template.substring(beginParamsIndexOf + 1, endParamsIndexOf).split(',')[0];
|
|
|
|
var args = {};
|
|
|
|
var beginArgsIndexOf = template.indexOf(',');
|
|
|
|
if(beginArgsIndexOf !== -1) {
|
|
|
|
var argsString = template.substring(beginArgsIndexOf + 1, endParamsIndexOf).trim();
|
|
|
|
var normalizedJsonString = argsString.replace(/(\w+)\s*:/g, '"$1":');
|
|
|
|
args = JSON.parse(normalizedJsonString);
|
|
}
|
|
|
|
templateLabelValue = executeFunctionByName(functionName, window, scope.getPropertyValueByAlias(fieldset, propertyAlias), scope, args);
|
|
}
|
|
//normal {{foo}} syntax
|
|
else {
|
|
propertyAlias = template;
|
|
|
|
var rawValue = scope.getPropertyValueByAlias(fieldset, propertyAlias);
|
|
|
|
templateLabelValue = rawValue;
|
|
|
|
//determine the type of editor
|
|
var propertyConfig = _.find(fieldsetConfigModel.properties, function(property){
|
|
return property.alias === propertyAlias;
|
|
});
|
|
|
|
if(propertyConfig) {
|
|
var datatype = archetypeCacheService.getDatatypeByGuid(propertyConfig.dataTypeGuid);
|
|
|
|
if(datatype) {
|
|
//try to get built-in label
|
|
var templateLabelValue = getNativeLabel(datatype, rawValue, scope);
|
|
|
|
//console.log("Native label...");
|
|
//console.log(templateLabelValue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process the value (i.e., reduce any functions or promises down to strings).
|
|
processLabelValue(templateLabelValue, promises, match);
|
|
});
|
|
|
|
// Wait for all of the promises to resolve before constructing the full fieldset label.
|
|
return repeatedlyWaitForPromises(promises).then(function () {
|
|
|
|
//console.log("done waiting...")
|
|
// Extract string values and combine them into a single string.
|
|
var substrings = _.map(matches, function (value) {
|
|
return value.value;
|
|
});
|
|
|
|
var combinedSubstrings = substrings.join('');
|
|
|
|
//console.log(combinedSubstrings);
|
|
|
|
// Return the title.
|
|
return combinedSubstrings;
|
|
});
|
|
}
|
|
}
|
|
}); |