diff --git a/Xrm.Portal.js b/Xrm.Portal.js index dbf4c15..360f18a 100644 --- a/Xrm.Portal.js +++ b/Xrm.Portal.js @@ -1,6 +1,740 @@ +/** + * https://github.com/dynamicscode/XrmPortalJS/blob/master/Xrm.Portal.js + */ + +//#region New Control Classes + +/** + * @abstract + * @class BaseControl + * Base class for all controls. + */ +class BaseControl { + /** + * Creates an instance of BaseControl. + * @param {jQuery} c - The jQuery element for the control. + */ + constructor(c) { + /** @type {jQuery} */ + this.c = c; + /** @type {string} */ + this.id = $(c).prop("id"); + /** @type {string} */ + this.vg = ""; + /** @type {object} */ + this.s = Xrm.Portal.Utility.Selector; + } + /** + * Sets the validation group. + * @param {string} g - The validation group. + * @returns {this} For method chaining. + */ + setValidationGroup(g) { + this.vg = g; + return this; + } +} + +/** + * @class GenericControl + * @extends BaseControl + * Generic control implementation for portal fields. + */ +class GenericControl extends BaseControl { + /** + * Creates an instance of GenericControl. + * @param {jQuery} c - The jQuery element. + */ + constructor(c) { + super(c); + /** @type {HTMLElement|null} */ + this.cc = document.getElementById(this.id + '_cc'); + /** @type {string} */ + this.typ = this.c.is("select.select2-hidden-accessible") ? "select2" : (this.c.is("select") ? "select" : "generic"); + } + /** + * Sets the label of the form field. + * This method updates the HTML content of the label element within the form. + * @param {string} label - The new label. + * @returns {this} For chaining. + */ + setLabel(label) { + this.c.parent().siblings(".table-info").children("label").html(label); + return this; + } + /** + * Gets the current label. + * TODO: Use Xrm.Portal.Utility.Selector.appendLabel + * @returns {string} The control label. + */ + getLabel() { + return this.c.parent().siblings(".table-info").children("label").html(); + } + /** + * Gets the current value. + * @returns {*} The control value. + */ + getValue() { + switch (this.typ) { + case "select2": + return this.c.select2('data'); + default: + return this.c.val(); + } + } + /** + * Sets the control value. + * @param {*} value - The value to set. + * @param {*} [name] - Optional alternative name (for lookups). + * @param {*} [logicalName] - Optional entity logical name (for lookups). + * @returns {this} For chaining. + */ + setValue(value, name, logicalName) { + if (value == null) { + this.c.val(value); + if (this.cc != null) this.cc.updateView(); + return this; + } + function checkMultiselectValue(value) { + const aval = Array.isArray(value) ? value : [value]; + if (aval.filter(v => v == null || !v.hasOwnProperty('id')).length > 0) + console.error(`Cannot set value '${JSON.stringify(aval)}'; missing 'id' property.`); + else aval.forEach(v => v.name = v.text); + return aval; + } + switch (this.typ) { + case "select": + this.c.val(checkMultiselectValue(value)[0].id); + break; + case "select2": + this.c.val(checkMultiselectValue(value).map(v => v.id)); + this.c.trigger('change'); + break; + default: + this.c.val(value); + break; + } + if (this.cc != null) this.cc.updateView(); + return this; + } + /** + * Determines if the control is visible. + * @returns {boolean} True if visible. + */ + isVisible() { + return this.c.parent().css('display') !== 'none' && + !this.c.is(':hidden') && + this.c.css('visibility') !== 'hidden' && + this.c.css('opacity') > 0; + } + /** + * Sets the control's visibility. + * @param {boolean} isVisible - True to show. + * @param {boolean} isMandatory - If required, validation is applied. + * @returns {this} For chaining. + */ + setVisible(isVisible, isMandatory) { + const g = this.c.parent().parent(); + this.setRequired(isMandatory); + isVisible ? g.show() : g.hide(); + return this; + } + /** + * Converts the field into a select2 control. + * @param {*} options - Options for select2. + * @returns {this} For chaining. + */ + select2(options) { + const config = { width: '100%', allowClear: true, placeholder: { id: null, name: "Select one" } }; + $.extend(true, config, options); + if (this.c.is(':not(select)')) throw "Only HTML SELECT elements can be converted to select2."; + $.getScript("/select2.js", () => { + if (!$("link[href='/select2.css']").length) + $('head').append(''); + this.c.select2(config); + }); + return this; + } + /** + * Sets the active state. + * @param {boolean} isActive - True if active. + * @returns {this} For chaining. + */ + setActive(isActive) { + this.c.toggleClass('disabled-pointer-events', !isActive); + return this; + } + /** + * Enables or disables the field. + * @param {boolean} isDisabled - True to disable. + * @returns {this} For chaining. + */ + setDisable(isDisabled) { + this.c.prop('disabled', isDisabled); + return this; + } + /** + * Sets the field as required. + * @param {boolean} isRequired - True if required. + * @param {Function} [customFunction] - Optional additional validation. + * @param {string} [customMessage] - Optional error message. + * @returns {this} For chaining. + */ + setRequired(isRequired, customFunction, customMessage) { + const g = this.c.parent().siblings(".table-info"); + if (isRequired || customFunction) + Xrm.Portal.Utility.Validation.setValidation(g, this, isRequired, this.vg, customFunction, customMessage); + else + Xrm.Portal.Utility.Validation.removeValidation(g, this); + return this; + } + /** + * Attaches an OnChange event. + * @param {Function} callback - The event handler. + * @param {boolean} [triggerOnLoad] - Whether to trigger initially. + * @returns {this} For chaining. + */ + attachOnChange(callback, triggerOnLoad) { + Xrm.Portal.Utility.Event.attachOnChange(this.c, callback, triggerOnLoad); + return this; + } + /** + * Removes the OnChange event. + * @returns {this} For chaining. + */ + removeOnChange() { + Xrm.Portal.Utility.Event.removeOnChange(this.c); + return this; + } + /** + * Transforms field to a canvas element for drawing. + * @returns {void} + */ + transformToCanvas() { + this.c.hide(); + if (this.c.parent().children().last()[0].tagName !== "CANVAS") { + const canvasId = this.id + "Canvas"; + this.c.parent().append(""); + Xrm.Portal.Control.Canvas(this.id); + } + } +} + +/** + * @class TabControl + * @extends GenericControl + * Represents a tab control. + */ +class TabControl extends GenericControl { + constructor(c) { + super(c); + } + /** + * Not implemented. + * @throws {Error} Always throws. + */ + getValue() { throw "TabControl: getValue not applicable"; } + /** + * Not implemented. + * @throws {Error} Always throws. + */ + setValue(value) { throw "TabControl: setValue not applicable"; } + /** + * Sets tab visibility along with its title. + * @param {boolean} isVisible + * @param {boolean} isMandatory + * @returns {this} + */ + setVisible(isVisible, isMandatory) { + const h = this.c.prev(); + if (isVisible) { + this.c.show(); + if (h.is('h2')) h.show(); + } else { + this.c.hide(); + if (h.is('h2')) h.hide(); + } + return this; + } +} + +/** + * @class SectionControl + * @extends GenericControl + * Represents a section control. + */ +class SectionControl extends GenericControl { + constructor(c) { + super(c); + } + getValue() { throw "SectionControl: getValue not applicable"; } + setValue(value) { throw "SectionControl: setValue not applicable"; } + /** + * Sets section visibility. + * @param {boolean} isVisible + * @param {boolean} isMandatory + * @returns {this} + */ + setVisible(isVisible, isMandatory) { + const g = this.c; + isVisible ? g.parent().show() : g.parent().hide(); + return this; + } +} + +/** + * @class NotesControl + * @extends GenericControl + * Represents a notes control. + */ +class NotesControl extends GenericControl { + constructor(c) { + super(c); + this.cc = document.getElementById(this.id + '_cc'); + } + getValue() { throw "NotesControl: getValue not implemented"; } + setValue(value) { throw "NotesControl: setValue not implemented"; } + /** + * Gets the count of attachments. + * @returns {number} + */ + getNumberofAttachments() { + return this.c.find('.notes').children().length; + } + /** + * Checks if attachments exist. + * @returns {boolean} + */ + hasAttachments() { + return this.c.find('.notes-empty').css('display') !== 'block'; + } + getCurrentPage() { throw "NotesControl: getCurrentPage not implemented"; } + /** + * Sets visibility for notes. + * @param {boolean} isVisible + * @param {boolean} isMandatory + * @returns {this} + */ + setVisible(isVisible, isMandatory) { + const g = this.c.parent().parent(); + isVisible ? g.show() : g.hide(); + return this; + } + setActive(isActive) { + this.c.toggleClass('disabled-pointer-events', !isActive); + return this; + } + setRequired(isRequired, customFunction, customMessage) { + throw "NotesControl: setRequired not implemented"; + } +} + +/** + * @class SubgridControl + * @extends GenericControl + * Represents a subgrid control. + */ +class SubgridControl extends GenericControl { + constructor(c) { + super(c); + this.cc = document.getElementById(this.id + '_cc'); + } + getValue() { throw "SubgridControl: getValue not implemented"; } + setValue(value) { throw "SubgridControl: setValue not implemented"; } + /** + * Gets the row count on the current page. + * @returns {number} + */ + getRowCountFromCurrentPage() { + return this.c.find('div > div.view-grid > table > tbody > tr').length; + } + getCurrentPage() { throw "SubgridControl: getCurrentPage not implemented"; } + /** + * Sets subgrid visibility. + * @param {boolean} isVisible + * @param {boolean} isMandatory + * @returns {this} + */ + setVisible(isVisible, isMandatory) { + const g = this.c.parent().parent(); + this.setRequired(isMandatory); + isVisible ? g.show() : g.hide(); + return this; + } + /** + * Controls the create button visibility. + * @param {boolean} isVisible + * @returns {this} + */ + setCreateVisible(isVisible) { + this.c.find('a[title=Create]').css('display', isVisible ? '' : 'none'); + return this; + } + /** + * Sets required state. + * @param {boolean} isRequired + * @param {Function} [customFunction] + * @param {string} [customMessage] + * @returns {this} + */ + setRequired(isRequired, customFunction, customMessage) { + const g = this.c.parent().siblings(".table-info"); + if (isRequired || customFunction) + Xrm.Portal.Utility.Validation.setValidation(g, this, isRequired, this.vg, customFunction, customMessage); + else + Xrm.Portal.Utility.Validation.removeValidation(g, this); + return this; + } + attachOnChange(callback) { + Xrm.Portal.Utility.Event.attachOnLoaded(this.c, callback); + return this; + } + /** + * Shows actions as links in the subgrid. + * @returns {this} + */ + showActionsAsLinks() { + this.c.find(".entity-grid.subgrid").on("loaded", function () { + $(this).children(".view-grid").find("tr[data-id]") + .each(function (i, e) { + let $tdActions = $(this).children("td:last"), + id = $(this).attr("data-id"); + $tdActions.find(".dropdown.action").hide(); + $tdActions.find(".dropdown.action > ul > li > a").addClass("btn,btn-info").appendTo($tdActions); + }); + }).trigger("loaded"); + return this; + } +} + +/** + * @class QuickViewControl + * @extends GenericControl + * Represents a quick view control. + */ +class QuickViewControl extends GenericControl { + constructor(c) { + super(c); + this.cc = document.getElementById(this.id + '_cc'); + } + getValue() { + const values = this.c.contents().find('.form-control'); + const allowFormats = ['DD/MM/YYYY', 'YYYY/MM/DD']; + const data = {}; + let aName = ''; + for (let i = 0; i < values.length; i++) { + aName = values[i].id; + if ($(values[i]).prop('className').indexOf('lookup') > -1) { + aName = aName.substr(0, aName.lastIndexOf('_name')); + data[aName] = values[i].value; + } else if ($(values[i]).prop('id').indexOf('_datepicker_description') > -1 || + $(values[i]).prop('className').indexOf('datetime') > -1) { + aName = aName.replace('_datepicker_description', ''); + data[aName] = ""; + if (values[i].value) { + data[aName] = moment(values[i].value, allowFormats).toDate().toString('dd/MM/yyyy'); + } + } else if ($(values[i]).prop('className').indexOf('picklist') > -1) { + data[aName + "_text"] = $(values[i]).find('option:selected').text(); + data[aName] = $(values[i]).find('option:selected').val(); + } else { + data[aName] = values[i].value; + } + } + return data; + } + setValue(value) { throw "QuickViewControl: setValue not implemented"; } + /** + * Sets quick view visibility. + * @param {boolean} isVisible + * @param {boolean} isMandatory + * @returns {this} + */ + setVisible(isVisible, isMandatory) { + const g = this.c.parent().parent(); + isVisible ? g.show() : g.hide(); + return this; + } + setDisable(isDisabled) { throw "QuickViewControl: setDisable not implemented"; } + setRequired(isRequired, customFunction, customMessage) { throw "QuickViewControl: setRequired not implemented"; } + attachOnChange(callback) { + Xrm.Portal.Utility.Event.attachOnLoad(this.c, callback); + return this; + } + /** + * Renders an Adaptive Card. + * @param {string} attribute - The target field. + * @param {object} card - The card template. + * @param {object} data - Data for the template. + * @returns {HTMLElement} The rendered card. + */ + renderAdaptiveCard(attribute, card, data) { + Xrm.Portal.Form.get(attribute).cL.parent().next().next().remove(); + if (Xrm.Portal.Form.get(attribute).getValue().id != "") { + const parsedCard = Xrm.Portal.Utility.AdaptiveCard.parseTemplate(card, data); + const adaptiveCard = new AdaptiveCards.AdaptiveCard(); + adaptiveCard.hostConfig = new AdaptiveCards.HostConfig({ + fontFamily: "Segoe UI, Helvetica Neue, sans-serif" + }); + adaptiveCard.parse(parsedCard); + const renderedCard = adaptiveCard.render(); + Xrm.Portal.Form.get(attribute).cL.parent().parent().append(renderedCard); + return renderedCard; + } + } +} + +/** + * @class LookupControl + * @extends GenericControl + * Represents a lookup control. + */ +class LookupControl extends GenericControl { + constructor(c) { + super(c); + this.cL = c; + this.cN = this.s.getLookupName(this.id); + this.cE = this.s.getLookupEntity(this.id); + } + enableOneClick() { + this.cN.on('click', () => this.cL.siblings('div.input-group-btn').children('button.launchentitylookup').click()); + return this; + } + getValue() { return { id: this.cL.val(), name: this.cN.val(), logicalname: this.cE.val() }; } + setValue(value, name, logicalName) { + if (value && value.id && value.name && value.logicalname) { + this.cL.val(value.id); + this.cN.val(value.name); + this.cE.val(value.logicalname); + } else { + this.cL.val(value); + this.cN.val(name); + this.cE.val(logicalName); + } + if (this.cL.val() !== '') { + let m = this.cN.nextAll('div.text-muted').first(); + if (m.length) m.hide(); + } + return this; + } + setVisible(isVisible, isMandatory) { + this.setRequired(isMandatory); + const g = this.cL.parent().parent().parent(); + isVisible ? g.show() : g.hide(); + return this; + } + setActive(isActive) { + this.c.toggleClass('disabled-pointer-events', !isActive); + return this; + } + setDisable(isDisabled) { + this.cN.prop('disabled', isDisabled); + this.cN.siblings('div.input-group-btn').toggle(!isDisabled); + return this; + } + setRequired(isRequired, customFunction, customMessage) { + const g = this.cL.parent().parent().siblings(".table-info"); + if (isRequired || customFunction) + Xrm.Portal.Utility.Validation.setValidation(g, this, isRequired, this.vg, customFunction, customMessage); + else + Xrm.Portal.Utility.Validation.removeValidation(g, this); + return this; + } + attachOnChange(callback, triggerOnLoad) { + Xrm.Portal.Utility.Event.attachOnChange(this.cL, callback, triggerOnLoad); + return this; + } +} + +/** + * @class CheckboxControl + * @extends GenericControl + * Represents a checkbox control. + */ +class CheckboxControl extends GenericControl { + constructor(c) { + super(c); + } + getValue() { return this.c.prop("checked"); } + setValue(value) { + this.c.prop("checked", value); + return this; + } + setVisible(isVisible, isMandatory) { + const g = this.c.parent().parent().parent(); + this.setRequired(isMandatory); + isVisible ? g.show() : g.hide(); + return this; + } +} + +/** + * @class RadioControl + * @extends GenericControl + * Represents a radio button control. + */ +class RadioControl extends GenericControl { + constructor(c) { + super(c); + } + /// + /// Sorts the radio buttons in the form. + /// + /// The sorting function to use. + /// The current instance for chaining. + /// + /// Xrm.Portal.Form.get("radio").sort(function(a, b) { + /// return $(a).val() > $(b).val() ? 1 : -1; + /// }); + /// + sort(sortFn = Xrm.Portal.SortFunctions_Radio.label, sortOrd = Xrm.Portal.SortOrder.Ascending) { + if (this.c.children('span.radio_item_wrapper').length === 0) { + this.c.children('input').each((i, inp) => { + const $i = $(`#${inp.id}`), + $il = $i.add($(`label[for=${inp.id}]`)), + groupname = `radio_item_wrapper_${$i.val()}`; + $il.wrapAll(``); + }); + } + const listItems = this.c.children('span.radio_item_wrapper').get(); + listItems.sort(sortFn); + if (sortOrd === Xrm.Portal.SortOrder.Descending) listItems.reverse(); + this.c.empty().append(listItems); + return this; + } + getValue() { return this.c.find("input:checked").val(); } + setValue(value) { + if (value != null) + this.c.find("input[value*=" + value + "]").prop("checked", value); + else + this.c.find('input[type=radio]').prop('checked', false); + return this; + } + setVisible(isVisible, isMandatory) { + const g = this.c.parent().parent(); + this.setRequired(isMandatory); + isVisible ? g.show() : g.hide(); + return this; + } + setDisable(isDisabled) { + this.c.find('input[type=radio]').prop("disabled", isDisabled); + return this; + } +} + +/** + * @class DatetimePickerControl + * @extends GenericControl + * Represents a datetime picker control. + */ +class DatetimePickerControl extends GenericControl { + constructor(c) { + super(c); + } + getValue() { return this.c.val(); } + getData() { return this.c.next().data('DateTimePicker'); } +} + +/** + * @class MultiselectControl + * @extends GenericControl + * Represents a multiselect control. + */ +class MultiselectControl extends GenericControl { + constructor(c) { + super(c); + } + isOptionSelected(optionValueOrLabel) { + const currentValue = this.getValue(); + return currentValue.some(existingItem => + existingItem.Value === optionValueOrLabel || existingItem.Label.UserLocalizedLabel.Label === optionValueOrLabel + ); + } + getValue() { + try { + const jsonValue = this.c.val(); + return JSON.parse(jsonValue || "[]"); + } catch (error) { + console.error("Error parsing Multiselect value as JSON:", error); + return []; + } + } + setValue(values) { + try { + const jsonString = JSON.stringify(values); + this.c.val(jsonString).trigger("change"); + } catch (error) { + console.error("Error stringifying Multiselect values:", error); + } + return this; + } + selectOption(optionValueOrLabel) { + try { + const options = this.getOptions(); + const validItem = options.find(opt => + opt.Value === optionValueOrLabel || opt.Label.UserLocalizedLabel.Label === optionValueOrLabel + ); + if (!validItem) { + console.warn("Invalid optionValueOrLabel. Cannot add to Multiselect:", optionValueOrLabel); + return this; + } + if (!this.isOptionSelected(validItem.Value)) { + const currentValue = this.getValue(); + currentValue.push(validItem); + this.setValue(currentValue); + } + } catch (error) { + console.error("Error adding optionValueOrLabel to Multiselect:", error); + } + return this; + } + unselectOption(optionValueOrLabel) { + try { + const options = this.getOptions(); + const validItem = options.find(opt => + opt.Value === optionValueOrLabel || opt.Label.UserLocalizedLabel.Label === optionValueOrLabel + ); + if (!validItem) { + console.warn("Invalid optionValueOrLabel. Cannot remove from Multiselect:", optionValueOrLabel); + return this; + } + if (this.isOptionSelected(validItem.Value)) { + const currentValue = this.getValue(); + const updatedValue = currentValue.filter(existingItem => + existingItem.Value !== validItem.Value + ); + this.setValue(updatedValue); + } + } catch (error) { + console.error("Error removing optionValueOrLabel from Multiselect:", error); + } + return this; + } + getOptions() { + try { + const options = JSON.parse( + this.c.closest('.control').find('[pcf-controlcontext]').attr("pcf-controlcontext") + ).ControlProperties.value_Options; + return options || []; + } catch (error) { + console.error("Error retrieving options for Multiselect:", error); + return []; + } + } + setVisible(isVisible, isMandatory) { + const g = this.c.parent().parent(); + this.setRequired(isMandatory); + isVisible ? g.show() : g.hide(); + return this; + } +} + +// #endregion New Control Classes +//#region Xrm Class var Xrm = Xrm || {}; Xrm.Portal = { + version: "ai.0.0.6", // User: { // getAsync: async function() { // var t = await Xrm.Portal.Utility.Auth.get(); @@ -11,10 +745,10 @@ Xrm.Portal = { Auth: { /*authorize: function() { },*/ - decode: function(token) { + decode: function (token) { if (token !== "") { var base64Url = token.split('.')[1]; - var base64 = decodeURIComponent(atob(base64Url).split('').map(function(c) { + var base64 = decodeURIComponent(atob(base64Url).split('').map(function (c) { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); @@ -22,77 +756,122 @@ Xrm.Portal = { } throw "No login user is detected."; }, - get: function() { + get: function () { return $.get("/_services/auth/token"); } }, - hasPage_Validators: function() { - return typeof(Page_Validators) !== typeof(undefined); + hasPage_Validators: function () { + return typeof (Page_Validators) !== typeof (undefined); }, Selector: { - appendSelector: function(id) { + prependSelector: function (id) { return "#" + id; }, - appendLabel: function(id) { + appendSelector: function (id) { + return this.prependSelector(id); + }, + appendLabel: function (id) { return id + "_label"; }, - getByControlId: function(id) { - return $(this.appendSelector(id)); + getByControlId: function (id) { + return $(this.prependSelector(id)); + }, + getByCSSSelector: function (cssSelector) { + return $(cssSelector); }, - getTextLabel: function(id) { - return $(this.appendSelector(id) + "_label"); + getTextLabel: function (id) { + return $(this.prependSelector(id) + "_label"); }, - getLookupName: function(id) { - return $(this.appendSelector(id) + "_name"); + getLookupName: function (id) { + return $(this.prependSelector(id) + "_name"); }, - getLookupEntity: function(id) { - return $(this.appendSelector(id) + "_entityname"); + getLookupEntity: function (id) { + return $(this.prependSelector(id) + "_entityname"); }, - getByDataName: function(id) { + getByDataName: function (id) { return $("[data-name=" + id + "]"); } }, Validation: { - postFix: "_Xrm_Client_Validation", - required: function(control) { - console.log("Validation.required -> id: " + control); + postFix: "Xrm_Client_Validation", + //Note: adding skipPrefixLabel: true will skip the prefix label in the error message and only show the message + validators: + { + "min2": { + callback: (field) => field.getValue()?.trim().length >= 2, + message: 'must be at least 2 characters long.' + }, + "commonchars": { + callback: (field) => /^[a-zA-Z0-9.,'"\s-]*$/.test(field.getValue()?.trim()), + message: 'can only contain letters, numbers, spaces, and common punctuation.' + }, + "d11": { + //empty or 11 digits + callback: (field) => /^\d{11}$/.test(field.getValue()?.trim()), + message: 'must be 11 digits.' + }, + "d9": { + callback: (field) => /^\d{9}$/.test(field.getValue()?.trim()), + message: 'must be 9 digits.' + }, + "email": { + callback: (field) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(field.getValue()?.trim()), + message: 'must be a valid email address.' + }, + "phone": { + callback: (field) => /^\d{10}$/.test(field.getValue()?.trim()), + message: 'must be 10 digits.' + }, + "postalcode": { + callback: (field) => /^\d{4}$/.test(field.getValue()?.trim()), + message: 'must be 4 digits.' + }, + "number": { + callback: (field) => /^\d+$/.test(field.getValue()?.trim()), + message: 'must be a number.' + } + }, + required: function (control) { + //console.log("Validation.required -> id: " + control); var value = control.getValue(); + value = typeof value === 'string' ? value.trim() : value; if (value == null || value == "" || (value.hasOwnProperty("id") && (value.id == "" || value.id == null))) { return false; } else { return true; } }, - removeValidation: function(groupObj, control) { - $(groupObj).attr("class", "info"); + removeValidation: function (groupObj, control, customPostFix = "") { + $(groupObj).attr("class", "table-info"); var l = Xrm.Portal.Utility.Selector.appendLabel(control.id); - var vid = l + this.postFix; + var vid = [l, this.postFix, customPostFix].filter(Boolean).join('_'); if (Xrm.Portal.Utility.hasPage_Validators()) { Page_Validators = $.grep(Page_Validators, - function(e) { + function (e) { return $(e).prop('controltovalidate') != "" && $(e).prop('id') != vid; } ); } }, - setValidation: function(groupObj, control, isRequired, validationGroup, validationFunction, customMessage) { + setValidation: function (groupObj, control, isRequired, validationGroup, validationFunction, customMessage, customPostFix = "") { var id = control.id; var l = Xrm.Portal.Utility.Selector.appendLabel(id); - var vid = l + this.postFix; + var vid = [l, this.postFix, customPostFix].filter(Boolean).join('_'); var g = groupObj; var c = Xrm.Portal.Utility.Selector.getByControlId(id); if (c.length > 0) { - isRequired && $(g).attr("class", "info required"); + isRequired && $(g).attr("class", "table-info required"); if (Xrm.Portal.Utility.hasPage_Validators()) { Page_Validators = $.grep(Page_Validators, - function(e) { + function (e) { return $(e).prop('controltovalidate') != "" && $(e).prop('id') != vid; } ); - validationGroup = (validationGroup == null || validationGroup == "") && Page_Validators.length > 0 ? Page_Validators[0].validationGroup : validationGroup; + validationGroup = (validationGroup == null || validationGroup == "") + && Page_Validators.length > 0 ? Page_Validators[0].validationGroup : validationGroup; - var vF = validationFunction == null && isRequired ? function() { + var vF = validationFunction == null && isRequired ? function () { return Xrm.Portal.Utility.Validation.required(control) } : validationFunction; @@ -107,12 +886,11 @@ Xrm.Portal = { nv.validationGroup = validationGroup; nv.initialvalue = ""; nv.evaluationfunction = vF; - // Add the new validator to the page validators array: Page_Validators.push(nv); // Wire-up the click event handler of the validation summary link - $("a[href='#" + l + "']").on("click", function() { + $("a[href='#" + l + "']").on("click", function () { scrollToAndFocus("'" + l + "'", "'" + id + "'"); }); } @@ -120,7 +898,7 @@ Xrm.Portal = { }, }, Event: { - wireUp: function(events) { + wireUp: function (events) { console.log("Event.wireUp -> events: " + events); for (var i in events) { var e = events[i]; @@ -140,26 +918,35 @@ Xrm.Portal = { } } }, - attachOnLoaded: function(control, callback) { + attachOnLoaded: function (control, callback) { control.on('loaded', callback); }, - attachOnLoad: function(control, callback) { + attachOnLoad: function (control, callback) { control.on('load', callback); }, - attachOnChange: function(control, callback, triggerOnLoad) { - control.change(callback); - if (triggerOnLoad != false) - control.trigger('change'); + attachOnChange: function (control, callback, triggerOnLoad) { + if (control.is("select[multiple]")) { + // Handle multiselect fields + control.on("change", function () { + const selectedValues = control.val(); // Get selected values + callback(selectedValues); // Pass selected values to the callback + }); + } else { + // Existing behavior for other controls + control.change(callback); + if (triggerOnLoad != false) + control.trigger('change'); + } }, - attachDateTimePickerOnChange: function(control, callback) { + attachDateTimePickerOnChange: function (control, callback) { control.next().datetimepicker().on('dp.change', callback); }, - removeOnChange: function(control) { + removeOnChange: function (control) { control.off('change'); } }, AdaptiveCard: { - parseTemplate: function(card, data) { + parseTemplate: function (card, data) { var str = JSON.stringify(card); var matches = str.match(/(\\)?"\$\{.+?\}(\\)?"/gm) if (matches != null) { @@ -180,12 +967,12 @@ Xrm.Portal = { } }, Ui: { - get: function(id) { + get: function (id) { var c = Xrm.Portal.Utility.Selector.getByDataName(id); var ct = this.getControlType(c); }, - getControlType: function(c) { - console.log("getControlType -> c: " + c); + getControlType: function (c) { + //console.log("getControlType -> c: " + c); if (c.length > 0) { if (c.attr("class").startsWith("tab")) { return this.controlType.Tab; @@ -199,42 +986,72 @@ Xrm.Portal = { Section: 2 } }, + Grid: { + get: function (cssSelector = ".entitylist") { + this.c = Xrm.Portal.Utility.Selector.getByCSSSelector(cssSelector); + this.ct = this.getControlType(this.c); + return this; + }, + getControlType: function (c) { + //console.log("getControlType -> c: " + c); + if (c.length > 0) { + if (c.prop('className') == 'entitylist') { + return this.controlType.Grid; + } + } + }, + showActionsAsLinks: function () { + if (!this.c) this.get(); + this.c.find(".entity-grid").on("loaded", function () { + $(this).children(".view-grid").find("tr[data-id]") + .each(function (i, e) { + let $tdActions = $(this).children("td:last"), + id = $(this).attr("data-id"); + $tdActions.find(".dropdown.action > ul > li > a").appendTo($tdActions); + $tdActions.find(".dropdown.action").hide(); + }); + }).trigger("loaded"); + }, + controlType: { + Grid: 1 + } + }, WebForm: { - isExisted: function() { + isExisted: function () { return $('#WebFormPanel').children('.form-readonly').length > 0; }, - isReadOnly: function() { + isReadOnly: function () { return Xrm.Portal.WebForm.isExisted() && $('#WebFormPanel').children('.form-readonly').length > 0; } }, Form: { Validation: { - assertRegex: function(cid, exp, message, isRequired) { - Xrm.Portal.Form.get(cid).setRequired(isRequired, function() { + assertRegex: function (cid, exp, message, isRequired) { + Xrm.Portal.Form.get(cid).setRequired(isRequired, function () { if (!isRequired && Xrm.Portal.Form.get(cid).getValue() == "") return true; else return exp.test(Xrm.Portal.Form.get(cid).getValue()); }, message); }, - denyPastDate: function(cid, message, isRequired) { - Xrm.Portal.Form.get(cid).setRequired(isRequired, function() { + denyPastDate: function (cid, message, isRequired) { + Xrm.Portal.Form.get(cid).setRequired(isRequired, function () { if (!isRequired && Xrm.Portal.Form.get(cid).getValue() == "") return true; else return new Date() <= new Date(Xrm.Portal.Form.get(cid).getValue()); }, message); }, - denyFutureDate: function(cid, message, isRequired) { - Xrm.Portal.Form.get(cid).setRequired(isRequired, function() { + denyFutureDate: function (cid, message, isRequired) { + Xrm.Portal.Form.get(cid).setRequired(isRequired, function () { if (!isRequired && Xrm.Portal.Form.get(cid).getValue() == "") return true; else return new Date() >= new Date(Xrm.Portal.Form.get(cid).getValue()); }, message); }, - compareDates: function(mainid, subid, message, isRequired) { - Xrm.Portal.Form.get(mainid).setRequired(isRequired, function() { + compareDates: function (mainid, subid, message, isRequired) { + Xrm.Portal.Form.get(mainid).setRequired(isRequired, function () { if (!isRequired && Xrm.Portal.Form.get(mainid).getValue() == "") return true; else return new Date(Xrm.Portal.Form.get(mainid).getValue()) > new Date(Xrm.Portal.Form.get(subid).getValue()) }, message); }, - setNumberRange: function(cid, min, max, message, isRequired) { - Xrm.Portal.Form.get(cid).setRequired(isRequired, function() { + setNumberRange: function (cid, min, max, message, isRequired) { + Xrm.Portal.Form.get(cid).setRequired(isRequired, function () { var isMin = true, isMax = true; if (min != undefined) { @@ -248,7 +1065,7 @@ Xrm.Portal = { }, message); } }, - get: function(id) { + get: function (id) { var c = Xrm.Portal.Utility.Selector.getByControlId(id); var ct, v; @@ -256,7 +1073,9 @@ Xrm.Portal = { ct = this.getControlType(c); } else { c = Xrm.Portal.Utility.Selector.getByDataName(id); - ct = this.getUiControlType(c); + if (c != undefined && c.length > 0) { + ct = this.getUiControlType(c); + } else console.warn("Control not found: " + id); } switch (ct) { @@ -272,6 +1091,9 @@ Xrm.Portal = { case this.controlType.Lookup: v = new Xrm.Portal.Control.Lookup(c); break; + case this.controlType.Multiselect: + v = new Xrm.Portal.Control.Multiselect(c); + break; case this.controlType.Tab: v = new Xrm.Portal.Control.Tab(c); break; @@ -293,8 +1115,8 @@ Xrm.Portal = { } return v; }, - getUiControlType: function(c) { - console.log("getUiControlType: -> c: " + c); + getUiControlType: function (c) { + //console.log("getUiControlType: -> c: " + c); if (c.length > 0) { if (c.attr("class").startsWith("tab")) { return this.controlType.Tab; @@ -303,27 +1125,36 @@ Xrm.Portal = { } } }, - getControlType: function(c) { - console.log("getControlType -> c: " + c); + getControlType: function (c) { + //console.log("getControlType -> c: " + c); + var ret = this.controlType.Control; if (c.length > 0) { if (c.attr("data-ui") == "datetimepicker") { - return this.controlType.DatetimePicker; + ret = this.controlType.DatetimePicker; } else if (c.attr("type") == "checkbox") { - return this.controlType.Checkbox; + ret = this.controlType.Checkbox; } else if (c.attr("type") == "hidden") { - return this.controlType.Lookup; + if ($(`div.control:has(#PcfControlConfig_${c.attr("id")})`).length > 0) { + //} else if ($(`div.control:has(#${c.attr("id")}):has(.MscrmControls.MultiSelectPicklist.UpdMSPicklistControl)`).length > 0) { + ret = this.controlType.Multiselect; + } else + ret = this.controlType.Lookup; } else if (c.attr("class") != null && (c.attr("class").indexOf("boolean-radio") >= 0 || c.attr("class").indexOf("picklist horizontal") >= 0 || c.attr("class").indexOf("picklist vertical") >= 0)) { - return this.controlType.Radio; + ret = this.controlType.Radio; + } else if (c.prop('className') == 'entitylist') { + ret = this.controlType.Grid; } else if (c.prop('className') == 'subgrid') { - return this.controlType.Subgrid; + ret = this.controlType.Subgrid; } else if (c.is('iframe')) { - return this.controlType.QuickView; + ret = this.controlType.QuickView; + //} else if (c.is("select[multiple]")) { } else if (c.children('.entity-notes').length > 0) { - return this.controlType.Notes; + ret = this.controlType.Notes; } else { - return this.controlType.Control; + ret = this.controlType.Control; } } + return ret; }, controlType: { Control: 1, @@ -331,6 +1162,8 @@ Xrm.Portal = { DatetimePicker: 3, Radio: 4, Checkbox: 5, + Dropdown: 6, + Multiselect: 7, Tab: 100, Section: 101, Subgrid: 1000, @@ -339,593 +1172,110 @@ Xrm.Portal = { } }, Control: { - Tab: function(c) { - this.s = Xrm.Portal.Utility.Selector; - this.id = $(c).prop("id"); - - this.c = c; - - this.getValue = function() { - throw "not implemented"; - }; - this.setValue = function(value) { - throw "not implemented"; - }; - this.setVisible = function(isVisible, isMandatory) { - var h = this.c.prev(); - //this.setRequired(isVisible && isMandatory); - if (isVisible) { - this.c.show(); - if (h.is('h2')) h.show(); - } else { - this.c.hide(); - if (h.is('h2')) h.hide(); - } - }; - this.setDisable = function(isDisabled) { - throw "not implemented"; - }; - this.setRequired = function(isRequired, customFunction, customMessage) { - throw "not implemented"; - c.children().each(function() { - Xrm.Portal.Form.get(this.id).setRequired(isRequired, customFunction, customMessage); - }); - }; - }, - Section: function(c) { - this.s = Xrm.Portal.Utility.Selector; - this.id = $(c).prop("id"); - - this.c = c; - - this.getValue = function() { - throw "not implemented"; - }; - this.setValue = function(value) { - throw "not implemented"; - }; - this.setVisible = function(isVisible, isMandatory) { - var g = this.c; - //this.setRequired(isVisible && isMandatory); - isVisible ? g.parent().show() : g.parent().hide(); - }; - this.setDisable = function(isDisabled) { - throw "not implemented"; - }; - this.setRequired = function(isRequired, customFunction, customMessage) { - throw "not implemented"; - c.children().each(function() { - Xrm.Portal.Form.get(this.id).setRequired(isRequired, customFunction, customMessage); - }); - }; - }, - Notes: function(c) { - this.s = Xrm.Portal.Utility.Selector; - this.id = $(c).prop("id"); - - this.cc = document.getElementById(this.id + '_cc'); - this.c = c; - this.vg = ""; - - this.getValue = function() { - throw 'not implemented'; - }; - this.setValue = function(value) { - throw 'not implemented'; - }; - this.getNumberofAttachments = function() { - return this.c.find('.notes').children().length; - }; - this.hasAttachments = function() { - return this.c.find('.notes-empty').css('display') != 'block'; - }; - this.getCurrentPage = function() { - throw 'not implemented'; - }; - this.setVisible = function(isVisible, isMandatory) { - var g = this.c.parent().parent(); - //this.setRequired(isMandatory); - isVisible ? g.show() : g.hide(); - return this; - }; - this.setCreateVisible = function(isVisible) { - //this.c.find('a[title=Create]').css('display', isVisible ? '' : 'none'); - }; - this.setDisable = function(isDisabled) { - //throw "not implemented"; - }; - this.setRequired = function(isRequired, customFunction, customMessage) { - // var g = c.parent().siblings(".info"); - // isRequired || customFunction != undefined ? - // Xrm.Portal.Utility.Validation.setValidation(g, this, isRequired, this.vg, customFunction, customMessage) : - // Xrm.Portal.Utility.Validation.removeValidation(g, this); - // return this; - throw 'not implemented'; - }; - this.attachOnChange = function(callback) { - throw 'not implemented'; - }; - this.removeOnChange = function() { - throw "not implemented"; - }; - this.setValidationGroup = function(g) { - this.vg = g; - return this; - }; - }, - Subgrid: function(c) { - this.s = Xrm.Portal.Utility.Selector; - this.id = $(c).prop("id"); - - this.cc = document.getElementById(this.id + '_cc'); - this.c = c; - this.vg = ""; - - this.getValue = function() { - throw "not implemented"; - }; - this.setValue = function(value) { - throw "not implemented"; - }; - this.getRowCountFromCurrentPage = function() { - return this.c.find('div > div.view-grid > table > tbody > tr').length; - }; - this.getCurrentPage = function() { - throw 'not implemented'; - }; - this.setVisible = function(isVisible, isMandatory) { - var g = this.c.parent().parent(); - this.setRequired(isMandatory); - isVisible ? g.show() : g.hide(); - return this; - }; - this.setCreateVisible = function(isVisible) { - this.c.find('a[title=Create]').css('display', isVisible ? '' : 'none'); - }; - this.setDisable = function(isDisabled) { - throw "not implemented"; - }; - this.setRequired = function(isRequired, customFunction, customMessage) { - var g = c.parent().siblings(".info"); - isRequired || customFunction != undefined ? - Xrm.Portal.Utility.Validation.setValidation(g, this, isRequired, this.vg, customFunction, customMessage) : - Xrm.Portal.Utility.Validation.removeValidation(g, this); - return this; - }; - this.attachOnChange = function(callback) { - Xrm.Portal.Utility.Event.attachOnLoaded(this.c, callback); - return this; - }; - this.removeOnChange = function() { - throw "not implemented"; - }; - this.setValidationGroup = function(g) { - this.vg = g; - return this; - }; - }, - QuickView: function(c) { - this.s = Xrm.Portal.Utility.Selector; - this.id = $(c).prop("id"); - - this.cc = document.getElementById(this.id + '_cc'); - this.c = c; - this.vg = ""; - - this.getValue = function() { - var values = this.c.contents().find('.form-control'); - var allowFormats = ['DD/MM/YYYY', 'YYYY/MM/DD'];; - var data = {}, - aName = ''; - for (var i = 0; i < values.length; i++) { - aName = values[i].id; - if ($(values[i]).prop('className').indexOf('lookup') > -1) { - aName = aName.substr(0, aName.lastIndexOf('_name')); - data[aName] = values[i].value; - } else if ($(values[i]).prop('id').indexOf('_datepicker_description') > -1 || $(values[i]).prop('className').indexOf('datetime') > -1) { - aName = aName.replace('_datepicker_description', ''); - data[aName] = ""; - if (values[i].value != null && values[i].value != "") { - data[aName] = moment(values[i].value, allowFormats).toDate().toString('dd/MM/yyyy'); - } - } else if ($(values[i]).prop('className').indexOf('picklist') > -1) { - data[aName + "_text"] = $(values[i]).find('option:selected').text(); - data[aName] = $(values[i]).find('option:selected').val(); - } else { - data[aName] = values[i].value; - } - } - return data; - }; - this.setValue = function(value) { - throw "not implemented"; - }; - this.setVisible = function(isVisible, isMandatory) { - var g = this.c.parent().parent(); - //this.setRequired(isMandatory); - isVisible ? g.show() : g.hide(); - return this; - }; - this.setDisable = function(isDisabled) { - throw 'not implemented'; - }; - this.setRequired = function(isRequired, customFunction, customMessage) { - throw 'not implemented' - }; - this.attachOnChange = function(callback) { - Xrm.Portal.Utility.Event.attachOnLoad(this.c, callback); - return this; - }; - this.removeOnChange = function() { - throw "not implemented"; - }; - this.setValidationGroup = function(g) { - throw 'not implemented' - }; - this.renderAdaptiveCard = function(attribute, card, data) { - Xrm.Portal.Form.get(attribute).cL.parent().next().next().remove(); - if (Xrm.Portal.Form.get(attribute).getValue().id != "") { - var parsedCard = Xrm.Portal.Utility.AdaptiveCard.parseTemplate(card, data); - var adaptiveCard = new AdaptiveCards.AdaptiveCard(); - adaptiveCard.hostConfig = new AdaptiveCards.HostConfig({ - fontFamily: "Segoe UI, Helvetica Neue, sans-serif" - }); - - adaptiveCard.parse(parsedCard); - var renderedCard = adaptiveCard.render(); - - Xrm.Portal.Form.get(attribute).cL.parent().parent().append(renderedCard); - return renderedCard; - } - } - }, - Generic: function(c) { - this.s = Xrm.Portal.Utility.Selector; - this.id = $(c).prop("id"); - - this.cc = document.getElementById(this.id + '_cc'); - this.c = c; - this.vg = ""; - - this.getValue = function() { - return this.c.val(); - }; - this.setValue = function(value) { - this.c.val(value); - if (this.cc != null) this.cc.updateView(); - return this; - }; - this.setVisible = function(isVisible, isMandatory) { - var g = this.c.parent().parent(); - this.setRequired(isMandatory); - isVisible ? g.show() : g.hide(); - return this; - }; - this.setDisable = function(isDisabled) { - this.c.prop('disabled', isDisabled); - return this; - }; - this.setRequired = function(isRequired, customFunction, customMessage) { - var g = c.parent().siblings(".info"); - isRequired || customFunction != undefined ? - Xrm.Portal.Utility.Validation.setValidation(g, this, isRequired, this.vg, customFunction, customMessage) : - Xrm.Portal.Utility.Validation.removeValidation(g, this); - return this; - }; - this.attachOnChange = function(callback, triggerOnLoad) { - Xrm.Portal.Utility.Event.attachOnChange(this.c, callback, triggerOnLoad); - return this; - }; - this.removeOnChange = function() { - Xrm.Portal.Utility.Event.removeOnChange(this.c); - return this; - }; - this.setValidationGroup = function(g) { - this.vg = g; - return this; - }; - this.transformToCanvas = function() { - this.c.hide(); - if (this.c.parent().children().last()[0].tagName !== "CANVAS") { - var canvasId = this.id + "Canvas"; - this.c.parent().append(""); - Xrm.Portal.Control.Canvas(this.id); - } + Tab: TabControl, + Section: SectionControl, + Notes: NotesControl, + Subgrid: SubgridControl, + QuickView: QuickViewControl, + Generic: GenericControl, + Lookup: LookupControl, + Checkbox: CheckboxControl, + Radio: RadioControl, + DatetimePicker: DatetimePickerControl, + Multiselect: MultiselectControl, + Canvas: CanvasControl + }, + SortFunctions_Radio: { + "label": function (a, b) { + function getLabel(a) { + return $(a).find("label").clone() //clone the element + .children() //select all the children + .remove() //remove all the children + .end() //again go back to selected element + .text(); } - }, - Lookup: function(c) { - this.s = Xrm.Portal.Utility.Selector; - this.id = $(c).prop("id"); - - this.cL = c; - this.cN = this.s.getLookupName(this.id); - this.cE = this.s.getLookupEntity(this.id); - this.vg = ""; - - this.enableOneClick = function() { - this.cN.on('click', () => this.cL.siblings('div.input-group-btn').children('button.launchentitylookup').click()); - }; - this.getValue = function() { - return { - "id": this.cL.val(), - "name": this.cN.val(), - "logicalname": this.cE.val() - }; - }; - this.setValue = function(value, name, logicalName) { - if (value != null && value.hasOwnProperty('id') && value.hasOwnProperty('name') && value.hasOwnProperty('logicalname')) { - this.cL.val(value.id); - this.cN.val(value.name); - this.cE.val(value.logicalname); - } else { - this.cL.val(value); - this.cN.val(name); - this.cE.val(logicalName); - } - return this; - }; - this.setVisible = function(isVisible, isMandatory) { - this.setRequired(isMandatory); - var g = this.cL.parent().parent().parent(); - isVisible ? g.show() : g.hide(); - return this; - }; - this.setDisable = function(isDisabled) { - this.cN.prop('disabled', isDisabled); - this.cN.siblings('div.input-group-btn').toggle(!isDisabled); - return this; - }; - this.setRequired = function(isRequired, customFunction, customMessage) { - var g = this.cL.parent().parent().siblings(".info"); - isRequired || customFunction != undefined ? - Xrm.Portal.Utility.Validation.setValidation(g, this, isRequired, this.vg, customFunction, customMessage) : - Xrm.Portal.Utility.Validation.removeValidation(g, this); - return this; - }; - this.attachOnChange = function(callback) { - Xrm.Portal.Utility.Event.attachOnChange(this.cL, callback); - return this; - }; - this.removeOnChange = function() { - Xrm.Portal.Utility.Event.removeOnChange(this.cL); - return this; - }; - this.setValidationGroup = function(g) { - this.vg = g; - return this; - }; - }, - Checkbox: function(c) { - this.s = Xrm.Portal.Utility.Selector; - this.id = $(c).prop("id"); - - this.c = c; - this.vg = ""; - this.getValue = function() { - return this.c.prop("checked"); - }; - this.setValue = function(value) { - this.c.prop("checked", value); - return this; - }; - this.setVisible = function(isVisible, isMandatory) { - var g = this.c.parent().parent().parent(); - this.setRequired(isMandatory); - isVisible ? g.show() : g.hide(); - return this; - }; - this.setDisable = function(isDisabled) { - this.c.prop('disabled', isDisabled); - return this; - }; - this.setRequired = function(isRequired, customFunction, customMessage) { - var g = c.parent().parent().siblings(".info"); - isRequired || customFunction != undefined ? - Xrm.Portal.Utility.Validation.setValidation(g, this, isRequired, this.vg, customFunction, customMessage) : - Xrm.Portal.Utility.Validation.removeValidation(g, this); - return this; - }; - this.attachOnChange = function(callback) { - Xrm.Portal.Utility.Event.attachOnChange(this.c, callback); - return this; - }; - this.removeOnChange = function() { - Xrm.Portal.Utility.Event.removeOnChange(this.c); - return this; - }; - this.setValidationGroup = function(g) { - this.vg = g; - return this; - }; - }, - Radio: function(c) { - this.s = Xrm.Portal.Utility.Selector; - this.id = $(c).prop("id"); - - this.c = c; - this.vg = ""; - - this.getValue = function() { - return this.c.find("input:checked").val(); - }; - this.setValue = function(value) { - if (value != null) { - this.c.find("input[value*=" + value + "]").prop("checked", value); - } else { - this.c.find('input[type=radio]').prop('checked', false); - } - - return this; - }; - this.setVisible = function(isVisible, isMandatory) { - var g = this.c.parent().parent(); - this.setRequired(isMandatory); - isVisible ? g.show() : g.hide(); - return this; - }; - this.setDisable = function(isDisabled) { - this.c.find('input[type=radio]').prop("disabled", isDisabled); - return this; - }; - this.setRequired = function(isRequired, customFunction, customMessage) { - var g = c.parent().siblings(".info"); - isRequired || customFunction != undefined ? - Xrm.Portal.Utility.Validation.setValidation(g, this, isRequired, this.vg, customFunction, customMessage) : - Xrm.Portal.Utility.Validation.removeValidation(g, this); - return this; - }; - this.attachOnChange = function(callback) { - Xrm.Portal.Utility.Event.attachOnChange(this.c, callback); - return this; - }; - this.removeOnChange = function() { - Xrm.Portal.Utility.Event.removeOnChange(this.c); - return this; - }; - this.setValidationGroup = function(g) { - this.vg = g; - return this; - }; - }, - DatetimePicker: function(c) { - this.s = Xrm.Portal.Utility.Selector; - this.id = $(c).prop("id"); - - this.c = c; - this.vg = ""; - - this.getValue = function() { - return this.c.val(); - }; - this.getData = function() { - return this.c.next().data('DateTimePicker'); - }; - this.setValue = function(value) { - this.c.val(value); - return this; - }; - this.setVisible = function(isVisible, isMandatory) { - var g = this.c.parent().parent(); - this.setRequired(isMandatory); - isVisible ? g.show() : g.hide(); - return this; - }; - this.setDisable = function(isDisabled) { - this.s.getTextLabel(this.id).prop('disabled', isDisabled); - return this; - }; - this.setRequired = function(isRequired, customFunction, customMessage) { - var g = c.parent().siblings(".info"); - isRequired || customFunction != undefined ? - Xrm.Portal.Utility.Validation.setValidation(g, this, isRequired, this.vg, customFunction, customMessage) : - Xrm.Portal.Utility.Validation.removeValidation(g, this); - return this; - }; - this.attachOnChange = function(callback) { - Xrm.Portal.Utility.Event.attachDateTimePickerOnChange(this.c, callback); - return this; - }; - this.removeOnChange = function() { - Xrm.Portal.Utility.Event.removeOnChange(this.c); - return this; - }; - this.setValidationGroup = function(g) { - this.vg = g; - return this; - }; + var A = getLabel(a).toUpperCase(), + B = getLabel(b).toUpperCase(); + return A.localeCompare(B); }, - Canvas: function(id) { - var canvas, context, tool; - - function init(id) { - // Find the canvas element. - canvas = document.getElementById(id + "Canvas"); - if (!canvas) { - alert('Error: I cannot find the canvas element!'); - return; - } - - if (!canvas.getContext) { - alert('Error: no canvas.getContext!'); - return; - } - - // Get the 2D canvas context. - context = canvas.getContext('2d'); - if (!context) { - alert('Error: failed to getContext!'); - return; - } - - // Pencil tool instance. - tool = new tool_pencil(id, canvas.id); - - // Attach the mousedown, mousemove and mouseup event listeners. - canvas.addEventListener('mousedown', ev_canvas, false); - canvas.addEventListener('mousemove', ev_canvas, false); - canvas.addEventListener('mouseup', ev_canvas, false); - } - - // This painting tool works like a drawing pencil which tracks the mouse - // movements. - function tool_pencil(id, canvasId) { - var id = id; - var canvasId = canvasId; - var tool = this; - this.started = false; - - // This is called when you start holding down the mouse button. - // This starts the pencil drawing. - this.mousedown = function(ev) { - context.beginPath(); - context.moveTo(ev._x, ev._y); - tool.started = true; - }; - - // This function is called every time you move the mouse. Obviously, it only - // draws if the tool.started state is set to true (when you are holding down - // the mouse button). - this.mousemove = function(ev) { - if (tool.started) { - context.lineTo(ev._x, ev._y); - context.stroke(); - } - }; - - // This is called when you release the mouse button. - this.mouseup = function(ev) { - if (tool.started) { - tool.mousemove(ev); - tool.started = false; - } - Xrm.Portal.Form.get(id).setValue(document.getElementById(canvasId).toDataURL()); - }; - } - - // The general-purpose event handler. This function just determines the mouse - // position relative to the canvas element. - function ev_canvas(ev) { - if (ev.layerX || ev.layerX == 0) { // Firefox - ev._x = ev.layerX; - ev._y = ev.layerY; - } else if (ev.offsetX || ev.offsetX == 0) { // Opera - ev._x = ev.offsetX; - ev._y = ev.offsetY; - } - - // Call the event handler of the tool. - var func = tool[ev.type]; - if (func) { - func(ev); - } - } - - init(id); + "value": function (a, b) { + throw "not implemented, to be tested"; + var A = $(a).val().toUpperCase(), + B = $(b).val().toUpperCase(); + return A.localeCompare(B); } }, + SortOrder: { + Asc: true, + Desc: false, + Ascending: true, + Descending: false + }, EventType: { OnChange: 1, OnClick: 2 + }, + Bools: { + Yes: true, + No: false, + Active: true, + Inactive: false, + "TRUE": true, + "FALSE": false, + 1: true, + 0: false + }, + /** + * Logs all interactive controls within #EntityFormView grouped by their type in an interactive format. + * + * Why: + * - To provide developers with an organized and interactive view of controls for debugging or customization. + * + * What: + * - Identifies controls (e.g., inputs, videos, etc.) within #EntityFormView, excluding those with IDs starting with "EntityFormView_". + * - Groups controls by their type and logs them as an object for easy exploration in the console. + * + * How: + * - Uses jQuery to find and filter controls. + * - Groups controls by their type using `reduce`. + * - Logs the grouped controls directly as an object for interactive inspection in the console. + */ + codeSampleInConsole: function () { + const controls = []; + // Dynamically select all interactive elements within #EntityFormView, excluding those with id starting with EntityFormView_ and buttons + $("#EntityFormView div.control") + .find(":input, audio, video, progress, meter, [contenteditable='true'], [tabindex]") + .not("[id^='EntityFormView_'], button") + .each(function () { + const fieldId = $(this).attr("id") || "Unknown"; // Use id attribute + const escapedFieldId = CSS.escape(fieldId); // Escape special characters + const label = Xrm.Portal.Utility.Selector.getTextLabel(escapedFieldId).text().trim() || "No Label"; + const control = Xrm.Portal.Form.get(fieldId); + const controlType = control ? control.constructor.name : this.tagName.toLowerCase(); + const inputType = $(this).attr("type") || $(this).attr("role") || "N/A"; // Detect type or role + + controls.push({ + SchemaName: fieldId, + Label: label, + Type: controlType, + InputType: inputType, // Include input type or role for better clarity + CodeSample: `Xrm.Portal.Form.get('${fieldId}')` + }); + }); + + // Group controls by type + const groupedControls = controls.reduce((acc, control) => { + if (!acc[control.Type]) { + acc[control.Type] = []; + } + acc[control.Type].push(control); + return acc; + }, {}); + + // Log grouped controls as an object for interactive inspection + console.debug(groupedControls); } -}; \ No newline at end of file +} +//#endregion +console.debug("Xrm.Portal loaded", Xrm.Portal.version);