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

Трудности перехода на 2016 версию

Вышла очередная версия 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.

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

 
Comments

Функция Mscrm.InlineDialogUtility.createInlineDialog никуда не делась. Просто её перенесли в другой frame. Из CustomScriptsFrame вызывается следующим образом:
window.parent.Mscrm.InlineDialogUtility.createInlineDialog

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

Так, а тут то дело не только в этой функции. Всеми системными недокументированными функциями пользоваться ненадёжно. Их бывают ещё и переименовывают 🙂 Но изменение фрейма и переименовывание — это не большая проблема. Ведь всегда можно создать свои alias на такие системные функции и поместить их в отдельный веб-ресурс (скрипт). А дальше уже ссылаться не на системные функции, а на эти алиасы. И если что-то переименуют, то в одном месте обновил код и всё дальше работает. Также и с фреймами: можно создать переменную, в которой будет путь к фрейму с системными функциями и также получается, что дело будет только в изменении одной переменной.
Другое дело, если функцию совсем удалят.