Кастомизация
15
Ноя
6

Редактируемый грид с помощью jqGrid

Сегодня создадим редактируемый грид для MS CRM 2011 на основе плагина для jQuery – jqGrid. Экспериментировать будем на объекте Организация и связанных Контактах, которые будем выводить в гриде на форме Организации. Грид будет не идеальным, но с чего-то нужно начинать…

Приступим:

  • Добавьте в CRM следующие Веб-ресурсы:
    Название Тип Описание
    ui.jqgrid.css CSS CSS стили для jqGrid’а.
    jquery.ui.datepicker.css CSS CSS стили для контрола выбора даты.
    jquery.ui.1.9.1.custom.min.css CSS Базовые CSS стили jQuery UI, откуда взят контрол выбора даты.
    jquery.1.8.2.min.js JS JS фреймворк.
    json2.js JS JS фреймворк для работы с JSON-объектами.
    grid.base.js JS Основа jqGrid’а.
    jquery.fmatter.js JS Модуль форматирования данных в таблице.
    grid.locale.en.js JS Локализация jqGrid’а.
    grid.common.js JS Основные функции необходимые для редактирования данных.
    grid.inlinedit.js JS Модуль редактирования данных целой строкой.
    jquery.ui.1.9.1.custom.min.js JS Основа jQuery UI.
    jquery.ui.datepicker.js JS Код для контрола выбора даты.
  • Создайте HTML Веб-ресурс с таким кодом:
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Grid</title>
    <link rel="stylesheet" type="text/css" media="screen" href="/WebResources/new_jquery.ui.1.9.1.custom.min.css" />
    <link rel="stylesheet" type="text/css" media="screen" href="/WebResources/new_jquery.ui.datepicker.css" />
    <link rel="stylesheet" type="text/css" media="screen" href="/WebResources/new_ui.jqgrid.css" />
    <style type="text/css">
        html, body {
            margin: 0;
            padding: 0;
            font-size: 75%;
        }
    </style>
    <script src="/WebResources/new_jquery.1.8.2.min.js" type="text/javascript"></script>
    <script src="/WebResources/new_jquery.fmatter.js" type="text/javascript"></script>
    <script src="/WebResources/new_json2.js" type="text/javascript"></script>
    <script src="/WebResources/new_grid.base.js" type="text/javascript"></script>
    <script src="/WebResources/new_grid.locale.en.js" type="text/javascript"></script>
    <script src="/WebResources/new_grid.common.js" type="text/javascript"></script>
    <script src="/WebResources/new_grid.inlinedit.js" type="text/javascript"></script>
    <script src="/WebResources/new_jquery.ui.1.9.1.custom.min.js" type="text/javascript"></script>
    <script src="/WebResources/new_ui.datepicker.js" type="text/javascript"></script>
     
    <script type="text/javascript">
        // Ссылка на родительский фрейм
        b = window.parent;
        // Базовый URL для oData-запросов
        oDataBase = b.Xrm.Page.context.getServerUrl() + "/XRMServices/2011/OrganizationData.svc";
    
        $(function () {
            var lastsel2; // переменная для хранения предыдущей выделенной строки
    
            // Иницируем создание редактируемого грида
            $("#list").jqGrid({
                datatype: "local",
                colNames: ['ContactId', 'Имя', 'Фамиля', 'Прайс Id', 'Прайс', 'День рождения', 'Описание', 'Кредит?', 'Кредитный лимит', 'Роль'],
                colModel: [
                    { name: 'id', index: 'ContactId', hidden: true },
                    { name: 'FirstName', index: 'FirstName', width: 55, editable: true, edittype: 'text' },
                    { name: 'LastName', index: 'LastName', width: 90, editable: true, edittype: 'text', formatter: 'showlink',
                        // Выводим содержимое ячейки в виде ссылки
                        formatoptions: { baseLinkUrl: 'http://crm2011:5555/superfirma/userdefined/edit.aspx', addParam: '&etc=2', idName: 'id' }
                    },
                    { name: 'DefaultPriceLevelId', index: 'DefaultPriceLevelId', hidden: true },
                    { name: 'DefaultPriceLevelName', index: 'DefaultPriceLevelName', width: 85, editable: true, edittype: 'text',
                        // Для поля "лукап", при получении фокуса выводим диалоговое окно лукапа...
                        editoptions: { dataInit: function (el) {
                                $(el).focus(function () {
                                    var lkp = window.showModalDialog("http://crm2011:5555/superfirma/_controls/lookup/lookupinfo.aspx?LookupStyle=single&objecttypes=1022#");
                                    if (lkp) {
                                        // и помещаем в ячейки...
                                        $(el).val(lkp.items[0].name); // ...отображаемое знаение 
                                        $("#list").jqGrid('setCell', el.id.substr(0, 36), 'DefaultPriceLevelId', lkp.items[0].id); // ... GUID записи
                                    }
                                    this.blur();
                                });
                            }
                        }
                    },
                    { name: 'BirthDate', index: 'BirthDate', width: 80, formatter: 'date', formatoptions: { newformat: 'd.m.Y' }, editable: true,
                        // Для поля даты выводим контрол выбора даты
                        editoptions: { size: 10, dataInit: function (el) { $(el).datepicker({ dateFormat: 'dd.mm.yy' }) } }
                    },
                    { name: 'Description', index: 'Description', width: 150, align: 'right', editable: true, edittype: 'textarea', editoptions: { rows: '2', cols: '25'} },
                    { name: 'CreditOnHold', index: 'CreditOnHold', width: 50, editable: true, edittype: 'checkbox', editoptions: { value: 'true:false' }, formatter: 'checkbox' },
                    { name: 'CreditLimit', index: 'CreditLimit', width: 150, editable: true, edittype: 'text', formatter: 'currency', formatoptions: { decimalSeparator: ",", thousandsSeparator: " ", decimalPlaces: 2, suffix: " руб"} },
                    { name: 'AccountRoleCode', index: 'AccountRoleCode', width: 150, editable: true, edittype: 'select', formatter: 'select', editoptions: { value: '1:Деловой специалист;2:Сотрудник;3:Влияние'} }
                ],
                pager: '#pager',
                rowNum: 10, // Количество строк в гриде
                rowList: [10, 20, 30],
                // При двойном клике на строке 
                ondblClickRow: function (id) {
                    // Если это строка отлична от той что редактировалась в прошлый раз
                    if (id && id !== lastsel2) {
                        // Восстанавливаем значение предыдущей строки (т.к. ее не "сохранили")
                        jQuery('#list').jqGrid('restoreRow', lastsel2);
                        // Иницируем редактирование строки по кторой дважды щелкнули
                        jQuery('#list').jqGrid('editRow', id, true, null, null, null, null, function (rowId) { upadetData(rowId); });
                        // Запоминаем ID редактируемой строки
                        lastsel2 = id;
                    }
                },
                sortname: 'LastName', // Сортируем по этому столбцу
                sortorder: 'asc',
                viewrecords: true,
                rownumbers: true,
                autowidth: true,
                shrinkToFit: true,
                height: 147,
                editurl: 'clientArray',
                caption: 'Контакты',
                loadtext: 'Загрузка...'
            }).ready(function () {
                // Вызываем функцию начального заполнения грида
                loadData();
            });
        });
    
        // 
        function loadData() {
            $.ajax({
                type: "GET",
                contentType: "application/json; charset=utf-8",
                datatype: "json",
                url: oDataBase + "/ContactSet?$select=ContactId,FirstName,LastName,DefaultPriceLevelId,BirthDate,Description,CreditOnHold,CreditLimit,AccountRoleCode&$filter=ParentCustomerId/Id eq guid'" + b.Xrm.Page.data.entity.getId().substr(1, 36) + "'",
                beforeSend: function (XMLHttpRequest) { XMLHttpRequest.setRequestHeader("Accept", "application/json"); },
                success: function (data, textStatus, XmlHttpRequest) {
                    var records = data.d.results;
                    for (key in records) {
                        // Меняем структура возвращенного объекта с данным на тот, который сможет "переварить" jqGrid
                        records[key].AccountRoleCode = records[key].AccountRoleCode.Value;
                        records[key].CreditLimit = records[key].CreditLimit.Value;
                        records[key].DefaultPriceLevelName = records[key].DefaultPriceLevelId.Name;
                        records[key].DefaultPriceLevelId = records[key].DefaultPriceLevelId.Id;
                        // Добавляем строку в jqGrid
                        jQuery("#list").jqGrid('addRowData', records[key].ContactId, records[key]);
                    }
                },
                error: function (xmlHttpRequest, textStatus, errorThrown) {
                    alert("Status: " + textStatus + "; ErrorThrown: " + errorThrown);
                }
            });
        }
    
        // Функция обновляет данные в БД после редактирования строки
        function upadetData(rowId) {
            var ret = jQuery("#list").jqGrid('getRowData', rowId);
    
            // Формируем "пакет" обновлений
            var changes = {
                FirstName: ret.FirstName,
                LastName: ret.LastName,
                BirthDate: ret.BirthDate == " " ? null : ret.BirthDate.substr(3, 2) + "/" + ret.BirthDate.substr(0, 2) + "/" + ret.BirthDate.substr(6, 4),
                DefaultPriceLevelId: { Id: ret.DefaultPriceLevelId, LogicalName: "pricelevel" },
                Description: ret.Description,
                CreditOnHold: ret.CreditOnHold,
                CreditLimit: { Value: ret.CreditLimit == "" ? null : ret.CreditLimit },
                AccountRoleCode: { Value: ret.AccountRoleCode }
            };
    
            // Вызываем асинхронную функцию AJAX для обновления записи CRM через OData
            $.ajax({
                type: "POST",
                contentType: "application/json; charset=utf-8",
                datatype: "json",
                data: window.JSON.stringify(changes), // Парсим "пакет" обновлений
                url: oDataBase + "/ContactSet(guid'" + rowId + "')",
                beforeSend: function (XMLHttpRequest) {
                    XMLHttpRequest.setRequestHeader("Accept", "application/json");
                    XMLHttpRequest.setRequestHeader("X-HTTP-Method", "MERGE");
                },
                error: function (XmlHttpRequest, textStatus, errorThrown) {
                    alert("Status: " + textStatus + "; ErrorThrown: " + errorThrown); 
                }
            });
        }
    </script>
    </head>
    <body>
    <table id="list"></table> 
    <div id="pager"></div> 
    </body>
    </html>
    

    Здесь происходит следующее:

    • Подключаем служебные CSS и JS файлы;
    • Самая большая часть собственно инициирует создание грида. Тут мы объявляем модель данных и соответствующий им набор столбцов;
    • Функция loadData запрашивает в CRM (с помощью oData) записи Контактов, связанных с Организацией, и загружает их в редактируемый грид;
    • Функция upadetData обновляет данные в CRM после того как они были изменены в строке.
  • Вынесите созданный HTML Веб-ресурс на форму Организации (при этом разрешите использование скриптов).

На этом все… идем играться…

Кстати редактирование строки инициируется двойным кликом по ней 🙂




З.Ы. что тут можно/нужно улучшить:

  • Допилить «лукап» чтобы выглядел более похожим на обычный. Т.е. была иконка справа и по ней открывалось диалоговое окно. Сейчас открывается при получении лукапом фокуса/ Для этого нужно применить кастомное редактирование;
  • Допилить ссылку, чтобы при щелчке по ней левой кнопкой мыши, содержимое открывалось в новом окне, а не в текущем фрейме. Сейчас только по клику на средней кнопки мыши открывается в новом окне. Для этого нужно применить кастомное форматирование;
  • Вынести кнопки добавления новых строк и удаление существующих.
Комментарии (6)
  • Андрей 15.11.2012

    Спасибо за код.

    Только ссылки подправьте в блоке
    «Добавьте в CRM следующие Веб-ресурсы:»

  • slivka_83 15.11.2012

    Спасибо, подправил 🙂

  • Евгений 15.11.2012

    Столкнулся с некоторыми проблемами при динамической загрузке jqGrid ColModel, кастомной загрузке содержимого и т.п.
    Постарался по максимуму описать все это на сайте http://fanisovich.ru/category/sharepoint/jqgrid/

  • Стас 15.11.2012

    Здравствуйте. Столкнулся с проблемой, при подключении js. Браузер выдаёт ошибку

    Ошибкасценария Internet Explorer
    Строка: 3
    Символ: 1
    Ошибка: Синтаксическая ошибка
    Код: 0
    URL: http://crm/WebResources/new_jquery.1.8.2.min.js

    И так по каждому .js файлу. Скрипты разрешены, антивирус на клиенте отключен. В чём может быть ошибка, не подскажите?

  • slivka_83 15.11.2012

    Добрый день.

    Ну, тут нужно локализовывать проблему… Один конкретный браузер? Или браузеры всех пользователей на всех доступных компьютерах. зависит ли ошибка от роли пользователя? Версия ослика корректная?

  • Стас 15.11.2012

    Спасибо за ответ. IE стоит 8й. Только им и пользуемся, потмоу что авторизация через Active-X. Оштбка на любых ролях, включая администратора.

*

code