Кастомизация
25
Май
28

Кастомизация представлений

Седня рассмотрим как радикально улучшить представления CRM. Способ при этом тоже буде использоваться радикальный – читай неподдерживаемый 🙂 Первый три примера будут основаны исключительно на JavaScript. А в последнем задействуем C# и httpModule. Приступим:

Меняем цвет текста в зависимости от его содержания

Предположим, мы хотим в представлении Бизнес-партнера подкрасить текст столбца Тип отнойшений (этого столбца по дефолту нет в представлении – я его туда вынес), в зависимости от того какое значение находится в каждой строке. Вся кастомизация заключается в добавлениее нескольких строк кода в два файла:

В файл <сайт CRM>\_static\_common\scripts\Global.js добавьте такие строки:

// Объявляем новый ассоциативный  массив, который будет содержать функции, вызываемые перед отображением какого-либо представления
var grid_onGridLoadDelegates = new Array();
// В массив в соответствие имени объекта (Account) ставим вызов функции (account_onBeforeGridLoad)
grid_onGridLoadDelegates[Account] = account_onBeforeGridLoad;

// Функция выделяет цветом текст в определенном столбце представления
// В качестве параметров принимает объекта родительского окна и представления
function account_onBeforeGridLoad(parentWindow, grid) {
    // Просматриваем все строки представления
    for (i = 0; i < grid.childNodes[1].childNodes.length; i++) {
        // Заносим в переменные ссылку на ячейку второго столбца (в данном случаи это Тип отношений)
        var cell = grid.childNodes[1].childNodes[i].childNodes[3];
        // ... и на ее текстовое содержимое
        var cellText =  cell.childNodes[0].innerText;

        // Если текс ячейки равен определенному значени - меняем цвет текста в ячейке
        switch (cellText) {
            case 'Поставщик':
                cell.style.color = "#008000";
                break
            case 'Клиент':
                cell.style.color = "#ff0000";
                break
            case 'Конкурент':
                cell.style.color = "#808000";
                break
            case 'Инвестор':
                cell.style.color = "#0000ff";
                break
            case 'Партнер':
                cell.style.color = "#ff00ff";
                break
        }
    }
}

Этот код делает три фундаментальные вещи: создает ассоциативный массив, в качестве ключей ассоциативного массива используются имена объектов и им же соответствует отпределенная функция, которая объявляется в третю очередь. Эта функция будет вызвается перед отображением представления, что дает нам возможность отредактировать его содержимое. В данном примере эта функция называется account_onBeforeGridLoad и она отвечает за просмотр представления Бизнес-партнера и изменение цвета текста второго столбца (который в данном случаи является Типом отношений). В данную функцию (впрочем как и в любую другую в реализации данного концепта) передаются два объекта: родительское окно и представление. Родительское окно нам без надобности в данном случаи. А вот представление мы просматриваем строку за строкой и «обращаем внимаение» на содержимое ячейки столбца Тип отношений. И если это содержимое равно одному из предопределенных значений, то мы меняем цвет текста его ячейки 🙂

Далее нам нужно иницировать вызов функции ассоциативного массива при загрузки представления. Для этого в файл <сайт CRM>\_static\_grid\AppGrid_DefaultData.htc, внутрь тега script добавьте следующий код:

// Если в массиве grid_onGridLoadDelegates существует переменная соответствующая имени объекта текущего представления, то...
if (grid_onGridLoadDelegates != null && grid_onGridLoadDelegates[this.element.oname] != null) {
    // ... вызываем сопоставленную ей функцию и передаем ей объект родительского окна и объект представления
    grid_onGridLoadDelegates[this.element.oname].call(this, window, this.element);
}

Этот код всего лишь перед отображение представления вызвает функцию ассоциативного массива, ключ которого соответствут названию объекта текущего представления. Вот и все – идем смотреть представление Бизнес-партнера 🙂



Заменяем текст в представлении картинкой

Вторая гипотетическая ситуация: в представлении Контакта мы хотим вместо текста какой-либо категории (в данном случаи я вынес во второй столбец представления поле Должность) отобразить соответствующую этой категории иконку… реализуем… 🙂

А хардкодить придется не много. Вся «инфраструктурная» логика уже релизована в предыдущем пункте. Все что осталось – это добавить в ассоциативный массив grid_onGridLoadDelegates в файле Global.js новый ключ, соответствующий Контакту и назначить для него функцию обработки представления:

// В массив в соответствие имени объекта Contact ставим вызов функции contact_onBeforeGridLoad
grid_onGridLoadDelegates[Contact] = contact_onBeforeGridLoad;

// Функция заменяет текст во втором столбце кажодой строки представления на рисунок, в зависимости от содержимого ячейки
function contact_onBeforeGridLoad(parentWindow, grid) {
    // Просматриваем все строки представления
    for (i = 0; i < grid.childNodes[1].childNodes.length; i++) {

        // Заносим в переменные ссылку на ячейку второго столбца (в данном случаи это Долность)
        var cell = grid.childNodes[1].childNodes[i].childNodes[3];
        cell.style.textAlign = "center";

        // В зависимости от значения ячейки - заменяем ее содержимое определенным рисунком
        switch (cell.innerText) {
            case 'Директор':
                cell.innerHTML = '<img alt="" src="/_imgs/ico_16_8.gif" />';
                break
            case 'Секретарь':
                cell.innerHTML = '<img alt="" src="/_imgs/ico_16_10.gif" />';
                break
            case 'Менеджер':
                cell.innerHTML = '<img alt="" src="/_imgs/ico_16_129.gif" />';
                break
            case 'Маркетолог':
                cell.innerHTML = '<img alt="" src="/_imgs/ico_16_5.gif" />';
                break
        }
    }
}

По аналогии с предыдущем пунктом функция contact_onBeforeGridLoad получает представление, просматривает его строки, в них второй столбец и если в нем значение равно одному из предопределенных – заменяет все содержимое ячейки тегом <IMG>, который отвечает за отображение рисунков в HTML-коде (сам же тег <IMG> ссылается на рисунок определенный в атрибуте src).


Подкрашиваем всю строку представления в зависимости от ее содержимого

Данная операция будет несколько отличаться от двух предыдущих, т.к. работе представления CRM есть определенная специфика, которая не позволяет просто так с помощью JS поменять фоноывй цвет строки. Поэтому придется переделовать бызовую логику работы CRM.

Задача: в представлении Действий изменить цвет фона строки в зависимости от типа Действия. Поехали…

  • Откройте в каком-нибудь редакторе файл <сайт CRM>\_grid\AppGrid.css.aspx. Данный файл отвечает за CSS-стили представления CRM. Нам его и нужно для начала подкорректировать. Найдите в нем определение двух классов и добавьте/измените в них определение цвета фона:
    • .ms-crm-List-Row (класс отвечает за отрисовку не выделенной строки представления)
      background-color: expression( rowColor(this) );
    • TR.ms-crm-List-SelectedRow (класс отвечает за отрисовку выделенной строки представления)
      background-color: expression( rowSelectedColor(this) );

    все что делает этот код – вызвает JS-функциию, которая определяет тип строки и возвращает HTML-цвет, в который необходимо закрасить фон строки. А в качестве параметра в функцию передается сама строка. Заметьте также, что для каждого класса мы вызваем разные функции, т.к. один класс отвечает за не выделенные строки, а другой за выделенные. Поэтому для выделенных строк мы должны возвращять более темные тона цвета не выделенных строк;

  • Далее необходимо задать сами функции, которые будут определять нужный цвет строки! Откройте старый знакомый нам файл Global.js (если забыли, то сможете найти его по адресу <сайт CRM>\_static\_common\scripts\) и добавьте в его конец две функции:
    // Функция форзвращает определенный цвет в зависимости от значения первого столбца
    function rowColor(row) {
        var cellText = row.childNodes[2].childNodes[0].innerText;
    
        switch (cellText) {
            case 'Задача':
                return "#FFFFCC";
                break
            case 'Звонок':
                return "#FFCCFF";
                break
            case 'Письмо':
                return "#bfddff";
                break
            case 'Факс':
                return "#CCFFCC";
                break
            default:
                return "#FFFFFF"
        };
    }
    
    // Функция форзвращает определенный цвет в зависимости от значения первого столбца строки представления
    function rowSelectedColor(row) {
        var cellText = row.childNodes[2].childNodes[0].innerText;
    
        switch (cellText) {
            case 'Задача':
                return "#dad871";
                break
            case 'Звонок':
                return "#c9a0dc";
                break
            case 'Письмо':
                return "#CCCCFF";
                break
            case 'Факс':
                return "#badbad";
                break
            default:
                return "#a7cdf0"
        };
    }
    
  • Эти функции получают строку представления Действия, смотрят ее второй столбец (в котором указан тип действия) и в зависимости от этого возвращают нужный цвет!
  • Открываем представление Действий 🙂



Ученье – свет, а не ученье – приятный полумрак 🙂

Как Вы наверно заметили у меня в коде используются всякие childNodes – это я с помощью них «путешествую» по структуре HTML-дерева представления. Не видя это дерево очень сложно писать код, поэтому вот Вам парочка способов как его рассмотреть:

  1. Добавьте в код функции привязанной к ассоциативному массиву grid_onGridLoadDelegates строчку debugger. Например так:
    grid_onGridLoadDelegates[Contact] = contact_onBeforeGridLoad;
    
    function contact_onBeforeGridLoad(parentWindow, grid) {
    	debugger
    }
    

    Затем в настройках браузера включите отладку скриптов (Свойства обозревателя — Дополнительно — раздел Обзор — снимите две галки отвечающие за отключение отладки). Ну и откройте целевое представление – в результате появится приглашение открыть Visual Studio для отладки скрипта – соглашайтесь 🙂 откроется новое окно VS с кодом, в котором была использована конструкция debugger. Перейдите Debug — Window — Locals — внизу экрана появится дополнительная секция содержащее два объекта, переданных функции – можете по ним путешествовать 🙂



  1. Второй способ немного попроще: в функцию сопоставленную ассоциативному массиву вызовите alert в качестве параметра которого укажите innerHTML объекта grid. Примерно так:
    grid_onGridLoadDelegates[Contact] = contact_onBeforeGridLoad;
    
    function contact_onBeforeGridLoad(parentWindow, grid) {
    	alert(grid.innerHTML);
    }
    

    Ну а дальше открываете представление, появляется алерт, щелкаете по нем, жмете Ctrl + C и вставляете скопированное содержимое в какой-нибудь редактор понимающий HTML 🙂


Использование httpModule для подмены значка в связанном представлении История

В одном из предыдущих постов мы уже пользовались httpModule для замены представления предварительного просмотра. Сейчас провернем тот же фоку но только подменим в представлении Истории иконки 🙂

Цель: подменить в представлении История иконки для Действий имеющих направление Исходящие (в данном случаю я подменяю стандартные иконки на произвольные, но в реале Вы таким образом можете идентифицировать направления Действия – создав например иконку Звонка с маленькой стрелкой направленной вправо).

  • Откройте в Visual Studion проект DirectionFilter;
  • Он состоит из двух файлов DirectionFilterModule.cs и DirectionFilter.cs. В первом обявляется сам http-модуль, выполняется перехват страницы areas.aspx и проверка на то, что это представление Истории. Далее перехваченный HTML-поток отправляется в файл DirectionFilter.cs, где и происходит основная его обработка. В первый файл мы не трогаем – там все настроено. А вот содержимое второго необходимо рассмотреть поподробнее:
    • Для начала как всегда настраиваем подключение с веб-службе CRM (из нее мы будем извлекать направление Действия);
    • Потом начинаем просматривать HTML-код и искать в нем вхождение подстроки:
      otype="

      которая входит в HTML-разметку каждой строки представления;

    • Вырезав последующие после otype четыре символа мы сможем узнать код объекта строки;
    • Определив тип объекта в строке мы передаем его вместе с GUID’ом (который мы также вырезали из HTML-кода) и замещающей иконкой в функцию Replace;
    • Функция Replace получив тип объекта и его GUID отправляет запрос в CRM (с помощью функции IsOutgoing) для определения направления Действия. И если направление Исходящее происходит замена старой иконки на новую;
    • Ну и в последнуюю очередь HTML-поток перезаписывается 🙂
  • Соберите проект и скопируйте dll сборку из папки bin\Debug в папку;
  • Откройте на редактирование web.config сайта CRM (находится к корне сайта) и добавьте в раздел httpModulesтакую сточку:
    <add name ="PreviewFilter" type="Magnetism.Crm.HttpModules.DirectionFilterModule, Magnetism.Crm.HttpModules" />
  • На этом все… Откройте какое нибудь представление Истории и смотрите как изменятся стандартные значки объектов Действий 🙂




П.С. данное решение основано на редактирование ответа страницы areas.aspx. Но если представление будет обновлено программно или вручную, то запрос будет направлен в веб-сервис AppGridWebService.asmx, на основе возвращенных данных которого и будет обновлено представление. Соответственно наш http модуль не сработает и значки не заменятся. Так что если Вы планируете использовать сие подход, рассмотрите все возможные варианты замены иконки 🙂
П.С.2 По аналоги с вышеописанными примерами можно придумать кучу других изменений представлений 🙂

Комментарии (28)
  • Михаил 25.05.2010

    Добрый день!
    Спасибо, за такой развернутый ответ 🙂

    Вот только самая первая кастомизация не работает 🙁
    Все попробовал уже — не хочет и все. Замена текста на картинку соответственно тоже не работет. Может где-то в коде ошибка?

  • slivka_83 25.05.2010

    Ctrl + F5 нажмите в браузере чтобы обновить кэш файлов 🙂

  • Михаил 25.05.2010

    Не, дело не в этом, тут какая-то другая проблема…

  • slivka_83 25.05.2010

    Повесьте в коде кучу алертов чтобы определить на комком моменте код перестает выполняться?

  • Михаил 25.05.2010

    На сколько я понял, проблема с самим циклом.
    ————-
    for (i = 0; i < grid.childNodes[1].childNodes.length; i++) {
    var cell = grid.childNodes[1].childNodes[i].childNodes[7];
    var cellText = cell.childNodes[0].innerText;
    switch (cellText) {
    case 'Поставщик':
    cell.style.color = "#008000";
    break

    }
    ————
    Алерт в него вставлять смысла нет.

    Код вроде весь выполняется, ошибок при выполнении нет.

  • slivka_83 25.05.2010

    Повесьте перед циклом

    alert(grid.childNodes[1].childNodes.length);
    

    a после cellText

    alert(cell + " " + cellText);
    
  • Михаил 25.05.2010

    Сделал.
    Первый алерт выдает 185.
    Второй по порядку выдает значения колонки «Тип отношений» в виде «[object] Поставщик».

  • Михаил 25.05.2010

    Есть предположение, что не работает из-за установленного Record Counter (http://mmcrm.ru/?p=1093).
    Возможно?

  • slivka_83 25.05.2010

    а ну ясно… значит сравнение не проходит. а все потому что файл Global.js сохранен не в кодеровке UTF 🙂 сохраните его в этой кодировке. Затем заново откройте и убедитесь что русские буквы корректно введены 🙂

  • Михаил 25.05.2010

    Радостный смайл 🙂
    Все получилось!
    Спасибо!

  • Михаил 25.05.2010

    Так, не все так радостно 🙂
    У меня не работает в созданных объектах.
    То есть в стандартных Account, Contact — нормально.
    А вот код
    grid_onGridLoadDelegates[new_docs_seldon] = new_docs_seldon_onBeforeGridLoad;
    выдает ошибку. В чем может быть проблема?

  • Павел 25.05.2010

    А это спаортед методы?

  • slivka_83 25.05.2010

    Добрый день 🙂

    -> Михаил
    Для кастомных объектов используйте в квадратных скобках вместо имени его код 🙂

    -> Павел
    Лютый ансапорт 🙂

  • Михаил 25.05.2010

    Очередное спасибо!

  • Dmitry 25.05.2010

    Привет!
    а как быть с пользовательскими представлениями (не дефалт)?
    возможно ли к ним прикрутить такую полезную фичу?

  • slivka_83 25.05.2010

    Добрый день 🙂

    Я не пробовал, но помоему код реагирует на объект, который отображается в представлении а не на то каким путем оно создано 🙂

  • Nek 25.05.2010

    Почемуто в шестом IE не работает?..Поставил alert после вызова функции, ничего не происходит. А в восьмой версии IE все норм

  • Nek 25.05.2010

    А если изменить код таким образом:
    var grid_onGridLoadDelegates = new Array();
    grid_onGridLoadDelegates[10039] = alert();
    То алерт вылетает когда загружается любой объект, а не только мой.

  • slivka_83 25.05.2010

    К сожалению я не могу это проверить или исправить — у меня нет IE6 🙂 И Вам советую переходит на более позднии версии — они гораздо быстрее 🙂

  • Nek 25.05.2010

    А можно ли получить из этой функции, которая обрабатывает строки таблицы, название представления?

  • slivka_83 25.05.2010

    Думаю что да. Вам нужно только из скрпта «добраться» по DOM-структуре до пиклиста с представлениями 🙂 Ну, и вытащить оттуда его название 🙂

  • Алексей 25.05.2010

    Здравствуйте!
    Классный способ и сайт!!!
    Не подскажете, каким свойством можно воспользоваться для идентификации пользовательского грида в CRM 2011 (у него тоже есть oname, но он содержит число, не связанное с названием грида. Можно зацепиться за него, но оно меняется от организации к организации и решение не будет переносимым…)

  • slivka_83 25.05.2010

    Добрый день 🙂
    На то оно и анасапортного решение — что бы не переносится, не апгрейдится и вообще не поддерживаться 🙂

  • Богданов 25.05.2010

    Дорый день,»Подкрашиваем всю строку представления в зависимости от ее содержимого» в CRM 2011 не работает, ошибки не выдаёт Glbal.JS сохранён в UTF,при проверке отображает что есть функция, а в AppGrid.css.aspx. если убрать «expression( rowColor(this)» и поменять к примеру на «#FFFOOO», то меняет все строчки на красный цвет.Может ли быть проблема в «expression( rowColor(this)» CRM2011 не распознаёт эту функцию?

  • slivka_83 25.05.2010

    Добрый день. Сам я не реализовывал подобной штуки в CRM 2011, поэтому сходу сказать не могу. Тут нужно дебажить. Если будет время как-нибудь займусь…

  • Михаил Трескин 25.05.2010

    slivka_83, а жаль, что в 2011 не пробовали 🙁 Очень и очень полезная штука. Самим в 2011 тоже пока не получилось сделать (

  • Наталья 25.05.2010

    Интересно, будет ли когда-нибудь, в 2013, например, «Подкрашиваем всю строку представления в зависимости от ее содержимого» родным функционалом? А то все ансаппорт… Для 2013 есть что-нибудь подобное?

  • slivka_83 25.05.2010

    Нет, в 2013 подобного нет. По крайней мере в веб-интерфейсе.
    Но подобного можно добиться родным функционалом в Outlook-клиенте для CRM (начиная с 2011 версии).

*

code