/** * @abstract * Provides extensive utilities for working with ELO-forms. * * * Using FormWrapper's simple state-manager, you can define * application states consisting of fields and their respective: * * - values * - labels * - tooltips * - placeholder * - attributes (readonly / hidden / optional) * - responders: * a responder is a function which will get called when the field's value is changed * - validators: * a validator is a function which will get called when a field is clicked. Afterwards * the validation massage is shown in the client. * * There is also support for behaviour templates which provide common behaviours to a group of fields. * You can use templates defined in FormWrapper.Templates (end of this file) * * # Setup * * The Wrapper must be initialized in a form's `Header.txt`. * e.g.: * * <script type="text/javascript" src="lib_sol.common.forms.FormWrapper"></script> * <script type="text/javascript" src="lib_sol.hr.forms.PersonnelFile"></script> * * `lib_sol.hr.forms.PersonnelFile` will be a separate class extending * the form-wrapper by the customer specific logic. Therefore, also add these entries to your `Header.txt`: * * // ELO < 10.1 does not support onInit here. Just add these lines to inputChanged source == null instead. * function onInit() { * this.form = sol.create("sol.hr.forms.PersonnelFile"); * this.form.OnInitAndTabChange(); * } * * function tabChanged(id) { * this.form.TabChanged(id); * } * * function inputChanged(source) { * source == null ? * window.setTimeout(function() { sol.common.forms.Utils.initializeForm(); }, 100) * : this.form.InputChanged(source); * } * * function nextClicked(id) { * return ( * sol.common.forms.Utils.disableCancelButtonValidation(id, ["sol.common.wf.node.cancel"]) * || this.form.OnSave() * ); * } * * function saveClicked() { * return this.form.OnSave(); * } * * function addLineClicked(addLineId, groupIndex) { * this.form.LineAdded(addLineId, groupIndex); * } * * function removeLineClicked(addLineId, groupIndex) { * return this.form.LineRemoved(addLineId, groupIndex); * } * * function onDynListItemSelected(item, inputName) { * this.form.InputChanged(item, inputName); * } * * By defining all customer specific logic in the extending class, your `Header.txt` will be much cleaner. * It is recommended not to call any additional customer specific util-functions in `Header.txt`. Instead, you * should `include` your customer-specific logic solely in the extending class. * * You can define and use multiple extending classes in one Header.txt. * * Minimal example for an extending class `PersonnelFile`: * * sol.define("sol.hr.forms.PersonnelFile", { * extend: "sol.common.forms.FormWrapper", * prefix: "HR_PERSONNEL" // usually all customer-specific fields have some kind of prefix. You can define it here. * } * * # Usage * The wrapper functions are now available in the form. These are some common use-cases: * * ### Field functions (this.form.fields.FIELDNAME) * Every field on the form (MAP,GRP) is represented as an object. * .value(opts)- returns the field's value. * (isodate for date-fields and the key for localized Kwls). * pass `{full: true}` to receive the localized value of an lKwl instead of key only * pass `{asNumber: true}` to receive the value as a number. (e.g. valuable for localized number strings) * * .set(val) - replaces the field's value by `val` * (for val, pass an isodate date-fields and for localizedKwls pass the key) * * .element() - returns the field's HTML element * * .show() - displays the field in the form * (will also show the field's label and selector if called with .(true, true)) * * .hide() - hides the field * (will also hide the field's label and selector if called with .(true, true)) * * .setAttribute("readonly", true) * - renders a field readonly (pass false to make it writeable again) * * .setAttribute("optional", true) * - makes a field optional. Pass false to make a field required * * .tooltip() * - if a tooltip is defined, returns the tooltip as a String * * .writeTooltip(tooltip) * - Adds a tooltip to the field * - removes tooltip if falsy value is passed * * .setPlaceholder(value) * - set a placeholder text on the related field input element * * .setImage(guid) - only works on image-fields! Downloads an image specified by guid and sets the image-fields src-attribute * if no guid is passed, but the sord contains a guid as the fields value, it will be used as a fallback * * show and hide of labels will only work, if you assign the label's "variable" field a value of "LBL_" + me.prefix + `Fieldname without (WF/IX)_(MAP/GRP) and without prefix` * and the label's respective input-field's variable field contains the prefix defined in the extending class definition (e.g. HR_PERSONNEL) * * ### Form functions (this.form) * * .setState(statename) * - transforms the form according to the state-definition * * .activeState * - stores the currently active state * * .getActiveTabId() * - returns the id of the currently active tab (e.g. "_501_time_phases") * * .today() - returns the current client date as an isodate * * .logFieldValues(b) - logs all fieldnames and their values to the console (as a table if implemented). pass true as parameter to return fields and values as an array instead of a console-log * * * ### Tabs functions (this.form.tabs) * ._tabName - returns the tab object of the tab "tabName" * * .activeTab - returns the tab object of the active tab * * .all - holds an object containing all templates which are available on the form * * ### Tab functions (this.form.tabs._tabName) * .parts - holds an object containing the tab's templates (called "parts" in this framework) * * .parts._partName - holds the part-object having the name "partName" * * .containsCover - boolean. does the tab contain a business solution coversheet? (__cover) * * .hidePartsContainingOnlyEmptyFields() - hides all parts of a tab which where none of each part's fields has a value. * * .hideUnnecessaryParts(array) - hides all parts which do not contain any of the fields listed in the passed array * * ### Template/Part functions (this.form.tabs_tabName.parts._partName) * .show() - shows the template/part * * .hide() - hides the template/part * * .isCover - is the part a business solution coversheet? (__cover) * * ### Table functions (this.form.tables[tableId]) * .columns - holds an array containing the names of all columns of the respective table * * .size - number of rows in the table * * .getRow(x) - receives the specified table row with index x. returns a row even if it does not exist. * you can check for a non-existant row by using .getRow(x).row == undefined * * .getRowByField(field) - receives table row by field. Use fieldindex to return the corresponding table row. * returns undefined if field isn't a column in given table. Method is only supported on table object. * example: form.tables.tableId.getRowByField(field) * * .insert(obj) - inserts an object or an array of objects containing column: value pairs. ({my_column: "test"}). * optional second parameter: array of fieldnames. if specified fields are all empty, the latest existing row is used. otherwise, data is inserted in a new row. * returns an array of all added rows * .forEach(cb) - like forEach of arrays. the callback receives an object containing the columns of a row. * * .map(cb) - like map of arrays. * * .filter(cb) - like filter of arrays. * * .slice() - like slice of arrays. useful to convert a table to an array on which other array function can be used. (e.g. .some, .any, .reduce ...) * * # States: * The class extending sol.common.forms.FormWrapper can define multiple states, which can be * set e.g. on input-changed events (responders). Usually, states are changed in "responder"-functions or * the "BeforeOnInitAndTabChange" function, which will be called once during initialization * of the wrapper and again on every tab-change. "AfterOnInitAndTabChange" is called * after all states' "OnInitAndTabChange"-functions were called. (see e.g. templates, which * define their own OnInitAndTabChange function). * There is also "BeforeInputChanged" and "AfterInputChanged", which will be called * before/after the changed field's responder has been executed. * LineRemoved calls OnRemoveLine, which you can define in your extending class. Also, * AfterOnRemoveLine is called when the form has finished removing the table row. You * will usually want to use AfterOnRemoveLine, since it also gets passed the removed row values. * You should not use the obsolete OnLineChange function, since you would have to find out * manually if a line has been added or removed each time (which could be impossible to determine). * * * ### Order of computation * (# means, you can define this function in the extending class) * initialize: called during sol.create * #OnInit * OnInitAndTabChange: called when initializing form and on a tab change * #BeforeOnInitAndTabChange * Calls all OnInitAndTabChange functions which are defined in templates or states. (#template.OnInitAndTabChange and #state.OnInitAndTabChange) * #AfterOnInitAndTabChange * InputChanged: called when an input field´s value changes * #BeforeInputChanged * Calls the changed field´s #responder function * #AfterInputChanged * LineRemoved: called, when a JS_REMOVELINE button is clicked. * #OnRemoveLine * #AfterOnRemoveLine * LineAdded: called, when a JS_ADDLINE button is clicked. * #OnAddLine * OnSave: called when "save" is clicked * #BeforeOnSave * Calls all rules defined in #onSaveRules * #AfterOnSave * * * * ### Exemplary Formwrapper extending class: * sol.define("sol.hr.forms.PersonnelFile", { * extend: "sol.common.forms.FormWrapper", * prefix: "HR_PERSONNEL", // IX_MAP_HR_PERSONNEL_DATEOFJOINING -> Prefix = HR_PERSONNEL * defaultState: "myDefaultState", * //states begin * states: { * stateInit: { //stateInit is called automatically when the form is initially loaded * //declare your desired initial state here (usually responders and validators) * fieldProperties: { * IX_MAP_DURATION_TYPE: { * responder: function(form, state, field, value) { * if (value == "") { form.setState("myDefaultState") * } else { form.setState("durationTypeSelected") }; * } * } * } * }, * myDefaultState: { * fieldProperties: { * IX_MAP_HR_PERSONNEL_DEPARTMENT: { * hidden: true * } * } * }, * durationTypeSelected: { * fieldProperties: { * IX_MAP_HR_PERSONNEL_DEPARTMENT: { * value: "Sales" * hidden: false * } * } * }, * departmentHasAValue: { * fieldProperties: { IX_MAP_HR_PERSONNEL_DEPARTMENT: { hidden: false } } * }, * } * } * * ### Explanation * * First, during initialization, the stateInit state is set. Here, we assign a responder * function to the field "IX_MAP_DURATION_TYPE", which will set the state "durationTypeSelected" * as soon as the field has any value. * * The "myDefaultState" is set directly after "stateInit", because we defined it as me.defaultState * In "myDefaultState", the field IX_MAP_HR_PERSONNEL_DEPARTMENT will be hidden. * * If someone enters a value into the "IX_MAP_DURATION_TYPE" field, its responder will be executed. * The responder sets the state "durationTypeSelected", which sets the DEPARTMENT-field's value to * "Sales" and unhides/shows the field in the form. * * Validators: * You can define Validators in the fieldProperties of a field by assigning a "validator:" property. * Tip: You don't have to define the validation function ("JS_VAL_...") in the form designer anymore! * * BodyClasses: * You can define CSS-Bodyclasses in every state: * myDefaultState: { bodyClasses: ["greatDesign", "flexibleTable"] } * Attention: body classes will be removed, as soon as you execute "setState" for another state. * To keep body-classes during state-change, you must define the property e.g. * "myDefaultState.removePreviousBodyClasses: false" in subsequent states * * Template: * To use a template, you have to define it instead of (or alongside) a field's properties. * e.g. * * ... * fieldProperties: { * IX_MAP_HR_PERSONNEL_CASSATIONTOGGLE: { * template: "toggle" * config: { ... } //see documentation at the end of this file for template configs. * } * } * ... * * Batchprocessing: * It is tedious to define field:{hidden: true} if you want to hide many fields at once. * You can use all field properties (hidden, readonly, optional, value, responder, ...) in batch * processing. Example: * * fieldProperties: { * _batch: [{ * hidden: { * val: true, * fields: [ * "IX_GRP_HR_PERSONNEL_DATEOFJOINING", "IX_MAP_HR_PERSONNEL_PROBATIONARYPERIODDURATION" * ] * } * }] * } * * Of course you can combine batch-processing and ´normal´ attribute setting. * * If you want to define your own templates, you can do so in the "OnInit" function. * Just add your own templates to form.templates. E.g.: * * form.templates.mygreattemplate = function (config) { ... return configuredTemplate; }; * * ### Conclusion * * OnInitAndTabChange should be used to set up the environment for the user. * (what happens if the user switches tabs,... ) * * Any other state should be defined as ´state´. * If you need more control than the standard field properties (value, hide,...) can offer, * you can and should implement the logic directly in a field's responder instead of creating * a state which won't have sufficient facilities to represent the desired state anyways. * * * # Additional features * ### Business Solution Coversheet * The Business Solution Coversheet provides an easy way to display information in a form as readonly-labels * instead of input boxes. * * To display any MAP/GRP field in the coversheet, prepend "VIEW_" to its name when defining it in the coversheet * template. e.g. IX_MAP_TEST -> VIEW_IX_MAP_TEST * * The template containing the coversheet must be named ending with "__cover" (two underscores) * * The required CSS rules will then be applied automatically. * * @author ESt, ELO Digital Office GmbH * * @elowf * @requires sol.common.DateUtils * @requires sol.common.forms.Utils */ sol.define("sol.common.forms.FormWrapper", { /** * will contain all fields of the form after initializeFields has been called */ fields: {}, /** * will contain all tables which are part of the form */ tables: {}, /** * will contain all tabs of the form after initializeTabs has been called */ tabs: { all: { parts: {} } }, /** * will contain rules, which are applied when a user presses the save button */ onSaveRules: {}, /** * will contain all template OnInitAndTabChange functions after setState has been called */ onInitAndTabChangeFunctions: [], /** * holds the active state's name as a string */ activeState: undefined, /** * true if the form is running in workflow mode. (set in initialize) */ workflowActive: false, /** * Version of ELO-wf parsed to an Integer. (10.04.032 = 1004032) */ wfVersion: 0, /** * Name of ELO-wf (e.g. wf-Solutions) */ wfName: "", /** * Default editor options. editor won't be initialize at start by default * because of backwards compatibility */ editorOptions: { initImmediately: false, redactorOptions: {} }, /** * logs all fieldnames and their values to the console (as a table if implemented in the JS-Engine). * @param {Boolean} nolog returns fields and their values as an array instead of doing a console-log * @returns {[{fieldName: String, fieldValue: String}] || undefined} */ logFieldValues: function (nolog) { var me = this; return (function (keys) { return (nolog && keys) || (console.table ? console.table(keys) : console.log(keys)); })( Object.keys(me.fields) .sort(function (a, b) { return (a).localeCompare(b); }) .map(function (field) { return { fieldName: field, fieldValue: me.fields[field].value() }; }) ); }, /** * returns true if the passed string `str` ends with passed string `needle`, otherwise false */ endsWith: function (str, needle) { return (typeof str === "string" && typeof needle === "string") && (str.lastIndexOf(needle) + needle.length === str.length); }, /** * returns true if the running WF's version is >= the passed `requiredVersion` * requiredVersion (String) * e.g. "10.02.000" */ wfMeetsVersionRequirement: function (requiredVersion) { var me = this; return (typeof requiredVersion === "string" ? +(requiredVersion.replace(/\./g, "", true)) : 0) <= me.wfVersion; }, /** * determines the active tab's id e.g. "_510_time_details" and returns it * @returns {String} */ getActiveTabId: function () { var activeTab = document.getElementsByClassName("selected"); if (activeTab.length > 0) { return activeTab[0].hash && activeTab[0].hash.replace(/^#/, "_") || "none"; } else { return "none"; } }, /** * sets a state from the "states"-object by name * @param {String} stateName name of the state as defined in the "states"-object */ setState: function (stateName) { var me = this, state = me.states[stateName], delOldClasses; if (state === undefined) { return; } delOldClasses = state.removePreviousBodyClasses; // default is to delete old classes (delOldClasses === undefined || delOldClasses) && (me.activeState != undefined) && me.removeOldBodyClasses(); me.setNewBodyClasses(state.bodyClasses); me.setFieldProperties(state.fieldProperties); me.setTabProperties(state.tabProperties); state.reassignlkwls && me.reassignLocalizedKwlValues({ force: true }); me.assignValidators(state); me.assignValidationFunctionsToWindow(); me.assignResponders(state); if (state.OnInitAndTabChange && !(state.OnInitAndTabChange in me.onInitAndTabChangeFunctions)) { me.onInitAndTabChangeFunctions.push(state.OnInitAndTabChange); } me.activeState = stateName; }, /** * sets localizedKwl keys again (e.g. to receive translated text) * this is usually run once during initialization, but can be called manually at any time * @param {force: Boolean} opts if defined, forces a reassignment of all localizedKwl values. * Usually, only incomplete localizedKwl values are reassigned */ reassignLocalizedKwlValues: function (opts) { var me = this; for (var fieldname in me.fields) { var field = me.fields[fieldname], fValue = field.value({ full: true }); if (field.isLocalized) { field.localizedDynKwlName && ( fValue && ( (opts && opts.force) || (fValue.indexOf(" - ") === -1) || (fValue.search(/\w+ - ?$/g) === 0) ) ) && field.set(field.value()); } } }, /** * removes all bodyClasses which were set by the previous state */ removeOldBodyClasses: function () { var me = this; me.removeBodyClasses(me.states[me.activeState].bodyClasses); }, /** * removes bodyclasses * @private */ removeBodyClasses: function (bodyClasses) { var me = this; bodyClasses && bodyClasses.forEach(function (bc) { me.removeBodyClass(bc); }); }, /** * @private */ setNewBodyClasses: function (bodyClasses) { var me = this; me.setBodyClasses(bodyClasses); }, /** * @private */ setBodyClasses: function (bodyClasses) { var me = this; bodyClasses && bodyClasses.forEach(function (bc) { me.addBodyClass(bc); }); }, /** * determines the current day as isoDate using the beginning of the day as time and returns it * @returns {Integer (isodate)} */ today: function () { return sol.common.DateUtils.dateToIso(new Date(), { startOfDay: true }); }, /** * iterates over the fieldProperties defined in a state and assigns these properties * to the respective field * @private */ setFieldProperties: function (fieldProperties) { var me = this; for (var fieldName in fieldProperties) { if (fieldName == "_batch") { me.batchProcessProperties(fieldProperties[fieldName]); } else { me.processAFieldsProperties(fieldName, fieldProperties[fieldName]); } } }, /** * iterates over the batchProperties defined in a state and performs the batch-tasks accordingly * @private */ batchProcessProperties: function (batchProperties) { var me = this, propsAsArray = []; if (!(batchProperties instanceof Array)) { for (var key in batchProperties) { propsAsArray.push(batchProperties[key]); batchProperties = propsAsArray; } } try { batchProperties.forEach(function (o) { for (var prop in o) { var fields = o[prop].fields, value = o[prop].val, property = {}, noLog; property[prop] = value; if (fields === "all") { noLog = true; fields = Object.keys(me.fields); } fields.forEach(function (name) { me.processAFieldsProperties(name, property, noLog); }); } }); } catch (e) { console.log("_batch property not defined correctly:", e); } }, /** * used in setFieldProperties to map the defined properties to functions * @private */ processAFieldsProperties: function (fieldName, aFieldsProperties, noLog) { var me = this, workflowOnlyModes = Array.isArray(aFieldsProperties["workflowOnly"]) && aFieldsProperties["workflowOnly"]; for (var option in aFieldsProperties) { var optionValue = aFieldsProperties[option], overwrite = aFieldsProperties["overwrite"], field = me.fields[fieldName]; if (!field || !field.element()) { noLog || console.log("Field '" + fieldName + "' does not exist! Skipping ..."); return; } if (!me.workflowActive && (workflowOnlyModes && (workflowOnlyModes.indexOf(option) > -1))) { noLog || console.log("Rule '" + option + "' for '" + fieldName + "' only active in workflows! Skipping ..."); continue; } switch (option) { case "value": (overwrite && field.set(optionValue)) || (!field.value() && field.set(optionValue)); break; case "readonly": field.setAttribute(option, optionValue); break; case "optional": field.setAttribute(option, optionValue); break; case "hidden": field.setAttribute(option, optionValue); break; case "tooltip": field.writeTooltip(optionValue); break; case "placeholder": field.setPlaceholder(optionValue); break; case "template": me.applyTemplate(optionValue); field.template = optionValue; // it's nice to have access to the assigned template later... break; default: break; } } }, /** * iterates over the tabProperties defined in a state and assigns these properties * to the respective tab. However, you should not try to assign properties to tabs * but to parts. (tab names can change too easily which winds up to orphaned rules) * @private */ setTabProperties: function (tabProperties) { var me = this; for (var tabId in tabProperties) { //if (tabId == "_batch") { //me.batchProcessProperties(tabProperties[fieldName]); // not implemented yet //} else { no more else, see description } for (var partId in tabProperties[tabId]) { me.processAPartsProperties(partId, tabId, tabProperties[tabId][partId]); } } }, /** * used in setTabProperties to map the defined properties to functions * @private */ processAPartsProperties: function (partId, tabId, aPartsProperties) { var me = this; for (var option in aPartsProperties) { var optionValue = aPartsProperties[option], tab = me.tabs[tabId], part, resolvedOptionValue; if (!tab) { console.log("Tab '" + tabId + "' does not exist! Skipping ..."); return; } part = tab.parts[partId]; if (!part) { console.log("Formpart '" + partId + "' does not exist! Skipping ..."); return; } switch (option) { case "hidden": resolvedOptionValue = typeof optionValue === "function" ? optionValue.call(this) : optionValue; part.setAttribute(option, resolvedOptionValue); break; case "template": me.applyTemplate(optionValue); part.template = optionValue; // it's nice to have access to the assigned template later... break; default: break; } } }, setRowProperties: function (row, rowProperties) { var me = this, field; for (var tableId in rowProperties) { for (var columnName in rowProperties[tableId]) { field = row[columnName]; field && (function (f) { me.processAFieldsProperties(f.name, rowProperties[tableId][columnName]); f.responder = rowProperties[tableId][columnName].responder; })(field); } } }, /** * renders a template using the passed config and adds it to the statemanager * @private */ applyTemplate: function (templateObject) { var me = this, tName = templateObject.name, tConfig = templateObject.config, template = me.templates[tName], result; result = template.call(me, tConfig); me.addTemplateStatesToStates(result.states); if (result.OnInitAndTabChange && !(result.OnInitAndTabChange in me.onInitAndTabChangeFunctions)) { me.onInitAndTabChangeFunctions.push(result.OnInitAndTabChange); } if (result.OnSaveRule) { me.onSaveRules[result.OnSaveRule.name] = result.OnSaveRule.rule; } if (result.GlobalFunctions) { for (var functionName in result.GlobalFunctions) { window[functionName] = result.GlobalFunctions[functionName]; } } }, /** * adds states to the statemanager * @private */ addTemplateStatesToStates: function (templateStates) { var me = this; for (var stateName in templateStates) { var state = templateStates[stateName]; if (!state) { continue; } if (me.states[stateName] === undefined) { me.states[stateName] = { fieldProperties: {} }; } for (var fieldName in state.fieldProperties) { var field = state.fieldProperties[fieldName]; if (me.states[stateName].fieldProperties[fieldName] === undefined) { me.states[stateName].fieldProperties[fieldName] = {}; } if (me.states[stateName].fieldProperties === undefined) { me.states[stateName].fieldProperties = {}; } for (var propName in field) { var fieldInState = me.states[stateName].fieldProperties[fieldName]; fieldInState[propName] = field[propName]; } } for (var tabId in state.tabProperties) { var tab = state.tabProperties[tabId]; if (me.states[stateName].tabProperties === undefined) { me.states[stateName].tabProperties = {}; } if (me.states[stateName].tabProperties[tabId] === undefined) { me.states[stateName].tabProperties[tabId] = {}; } for (var propName in tab) { var tabInState = me.states[stateName].tabProperties[tabId]; tabInState[propName] = tab[propName]; } } } }, removeBodyClass: sol.common.forms.Utils.removeBodyClass, addBodyClass: sol.common.forms.Utils.addBodyClass, /** * determines a short name of a field using the prefix e.g. HR_PERSONNEL. * * IX_MAP_HR_PERSONNEL_DEPARTMENT -> department * @param {String} fieldName e.g. IX_MAP_HR_PERSONNEL_DEPARTMENT * @param {String} prefix e.g. HR_PERSONNEL * @returns {String} */ getShortName: function (fieldName, prefix) { if (fieldName.indexOf(prefix) != -1) { return fieldName.slice(fieldName.indexOf(prefix) + prefix.length + 1).toLowerCase(); } else { var match = /IX_\w{2,5}_(.+)/g.exec(fieldName); if (match && match[1]) { return match[1].toLowerCase(); } } return fieldName; }, determineUndefinedFields: function (params) { var me = this; return Object.keys(params || {}) .filter(function (param) { return me.propIsField(param) && (!$var(param)); }); }, /** * builds the "fields" object, which is accessible via me.fields * using the FormWrapper.Field Class * @private */ initializeFields: function () { var me = this; me.getNamesOfAllFieldsOnForm().forEach(function (name) { var field = sol.create("sol.common.forms.FormWrapper.Field", { fName: name, shortName: me.getShortName(name, me.prefix), validator: undefined, responder: undefined, prefix: me.prefix, form: me }); if (field) { me.fields[field.fName] = field; // makes field accessible by long name field.viewSource = field.fName.indexOf("VIEW_") === 0 ? field.fName.replace(/^VIEW_/, "") : undefined; } }); // if fields are views, fill them with values from their source for (var fieldName in me.fields) { var field = me.fields[fieldName], viewSource, num, numVal = undefined; if (field.viewSource) { field.viewSource = me.fields[field.viewSource]; field.element().classList.add("sol-coversheetfield"); if (field.viewSource && field.viewSource.value()) { if (num = ((viewSource = field.viewSource).element().getAttribute("inputtype") === "num")) { numVal = ELOF.numberToAmount(String(viewSource.value({ asNumber: true })), viewSource.element()); field.element().innerHTML = numVal; } if (!num || numVal == undefined) { field.viewSource.isDate ? field.set(elo.wf.date.format(field.viewSource.value())) : field.element().innerHTML = (field.viewSource.value({ localizedStringOnly: true }).replace(/(?:\r\n|\r|\n)/g, "<br />")); } } } } }, setFocusField: function () { var me = this, startElement = me.getStartElementToFocus(); if (startElement) { startElement.focus(); } }, getStartElementToFocus: function () { var activeTabButton = document.querySelector("div.tabs > ul a.selected[tabfocus]"), startElementName = activeTabButton ? activeTabButton.getAttribute("tabfocus") : null; return startElementName ? $var(startElementName) : null; }, determineTableSize: function (tableCols) { var me = this, tableSize = 0; Object.keys(me.fields) .forEach(function (name) { var match = name.match(/(.*[^0-9])(\d+)$/), row; if (match && ~tableCols.indexOf(match[1])) { row = +(match[2]); (row > tableSize) && (tableSize = row); } }); return tableSize; }, initializeTables: function () { var me = this, tables = {}; Array.prototype.slice.call(document.querySelectorAll("tbody[eloelems*='JS_ADDLINE']")) .forEach(function (table) { var jsAdd, id, colCount, cols, tb; jsAdd = table.querySelector("input[name='JS_ADDLINE']"); if (jsAdd) { id = jsAdd.getAttribute("addlineid") || "default"; tb = (tables[id] = {}); tb.columns = table.getAttribute("eloelems").split(",") .filter(function (name) { return (name.indexOf("1") === (name.length - 1)) && me.fields[name]; }) .map(function (col) { return col.substr(0, col.length - 1); }); colCount = (cols = tb.columns).length; tb.size = me.determineTableSize(tb.columns); tb.id = id; tb.form = me; tb.getRow = me.getTableRow.bind(me, cols, colCount); tb.isEmpty = me.isEmpty; tb.isEmptyRow = me.isEmptyRow; tb.addRow = me.addRow.bind(tb, jsAdd.onclick.bind(jsAdd)); tb.removeRow = me.removeRow.bind(tb); tb.insert = me.insert.bind(tb); tb.forEach = me.tableForEach; tb.map = me.tableMap; tb.filter = me.tableFilter; tb.slice = me.tableSlice; tb.applyRowProperties = me.applyRowProperties; tb.getRowByField = me.getRowByField.bind(tb); // bind current table at 'this' context /** * each field should have access to it's own table via tableId */ tb.forEach(function (row) { Object.keys(row) .filter(function (prop) { return prop !== "row"; }) .map(function (fieldName) { return row[fieldName]; }) .forEach(function (colField) { colField && (colField.tableId = tb.id); }); }); } }); me.tables = tables; Object.keys(tables) .forEach(function (tableId) { var table = tables[tableId]; me.removeDeletedTrailingRows(table.id, 1, table.size); }); me.fields = me.getNewFieldsObject(); }, /** * Determine whether the table is empty or not. * * @param {Array|String|undefined} [checkColumns] - checks every passed column whether columns has some content * default all row columns * @returns true if each column is empty of the whole table */ isEmpty: function (checkColumns) { var me = this, cols = checkColumns || me.columns; // use all columns if columns was not set cols = Array.isArray(cols) ? cols : [cols]; return me.map(function (row) { return row; }) .every(function (row) { return me.isEmptyRow(row, cols); }); }, isEmptyRow: function (row, columns) { return !columns.some(function (fieldName) { var field = row[fieldName], value = field && field.value(); return (value !== undefined && value !== ""); }); }, addRow: function (fn, empty) { var me = this, isEmpty = false, row = me.getRow(me.size); if (empty) { // TODO: refactor to use isEmptyRow here isEmpty = !(Array.isArray(empty) ? empty : [empty]) .some(function (fieldName) { var field = row[fieldName], value = field && field.value(); return (value !== undefined && value !== ""); }); } isEmpty || fn(); // only add new row if last row is not empty return me.getRow(me.size); }, applyRowProperties: function (row, attributes, options) { var me = this, columns; options = options || {}; attributes = attributes || {}; // client can override columns to use via options columns = options.columns || me.columns; if (Object.keys(attributes).length === 0) { return; } if (!Array.isArray(columns)) { throw Error("columns should be provided as an Array!", columns); } // set all attributes to each cell of the passed row columns.forEach(function (column) { var cell = row[column]; cell && me.form.processAFieldsProperties(cell.fName, attributes); }); }, /** * Remove the passed row object * * Unfortunately there is no well provided api function * to remove a row from a table, so be careful with this function. * * It's actually a workaround to find the * remove button within the same row and simulate a click on * the JS_REMOVELINE button (like addRow function in formwrapper) * * @param {Object} row - the table row object which should be removed * @param {Object} [options] * @param {String} [options.indicatorField] source column to find JS_REMOVELINE Button * If not set use first column name as default */ removeRow: function (row, options) { var me = this, indicatorColumn = options && options.indicatorColumn; if (!indicatorColumn && me.columns.length > 0) { // use first column as an indicatorColumn indicatorColumn = me.columns[0]; } if (row) { try { row[indicatorColumn].element() .closest("tr") // find parent tr and search then the JS_REMOVELINE Button from there .querySelector("a.jsRemoveLine") .click(); } catch (ex) { console.warn("Could not determine remove button - Did you add JS_REMOVELINE and JS_ADDLINE in your form?"); } } else { console.warn("row could not be remove because row is not set"); } }, insert: function (data, emptyFields) { var me = this, inserted = []; (Array.isArray(data) ? data : [data]) .forEach(function (obj) { var row = me.addRow(emptyFields); Object.keys(obj || {}) .forEach(function (col) { row[col] && row[col].set(obj[col]); }); inserted.push(row); }); return inserted; }, getTableRow: function (cols, colCount, row) { var me = this, sizeOutOfBounds = (row > me.size || row < 1), prettyCol = { row: sizeOutOfBounds ? undefined : row }, formFields = me.fields, k, col; for (k = 0; k < colCount; k++) { prettyCol[col = cols[k]] = formFields[col + row]; } return prettyCol; }, tableForEach: function (cb, thisArg) { var table = thisArg || this, size = table.size, i; for (i = 1; i <= size; i++) { cb.call(table, table.getRow(i), i, table); } }, tableMap: function (cb, thisArg) { var table = thisArg || this, size = table.size, i, result = []; for (i = 1; i <= size; i++) { result.push(cb.call(table, table.getRow(i), i, table)); } return result; }, tableFilter: function (cb, thisArg) { var table = thisArg || this, size = table.size, i, prettyCol, result = []; for (i = 1; i <= size; i++) { cb.call(table, (prettyCol = table.getRow(i)), i, table) && result.push(prettyCol); } return result; }, tableSlice: function (from, to) { var me = this, size = me.size, i, result = [], max = to || size + 1; for (i = from || 1; i <= size && i < max; i++) { result.push(me.getRow(i)); } return result; }, getRowByField: function (field) { var me = this, // should be a table object via bind result; if (!field) { return; } result = me.columns .filter(function (col) { return field.fName.startsWith(col); }) .map(function (colName) { return field; }) .map(function (colField) { return me.getRow(sol.common.forms.Utils.getFieldNameIndex(colField.fName)); }); return result[0]; // return undefined if field is not a column field }, /** * determines if the passed string is a field which can be handled by the FormWrapper. * @returns {Boolean} */ propIsField: function (prop) { return ( prop.indexOf("IX_MAP_") === 0 || prop.indexOf("WF_MAP_") === 0 || prop.indexOf("IX_GRP_") === 0 || prop.indexOf("VIEW_") === 0 ); }, /** * collects all fields that may be used in the form * @private */ getNamesOfAllFieldsOnForm: function () { var me = this, tables = document.getElementsByTagName("tbody"), fields = [], tableNo = -1; while (tables[++tableNo]) { fields = fields.concat(tables[tableNo].getAttribute("eloelems").split(",")); } // find viewfields for (var tabName in me.tabs) { var tab = me.tabs[tabName]; if (tab && tab.containsCover) { Array.prototype.slice.call(tab.element.querySelectorAll("[elonodename]")).filter(function (element) { return element.id.indexOf("VIEW_") === 0; }).forEach(function (field) { fields.push(field.id.trim()); }); } } for (var prop in ELO_PARAMS) { var me = this, value = ELO_PARAMS[prop]; typeof value === "string" && me.propIsField(prop) && fields.push(prop); } return fields.filter(function (item, index, a) { return a.indexOf(item) == index; }); }, removeFromUndefinedFields: function (name) { var me = this, foundAt; ((foundAt = me.undefinedFields.indexOf(name)) > -1) && me.undefinedFields.splice(foundAt, 1); }, /** * adds a new field to the "fields" object, which is accessible via me.fields * using the FormWrapper.Field Class * @private */ initializeNewField: function (name) { var me = this, field; field = sol.create("sol.common.forms.FormWrapper.Field", { fName: name, shortName: me.getShortName(name, me.prefix), validator: undefined, responder: undefined, prefix: me.prefix, form: me }); if (field) { me.fields[field.fName] = field; // makes field accessible by long name me.removeFromUndefinedFields(name); // field has been defined on the form, so remove it from undefined fields return field; } }, /** * adds new fields (which were added to the form since initialize) to the "fields" object, which is accessible via me.fields * using the FormWrapper.Field Class */ initializeNewFields: function () { var me = this; me.getNewFieldsOnForm().forEach(function (name) { var field = sol.create("sol.common.forms.FormWrapper.Field", { fName: name, shortName: me.getShortName(name, me.prefix), validator: undefined, responder: undefined, prefix: me.prefix, form: me }); if (field) { me.fields[field.fName] = field; // makes field accessible by long name } }); }, /** * collects all fields that were added to the form since initialize * @private */ getNewFieldsOnForm: function () { var me = this, oldFields = Object.keys(me.fields), newFields = []; for (var prop in ELO_PARAMS) { var value = ELO_PARAMS[prop]; typeof value === "string" && me.propIsField(prop) && oldFields.indexOf(prop) === -1 && newFields.push(prop); } return newFields; }, /** * Takes an arbitrary amount of arguments which can either be a String or an object. * When an argument is a string, it will be used to look up the respective field/template/table * from the form. Otherwise the argument is passed to the callback as is. * * The callback, which must be passed as the last parameter, will only be executed, if none of * the calls arguments are undefined. If some of the arguments are undefined the function * always returns false. * * Example: * If the field IX_GRP_MY_FIELD exists and the field IX_DESC exists and a template called mytemplate exists * and a table called mytable exists in the form, execute the callback. * * * form.when("IX_GRP_MY_FIELD", form.fields.IX_DESC, "mytemplate", "mytable", function (field, desc, template) { * field.set(desc.value()); * template.hide(); * return field.value(); * }); */ when: function () { function isDefined(arg) { return arg !== undefined; } function toWrapperObj(r) { if (typeof r == "string" && (r = r.trim())) { // sanitize return me.fields[r] || me.tabs.all.parts[r] || me.tables[r]; } return r; } var me = this, required = Array.prototype.slice.call(arguments), cb = required.pop(), args = required.map(toWrapperObj), allDefined = args.every(isDefined); if (typeof cb == "function" && allDefined) { return cb.apply(cb, args); } else { return allDefined; } }, /** * builds the "tabs" object, which is accessible via me.tabs * using the FormWrapper.Tab Class * @private */ initializeTabs: function () { var me = this; me.tabs.all = sol.create("sol.common.forms.FormWrapper.Tab", { parentForm: me, parts: {} }); me.getElementsOfAllTabsOnForm().forEach(function (tabDiv) { var tab = sol.create("sol.common.forms.FormWrapper.Tab", { name: tabDiv.id, element: tabDiv, parentForm: me }); if (tab && tab.name && tab.element) { tab.id = tab.name.replace(/^/, "_"); me.tabs[tab.id] = tab; // makes tab accessible me.tabs[tab.id].parts = {}; me.getPartsOfTab(tab).forEach(function (partDiv) { var children, part; children = partDiv && partDiv.id && partDiv.getAttribute && partDiv.getAttribute("eloelems"); part = sol.create("sol.common.forms.FormWrapper.Part", { name: partDiv && partDiv.id, element: partDiv, childrenFields: children ? children.split(",") : [] }); if (part && part.name && part.element) { part.id = part.name.replace(/^part/, ""); part.isCover = me.endsWith(part.id, "__cover") ? true : false; if (me.endsWith(part.id, "__cover")) { tab.element.classList.add("sol-coversheet-tab"); tab.containsCover = true; } part.isCover && part.element.classList.add("sol-coversheet"); if (part.id) { me.tabs[tab.id].parts[part.id] = part; me.tabs["all"].parts[part.id] = part; } } }); } }); }, /** * determines all tab Elements * @returns [{} (HTML-div)] * @private */ getElementsOfAllTabsOnForm: function () { var tabDivs = [], tabElements = document.getElementsByClassName("tabContent"); for (var n in tabElements) { var tabDiv = tabElements.namedItem ? (tabElements.namedItem((!isNaN(parseFloat(n)) && isFinite(n)) ? tabElements[n].id : n)) : tabElements[n]; tabDiv && tabDivs.push(tabDiv); } return tabDivs; }, /** * determines all template/part elements inside tabs * @returns [{} (HTML-div)] * @private */ getPartsOfTab: function (tab) { var partDivs = [], partElements = tab.element.getElementsByTagName("tbody"); for (var n in partElements) { var partDiv = partElements.namedItem ? partElements.namedItem((!isNaN(parseFloat(n)) && isFinite(n)) ? partElements[n].id : n) : partElements[n]; partDiv && partDivs.push(partDiv); } return partDivs; }, /** * assigns the validator function to the window object, just like ELO WF does it. * @private */ assignValidationFunctionsToWindow: function () { var me = this; for (var field in me.fields) { if (me.fields[field].validator || (me.fields[field].validator === false)) { window["JS_VAL_" + field.toUpperCase()] = me.fields[field].validator; me.reassignEloVerify(me.fields[field].element(), "JS_VAL_" + field.toUpperCase()); } } }, /** * used by assignValidationFunctionToWindow * @private */ reassignEloVerify: function (element, newJsVal) { var eloVerify = element.getAttribute("eloverify"), match, newVerify = newJsVal; if (eloVerify) { if (eloVerify.indexOf("JS_VAL_") == -1) { newVerify = eloVerify + " " + newJsVal; } else { match = /(.*) ?JS_VAL_[^ ](.*)/g.exec(eloVerify); newVerify = match && match[1] + " " + newJsVal + " " + match[2]; } } element.setAttribute("eloverify", newVerify); }, /** * assigns props defined in a state object to the respective fields */ assignPropToFields: function (state, propname) { var me = this; for (var fieldName in state.fieldProperties) { var field = state.fieldProperties[fieldName]; if (me.fields[fieldName] && (field[propname] || field[propname] === false)) { me.fields[fieldName][propname] = field[propname]; } } }, /** * Convert a MultiIndexField to a mapTable * * Current restriction limited to one column * @param {Field} multiIndexField * @param {String} mapTableColumn * * @throws If multiIndexField is undefined * @throws If mapTableColumn is undefined * @since 1.11.000 */ updateMultiIndexFromMapTableColumn: function (multiIndexField, mapTableColumn) { if (!multiIndexField) { throw Error("multiIndexField must be set"); } if (!mapTableColumn) { throw Error("mapTableColumn must be set"); } var indexArray = sol.common.forms.Utils.MultiIndex(), fields = multiIndexField.form.fields, tableIndexPos = mapTableColumn.length, pilcrows = new RegExp(String.fromCharCode(182), "g"); Object.keys(fields) .filter(function (fieldName) { return fieldName.indexOf(mapTableColumn) === 0; }) .filter(function (fieldName) { return !isNaN(+(fieldName.substr(tableIndexPos))); }) .sort(function (a, b) { return +(a.substr(tableIndexPos)) - +(b.substr(tableIndexPos)); }) .forEach(function (fieldName) { var val = fields[fieldName].value({ full: true }).replace(pilcrows, " "); // if dynkwl field, receive complete value val && indexArray.add(val); }); indexArray.save(multiIndexField.fName); }, /** * Convert each value of a MultiIndex field to a specific column of a table * * The function create dyanmically a new row for each value of the MultiIndex value * @param {Field} multiIndexField source * @param {String} mapTableColumn All values of the MultiIndex field copied to this column * @param {String} [tableId=default] the table of the column * * @throws Will throw an error if tableId doesn't exist in form * @since 1.11.000 */ updateMapTableColumnsFromMultiIndex: function (multiIndexField, mapTableColumn, tableId) { var me = this, multiIndexArray, firstColumn = mapTableColumn + "1", pilcrows = new RegExp(String.fromCharCode(182), "g"), table = tableId || "default"; if (!me.tables || !me.tables[tableId]) { throw Error("table " + tableId + " doesn't exist in form. Do you use the right name?"); } multiIndexArray = multiIndexField.value().split(pilcrows); // set first entry manually because the row exists per definition me.fields[firstColumn].set(multiIndexArray.shift()); setTimeout(function () { multiIndexArray.forEach( function (value) { var row = me.tables[table].addRow(); row[mapTableColumn].set(value); } ); }, 0); }, /** * calculates a date in the future using input parameters * @param {Integer }isoDate isoDate as starting point * @param {Integer }durationNumber number representing e.g. weeks, days (any momentJS unit) * @param {String} durationUnit momentJS unit descriptor (y, Q, M, w, d) * @param {String} terminationPoint momentJS unit descriptor (y, Q, M, w, d) * @returns {Integer} the new date as an isoDate */ calculateDate: function (isoDate, durationNumber, durationUnit, terminationPoint, offsetNumber, offsetUnit) { var srcDate, dstDate; srcDate = sol.common.DateUtils.isoToDate(String(isoDate)); durationNumber = Number(durationNumber); dstDate = sol.common.DateUtils.shift(srcDate, durationNumber, { unit: durationUnit }); dstDate = (terminationPoint && (moment(dstDate.getTime())).endOf(terminationPoint).toDate()) || dstDate; if (offsetNumber) { dstDate = sol.common.DateUtils.shift(dstDate, offsetNumber, { unit: offsetUnit || "d" }); } return sol.common.DateUtils.dateToIso(dstDate, { startOfDay: true }); }, /** * see setCalculatedDate */ returnCalculatedDate: function (srcDateFieldName, durationFieldName, terminationPointFieldName, dstDateFieldName, offsetNumber, offsetUnit) { return this.setCalculatedDate(srcDateFieldName, durationFieldName, terminationPointFieldName, dstDateFieldName, offsetNumber, offsetUnit, true); }, dayIsDay: function (isoDate1, isoDate2) { return isoDate1.substr(6, 2) === isoDate2.substr(6, 2); }, /** * uses calculateDate internally to simplify setting the value to a target field * @param {String} srcDateFieldName start date field name or isoDate * @param {String} durationFieldName field name of a field which holds a durationNumber * @param {String} terminationPointFieldName field name of a field which holds a termination point (at the end of the month, year, quarter,...) * @param {String} dstDateFieldName this field will receive the return value of calculateDate * @param {String} offsetNumber adjusts the calculated date by value (+-x) * @param {String} offsetUnit unit for adjustment * @param {Boolean} onlyReturn if true, returns the calculated value instead of setting the value * @returns {undefined || Integer (isoDate)} */ setCalculatedDate: function (srcDateFieldName, durationFieldName, terminationPointFieldName, dstDateFieldName, offsetNumber, offsetUnit, onlyreturn) { var me = this, srcIsoDate, durationNumber, durationUnit, terminationPoint, dstIsoDate; if (srcDateFieldName === undefined) { srcIsoDate = sol.common.DateUtils.dateToIso(new Date(), { startOfDay: true }); //today } else if (String(srcDateFieldName) !== "" && (String(srcDateFieldName).indexOf("IX_") == -1)) { srcIsoDate = +(srcDateFieldName); // isoDate from parameter } else { srcIsoDate = me.fields[srcDateFieldName] && me.fields[srcDateFieldName].value(); // isoDate from Field } if (srcIsoDate && me.fields[durationFieldName]) { durationNumber = me.fields[durationFieldName].value() || 0; terminationPoint = (terminationPointFieldName && me.fields[terminationPointFieldName].value()) || ""; durationUnit = me.fields[me.fields[durationFieldName].selector.name].value(); // B-) // moment.js initial calculation dstIsoDate = me.calculateDate(srcIsoDate, durationNumber, durationUnit, terminationPoint); // lap years if ((!terminationPoint || (terminationPoint === "d")) && ((durationUnit !== "d" && me.dayIsDay(dstIsoDate, srcIsoDate)) || (durationUnit === "d" || durationUnit === "w"))) { dstIsoDate = me.calculateDate(dstIsoDate, -1, "d"); } // calculate offset if (offsetNumber && offsetUnit) { dstIsoDate = me.calculateDate(dstIsoDate, 0, "d", undefined, offsetNumber, offsetUnit); } if (onlyreturn) { return dstIsoDate; } me.fields[dstDateFieldName].set(dstIsoDate); } }, /** * assigns validators of a state to the respective field * @private */ assignValidators: function (state) { var me = this; me.assignPropToFields(state, "validator"); }, /** * assings responders of a state to the respective field * @private */ assignResponders: function (state) { var me = this; me.assignPropToFields(state, "responder"); }, OnLineChange: function (lineAdded, name, index) { return true; }, OnAddLine: function (name, index, rowFields) { return true; }, OnRemoveLine: function (name, index, rowFields) { return true; }, AfterOnRemoveLine: function (name, index, removedFieldValues) { return true; }, LineAdded: function (tableId, index) { var me = this; tableId || (tableId = "default"); (index > me.tables[tableId].size) && (me.tables[tableId].size = index); return me.OnAddLine(tableId, index, me.initializeFieldsOfTableRow(tableId, index)); }, anyIdChanged: function (ids) { return ids .some(function (arr) { return arr[0].element().id !== arr[1]; }); }, LRCb: function (tableId, index, ids, removedFieldValues) { var me = this; if (me.anyIdChanged(ids) || me.tableSizeChanged(tableId)) { me.removeDeletedTrailingRows(tableId, index, me.tables[tableId].size); me.tables[tableId].size = me.findTableSize(tableId, me.tables[tableId].size); me.fields = me.getNewFieldsObject(); me.AfterOnRemoveLine(tableId, index, removedFieldValues); } else { window.setTimeout(function () { me.LRCb(tableId, index, ids, removedFieldValues); }, 1); } }, tableSizeChanged: function (tableId) { var me = this; return me.tables[tableId].size != me.findTableSize(tableId, me.tables[tableId].size); }, LineRemoved: function (tableId, index) { var me = this, ids, possiblyRemovedFields = {}, possiblyRemovedFieldValues = {}; tableId || (tableId = "default"); (ids = me.collectIdsOfTableRow(tableId, index)) .forEach(function (arr) { var cur = arr[0]; possiblyRemovedFields[cur.fName] = cur; possiblyRemovedFieldValues[cur.fName] = cur.value(); }); me.LRCb(tableId, index, ids, possiblyRemovedFieldValues); // start timeout callback for AfterOnRemoveLine return me.OnRemoveLine(tableId, index, possiblyRemovedFields); }, removeDeletedTrailingRows: function (tableId, startFrom, tableSize) { var me = this, cur; for (cur = startFrom; cur <= tableSize; cur++) { me.initializeFieldsOfTableRow(tableId, cur); // reinitialize. } }, initializeFieldsOfTableRow: function (tableId, rowNo) { var me = this, tableFields = me.tables[tableId].columns, length = tableFields.length, i, name, sName, fields = {}, state = me.getStateInit(); // TODO: we should set responder of current state and tableId to the initializedField for (i = 0; i < length; i++) { if (me.element(name = (sName = tableFields[i]) + rowNo)) { !me.fields[name] && (function () { var f = me.initializeNewField(name); f.tableId = tableId; fields[sName] = f; })(); } else { me.fields[name] = undefined; } } // initialize state for each row state && state.rowProperties && me.setRowProperties(me.tables[tableId].getRow(rowNo), state.rowProperties); return fields; }, /** * wrapper around $var, can also work with images (which $var can't) * @param {String} fName the field name * @returns {HTML-DIV} */ element: function (fName) { var el = $var(fName); if (!el) { el = document.getElementsByName(fName); el = el.length > 0 && el[0]; // var does not seem to find images, therefore try this } return el; }, getActiveState: function () { var me = this; return me.states[me.activeState] || me.getStateInit(); }, getStateInit: function () { var me = this; return me.states["stateInit"]; }, collectIdsOfTableRow: function (tableId, rowNo) { var me = this, tableFields = me.tables[tableId].columns, length = tableFields.length, i, name, el, ids = []; for (i = 0; i < length; i++) { if (el = $var(name = tableFields[i] + rowNo)) { ids.push([me.fields[name], el.id]); } } return ids; }, findTableSize: function (tableId, lastKnown) { var me = this, tableFields = me.tables[tableId].columns, length = tableFields.length, i; // start checking backwards from the last column to be known to be the largest while (lastKnown > 1) { for (i = 0; i < length; i++) { if ($var(tableFields[i] + lastKnown)) { return lastKnown; } } lastKnown--; } return lastKnown; }, getNewFieldsObject: function () { var me = this, newFieldsObject = {}, fieldName, fields = me.fields, field; for (fieldName in fields) { (fieldName !== "") && (field = me.fields[fieldName]) && (newFieldsObject[fieldName] = field); } return newFieldsObject; }, /** * wrapper around OnInitAndTabChange which also unhides all fields on the form * @private */ TabChanged: function () { var me = this; for (var fieldName in me.fields) { if (fieldName !== "") { var field = me.fields[fieldName]; field.show(true, true); } } for (var tabId in me.tabs) { if (tabId !== "") { var tab = me.tabs[tabId]; for (var partId in tab.parts) { if (partId !== "") { var part = tab.parts[partId]; part.show(); } } } } me.OnInitAndTabChange(); }, /** * Executes registered callbacks when the tab is changed or the form is initialized. * @private */ OnInitAndTabChange: function () { var me = this; me.tabs.activeTab = me.tabs[me.getActiveTabId()]; if (me.tabs.activeTab === undefined) { me.tabs.activeTab = {}; me.tabs.activeTab.name = ""; } // can be defined in extending class me.BeforeOnInitAndTabChange && me.BeforeOnInitAndTabChange(me.tabs.activeTab); me.onInitAndTabChangeFunctions.forEach(function (f) { f.call(me, me.tabs.activeTab); }); // can be defined in extending class me.AfterOnInitAndTabChange && me.AfterOnInitAndTabChange(me.tabs.activeTab); }, getNameOfKwlSource: function (source) { if (typeof source === "object" && source.$KEY && source.$VALUE) { return Object.keys(source).filter(function (prop) { return (prop !== "$KEY") && (prop !== "$VALUE"); })[0]; } }, initializeRowFieldsOfMapTableKwl: function (kwlFields) { var me = this; kwlFields .forEach(function (field) { me.propIsField(field) && !me.fields[field] && me.initializeNewField(field); }); }, /** * Basically executes the responder of a field which was changed by user input. * It is also possible to define a BeforeInputChanged and a AfterInputChanged * function in the extending class to make changes before/after any field-change * without specifically depending on one field. * @private */ InputChanged: function (source, name) { var me = this, fieldname, field, val, row; fieldname = source.name || name || me.getNameOfKwlSource(source); if (!me.fields[fieldname] && source.$KEY && source.$VALUE && ((fieldname.indexOf("IX_") !== 0) && (fieldname.indexOf("WF_") !== 0))) { //kwl fieldname = "IX_GRP_" + fieldname; } !me.fields[fieldname] && me.initializeNewField(fieldname); (!source.name && name) && me.initializeRowFieldsOfMapTableKwl(Object.keys(source || {})); field = me.fields[fieldname]; val = field.value(); // can be defined in extending class me.BeforeInputChanged && me.BeforeInputChanged(field); if (field.tableId) { row = me.tables[field.tableId].getRowByField(field); } field.responder && field.responder(me, me.activeState, field, val, row); // row is undefined if field is not a table row object // can be defined in extending class me.AfterInputChanged && me.AfterInputChanged(field); }, /** * @private * Only returns false if the rule matches and saveValues is false. * Also executes "registerUpdate", if defined. * @param {Object} rule * @return {Boolean} */ shouldISave: function (rule) { var mask = rule.maskName, soltype = rule.solType, ruleActive, saveValues; ruleActive = (mask && soltype && mask === ELO_PARAMS.IX_MASKNAME && soltype === ELO_PARAMS.IX_GRP_SOL_TYPE) || (mask && !soltype && mask === ELO_PARAMS.IX_MASKNAME) || (!mask && soltype && soltype === ELO_PARAMS.IX_GRP_SOL_TYPE); if (ruleActive) { rule.registerUpdate && sol.common.forms.Utils.registerUpdate((typeof rule.registerUpdate === "string") ? rule.registerUpdate : null); saveValues = ((typeof rule.saveValues) === "function") ? rule.saveValues.call(this) : rule.saveValues; return (saveValues !== undefined) ? saveValues : true; } else { return true; } }, /** * executes OnSaveRules and consolidates their results * @private */ executeOnSaveRules: function () { var me = this, combinedResults = true, result; for (var ruleName in me.onSaveRules) { var rule = me.onSaveRules[ruleName]; if (rule) { result = me.shouldISave(rule); if (combinedResults) { combinedResults = result; } } } return combinedResults; }, /** * OnSave callback * @private */ OnSave: function () { var me = this, result = true; me.BeforeOnSave && me.BeforeOnSave(); result = me.executeOnSaveRules(); me.AfterOnSave && me.AfterOnSave(result); return result; }, /** * initializes the class. */ initialize: function () { var me = this, defaultState; me.workflowActive = ELO_PARAMS.ELO_FLOWID !== "-1"; me.wfVersion = typeof ELOWF_VERSION === "string" ? +(ELOWF_VERSION.replace(/\./g, "", true)) : 0; if (window.location && typeof window.location.pathname === "string") { if (window.location.pathname.indexOf("plugin/de.elo.ix.plugin.proxy/wf")) { me.wfName = window.location.pathname.split("/"); me.wfName = me.wfName.slice(1, me.wfName.length - 2).join("/"); } else { me.wfName = window.location.pathname.split("/"); me.wfName = me.wfName.length > 1 ? me.wfName[1] : ""; } } me.initializeTabs(); me.tabs.activeTab = me.tabs[me.getActiveTabId()]; me.templates = sol.create("sol.common.forms.FormWrapper.Templates"); me.undefinedFields = me.determineUndefinedFields(ELO_PARAMS); // fields which are not on the form, but in ELO_PARAMS me.initializeFields(); me.setFocusField(); me.initializeTables(); me.OnInit && me.OnInit(); me.setState("stateInit"); defaultState = (typeof me.defaultState === "function") ? me.defaultState.call(me) : me.defaultState; defaultState && me.setState(defaultState); me.reassignLocalizedKwlValues(); } }); sol.define("sol.common.forms.FormWrapper.Tab", { initialize: function (config) { var me = this; me.$super("sol.Base", "initialize", [config]); }, /** * hides all parts of a tab which where none of each part's fields has a value. */ hidePartsContainingOnlyEmptyFields: function () { var me = this; Object.keys(me.parts) .filter(function (partName) { return ( me.parts[partName].childrenFields .filter(function (fieldName) { return me.parentForm.fields[fieldName].value() != ""; }).length === 0 ); }) .forEach(function (partName) { me.parts[partName].hide(); }); }, /** * hides all parts which do not contain any of the fields listed in the passed array */ hideUnnecessaryParts: function (necessaryFields) { var me = this; Object.keys(me.parts) .filter(function (partName) { return ( me.parts[partName].childrenFields .filter(function (fieldName) { return necessaryFields.indexOf(fieldName) > -1; }).length === 0 ); }) .forEach(function (partName) { me.parts[partName].hide(); }); } }); /** * Represents a form-tab's template/part * * @author ESt, ELO Digital Office GmbH * */ sol.define("sol.common.forms.FormWrapper.Part", { initialize: function (config) { var me = this; me.$super("sol.Base", "initialize", [config]); }, /** * Hides the template/part */ hide: function () { var me = this; me.changeVisibility(false); }, /** * Unhides/shows the template/part */ show: function () { var me = this; me.changeVisibility(true); }, /** * sets the style attribute accordingly * @private */ changeVisibility: function (visible) { var me = this; me.element.style.display = visible ? "" : "none"; }, /** * used by FormWrapper to map state properties to the respective functions * @private */ setAttribute: function (attribute, value) { var me = this; switch (attribute) { case "hidden": value ? me.hide() : me.show(); break; default: break; } } }); /** * Represents a form-template's field. * * @author ESt, ELO Digital Office GmbH * * Fields are automatically linked to their label, if they are called * * "LBL_" + me.prefix + me.getShortName(); * * To make things easier, localizedKwls will automatically be linked to * a selector: * * me.fName + "_UNIT" * * e.g. * * IX_MAP_HR_PERSONNEL_DEPARTMENT * label: LBL_HR_PERSONNEL_DEPARTMENT * selector: IX_MAP_HR_PERSONNEL_DEPARTMENT_UNIT * * This is important for the ´smart´ "set" and "value" functions to work! * * Setting a localizedKwl Value the old way: * * //insert complicated, localizedKwl specific code here * * Setting a localizedKwl Value the new way: * * form.fields.IX_MAP_HR_PERSONNEL_DEPARTMENT.set("DEV") * * The same also works for dates and of course "normal fields". * * field.show() and field.hide() take two parameters: * includingLabel Boolean * includingSelector Boolean * If both parameters are set to true, the field, its label and its unit-selector will be hidden/shown. */ sol.define("sol.common.forms.FormWrapper.Field", { initialize: function (config) { var me = this, parent, selector, unitSelectorName; me.$super("sol.Base", "initialize", [config]); me.name = me.fName; if (!me.name) { return; } parent = $var(me.fName); parent = parent && parent.parentElement; me.isDate = (parent && parent.getAttribute("elonodename") == "DATE") || (me.element() && me.element().getAttribute("eloverify") == "date"); me.datePicker = me.isDate && me.element().parentElement.children[1]; me.editor = me.isEditor() && sol.create("sol.common.forms.FormWrapper.RedactorApi", { field: me, immediately: me.form.editorOptions.initImmediately, redactorOptions: me.form.editorOptions.redactorOptions }); unitSelectorName = me.prefix + "_" + me.shortName.toUpperCase() + "_UNIT"; me.label = $var("LBL_" + me.prefix + "_" + me.shortName.toUpperCase()); if (me.isDate) { selector = $var(me.fName); // yep } else { selector = $var("IX_MAP_" + unitSelectorName) || $var("IX_GRP_" + unitSelectorName); } me.selector = selector; me.localizedDynKwlName = me.getDynKwlName(); me.localizedDynKwl = me.localizedDynKwlName && me.element().nextSibling; }, isEditor: function () { var me = this; return !!(me.element() && me.element().closest(".redactor3")); }, /** * determines the dynamic keyword list's name, if the field is linked to one and returns it * @returns {String} */ getDynKwlName: function () { var me = this, kwl = me.element() && me.element().nextSibling, name, result, elocompl; if (!me.element()) { return; } name = (me.element().getAttribute("swlname") || (kwl && kwl.getAttribute("swlname"))); elocompl = (me.element().getAttribute("elocompl") || (kwl && kwl.getAttribute("elocompl"))); me.localizedDynKwlOrigName = name || elocompl; name = (name === "#DATE#" ? undefined : name); result = name && ((name.indexOf("DYNSWL_") == 0 && name.replace("DYNSWL_", "")) || name) || undefined; me.isLocalized = result && (!elocompl); //localized kwls do not have elocompl attribute return result; }, /** * removes selector from a kwl-field (e.g. for making it readonly) * @private */ kwlStyle: function (mode) { var me = this; mode === "remove" ? me.localizedDynKwlName && me.element().removeAttribute("elocompl") : me.localizedDynKwlName && me.element().setAttribute("elocompl", me.localizedDynKwlOrigName || me.localizedDynKwlName); }, /** * wrapper around $var, can also work with images (which $var can't) * @returns {HTML-DIV} */ element: function () { var el = $var(this.fName); if (!el) { el = document.getElementsByName(this.fName); el = el.length > 0 && el[0]; // var does not seem to find images, therefore try this } return el; }, valueFromEloParams: function () { var me = this; return (me.form.undefinedFields.indexOf(me.fName) === -1) ? "" // only lookup in ELO_PARAMS if field was never on form : (ELO_PARAMS[me.fName] === "undefined" ? "" : ELO_PARAMS[me.fName]); }, /** * determines the field's value. * * returns an isoDate if the field is a date. * returns its key, if the field is a localizedKwl. * @returns {String} */ value: function (opts) { var me = this, value; value = ( (me.isDate && sol.common.forms.Utils.getIsoDate(me.fName, { startOfDay: true })) || (me.localizedDynKwlName && me.getSelectedLocalizedDynKwlValue(opts)) || ($var(me.fName) ? $val(me.fName) : ($val(me.fName) || me.valueFromEloParams())) || "" ); return (opts && opts.asNumber) ? toNum(value) // ELO WF global function : value; }, /** * returns current tooltip as a String */ tooltip: function () { var me = this; return (me.element() && me.element().getAttribute("savedtitle")) || ""; }, /** * writes tip as the new tooltip */ writeTooltip: function (tip) { var me = this, el = me.element(); if (!el) { return; } if (typeof tip === "string" && tip) { if (el.getAttribute("savedtitle")) { el.setAttribute("savedtitle", tip); } else { el.setAttribute("title", tip); ELOF.assignTooltips(el.parentElement); } } else { el.getAttribute("savedtitle") ? el.onmouseover = undefined : console.log(me.fName + ": No tooltip created for value '" + tip + "' (type:" + typeof tip + ")"); } }, /** * Write a placeholder to the current field element */ setPlaceholder: function (placeholderValue) { var me = this, el = me.element(), localizedValue; if (!el) { return; } localizedValue = placeholderValue; if (typeof localizedValue === "object") { try { localizedValue = elo.helpers.Text.getText(placeholderValue.key); } catch (e) { /** In case of an error, the placeholder should reset to avoid inconsistent state */ console.warn("could not translate " + placeholderValue.key, e); localizedValue = ""; } } el.setAttribute("placeholder", localizedValue); }, /** * @private please use value() for the same effect */ getSelectedLocalizedDynKwlValue: function (opts) { var me = this; if (opts) { if (opts.full) { return $val(me.fName) || ""; } if (opts.localizedStringOnly) { return me.getSelectedLocalizedDynKwlString() || ""; } } return me.getSelectedLocalizedDynKwlKey() || ""; }, /** * @private please use getSelectedLocalizedDynKwlValue() without a parameter for the same effect */ getSelectedLocalizedDynKwlKey: function (key) { var me = this, val = key || $val(me.name) || ""; if (val.search(/\w+ - ?.*/g) === -1) { return val; } else { return val.slice(0, val.indexOf(" -")); } }, /** * @private please use getSelectedLocalizedDynKwlValue() with { localizedStringOnly:true } for the same effect */ getSelectedLocalizedDynKwlString: function () { var me = this, val = $val(me.name); if (val.search(/\w+ - ?.*/g) === -1) { return val; } else { return val.slice(val.indexOf(" -") + 3); } }, /** * set a field's value to `value`. * * pass an isoDate if the field is a date. * * pass a key, if it's a localizedKwl * * @param {String || Integer(isoDate)} value */ set: function (value) { var me = this, children; (me.isDate && value === "" && ($update(me.fName, value) || true)) || (me.isDate && (sol.common.forms.Utils.setIsoDate(me.fName, value) || true)) || (!me.localizedDynKwl && me.localizedDynKwlName && (me.selectDynKwlEntry(value) || true)) || (me.localizedDynKwlName && (me.setLocalizedDynKwlKey(value) || true)) || (me.editor && (me.editor.set(value) || true)) || $update(me.fName, value); children = me.element().childNodes; value && me.viewSource && children.length === 1 && children[0].tagName === "ACRONYM" && me.element().replaceChild(document.createTextNode(value), children[0]); }, selectDynKwlEntry: function (value) { var me = this, obj = {}; obj[me.fName] = value; (typeof scatterDynamicField === "function" ? scatterDynamicField : _scatterDynamicField)(obj, me.fName); //wf function }, convertToDiv: function (url) { function replace(a, b) { a.parentElement.appendChild(b).parentElement.removeChild(a); } var img = this.element(), div = img.tagName === "DIV" ? img : document.createElement("div"); div.setAttribute("name", img.getAttribute("name")); div.style = "background-image:url(\'" + url + "\');"; div.value || (div.value = ""); (img.tagName !== "DIV") && replace(img.parentElement, div); }, /** * sets an Image divs Image to the image having the passed guid. If no guid is passed, and the field's value contains a guid, it will be used instead * @param {String} guid */ setImage: function (guid, elementType) { var me = this, url, el = me.element(); guid = guid || me.value(); !guid && me.hide(); if (me.form.wfMeetsVersionRequirement("10.02.000") && me.form.wfName) { url = "/" + me.form.wfName + "/apps/rest/api/download/" + guid + "?ticket=" + ELO_PARAMS.ELO_TICKET; if (elementType === "div") { me.convertToDiv(url); } else { el.src = url; } } else { guid && sol.common.forms.Utils.initializeIxSession(function () { elo.IX.ix().checkoutDoc(guid, null, elo.CONST.EDIT_INFO.mbDocument, elo.CONST.LOCK.NO, new de.elo.ix.client.AsyncCallback( function (doc) { url = doc.document.docs[0].url; if (elementType === "div") { me.convertToDiv(url); (url === "./images/") ? me.hide() : me.show(); } else { el.src = url; (url === "./images/") ? me.hide() : me.show(); } }, function () { null; })); }); } }, /** * Sets an image by an URL * @param {String} url URL */ setImageUrl: function (url, elementType) { var me = this, el = me.element(); if (url) { if (elementType === "div") { me.convertToDiv(url); me.show(); el.style = "background-image:url('" + url + "');"; } else { me.show(); el.src = url; } } }, /** * @private please use set() for the same effect * @param {String} key */ setLocalizedDynKwlKey: function (key) { var me = this; if (key) { $update(me.fName, me.getSelectedLocalizedDynKwlKey(key)); $listDyn(me.localizedDynKwlName, me.fName, undefined, function (data) { var result = data.table.find(function (entry) { return entry[0] === key; }); result && $update(me.fName, result[2]); }); } else { $update(me.fName, ""); } }, /** * used by FormWrapper to map state properties to the respective functions * @private */ setAttribute: function (attribute, value) { var me = this, element = me.element(), parentFun; if (!element) { // setAttribute on missing field element should not have any impact console.warn("setAttribute(" + attribute + ") on " + me.name + " skipped"); return; } switch (attribute) { case "readonly": if (element.type === "checkbox") { element[value ? "setAttribute" : "removeAttribute"]("disabled", value); } else { parentFun = value && element.parentElement.setAttribute.bind(element.parentElement) || element.parentElement.removeAttribute.bind(element.parentElement); element.readOnly = value; parentFun("isreadonly", value); $setReadOnly(me.fName, value); me.kwlStyle(value && "remove"); } break; case "optional": value ? me.makeOptional() : me.makeMandatory(); break; case "hidden": value ? me.hide(true, true) : me.show(true, true); break; default: break; } }, /** * @private please use setAttribute("optional", true) */ makeOptional: function () { var me = this; me.modifyEloVerify(me.element(), "remove"); me.label && me.label.parentElement && me.label.parentElement.classList.remove("required"); }, /** * used by assignValidationFunctionToWindow * @private */ modifyEloVerify: function (element, action) { var me = this, eloVerify = element.getAttribute("eloverify"), match; if (eloVerify || (eloVerify.trim() === "")) { switch (action) { case "remove": element.setAttribute("eloverify", me.removeKeyFromEloVerify(eloVerify)); break; case "add": element.setAttribute("eloverify", me.addKeyToEloVerify(eloVerify)); break; default: break; } } }, removeKeyFromEloVerify: function (eloVerify) { var match = /(.* ?)notemptyforward( ?.*)/g.exec(eloVerify); if (match) { eloVerify = match[1] + match[2]; } return eloVerify.trim(); }, /** * @private please use setAttribute("optional", false) */ makeMandatory: function () { var me = this; me.modifyEloVerify(me.element(), "add"); me.label && me.label.parentElement && me.label.parentElement.classList.add("required"); }, addKeyToEloVerify: function (eloVerify) { var match = /(.* ?)notemptyforward( ?.*)/g.exec(eloVerify); if (!match) { eloVerify += " notemptyforward"; } return eloVerify.trim(); }, /** * can be used to set an HTML Attribute * @param {String} attribute name of the style attribute * @param {String} value value for the attribute * @param {Boolean} includingLabel also set attribute for field's label * @param {Boolean} includingSelector also set attribute for field's selector */ setStyleAttribute: function (attribute, value, includingLabel, includingSelector) { var me = this, field = me.element(), label = me.label, selector = me.selector, selectorButton = selector && selector.parentElement.children[1]; field.style[attribute] = value; if (includingLabel && label) { label.style[attribute] = value; } if (includingSelector && selector) { selector.style[attribute] = value; selectorButton.style[attribute] = value; } }, /** * applies a manipulator function to the field * @private */ applyStandardManipulator: function (manipulator, includingLabel, includingSelector) { var me = this, field = me.fName, label = me.label && me.label.id, selector = me.selector && me.selector.name; manipulator(field); includingLabel && manipulator(label); includingSelector && manipulator(selector); }, /** * @private please use show() or hide() for the same effect */ changeVisibility: function (visible, includingLabel, includingSelector) { var me = this; // 9.3 if (visible) { if (me.localizedDynKwl) { me.localizedDynKwl.style.visibility = "visible"; } if (me.datePicker) { me.datePicker.children[0].classList.add("calbutton"); } } else { if (me.localizedDynKwl) { me.localizedDynKwl.style.visibility = "visible"; } if (me.datePicker) { me.datePicker.children[0].classList.remove("calbutton"); } } // 9.3 end me.applyStandardManipulator(visible ? $show.bind(me) : $hide.bind(me), includingLabel, includingSelector); }, /** * unhides/shows the field on the form * @param {Boolean} includingLabel also unhide/show the field's label * @param {Boolean} includingSelector also unhide/show the field's selector */ show: function (includingLabel, includingSelector) { this.changeVisibility(true, includingLabel, includingSelector); }, /** * hides the field on the form * @param {Boolean} includingLabel also hides the field's label * @param {Boolean} includingSelector also hides the field's selector */ hide: function (includingLabel, includingSelector) { this.changeVisibility(false, includingLabel, includingSelector); } }); sol.define("sol.common.forms.FormWrapper.RedactorApi", { requiredConfig: ["field"], initialize: function (config) { var me = this; me.$super("sol.Base", "initialize", [config]); console.log("init redactor field", me.field); if (me.immediately) { me.initRedactor(me.redactorOptions); } }, /** * */ initRedactor: function (options) { var me = this, el = me.field.element(); $R(el, options || {}); }, /** * */ set: function (content) { var me = this, el = me.field.element(); el && $R(el, "source.setCode", content); }, /** * */ get: function (content) { var me = this, el = me.field.element(); return (el && $R(el, "source.getCode")); } }); /** * FormWrapper Templates * * @author ESt, ELO Digital Office GmbH * * @elowf * * The FormWrapper provides a simple templating mechanism. * To use a template, define a field in any state and set its template.name property * to the name of the template: * * IX_MAP_HR_CASSATIONTOGGLE: { * template: { name: "toggle", config: { ... } } * } * * A template is simply a function which creates a state-object using the config passed via template.config * and returns the created state-object in the end. * * Simplest template: * * myReadOnlyTemplate: function (config) { * var configuredTemplate; * * configuredTemplate = { * states: { * myDefaultState: { * fieldProperties: {}, * tabProperties: {} * } * } * } * * configuredTemplate.states.myDefaultState.fieldProperties[config.name] = {readonly: config.value}; * return configuredTemplate; * } * * This template `myReadOnlyTemplate` would do only one thing: set a field readonly when the defaultState is set. * * If you define state properties in a template, they will overwrite existing states, be careful. Never overwrite the stateInit state itself, but only a field's properties in its `fieldProperties`! * * You can define a template like the one above in the OnInit function, if you define it in the extending class. * * OnInit: function { * var me = this; * me.templates.myReadOnlyTemplate = me.myReadOnlyTemplate; * } * * Now you can use the template in stateInit: * * IX_GRP_HR_PERSONNEL_FIRSTNAME: { * template: { name: "myReadOnlyTemplate", config: { name: "IX_GRP_HR_PERSONNEL_FIRSTNAME", value: true } }; * } * * Using the template in stateInit initializes the template. The template then adds the desired attributes to the myDefaultState state. * You usually don't use templates in other states than stateInit. * * Of course, more complex templates make more sense, since a fieldProperty can only be assigned one single template. * * You can not use a template and properties (e.g. "hidden", "readonly", ...) in a fieldProperty at the same time! * * Hint: * * Define a template in OnInit. Initialize it in stateInit. The template then takes in effect when the specific state is triggered (in this case myDefaultState) */ sol.define("sol.common.forms.FormWrapper.Templates", { /** * Toggle Template. * * ### Example config * You have a form containing a toggle. Depending on which option is toggled, you want to hide, or respectively unhide fields. * * A toggle always has an "on-state", an "off-state" and an internal "nothing toggled" state * * { * toggleOptionFieldName: "IX_MAP_HR_CASSATIONTOGGLE", * onValue: "sol.hr.form.personnelfiledocument.cassationactive", * offValue: "sol.hr.form.personnelfiledocument.nocassation", * onState: { * fieldProperties: { * IX_MAP_HR_PERSONNEL_CASSATIONPERIOD: { * hidden: false * }, * IX_MAP_HR_PERSONNEL_CASSATIONPERIOD_UNIT: { * hidden: false * }, * IX_GRP_HR_PERSONNEL_CASSATIONDATE: { * hidden: false * } * } * }, * offState: { * fieldProperties: { * IX_MAP_HR_PERSONNEL_CASSATIONPERIOD: { * hidden: true * }, * IX_MAP_HR_PERSONNEL_CASSATIONPERIOD_UNIT: { * hidden: true * }, * IX_GRP_HR_PERSONNEL_CASSATIONDATE: { * hidden: true, * value: "" * } * } * } * } */ toggle: function (config) { var configuredTemplate, triggerLogic = function (toggledOption) { var me = this; if (toggledOption == config.onValue) { me.setState("toggle_" + config.toggleOptionFieldName + "_anyOptionSelected"); me.setState("toggle_" + config.toggleOptionFieldName + "_OnOptionSelected"); } else if (toggledOption == config.offValue) { me.setState("toggle_" + config.toggleOptionFieldName + "_anyOptionSelected"); me.setState("toggle_" + config.toggleOptionFieldName + "_OffOptionSelected"); } }; configuredTemplate = { states: { stateInit: { fieldProperties: {}, tabProperties: {} } }, OnInitAndTabChange: function () { var me = this, toggledOption = me.fields[config.toggleOptionFieldName].value(); if (!config.tabs || config.tabs.length === 0 || config.tabs.indexOf(me.tabs.activeTab.name) > -1) { toggledOption == "" ? me.setState("toggle_" + config.toggleOptionFieldName + "_defaultState") : triggerLogic.call(me, toggledOption); } } }; configuredTemplate.states.stateInit.fieldProperties[config.toggleOptionFieldName] = { responder: function (form, state, field, value) { triggerLogic.call(form, value); } }; configuredTemplate.states["toggle_" + config.toggleOptionFieldName + "_defaultState"] = config.defaultState; configuredTemplate.states["toggle_" + config.toggleOptionFieldName + "_OnOptionSelected"] = config.onState; configuredTemplate.states["toggle_" + config.toggleOptionFieldName + "_OffOptionSelected"] = config.offState; configuredTemplate.states["toggle_" + config.toggleOptionFieldName + "_anyOptionSelected"] = config.anyState; return configuredTemplate; }, /** * Filechooser Variants (drag&drop, filechooser, webcam picture capturing). * This template has various dependencies (jar-file, libs, ...) * * In some forms, you'd maybe like to let the user select a picture. And save it for later use. * If so, please use the 113_capturepic_webcam "HR"-Form as an inspiration. * * As soon as you defined your own form having all required fields, you can use the following template * ### Example config * * { * name: "personnelphotopicker", * webcamName: "personnelphotocam", * webcamConfig: { * javaStartupButton: "WEBCAM_JAVA", * varNameBtnReset: "JS_WEBCAM_RESET", * varNameBtnSnap: "JS_WEBCAM_SNAP", * varNameContainer: "WEBCAM_INIT", * width: 540, * height: 390, * dest_width: 720, * dest_height: 520, * crop_width: 400, * crop_height: 520, * image_format: "jpeg", * jpeg_quality: 90, * swfURL: "lib_webcam.swf", * fps: 45, * showIfNoCam: true * }, * dropZoneId: "dropZone", * filePickerId: "filePicker", * accept: "image/jpeg, image/jpg, image/png", * maxSize: "3", //Megabyte (float values possible) * maskNameForRule: "Personnel file", * solTypeForRule: "PERSONNELFILE", * photoReferenceField: "HR_PERSONNEL_PHOTO_GUID", * photoReferenceFieldObjId: "HR_PERSONNEL_PHOTO_OBJID", * clearPreviewField: "JS_PICTURE_CLEAR", * filePickerField: "JS_FILEPICKER", * photoConfig: { * maskName: "Personnel file document", * pictureName: "Mitarbeiterfoto" * } * } */ fileChooserVariants: function (config) { var configuredTemplate; configuredTemplate = { states: { stateInit: { fieldProperties: {}, tabProperties: {} } }, OnInitAndTabChange: function () { var me = this; me.fcv = sol.create("sol.common.forms.FileChooserVariants", config); }, OnSaveRule: { name: "FileDragDropAndDialog" + config.name, rule: { maskName: config.maskNameForRule, solType: config.solTypeForRule, saveValues: function () { var me = this; me.fcv.uploadFile(); return true; } } } }; return configuredTemplate; }, /** * Template for a date which will receive its value from a unit field and its selector. * * Validation messages will be displayed, if the user tries to change a field which would falsify the calculation * * e.g. * Start date "20170101" * * unit "4" * * unitselector value "days" * * target date = 20170105 * * Please always name your selector unit field "FIELDNAME_UNIT" * Other names are not supported yet. * * ### Example config * * { * startDateFnOrValue: "IX_GRP_HR_PERSONNEL_DATEOFJOINING", * unitValueFieldName: "IX_MAP_HR_PERSONNEL_PROBATIONARYPERIODDURATION", * unitSelectorFieldName: "IX_MAP_HR_PERSONNEL_PROBATIONARYPERIODDURATION_UNIT", * targetDateFieldName: "IX_MAP_HR_PERSONNEL_ENDOFPROBATIONARY", * validationMessage: "Da im Feld `Ende der Probezeit` ein von der Berechnung abweichender Wert eingegeben wurde, ist dieses Feld jetzt gesperrt. Leeren Sie das Feld, wenn Sie die Berechnungsfunktion verwenden möchten!" * } * */ dateFromUnitSelectorRestrictive: function (config) { var configuredTemplate; configuredTemplate = { states: { stateInit: { fieldProperties: {}, tabProperties: {} } } }; configuredTemplate.states["dfus_" + config.targetDateFieldName + "_HasNoValue"] = { fieldProperties: {} }; configuredTemplate.states["dfus_" + config.targetDateFieldName + "_HasNoValue"].fieldProperties[config.unitValueFieldName] = { readonly: false, validator: false }; configuredTemplate.states["dfus_" + config.targetDateFieldName + "_HasNoValue"].fieldProperties[config.unitSelectorFieldName] = { readonly: false, validator: false }; configuredTemplate.states["dfus_" + config.targetDateFieldName + "_HasCustomValue"] = { fieldProperties: {} }; configuredTemplate.states["dfus_" + config.targetDateFieldName + "_HasCustomValue"].fieldProperties[config.unitValueFieldName] = { readonly: true, validator: function () { return config.validationMessage; } }; configuredTemplate.states["dfus_" + config.targetDateFieldName + "_HasCustomValue"].fieldProperties[config.unitSelectorFieldName] = { readonly: true, validator: function () { return config.validationMessage; } }; configuredTemplate.states.stateInit.fieldProperties[config.targetDateFieldName] = { responder: function (form, state, field, value) { if (value === "") { form.setState("dfus_" + config.targetDateFieldName + "_HasNoValue"); } else if (value !== form.returnCalculatedDate(config.startDateFnOrValue, config.unitValueFieldName, config.targetDateFieldName, config.offsetNumber, config.offsetUnit)) { form.setState("dfus_" + config.targetDateFieldName + "_HasCustomValue"); } } }; configuredTemplate.states.stateInit.fieldProperties[config.unitValueFieldName] = { responder: function (form, state, field, value) { if (field.value() && form.fields[config.unitSelectorFieldName].value()) { form.setCalculatedDate(config.startDateFnOrValue, config.unitValueFieldName, config.targetDateFieldName, config.offsetNumber, config.offsetUnit); } } }; configuredTemplate.states.stateInit.fieldProperties[config.unitSelectorFieldName] = { responder: function (form, state, field, value) { if (field.value() && form.fields[config.unitSelectorFieldName].value()) { form.setCalculatedDate(config.startDateFnOrValue, config.unitValueFieldName, config.targetDateFieldName, config.offsetNumber, config.offsetUnit); } } }; return configuredTemplate; }, /** * Template for a date which will receive its value from a unit field, its duration-selector and an optional "termination-Point" selector. * * Please always name your selector unit field "FIELDNAME_UNIT" * Other names are not supported yet. * * Also, it is recommended, to name your termination point field "FIELDNAME_TP" * * ### Example config * * IX_MAP_HR_PERSONNEL_NEXTPOSSIBLEDISMISSAL: { * template: { * name: "dateFromUnitSelector", * config: { * recalculate: true, * startDateFnOrValue: undefined, // == today * unitValueFieldName: "IX_MAP_HR_PERSONNEL_PERIODOFNOTICE", * unitSelectorFieldName: "IX_MAP_HR_PERSONNEL_PERIODOFNOTICE_UNIT", * terminationPointFieldName: "IX_MAP_HR_PERSONNEL_PERIODOFNOTICE_TP", * targetDateFieldName: "IX_MAP_HR_PERSONNEL_NEXTPOSSIBLEDISMISSAL", * } * } * } * */ dateFromUnitSelector: function (config) { var configuredTemplate; configuredTemplate = { states: { stateInit: { fieldProperties: {}, tabProperties: {} } } }; configuredTemplate.states["dfus_" + config.targetDateFieldName + "_HasCustomValue"] = { fieldProperties: {} }; configuredTemplate.states["dfus_" + config.targetDateFieldName + "_HasCustomValue"].fieldProperties[config.unitValueFieldName] = { value: "", overwrite: true }; configuredTemplate.states["dfus_" + config.targetDateFieldName + "_HasCustomValue"].fieldProperties[config.unitSelectorFieldName] = { value: "", overwrite: true }; configuredTemplate.states.stateInit.fieldProperties[config.targetDateFieldName] = { responder: function (form, state, field, value) { if (value !== form.returnCalculatedDate(config.startDateFnOrValue, config.unitValueFieldName, config.terminationPointFieldName || "", config.targetDateFieldName, config.offsetNumber, config.offsetUnit)) { form.setState("dfus_" + config.targetDateFieldName + "_HasCustomValue"); } } }; if (config.terminationPointFieldName) { configuredTemplate.states.stateInit.fieldProperties[config.terminationPointFieldName] = { responder: function (form, state, field, value) { if (field.value() && form.fields[config.terminationPointFieldName].value()) { form.setCalculatedDate(config.startDateFnOrValue, config.unitValueFieldName, config.terminationPointFieldName || "", config.targetDateFieldName, config.offsetNumber, config.offsetUnit); } } }; } configuredTemplate.states.stateInit.fieldProperties[config.unitValueFieldName] = { responder: function (form, state, field, value) { if (field.value() && form.fields[config.unitSelectorFieldName].value()) { form.setCalculatedDate(config.startDateFnOrValue, config.unitValueFieldName, config.terminationPointFieldName || "", config.targetDateFieldName, config.offsetNumber, config.offsetUnit); } } }; configuredTemplate.states.stateInit.fieldProperties[config.unitSelectorFieldName] = { responder: function (form, state, field, value) { if (field.value() && form.fields[config.unitSelectorFieldName].value()) { form.setCalculatedDate(config.startDateFnOrValue, config.unitValueFieldName, config.terminationPointFieldName || "", config.targetDateFieldName, config.offsetNumber, config.offsetUnit); } } }; if (config.recalculate) { configuredTemplate.OnInitAndTabChange = function () { var me = this, unitValueField = me.fields[config.unitValueFieldName]; if (unitValueField && unitValueField.value()) { me.setCalculatedDate(config.startDateFnOrValue, config.unitValueFieldName, config.terminationPointFieldName || "", config.targetDateFieldName, config.offsetNumber, config.offsetUnit); } }; } return configuredTemplate; } });