/** * @class IDU.Core.Web.Enums.Enum * @alternateClassName Enum * Enum-Typ für JavaScript. Verhält sich ähnlich den C#-Enums und bietet die gängigen Methoden dafür an. * * Ein serverseitig serialisierter Enum-Typ bildet clientseitig einen Typ dieser Klasse, * der wiederum für jedes Enum-Feld eine Eigenschaft vom Typ {@link IDU.Core.Web.Enums.Enum.EnumField} hat. * Die Description-Attribute werden aufgelöst und ebenfalls in den Typ serialisiert. Sie können somit * zu Anzeigezwecken herangezogen werden. Fehlt das Description-Attribut, gibt * `{@link IDU.Core.Web.Enums.Enum.EnumField#getDescription getDescription()}` den Namen zurück. * Flags werden unterstützt. * * ## Serialisierung * * ### C# * * namespace IDU.Core.Web.FileManagement * { * [Flags] * public enum WellKnownTypeGroup * { * [Description("Unbekannt")] * Unknown = 0, * [Description("Grafikdatei")] * Image = 1, * [Description("Vom Browser unterstützte Grafikdatei")] * WebImage = Image | BrowserSupported, * [Description("Ausführbare Datei")] * Executable = 2, * [Description("Script-Datei")] * ScriptFile = 4, * [Description("Im Borwser darstellbares Datei-Format")] * BrowserSupported = 1024 * } * } * * IDU.Core.Web.Enums.EnumHelper.WebEnum<IDU.Core.Web.FileManagement.WellKnownTypeGroup>.Default.RegisterEnumInPage(ResourceCollector); * * ### JavaScript * * var typeGroup = IDU.Core.Web.FileManagement.WellKnownTypeGroup.WebImage; * if (typeGroup.hasFlag(IDU.Core.Web.FileManagement.WellKnownTypeGroup)) * alert('Dateien vom Typ "' + typeGroup.getDescription() + '" sind im Browser darstellbar.'); * * ## Parsen * * Enums können anhand ihres Zahlenwertes oder Namens mittels {@link IDU.Core.Web.Enums.Enum#static-method-tryParse tryParse} * in einen EnumField-Typ konvertiert werden. * * var typeGroup = Enum.tryParse(IDU.Core.Web.FileManagement.WellKnownTypeGroup, 1); * * var typeGroup = Enum.tryParse(IDU.Core.Web.FileManagement.WellKnownTypeGroup, 'Image'); * * Auch Kombinationen, die nicht als eigenes Feld im Enum-Typ vorhanden sind, werden erkannt. Die Namen und Beschreibungen * werden dann wie bei C# durch Komma getrennt aneinander gereiht. * * var typeGroup = Enum.tryParse(IDU.Core.Web.FileManagement.WellKnownTypeGroup, 1028); * var name = typeGroup.getName(); // 'ScriptFile, BrowserSupported' * var description = typeGroup.getDescription(); // 'Script-Datei, Im Borwser darstellbares Datei-Format' * * var typeGroup = Enum.tryParse(IDU.Core.Web.FileManagement.WellKnownTypeGroup, 'Executable, BrowserSupported'); * var value = typeGroup.getValue(); // 1026 * * ## Logische Verknüpfungen * * Enums können mit Binäroperatoren wie in C# verknüpft werden. Zu beachten ist jedoch, dass das Ergebnis der Berechnung immer den Zahlenwert liefert. * Das ist JavaScript-bedingt nicht anders möglich. Um das Ergebnis wieder in einen Enum-Typ zu konvertieren, muss dieser Wert * durch {@link IDU.Core.Web.Enums.Enum#static-method-createCalculated} geschickt werden. Diese Methode ist ähnlich tryParse, außer, dass * sie den Enum-Typ schon weiß und definitiv einen Zahlenwert erwartet. Diese Funktion sollte nur zu diesem Zweck verwendet werden. * * var typeGroup = Enum.createCalculated(IDU.Core.Web.FileManagement.WellKnownTypeGroup.WebImage & ~IDU.Core.Web.FileManagement.WellKnownTypeGroup.BrowserSupported); * var name = typeGroup.getName(); // Image * * ## Enum-Werte per Ajax verschicken * * EnumFields können ohne weiteres Zutun als Parameter für einen Ajax-Request angegeben werden. die * {@link IDU.Core.Web.Enums.Enum.EnumField#toJSON toJSON}-Methode wandelt den Wert bei der JSON-Serialisierung in den entsprechenden * Zahlenwert um. * * ## ExtJS * * ### Serialisierung * * Enums, die im ExtJS-Umfeld verwendet werden, sollten i.d.R. über das Extensionpack (nicht über die EnumHelper-Klasse) registriert werden. * * this.RegisterExtensionPack( * new IDU.Core.Web.Sencha.Common.ExtensionPacks.Enums( * IDU.Core.Web.Enums.EnumHelper.WebEnum<IDU.Core.Web.FileManagement.WellKnownTypeGroup>.Default)); * * Es steht dann die Hilfsklasse {@link Ext.ux.Enum} zur Verfügung. * * ### Models und Stores * * Außerdem ist es dann möglich, Enums als DatenTyp in {@link Ext.data.Model}s zu verwenden. * * Ext4.define('My.Model', { * extend: 'Ext.data.Model', * fields: [ * { name: 'typeGroup', type: 'enum', enumType: 'IDU.Core.Web.FileManagement.WellKnownTypeGroup' } * ... * ] * }); * * var record = new My.Model({ * typeGroup: 1 * }); * * var typeGroup = record.get('typeGroup'); // Es fällt ein EnumField raus * var name = typeGroup.getName(); // Image * * Bei Verwendung des Models in einem Store ist das Laden des Stores über Ajax ebenfalls ohne weiteres Zutun * möglich, wenn serverseitig die entsprechende Eigenschaft vom Typ des serialisierten Enums ist. * Es fällt am Ende aus den Records immer ein korrekter Typ raus. * * ### ComboBox * * Wenn bei Registrierung des ExtensionPacks das entsprechende Feature angegeben wird, * steht clientseitig die Klasse {@link Ext.ux.form.field.EnumComboBox} zur Verfügung. * Mehr Informationen in dieser Klasse. * * @extensionpack IDU.Core.Web.Sencha.Common.ExtensionPacks.Enums * @registrator IDU.Core.Web.Enums.EnumHelper.RegisterEnumInPage() * @author Robin * @date 2012-07-04 */ (function(global) { var _combinedValueType, _isCombined = function(v) { if (isNaN(v) || v === null) return false; // schauen, ob im Binärstring mehr als eine 1 vorkommt return /1.*1/.test(v.toString(2)); }, _combinedStringRx = /\s*,\s*/g, _comma = ',', _registeredTypes = {}; ////////////////////////////////////////////////// // #region Enum-Member ////////////////////////////////////////////////// // #region CTOR ////////////////////////////////////////////////// /** * @class EnumField * @ignore */ /** * @class IDU.Core.Web.Enums.Enum.EnumField * Feld eines Enums. * Dieses kann nicht direkt erstellt werden, sondern wird von {@link Enum#static-method-registerType} * am zugehörigen Enum registriert. * @author Robin * @date 2012-07-04 */ /** * @constructor * @member IDU.Core.Web.Enums.Enum.EnumField * Erstellt einen neuen Enum-Member. * Alternativ können beliebig viele EnumField als Parameter übergeben werden. * In diesem Fall wird ein kombinierter Enum-Wert (Flags) erstellt. * @param {Enum} type Enum-Typ * @param {String} name Name * @param {Number} value Wert * @param {String} description Beschreibung * @private */ var EnumField = function(type, name, value, description) { if (!(type instanceof Enum)) throw new Error("type muss ein Enum sein"); if (typeof name !== 'string') throw new Error("name muss ein String sein"); if (typeof value !== 'number' || parseInt(value, 10) != value) throw new Error("value muss ein Integer sein"); var me = this; me.type = type; me.name = name; me.value = value; me.description = description || name; // Zur Prüfung statt "instanceof" wg. frameübergreifenden Vergleichen me.___isEnumField = true; }; ////////////////////////////////////////////////// // #endregion ////////////////////////////////////////////////// // #region Prototyp-Sachen ////////////////////////////////////////////////// /** * @member IDU.Core.Web.Enums.Enum.EnumField * Gibt an, ob der EnumField gleich diesem ist. * Da in JS keine Operator-Überladungen möglich sind, ist diese Methode * vor === bevorzugt zu verwenden, da sie die Enum-Typen vergleicht und nicht nur die numerischen Werte. * @param {IDU.Core.Web.Enums.Enum.EnumField} value * @return {Boolean} */ EnumField.prototype.equals = function(value) { // zurücksetzen, da hier definitiv was anderes gemacht wird als eine Berechnung _combinedValueType = undefined; ///if (!(value instanceof EnumField && value.type === this.type)) if (value === undefined || value === null || !(value.___isEnumField && value.type.getTypeName() === this.type.getTypeName())) throw new Error("value ist nicht vom Typ " + this.type.getTypeName()); return this.value === value.value; }; /** * @member IDU.Core.Web.Enums.Enum.EnumField * Gibt an, ob der Enum ein Flag enthält * @param {IDU.Core.Web.Enums.Enum.EnumField} flag Flag, muss vom gleichen Typ wie dieser Enum sein * @return {Boolean} */ EnumField.prototype.hasFlag = function(flag) { // zurücksetzen, da hier definitiv was anderes gemacht wird als eine Berechnung _combinedValueType = undefined; ///if (!(flag instanceof EnumField && flag.type === this.type)) if (flag === undefined || flag === null || !(flag.___isEnumField && flag.type.getTypeName() === this.type.getTypeName())) throw new Error("flag ist nicht vom Typ " + this.type.getTypeName()); return (this.value & flag.value) === flag.value; }; /** * @member IDU.Core.Web.Enums.Enum.EnumField * Gibt den Wert für JSON zurück (native JSON.stringify nutzt toJSON, wenn es diese Methode gibt). * Das muss hier hin, da wir aufgrund der cirular Reference (mit type) sonst in eine Endlosschleife kommen würden. * ExtJS4 JSON-Encoder prüft diese Methode ebenfalls ab. * @private */ EnumField.prototype.toJSON = function() { return this.getValue(); }; /** * @member IDU.Core.Web.Enums.Enum.EnumField * Gibt den Namen des Enums aus. * @return {String} */ EnumField.prototype.toString = function() { return this.getName(); }; /** * @member IDU.Core.Web.Enums.Enum.EnumField * Wert des Enums für Operationen -> binäre Verknüpfungen * Wichtig! Um einen Enum-Member und nicht nur die Zahl zu erhalten, muss das * Ergebnis in Enum.createCalculated übergeben werden! * @return {Number} * @private */ EnumField.prototype.valueOf = function() { //if (_combinedValueType && _combinedValueType !== this.type) if (_combinedValueType && _combinedValueType.getTypeName() !== this.type.getTypeName()) throw new Error("Operatoren können nur zwischen Enums gleichen Typs verwendet werden"); _combinedValueType = this.type; return this.value; }; /** * @member IDU.Core.Web.Enums.Enum.EnumField * Gibt den Namen des Enums aus. * @return {String} */ EnumField.prototype.getName = function() { // zurücksetzen, da hier definitiv was anderes gemacht wird als eine Berechnung _combinedValueType = undefined; return this.name; }; /** * @member IDU.Core.Web.Enums.Enum.EnumField * Gibt den Enum-Typs aus. * @return {Enum} */ EnumField.prototype.getType = function() { // zurücksetzen, da hier definitiv was anderes gemacht wird als eine Berechnung _combinedValueType = undefined; return this.type; }; /** * @member IDU.Core.Web.Enums.Enum.EnumField * Gibt den Wert aus. * @return {Number} */ EnumField.prototype.getValue = function() { // zurücksetzen, da hier definitiv was anderes gemacht wird als eine Berechnung _combinedValueType = undefined; return this.value; }; /** * @member IDU.Core.Web.Enums.Enum.EnumField * Gibt den Anzeigetext des Enums aus. * @return {String} */ EnumField.prototype.getDescription = function() { // zurücksetzen, da hier definitiv was anderes gemacht wird als eine Berechnung _combinedValueType = undefined; return this.description || this.name; }; ////////////////////////////////////////////////// // #endregion ////////////////////////////////////////////////// // #endregion ////////////////////////////////////////////////// // #region CTOR ////////////////////////////////////////////////// /** * @constructor * @member IDU.Core.Web.Enums.Enum * Erstellt einen neuen Enum. * Ein Enum sollte nicht clientseitig erstellt, sondern immer über * IDU.Core.Web.Enums.EnumHelper.RegisterEnumInPage in der Seite registriert werden. * @param {String} typeName Name des Typs * @param {Object} values Enum-Werte (Key = Name, Value = Wert (int)) * @param {Object} descriptions Anzeigenamen (optional) (Key = Name, Value = Anzeigetext) * @param {Boolean} hasFlags Gibt an, ob es ein Flags-Enum ist * @private */ var Enum = function(typeName, values, descriptions, hasFlags) { if (typeof typeName !== 'string') throw new Error("typeName muss ein String sein"); if (!values || typeof values !== 'object') throw new Error("values muss ein Objekt sein"); var me = this, parts = typeName.split('.'), i = 0, len = parts.length - 1, leaf = parts[len], current = global, part, o; me.___typeName = typeName; me.___hasFlags = !!hasFlags; me.___isEnum = true; descriptions = descriptions || {}; // Enum-Member zuweisen for (o in values) if (values.hasOwnProperty(o)) me[o] = new EnumField(me, o, values[o], descriptions[o]); // Enum noch unter dem Typ-Namen global verfügbar machen for (; i < len; i++) { part = parts[i]; if (!current[part]) current[part] = {}; current = current[part]; } _registeredTypes[typeName] = current[leaf] = me; }; ////////////////////////////////////////////////// // #endregion ////////////////////////////////////////////////// // #region Prototyp-Sachen ////////////////////////////////////////////////// /** * @member IDU.Core.Web.Enums.Enum * Gibt den Namen des Typs aus. * @return {String} */ Enum.prototype.getTypeName = function() { return this.___typeName; }; /** * @member IDU.Core.Web.Enums.Enum * Gibt an, ob es ein Flag-Enum ist * @return {Boolean} */ Enum.prototype.hasFlags = function() { return !!this.___hasFlags; }; ////////////////////////////////////////////////// // #endregion ////////////////////////////////////////////////// // #region Statische Sachen ////////////////////////////////////////////////// /** * @member IDU.Core.Web.Enums.Enum * Gibt die Namen eines Enums zurück * @param {IDU.Core.Web.Enums.Enum} enumType Enum * @return {String[]} Namen * @static */ Enum.getNames = function() { var _cache = {}; return function(enumType) { //if (!(enumType instanceof Enum)) if (!enumType || !enumType.___isEnum) throw new Error("enumType ist nicht vom Typ Enum"); var typeName = enumType.getTypeName(), names, i; if (!_cache[typeName]) { names = []; for (i in enumType) //if (enumType.hasOwnProperty(i) && enumType[i] instanceof EnumField) if (enumType.hasOwnProperty(i) && enumType[i].___isEnumField) names.push(enumType[i].getName()); _cache[typeName] = names; } return _cache[typeName]; }; }(); /** * @member IDU.Core.Web.Enums.Enum * Gibt die Werte eines Enums zurück * @param {IDU.Core.Web.Enums.Enum} enumType Enum * @return {Number[]} Werte * @static */ Enum.getValues = function() { var _cache = {}; return function(enumType) { //if (!(enumType instanceof Enum)) if (!enumType || !enumType.___isEnum) throw new Error("enumType ist nicht vom Typ Enum"); var typeName = enumType.getTypeName(), names, i; if (!_cache[typeName]) { names = []; for (i in enumType) //if (enumType.hasOwnProperty(i) && enumType[i] instanceof EnumField) if (enumType.hasOwnProperty(i) && enumType[i].___isEnumField) names.push(enumType[i].getValue()); _cache[typeName] = names; } return _cache[typeName]; }; }(); /** * @member IDU.Core.Web.Enums.Enum * Versucht, anhand des Typs und des Wertes oder Namen ein den entsprechenden Enum-Member zu finden. * Gibt im Fehlerfall null zurück * @param {IDU.Core.Web.Enums.Enum} enumType Enum * @param {String/Number} value Wert oder Name * @param {Boolean} ignoreCase true, um den Vergleich case-insensitiv durchzuführen (wenn value der Name ist) * @return {IDU.Core.Web.Enums.Enum.EnumField} Enum-Member oder null im Fehlerfall * @static */ Enum.tryParse = function(enumType, value, ignoreCase) { if (!(enumType instanceof Enum)) throw new Error("enumType ist nicht vom Typ Enum"); // zurücksetzen, hier geht die Berechnung ja erst los _combinedValueType = undefined; if (value && value.___isEnumField && Enum.isMemberOf(enumType, value)) return value; var i, e, n, v, isString = typeof value === 'string', isCombined = isString ? value.indexOf(_comma) !== -1 : _isCombined(value); // kombinierten String vorbereiten if (isString && isCombined) { value = _comma + value.replace(_combinedStringRx, _comma) + _comma; if (ignoreCase === true) value = value.toUpperCase(); } for (i in enumType) { //if (enumType.hasOwnProperty(i) && enumType[i] instanceof EnumField) if (enumType.hasOwnProperty(i) && enumType[i].___isEnumField) { e = enumType[i]; n = e.value; // wird ein vordefinierter Enum-Member gefunden, dann wird dieser auch zurückgegeben if (e === value || n === value || e.name === value || (ignoreCase === true && e.name.toUpperCase() === String(value).toUpperCase())) { _combinedValueType = undefined; return e; } // sonst kombinieren wir else if (isCombined && ((typeof value === 'number' && (n & value) === n) || (isString && value.indexOf(_comma + (ignoreCase === true ? e.name.toUpperCase() : e.name) + _comma) !== -1))) { if (typeof v === 'undefined') v = e.valueOf(); else v |= e; } } } return v ? Enum.createCalculated(v) : null; }; /** * @member IDU.Core.Web.Enums.Enum * Erstellt einen Enum-Member aus einem (kombinierten) Wert * @param {Number} value Wert, der über Binäroperationen mehrerer EnumField erstellt wurde * @return {IDU.Core.Web.Enums.Enum.EnumField} EnumField * @static */ Enum.createCalculated = function() { var _cache = {}; return function(value) { if (!(typeof value === 'number' && _combinedValueType instanceof Enum)) throw new Error("Der Wert muss über Binäroperationen mehrerer EnumField erstellt worden sein. Andernfalls Enum.tryParse verwenden."); var type = _combinedValueType, typeName = type.getTypeName(); _combinedValueType = undefined; // schon mal gemacht -> zurückgeben if (_cache[typeName] && _cache[typeName][value]) return _cache[typeName][value]; if (!_cache[typeName]) _cache[typeName] = {}; var names = [], nullName, descriptions = [], nullDescription, v = 0, val, i, map = {}, values = [], hasFlags = type.hasFlags(); for (i in type) { //if (type.hasOwnProperty(i) && type[i] instanceof EnumField) if (type.hasOwnProperty(i) && type[i].___isEnumField) { val = type[i].getValue(); // wenn es bereits einen vordefinierten (kombinierten) Member gibt, dann nehmen wir diesen auch if (val === value) { _cache[typeName][value] = type[i]; return type[i]; } if ((value & val) === val) { if (val > 0) { // merken -> muss nachher rückwärts nach Größe der Werte abgearbeitet werden map[val] = type[i]; values.push(val); names.push(type[i].getName()); descriptions.push(type[i].getDescription()); } // 0-Wert nur nutzen, wenn kein anderer dabei ist else { nullName = type[i].getName(); nullDescription = type[i].getDescription(); } v |= val; } } } // jetzt nochmal rückwärts ablaufen wg. evtl. Doppelungen // z.B. // Enum (None = 0, One = 1, Two = 2, Five = One | 4, Six = Two | 4) // v == 7 -> One, Six if (hasFlags && _isCombined(v) && values.length > 0) { values.sort(); names.length = 0; descriptions.length = 0; val = 0; for (i = values.length - 1; i >= 0; i--) { if ((values[i] & val) > 0) continue; names.unshift(map[values[i]].getName()); descriptions.unshift(map[values[i]].getDescription()); val |= values[i]; } } // Namen und Beschreibung werden nur kombiniert verwendet, wenn es ein Flag-Enum ist und überhaupt // Einzelwerte gefunden wurden, sonst den Wert als String nehmen (wie in C#) _cache[typeName][value] = new EnumField( type, hasFlags && names.length > 0 ? names.join(", ") : (nullName || v.toString()), v, hasFlags && descriptions.length > 0 ? descriptions.join(", ") : (nullDescription || v.toString())); return _cache[typeName][value]; }; }(); /** * @member IDU.Core.Web.Enums.Enum * Gibt an, ob der angegebene Wert als Enum definiert ist * @param {IDU.Core.Web.Enums.Enum} enumType Typ des Enums * @param {Object} value der zu prüfende Wert (Zahl, Name (case-sensitiv) oder EnumField) * @return {Boolean} isDefined * @static */ Enum.isDefined = function(enumType, value) { if (value === undefined || value === null) return false; if (Enum.isMemberOf(enumType, value) && !enumType.hasFlags()) return true; //value = value instanceof EnumField ? value : Enum.tryParse(enumType, value, false); value = value.___isEnumField ? value : Enum.tryParse(enumType, value, false); return value && enumType[value.getName()] instanceof EnumField; }; /** * @member IDU.Core.Web.Enums.Enum * Gibt an, ob der angegebene Wert ein Member des angegebenen Typs ist * @param {IDU.Core.Web.Enums.Enum} enumType Typ des Enums * @param {Object} value der zu prüfende Wert * @return {Boolean} isMemberOf * @static */ Enum.isMemberOf = function(enumType, value) { if (value === undefined || value === null) return false; if (!(enumType instanceof Enum)) throw new Error("enumType ist nicht vom Typ Enum"); //if (!(value instanceof EnumField) || value.getType() != enumType) if (!value || !value.___isEnumField || value.getType().getTypeName() !== enumType.getTypeName()) return false; return enumType.hasFlags() || enumType[value.getName()].getName() === value.getName(); }; /** * @member IDU.Core.Web.Enums.Enum * Registriert einen Typ in der Seite * Ein Enum sollte nicht clientseitig erstellt, sondern immer über * IDU.Core.Web.Enums.EnumHelper.RegisterEnumInPage in der Seite registriert werden. * @param {String} typeName Name des Typs * @param {Object} values Enum-Werte (Key = Name, Value = Wert (int)) * @param {Object} descriptions Anzeigenamen (optional) (Key = Name, Value = Anzeigetext) * @param {Boolean} hasFlags Gibt an, ob es ein Flags-Enum ist * @return {IDU.Core.Web.Enums.Enum} Enum * @private * @static */ Enum.registerType = function(typeName, values, descriptions, hasFlags) { // nicht doppelt registrieren (z.B. bei dynamischen Nachladen von ClientScripts), // sonst passen Vergleiche u.U. nicht mehr, da type != type // CTOR kümmert sich um Registrierung return _registeredTypes[typeName] || new Enum(typeName, values, descriptions, hasFlags); }; ////////////////////////////////////////////////// // #endregion ////////////////////////////////////////////////// // Enum öffentlich machen global.Enum = IDU.Core.Web.namespace('IDU.Core.Web.Enums.Enum', Enum); })(this);