Записки на лету

All posts tagged CRM2016

С версии 2013 появилось несколько новых методов нотификации пользователя при работе на форме. Расскажу про них и в каких случаях можно их использовать.

1. Поддерживаемая версия alert().

Раньше мы использовали стандартный alert("Сообщение!");

Теперь у нас есть два варианта:

  1. Простое сообщение: Xrm.Utility.alertDialog(message,onCloseCallback);
    где onCloseCallback колбэк функция выполняемая после нажатия кнопки ОК окошка сообщения.
    Пример использования (Проверять на загрузке формы заполнение поля и если оно не заполнено то просить его заполнить и ставить в него фокус):

    function formOnLoad() {
        /// <summary>Функция, вызываемая при загрузки формы</summary>
        if (Xrm.Page.getAttribute("mobilephone") && !Xrm.Page.getAttribute("mobilephone").getValue()) {
            Xrm.Utility.alertDialog("Не заполнен мобильный телефон",
                function () {
                    Xrm.Page.getControl("mobilephone").setFocus();
                });
        }
    }

    AlertMessage

  2. Диалог подтверждения: Xrm.Utility.confirmDialog(message,yesCloseCallback,noCloseCallback);
    где yesCloseCallback колбэк функция выполняемая после нажатия кнопки ОК окошка сообщения и noCloseCallback колбэк функция выполняемая после нажатия кнопки ОК окошка сообщения.
    Пример использования (Проверять на сохранении формы заполнение поля и если оно не заполнено то предложить его заполнить и ставить в него фокус):

    function formOnSave(context) {
        /// <summary>Функция, вызываемая при сохранении формы</summary>  
        if (Xrm.Page.getAttribute("mobilephone") && !Xrm.Page.getAttribute("mobilephone").getValue()) {
            Xrm.Utility.confirmDialog("Не заполнен мобильный телефон. Заполнить?",
                // Если нажали ОК
                function () {
                    // Ставим фокус
                    Xrm.Page.getControl("mobilephone").setFocus();
                    //Отменяем сохранение
                    var eventArgs = context.getEventArgs();
                    eventArgs.preventDefault();
                },
                // Если нажали Cancel
                function () {
                    // Ничего не делаем
                });
        }
    }

    AlertDialogMessage

2. Панель нотификации.

Тоже самое можно реализовать с помощью панели нотификации.

Xrm.Page.ui.setFormNotification(message, level, uniqueId);

где:

  • message (string) — сам текст сообщения.
  • level (string) — тип иконки, возможные варианты: «ERROR» , «WARNING», «INFO».
  • uniqueId (string) — идентификатор сообщения (используется для убирания конкретного сообщения).

При добавлении исполнении следующих строк:

Xrm.Page.ui.setFormNotification('Произошла ошибка...!', 'ERROR', 'id1');
Xrm.Page.ui.setFormNotification('Предупреждение...', 'WARNING', 'id2');
Xrm.Page.ui.setFormNotification('Для информации', 'INFORMATION', 'id3');

Мы увидим следующее.

NotificationMessages

Но тут необходимо помнить, что свое сообщение необходимо скрывать явно, т.к. иначе оно будет висеть до перезагрузки формы. Для чего нам и необходим третий параметр uniqueId, без него мы сможем только скрыть все сообщения, а на форме могут быть еще другие сообщения, как наши так и системные.

Скрываются сообщения так:

Xrm.Page.ui.clearFormNotification('id1');  // Сообщение с идентификатором 'id1'

Попробуем реализовать пример выше с использованием понели нотификации. Нам понадобятся две функции, на загрузку формы formOnLoad() и на изменение поля Мобильный телефон mobilePhoneOnChange().

function formOnLoad() {
    /// <summary>Функция, вызываемая при загрузки формы</summary>
    if (Xrm.Page.getAttribute("mobilephone") && !Xrm.Page.getAttribute("mobilephone").getValue()) {
        Xrm.Page.ui.setFormNotification("Не заполнен мобильный телефон", 'WARNING', "mobilephoneAlert");
    }
}

function mobilePhoneOnChange() {
    /// <summary>Функция, вызываемая при изменении поля телефон</summary>
    if (Xrm.Page.getAttribute("mobilephone") && !Xrm.Page.getAttribute("mobilephone").getValue()) {
        Xrm.Page.ui.setFormNotification("Не заполнен мобильный телефон", 'WARNING', "mobilephoneAlert");
    } else {
        Xrm.Page.ui.clearFormNotification("mobilephoneAlert");
    }
}

Выглядеть будет так.NotificationMessages2

3. Нотификация поля.

Третий вариант, который мы можем использовать это нотификация непосредственно на поле, что актуально при сообщении касающегося какого-то конкретного поля. Это очень похоже на предыдущий вариант:

Xrm.Page.getControl(fieldName).setNotification(message, uniqueId);  // Показать сообщение
Xrm.Page.getControl(fieldName).clearNotification(uniqueId);         // Скрыть сообщение

где:

  • fieldName (string) — имя поля.
  • message (string) — текст сообщния.
  • uniqueId (string) — идентификатор сообщения (используется для убирания конкретного сообщения).

Выглядит так:NotificationMessages3

Кстати, пока Вы видите такое сообщение CRM не даст Вам сохранить форму.

Соответственно, наш пример будет выглядеть так:

function formOnLoad() {
    /// <summary>Функция, вызываемая при загрузки формы</summary>
    if (Xrm.Page.getAttribute("mobilephone") && !Xrm.Page.getAttribute("mobilephone").getValue()) {
        Xrm.Page.getControl("mobilephone").setNotification("Не заполнен мобильный телефон", "EmptyMobilephoneAlert");
    }
    else {
        Xrm.Page.getControl("mobilephone").clearNotification("EmptyMobilephoneAlert");
    }
}

function mobilePhoneOnChange() {
    /// <summary>Функция, вызываемая при изменении поля телефон</summary>
    if (Xrm.Page.getAttribute("mobilephone")) {
        if (!Xrm.Page.getAttribute("mobilephone").getValue()) {
            Xrm.Page.getControl("mobilephone").setNotification("Не заполнен мобильный телефон", "EmptyMobilephoneAlert");
        } else {
            Xrm.Page.getControl("mobilephone").clearNotification("EmptyMobilephoneAlert");
        }
    }
}

Такого же результата мы можем достигнуть создав «Бизнес-правило».AlertBusinessRule

В результате, CRM также добавит скрипт на форму, сгенеренный по этому правилу. Если кому интересно, ниже скрипт сгенеренный для приведенного правила.

 

function pbl_eb4eb395d711e61180ce00155de610f8() {
    try {
        var v0 = Xrm.Page.data.entity.attributes.get('mobilephone');
        var v2 = '';
        if ((v0 == undefined || v0 == null || v0 === ""))
        { return; }
        var v1 = v0.getValue();
        if ((v1) == undefined || (v1) == null || (v1) === "") {
            v0.controls.forEach(function (c, i) {
                c.setNotification(Mscrm.BusinessRulesScript.GetResourceString('e59db157-30ca-9aa2-05c3-d3da4ac312c0', 'Не заполнен мобильный телефон'), 'eb4eb395-d711-e611-80ce-00155de610f8SetMessageStep4');
            });
            v2 = v2 + 'eb4eb395-d711-e611-80ce-00155de610f8SetMessageStep4\x3b';
        }
        var v3 = [{ 'CId': 'mobilephone', 'SId': 'eb4eb395-d711-e611-80ce-00155de610f8SetMessageStep4' }];
        for (var i = 0; i < v3.length; i++) {
            var l1 = v3[i];
            if (v2.indexOf(l1.SId + '\x3b') === -1) {
                Xrm.Page.data.entity.attributes.get(l1.CId).controls.forEach(function (c, i) { c.clearNotification(l1.SId); });
            }
        }
    } catch (e) {
        Mscrm.BusinessRules.ErrorHandlerFactory.getHandler(e, arguments.callee).handleError();
    }
}
Mscrm.BusinessRulesScript.Initialize = function () {
    Mscrm.BusinessRulesScript.AttributesOnChangeHandlers = {};
    (function () {
        var onchangehandler = function () {
            pbl_eb4eb395d711e61180ce00155de610f8();
        };
        Mscrm.BusinessRulesScript.AttributesOnChangeHandlers['mobilephone'] = onchangehandler;
        var attributeObject = Xrm.Page.data.entity.attributes.get('mobilephone');
        if (attributeObject != null && attributeObject != undefined) {
            attributeObject.addOnChange(onchangehandler);
        }
    })(); pbl_eb4eb395d711e61180ce00155de610f8();
};

4. Всплывающая подсказка tooltip.

Также, следует помнить, что всплывающие подсказки для полей в CRM уже реализованы. По-умолчанию всплывающей подсказкой для поля является описание в настройках поля.AlertTooltipSettings

AlertTooltip

 


Вышла очередная версия MS Dynamics CRM версия 2016. Хотя кое-что в ней до сих пор от 2011 версии, диалоги например.

Но сегодня речь пойдет не об этом. А о том с какими сложностями я встретился при переносе решения с 2013/2015 на 2016.

Начнем с той части где оказалось больше всего проблем, а именно, с форм. Начиная с версии 2015 Update 1 в CRM появился новый движок рендеринга форм (подробнее здесь Microsoft Dynamics CRM Online 2015 Update 1 – New Form Rendering Engine) на котором перестал работать почти весь «Unsupported» код, который я использовал еще с версии 2011. А кому сейчас легко, клиент всегда прав и если он говорит — «Хочу так!», то приходится делать как говорят.

Для тех, кто не хочет вникать в детали и хочет просто чтобы все работало как раньше скажу сразу, что есть волшебная галочка которая отключает новые формы и возвращает старые.

Находится она по такому пути: Параметры -> Администрирование -> Системные параметры Вкладка Общие сведения где в самом низу есть секция Использовать отображение форм предыдущих версий где есть единственный пункт Для совместимости использовать подсистему отображения форм предыдущих версий. Учтите, что это может отрицательно влиять на производительность вот напротив него ставим Да.

Перво наперво с форм пропал jQuery. И почти на каждой странице я стал получать ошибку: «ReferenceError: $ is not defined»

Оказалось, что кастомные скрипты теперь грузятся в отдельном фрейме и jQuery на самом деле есть но уровнем выше.

Лечится так:

 

if (typeof($) === 'undefined') {
    $ = parent.$;
}
if (typeof(jQuery) === 'undefined') {
    jQuery = parent.jQuery;
}

Так же исчезла внутренняя функция Mscrm.InlineDialogUtility.createInlineDialog(), которую я использовал для создания кастомных диалогов. Пример использования такой:

 

var url = Mscrm.CrmUri.create(String.format("$webresource:{0}", "gz_/html/datainputdialog.html"));
var dlgOptions = {};
dlgOptions.param1 = "Some param";   
var url = Mscrm.CrmUri.create(String.format("$webresource:{0}", "YouWebResourceName")

Mscrm.InlineDialogUtility.createInlineDialog(url, dlgOptions, width, height, left, top, zindex);

Но, осталась функция Xrm.Internal.openDialog(), пример использования такой:

 

var url = Mscrm.CrmUri.create(String.format("$webresource:{0}", "gz_/html/datainputdialog.html"));
var dlgOptions = {};
params.sl_contact = { id: Xrm.Page.data.entity.getId(), typename: 'contact' };       
var DialogOption = new Xrm.DialogOptions;
DialogOption.width = 640; DialogOption.height = 440;
Xrm.Internal.openDialog(url.toString(), DialogOption, params, null, CallbackFunction);
function CallbackFunction(returnValue){
}

И в диалоге, если надо вернуть параметр в колбэк фунцию то это делается с помощью следующего скрипта:

 

var oReturn = {};
oReturn.smeparam = ""someparam;
Mscrm.Utilities.setReturnValue(oReturn);
 try {
    closeWindow(true);
}
catch (e) { }

И в самом веб-ресурсе необходимо добавить ссылку на ClientGlobalContext:

 

<script src="../../ClientGlobalContext.js.aspx" type="text/javascript"></script>

Еще есть у меня функция, которая пробегалась по полям формы и проверяла заполнены ли обязательные поля. Она стала возвращать false когда такого быть не должно. Исходная функция ниже:

 

function checkIfAllFieldsFilled() {
    var attributes = Xrm.Page.data.entity.attributes.get();
    for (var i = 0; i > attributes.length; i++) {
        if (attributes[i].getRequiredLevel() == "required") {
            if (attributes[i].getValue() == null) {
                return false;
            }
        }
    }
    return true;
}

Как оказалось, функция Xrm.Page.data.entity.attributes.get() теперь возвращает поля не только текущей сущности но и связных сущностей тоже, т.е. для Возможной сделки, например, я получил еще и поля связных звонков и т.д.
Решается дополнительной проверкой родителя у поля:

 

function checkIfAllFieldsFilled() {
    var isOk = true;
    Xrm.Page.data.entity.attributes.forEach(function (attribute, index) {
        if (attribute.getParent() 
            && attribute.getParent().getEntityName() === "opportunity" 
            && attribute.getRequiredLevel() === "required") {
            if (attribute.getValue() === null) {
                isOk = false;
            }
        }
    });
    return isOk;
}

Изменилась вестка гридов и объектная модель работы с ними. Про новые методы можно посмотреть здесь Grid objects and methods (client-side reference).

Соответственно, старые «Unsupported» скрипты по подучению выбранных записей не работают. А вот по фильтрации работают, надо только грид надо теперь получать через parent, как и jQuery.

Но зато теперь, получить все или выбранные записи в гриде весьма легко. 🙂

В связи со всем вышеперечисленным возникает вопрос: — «А как я пойму включены ли новые формы или нет из самого скрипта?». Ведь получается, что местами придется писать два варианта кода, для новых форм и старых.

К счастью, покапавшись в дебаггере, я нашел функцию, которая возвращает включен новый движок форм или нет.

 

Xrm.Internal.isTurboForm()

Это, что касается скриптов. Также, обнаружил что в CRM Online эта функция всегда возвращает false.

С плагинами и процессами проблем почти не было.

Единственное на что я пока наткнулся это ошибка «Method not found: ‘!!0[] System.Array.Empty()’.»

Появилаcь она из-за того что я обновил dll с плагинами ипользуя SDK v.8.0.2 а в нем используется .NET Framework 4.6.1.1 а при установке CRM 2016 ставится 4.6.0.

Лечится установкой .NET Framework 4.6.1.1 на сервере CRM.

Продолжение следует…