Кастомизация
11
Мар
13

Приручаем стандартные лукапы в CRM 2011

Лукап с множественным выбором

Если Вы создадите кастомный лукап в CRM, то по умолчанию сможете выбрать в нем только запись из одного объекта. Сейчас немножко расширим этот функционал. Сделаем так, чтобы в нем можно было выбирать: Интерес, Контакт и Пользователя. В примере лукап называется «new_superclient» и по умолчанию в нем выбирается Интерес.
Т.к. в лукапе можно хранить только «родной» объект, то нам придется создать служебные поля (можете их скрыть на продакшене) для хранения «нетипичных» значений (тип полей – строковый):

  • new_type;
  • new_guid;
  • new_name.

Создайте JS Веб-ресурс со следующим кодом:

function onchange() {
    var superclient = Xrm.Page.getAttribute("new_superclient").getValue();
    if (IsNull(superclient) || superclient[0].type == "4") {
        // Если лукап не заполнен или в нем выбрана "родной" - очищаем вспомогательные поля (достаточно одного).
        Xrm.Page.getAttribute("new_guid").setValue(null);
    } else {
        // Если лукап содержит значение не "родного" объекта, то сохраняем компоненты лукапа во вспомогательных полях.
        Xrm.Page.getAttribute("new_type").setValue(superclient[0].type);
        Xrm.Page.getAttribute("new_guid").setValue(superclient[0].id);
        Xrm.Page.getAttribute("new_name").setValue(superclient[0].name);
    }
}

function onsave() {
    // Если в лукапе выбран не родной объект - очищаем лукап при сохранении.
    var superclient = Xrm.Page.getAttribute("new_superclient").getValue();
    if (!IsNull(superclient) && superclient[0].type != '4')
        Xrm.Page.getAttribute("new_superclient").setValue(null);
}

function onload() {
    // Перечисляем икноки, имя объектов и коды объектов.
    lookuptypeIcons = '/_imgs/ico_16_2.gif:/_imgs/ico_16_4.gif:/_imgs/ico_16_1.gif';
    lookuptypenames = 'contact:2:Contact,lead:4:Lead,account:1:Account';
    lookuptypes = '2,4,1';

    // Получаем значения "будущего лукапа" из служебных полей
    var savedId = Xrm.Page.getAttribute("new_guid").getValue();
    var savedType = Xrm.Page.getAttribute("new_type").getValue();
    var savedName = Xrm.Page.getAttribute("new_name").getValue();

    // "Настраиваем" лукап
    var savedEntityName = savedType == "1" ? "account" : "contact";
    document.getElementById("new_superclient").setAttribute("lookuptypes", lookuptypes);
    document.getElementById("new_superclient").setAttribute("lookuptypenames", lookuptypenames);
    document.getElementById("new_superclient").setAttribute("lookuptypeIcons", lookuptypeIcons);
    document.getElementById("new_superclient").setAttribute("defaulttype", "4");
    
    // Подставляем значение в лукап
    var superclient = Xrm.Page.getAttribute("new_superclient").getValue();
    if (IsNull(superclient) && !IsNull(savedId)) {
        var value = new Array();
        value[0] = new Object();
        value[0].displayClass = "ms-crm-Lookup-Item";
        value[0].keyValues = new Object();
        value[0].values = new Object();
        value[0].onclick = "openlui()";
        value[0].id = savedId;
        value[0].entityType = savedEntityName;
        value[0].typename = savedEntityName;
        value[0].name = savedName;
        value[0].type = savedType;
        Xrm.Page.getAttribute("new_superclient").setValue(value);
    }
}

Здесь у нас три функции:

  • onchange – если выбранное значение не «родное», то записываем составляющие лукапа в служебные поля;
  • onsave – очищаем лукап если в нем было выбрано не «родное» значение;
  • onload – при загрузке подтягиваем из служебных полей значения лукапа и помещаем их в лукап.

Теперь Вы можете выбрать из трех различных объектов в одном лукапе 🙂



N:N лукап

Создадим лукап на подобие лукапа «Кому» на форме Электронной почты. Т.е. мы с одном лукапе сможем выбирать множество записей за раз.

Для решения нам понадобятся:

  • Связь N:1 и лукап на форме;
  • Связь N:N, в которой будут храниться записи в выбранные в «N:N лукапе».

Далее:

  • Создайте новый JS Веб-ресурс со следующим кодом:
    RetreiveAssociatedEntities = function (relationshipSchemaName, entity1SchemaName, entity1KeyValue, retreiveAttribute) {
        var fetchXml = "<fetch mapping='logical'>"
        + " <entity name='" + relationshipSchemaName + "'>"
        + " <all-attributes />"
        + " <filter>"
        + " <condition attribute='" + entity1SchemaName + "id' operator='eq' value ='" + entity1KeyValue + "' />"
        + " </filter>"
        + " </entity>"
        + "</fetch>";
    
        var fetchResults = Fetch(fetchXml);
        var nodeList = fetchResults.selectNodes("resultset/result");
    
        var returnList = new Array();
        if (nodeList == null || nodeList.length == 0) {
            return returnList;
        } else {
            for (i = 0; i < nodeList.length; i++) {
                var idValue = nodeList[i].selectSingleNode('./' + retreiveAttribute).nodeTypedValue;
                returnList[i] = idValue;
            }
            return returnList;
        }
    }
    
    MischiefMayhemSOAP = function (serviceUrl, xmlSoapBody, soapActionHeader, suppressError) {
        var xmlReq = "<?xml version='1.0' encoding='utf-8'?>"
        + "<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'"
        + " xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'"
        + " xmlns:xsd='http://www.w3.org/2001/XMLSchema'>"
        + GenerateAuthenticationHeader()
        + " <soap:Body>"
        + xmlSoapBody
        + " </soap:Body>"
        + "</soap:Envelope>";
    
        var httpObj = new ActiveXObject("Msxml2.XMLHTTP");
        httpObj.open("POST", serviceUrl, false);
    
        httpObj.setRequestHeader("SOAPAction", soapActionHeader);
        httpObj.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
        httpObj.setRequestHeader("Content-Length", xmlReq.length);
        httpObj.send(xmlReq);
    
        var resultXml = httpObj.responseXML;
        var errorCount = resultXml.selectNodes("//error").length;
        if (errorCount != 0) {
            var msg = resultXml.selectSingleNode("//description").nodeTypedValue;
    
            if (typeof (suppressError) == "undefined" || suppressError == null) {
                alert("The following error was encountered: " + msg);
            }
            return null;
        } else {
            return resultXml;
        }
    }
    
    Fetch = function (fetchXml) {
        var xmlSoapBody = "<Fetch xmlns='http://schemas.microsoft.com/crm/2007/WebServices'>"
        + " <fetchXml>"
        + FetchEncode(fetchXml)
        + " </fetchXml>"
        + "</Fetch>";
    
        var fetchResponse = MischiefMayhemSOAP("/MSCRMServices/2007/CrmService.asmx", xmlSoapBody, "http://schemas.microsoft.com/crm/2007/WebServices/Fetch");
    
        if (fetchResponse != null) {
            var fetchResults = new ActiveXObject("Msxml2.DOMDocument");
            fetchResults.async = false;
            fetchResults.resolveExternals = false;
            fetchResults.loadXML(fetchResponse.text);
            return fetchResults;
        } else {
            return null;
        }
    }
    
    FetchEncode = function (strInput) { //_HtmlEncode
        var c;
        var HtmlEncode = '';
    
        if (strInput == null) return null;
        if (strInput == '') return '';
    
        for (var cnt = 0; cnt < strInput.length; cnt++) {
            c = strInput.charCodeAt(cnt);
    
            if (((c > 96) && (c < 123)) ||
                ((c > 64) && (c < 91)) ||
                (c == 32) ||
                ((c > 47) && (c < 58)) ||
                (c == 46) ||
                (c == 44) ||
                (c == 45) ||
                (c == 95)) {
                    HtmlEncode = HtmlEncode + String.fromCharCode(c);
            } else {
                HtmlEncode = HtmlEncode + '&#' + c + ';';
            }
        }
        return HtmlEncode;
    }
    
    AssociateEntities = function (moniker1name, moniker1id, moniker2name, moniker2id, RelationshipName) {
        var authenticationHeader = GenerateAuthenticationHeader();
        // Готовим SOAP сообщение
        var xml = "<?xml version='1.0' encoding='utf-8'?>";
        xml += "<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance\' xmlns:xsd='http://www.w3.org/2001/XMLSchema\'>";
        xml += authenticationHeader;
        xml += "<soap:Body><Execute xmlns='http://schemas.microsoft.com/crm/2007/WebServices'><Request xsi:type='AssociateEntitiesRequest'>";
        xml += "<Moniker1><Id xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>" + moniker1id + "</Id>";
        xml += "<Name xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>" + moniker1name + "</Name></Moniker1>";
        xml += "<Moniker2><Id xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>" + moniker2id + "</Id>";
        xml += "<Name xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>" + moniker2name + "</Name></Moniker2>";
        xml += "<RelationshipName>" + RelationshipName + "</RelationshipName>";
        xml += "</Request></Execute></soap:Body></soap:Envelope>";
    
        // Готовим xmlHttpObject и отправляем сообщение
        var xHReq = new ActiveXObject("Msxml2.XMLHTTP");
        xHReq.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
        xHReq.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/Execute");
        xHReq.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
        xHReq.setRequestHeader("Content-Length", xml.length);
        xHReq.send(xml);
    
        var resultXml = xHReq.responseXML; // Получаем результат запроса
        var errorCount = resultXml.selectNodes('//error').length; // Проверяем ошибки
    
        if (errorCount != 0) {
            var msg = resultXml.selectSingleNode('//description').nodeTypedValue;
            alert(msg);
        }
    }
    
    DisassociateEntities = function (moniker1name, moniker1id, moniker2name, moniker2id, RelationshipName) {
        var authenticationHeader = GenerateAuthenticationHeader();
        // Готовим SOAP сообщение
        var xml = "<?xml version='1.0' encoding='utf-8'?>";
        xml += "<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance\' xmlns:xsd='http://www.w3.org/2001/XMLSchema\'>";
        xml += authenticationHeader;
        xml += "<soap:Body><Execute xmlns='http://schemas.microsoft.com/crm/2007/WebServices'><Request xsi:type='DisassociateEntitiesRequest'>";
        xml += "<Moniker1><Id xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>" + moniker1id + "</Id>";
        xml += "<Name xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>" + moniker1name + "</Name></Moniker1>";
        xml += "<Moniker2><Id xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>" + moniker2id + "</Id>";
        xml += "<Name xmlns='http://schemas.microsoft.com/crm/2006/CoreTypes'>" + moniker2name + "</Name></Moniker2>";
        xml += "<RelationshipName>" + RelationshipName + "</RelationshipName>";
        xml += "</Request></Execute></soap:Body></soap:Envelope>";
    
        // Готовим xmlHttpObject и отправляем сообщение
        var xHReq = new ActiveXObject("Msxml2.XMLHTTP");
        xHReq.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
        xHReq.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/Execute");
        xHReq.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
        xHReq.setRequestHeader("Content-Length", xml.length);
        xHReq.send(xml);
    
        var resultXml = xHReq.responseXML; // Получаем результат запроса
        var errorCount = resultXml.selectNodes('//error').length; // Проверяем ошибки
    
        if (errorCount != 0) {
            var msg = resultXml.selectSingleNode('//description').nodeTypedValue;
            alert(msg);
        }
    }
    
    // Возвращает расположение элемента в массиве, если таковой не найден возвращаем -1
    GetIndexFromArray = function (item, recordArr) {
        for (var i = 0; i < recordArr.length; i++) {
            if (recordArr[i] != null && recordArr[i].id == item) {
                return i;
            }
        }
        return -1;
    }
    
    FillMultiLookup = function (relationshipSchemaName, lookupSchemaName, relatedEntitySchemaName, relatedEntityPrimaryAttributeSchemaName) {
        var relatedValues = RetreiveAssociatedEntities(relationshipSchemaName, Xrm.Page.data.entity.getEntityName(), Xrm.Page.data.entity.getId(), lookupSchemaName);
        var value = new Array();
        for (var i = 0; i < relatedValues.length; i++) {
            value[i] = new Object();
            value[i].id = relatedValues[i];
            value[i].name = RetreiveAssociatedEntities(relatedEntitySchemaName, relatedEntitySchemaName, relatedValues[i], relatedEntityPrimaryAttributeSchemaName)[0];
            value[i].typename = relatedEntitySchemaName;
        }
        crmForm.all[lookupSchemaName].DataValue = value;
    }
    
    UpdateN2N = function (nnId, relatedEntitySchemaName, relatedEntitySchemaId) {
        var oldValues = RetreiveAssociatedEntities(nnId, Xrm.Page.data.entity.getEntityName(), Xrm.Page.data.entity.getId(), relatedEntitySchemaId);
        var value = crmForm.all[relatedEntitySchemaId].DataValue;
        // Если в лукапе есть записи
        if (value != null) {
            // Просматриваем все связанные записи и удалите их если их нет в новом списке записей (в лукапе)
            var temp = value;
            for (var i = 0; i < oldValues.length; i++) {
                // Если нет в новом списке - развязываем их
                var index = GetIndexFromArray(oldValues[i], temp);
                if (index == -1) {
                    DisassociateEntities(Xrm.Page.data.entity.getEntityName(), Xrm.Page.data.entity.getId(), relatedEntitySchemaName, oldValues[i], nnId);
                }
                else { // Если запись есть списке, то удаляем ее из списка
                    temp[index] = null;
                }
            }
            // Просматриваем все оставшиеся записи и связываем их
            for (var i = 0; i < temp.length; i++) {
                if (temp[i] != null) {
                    AssociateEntities(Xrm.Page.data.entity.getEntityName(), Xrm.Page.data.entity.getId(), relatedEntitySchemaName, temp[i].id, nnId);
                }
            }
        }
        else if (oldValues != null) {
            for (var i = 0; i < oldValues.length; i++) {
                DisassociateEntities(Xrm.Page.data.entity.getEntityName(), Xrm.Page.data.entity.getId(), relatedEntitySchemaName, oldValues[i], nnId);
            }
        }
    }
    
    FilterN2NLookup = function (filterByLookup, filterLookup, entityName, nnName, nnFrom, linkedEntityName, linkedAttributeName) {
        debugger;
        var filter = '';
        var values = filterByLookup.DataValue;
        if (values != null) {
            filter = '<link-entity entity="' + entityName + '" name="' + nnName + '" from="' + nnFrom + '" to="' + nnFrom + '" visible="false" intersect="true"><link-entity name="' + linkedEntityName + '" from="' + linkedAttributeName + '" to="' + linkedAttributeName + '"><filter type="and"><condition attribute="' + linkedAttributeName + '" operator="in">';
            for (var i = 0; i < values.length; i++)
                filter += '<value uiname="' + values[i].name + '">' + values[i].id + '</value>';
            filter += '</condition></filter></link-entity></link-entity>';
        }
        filterLookup.AddParam('filters', filter);
    }
    
    function ConvertN2N(relationshipSchemaName, lookupSchemaName, relatedEntitySchemaName, relatedEntityPrimaryAttributeSchemaName) {
        document.getElementById(lookupSchemaName).setAttribute("lookupstyle", "multi");
        document.getElementById(lookupSchemaName).setAttribute("_lookupstyle", "multi");
        document.getElementById(lookupSchemaName).onchange = function () {
            UpdateN2N(relationshipSchemaName, relatedEntitySchemaName, lookupSchemaName)
        };
    
        Xrm.Page.getAttribute(lookupSchemaName).setSubmitMode("never");
    
        if (crmForm.FormType != 1) {
            FillMultiLookup(relationshipSchemaName, lookupSchemaName, relatedEntitySchemaName, relatedEntityPrimaryAttributeSchemaName);
        }
        else {
            Xrm.Page.getAttribute(lookupSchemaName).setDisabled(true);
        }
    }
    
  • Подключите JS-код к объект, на который вынесли лукап;
  • Вызовите на онлоаде функцию «ConvertN2N» и передайте ей следующие праметры:
    • Имя N:N связи;
    • Имя лукапа;
    • Имя объекта, который выбирается в лукапе;
    • Имя основного атрибута, объекта который выбирается в лукапе.

    Например: «new_account_lead»,»new_clientid»,»account»,»name».

Готово…




Комментарии (13)
  • Круто! Правда некруто не указывать ссылки на первоисточник. Вот ссылка на первую часть, если позабыли http://mscrmgoodies.blogspot.com/ 😉

  • Кирилл 11.03.2012

    А что делать, если надо добавить N:N лукап на форму какого-нибудь действия (например, звонка)? Как я понимаю, для сущностей действий нельзя создавать связи многие-ко-многим.

  • slivka_83 11.03.2012

    Ну, разве что создать какуб-либо промежуточную сущность и ссылаться на нее через лукап…

  • less 11.03.2012

    Привет!
    Такая проблема — записи связываются, но каждый раз при открытии формы вылезает «Не удалось получить свойство «NodeTypedValue» ссылки, значение которой не определено или является NULL»

    И при удалении каких-либо элементов в лукапе и замене на другие, не происходит пересвязывания 🙂

  • slivka_83 11.03.2012

    Попробуйте отладить с помощью DevToolBar’а. Может ролап поновее поставить… или снести какй-нить. А вобще, ансапорт, он на то и ансапорт — может в любой момент сломаться.

  • webmaster 11.03.2012

    Чтобы не выдавало ошибки о NodeTypedValue замените в функции FillMultiLookup следующую строку:
    «var relatedValues = RetreiveAssociatedEntities(relationshipSchemaName, Xrm.Page.data.entity.getEntityName(), Xrm.Page.data.entity.getId(), lookupSchemaName);»
    на
    «var relatedValues = RetreiveAssociatedEntities(relationshipSchemaName, Xrm.Page.data.entity.getEntityName(), Xrm.Page.data.entity.getId(), relatedEntitySchemaName+’id’);»
    и все заработает нормально.

  • Leo 11.03.2012

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

  • Борис 11.03.2012

    Коллеги, появилась проблема с переопределением лукапов в CRM 2013. никто не сталкивался с этим как обойти?

  • Сергей 11.03.2012

    Переопределение лукапов. Решение для CRM 2013

    document.getElementById(«superclient»).getElementsByTagName(‘div’)[0].click();
    //если нужен выбор сразу нескольких объектов, используем строку ниже
    //document.getElementById(«superclient_i»).setAttribute(«lookupstyle», «multi»);
    document.getElementById(«superclient_i»).setAttribute(«lookuptypes», lookuptypes);
    document.getElementById(«superclient_i»).setAttribute(«lookuptypenames», lookuptypenames);
    document.getElementById(«superclient_i»).setAttribute(«lookuptypeIcons», lookuptypeIcons);
    document.getElementById(«superclient_i»).setAttribute(«defaulttype», «1088»);//для примера
    document.getElementById(«superclient_i»).setAttribute(«lookupDialogMultipleSelect»,»0″);
    document.getElementById(«superclient_i»).setAttribute(«lookupbrowse»,»0″);
    document.getElementById(«superclient_i»).setAttribute(«isEnableInlineLookupForEditForms»,»1″);
    //включает поле для быстрого поиска
    document.getElementById(«superclient_i»).setAttribute(«disableQuickFind»,»0″);
    Xrm.Page.getAttribute(«superclient»).setValue(null);

    _i — добавляем для вашего лукпа везде, кроме первой и последней строчки

  • Сергей 11.03.2012

    При сохранении кода почему то не правильные кавычки поставились, те что парные.

  • slivka_83 11.03.2012

    Спасибо за пример 🙂 Если не возражаете оформлю его как-нибудь в виде отдельной статьи 🙂

  • Борис 11.03.2012

    Сергей, спасибо, действительно помогло)

  • OldCat65 11.03.2012

    Сергей, у меня есть необходимость сделать lookup (systemuser) в Opportunity. При этом в нем нужно хранить множество значений. Содержимое lookup будет управляться программно JS (т.е. нет необходимости там что-то выбирать). Поле такое нужно для использования в workflows в качестве списка реципиентов activity. Вопрос — можно ли такой lookup сделать в CRM 2011 ?

*

code