Кастомизация
27
Ноя
22

Динамическое представление в iFrame

В предыдущих двух постах Связанные записи в iFrame и Отображение результатов расширенного поиска в IFrame рассказывалось, как встроить стороннее представление в форму CRM. У этих двух способов есть два больших недостатка: первый отображает только связанные записи, а второй только записи предопределенные жестко фиксированным запросом расширенного поиска. Вообщем, полное отсутствие возможности маневра 🙂

Сейчас рассмотрим способ создания динамического запроса и такого же динамического вывода результата.

  1. Сначала откройте расширенный поиск и нажмите Ctrl + N – это откроет новое окно расширенного поиска, но уже с адресной строкой. Составьте нужный запрос, отберите нужные поля и нажмите Найти. Отобразятся результаты поиска – введите в адресной строке:
    javascript:void( new function(){ prompt("Fetch Parameters:",getFetchParams());function getFetchParams(){ return "FetchXml:\n" + advFind.FetchXml + "\n\n" + "LayoutXml:\n" + advFind.LayoutXml + "\n\n" + "EntityName:\n" + advFind.EntityName + "\n\n" + "DefaultAdvancedFindViewId:\n" + advFind.DefaultAdvancedFindViewId } } )
    

    В выводе Вы получите примерно такие строки:

    FetchXml:
    <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false"><entity name="contact"><attribute name="fullname"/><attribute name="parentcustomerid"/><attribute name="telephone1"/><attribute name="emailaddress1"/><attribute name="contactid"/><order attribute="fullname" descending="false"/><filter type="and"><condition attribute="ownerid" operator="eq-userid"/><condition attribute="statecode" operator="eq" value="0"/></filter></entity></fetch>
    
    LayoutXml:
    <grid name="resultset" object="2" jump="fullname" select="1" icon="1" preview="1"><row name="result" id="contactid"><cell name="fullname" width="300" /><cell name="emailaddress1" width="150" /><cell name="parentcustomerid" width="150" /><cell name="telephone1" width="125" /></row></grid>
    
    EntityName:
    contact
    
    DefaultAdvancedFindViewId:
    {00000000-0000-0000-00AA-000000666400}
    

    Скопируйте их куда-нибудь – они нам потом понадобится.

  2. Откройте настройку формы (в данном случаи Контакта), создайте новую вкладку, а на ней новую секцию.
    Затем создайте новый iFrmae с такими параметрами:

    • Имя: test;
    • URL: about:blank;
    • Снимите галку «Ограничить использование сценариев между кадрами»;
    • На вкладке форматирование поставьте автоматическое развертывание и уберите прокрутку.
  3. На онлоад повесьте такой код:
    window.fetchContacts = new FetchViewer("IFRAME_test");
    fetchContacts.WithParentContext = true;
    fetchContacts.EntityCode = 2;
    fetchContacts.FetchXml  = getFetchXml();
    fetchContacts.LayoutXml = getLayoutXml();
    fetchContacts.Entity    = "contact";
    fetchContacts.QueryId   = "{00000000-0000-0000-00AA-000000666400}";
    fetchContacts.RegisterOnTab(0); // iFrame на первой вкладке
    
    function getFetchXml() {
    	return '<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false"><entity name="contact"><attribute name="fullname"/><attribute name="parentcustomerid"/><attribute name="telephone1"/><attribute name="emailaddress1"/><attribute name="contactid"/><order attribute="fullname" descending="false"/><filter type="and"><condition attribute="ownerid" operator="eq-userid"/><condition attribute="statecode" operator="eq" value="0"/></filter></entity></fetch>';
    }
    
    
    
    function getLayoutXml() {
    	return '<grid name="resultset" object="2" jump="fullname" select="1" icon="1" preview="1"><row name="result" id="contactid"><cell name="fullname" width="300" /><cell name="emailaddress1" width="150" /><cell name="parentcustomerid" width="150" /><cell name="telephone1" width="125" /></row></grid>';    
    }
    
    function FetchViewer( iframeId ) {
    
    	var Instance = this;
    	var vDynamicForm;
    	var m_iframeTab;
    	var m_iframeDoc;
    	var m_iframeShowModalDialogFunc = null;
    	var m_windowAutoFunc = null;
     
    	Instance.WithParentContext = false;
    	Instance.EntityCode = 0;
    	Instance.Entity    = "";
    	Instance.Iframe    = null;
    	Instance.FetchXml  = "";
    	Instance.QueryId   = "";
    	Instance.LayoutXml = "";
    
    	Instance.RegisterOnTab  = function( tabIndex ) {
         
    		Instance.Iframe = document.getElementById( iframeId );  
    
    		if( !Instance.Iframe ) {
    			return alert( "Iframe " + iframeId + " is undefined" );
            }
    
    		m_iframeDoc = getIframeDocument();
    		var loadingGifHTML  = "<table height='100%' width='100%' style='cursor:wait'>";
    		loadingGifHTML += "<tr>";
    		loadingGifHTML += "<td valign='middle' align='center'>";
    		loadingGifHTML += "<img alt='' src='/_imgs/AdvFind/progress.gif'/>";
    		loadingGifHTML += "<div/><b>Loading View...</b>";
    		loadingGifHTML += "</td></tr></table>";
    		m_iframeDoc.body.innerHTML = loadingGifHTML;
    
    		if( parseInt( "0" + tabIndex ) == 0 ) {
    			Instance.Refresh();
            } else {
    			Instance.Iframe.attachEvent( "onreadystatechange" , RefreshOnReadyStateChange ); 
    		}
    	}
    
    	function RefreshOnReadyStateChange() {
    		if( Instance.Iframe.readyState != 'complete' ) {
    			return;
    		}
      
    		Instance.Refresh();
        }
    
    	Instance.Refresh = function() {
            if( !Instance.Iframe ) {
    			return alert( "Iframe " + iframeId + " is undefined" );
    		}
     
    		m_iframeDoc = getIframeDocument();  
    
    		Instance.Iframe.detachEvent( "onreadystatechange" , RefreshOnReadyStateChange );
    
    		var create  = m_iframeDoc.createElement;
    		var append1 = m_iframeDoc.appendChild; 
    		vDynamicForm = create("<FORM name='vDynamicForm' method='post'>");
    
    		var append2 = vDynamicForm.appendChild;
    		append2(create("<INPUT type='hidden' name='FetchXml'>"));
    		append2(create("<INPUT type='hidden' name='LayoutXml'>"));
    		append2(create("<INPUT type='hidden' name='EntityName'>"));
    		append2(create("<INPUT type='hidden' name='DefaultAdvFindViewId'>"));
    		append2(create("<INPUT type='hidden' name='ViewType'>"));
    		append1( vDynamicForm );
    
    		vDynamicForm.action = "/" + ORG_UNIQUE_NAME + "/AdvancedFind/fetchData.aspx";
    		vDynamicForm.FetchXml.value   = Instance.FetchXml;
    		vDynamicForm.LayoutXml.value  = Instance.LayoutXml;
    		vDynamicForm.EntityName.value = Instance.Entity;
    		vDynamicForm.DefaultAdvFindViewId.value = Instance.QueryId;
    		vDynamicForm.ViewType.value = 1039;
    		vDynamicForm.submit();
    
    		Instance.Iframe.attachEvent( "onreadystatechange" , OnViewReady );
    	}
    
    	function OnViewReady() {
    		if( Instance.Iframe.readyState != 'complete' ) {
    			return;
    		}
      
    		Instance.Iframe.style.border = 0;
    		Instance.Iframe.detachEvent( "onreadystatechange" , OnViewReady );
       
    		if (Instance.WithParentContext == true) {
    			getIframeWindow().open = OnWindowOpen;
    		}
      
    		if (m_iframeShowModalDialogFunc == null) {
    			m_iframeShowModalDialogFunc = getIframeWindow().showModalDialog;
    			getIframeWindow().showModalDialog = OnIframeShowModalDialog;
    		}
      
    		if (Instance.EntityCode > 0) { 
    			if (m_windowAutoFunc == null) {
    				m_windowAutoFunc = window.auto;
    				window.auto = OnWindowAuto;
    			}
    		}
      
    		m_iframeDoc = getIframeDocument();
    		m_iframeDoc.body.scroll = "no";
    		m_iframeDoc.body.style.padding = "0px";   
    	}
    
    	function OnWindowOpen(url, name, features) {
    		// новое окно
    		if (url.indexOf('?') == -1) {
    			if (url.indexOf('userdefined') == -1 ) {
    				url = url + "?_CreateFromType=" + crmForm.ObjectTypeCode + "&_CreateFromId=" + crmForm.ObjectId;
    			} else {
    				url = url + "?_CreateFromType=" + crmForm.ObjectTypeCode + "&_CreateFromId=" + crmForm.ObjectId + "&etc=" + Instance.EntityCode + "#";
    			}
    		} 
       
    		return window.open(url, name, features);
    	}
     
    	function OnIframeShowModalDialog(sUrl, vArguments, sFeatures) {
    		m_iframeShowModalDialogFunc(sUrl, vArguments, sFeatures);
    		Instance.Refresh();
    	}
     
    	function OnWindowAuto(otc) {
    		if ( otc == Instance.EntityCode ) {
    			getIframeDocument().all.crmGrid.Refresh();
    		}
    
    		m_windowAutoFunc(otc);
    	}
    
    	function getIframeDocument() {
    		return getIframeWindow().document;
    	}
     
    	function getIframeWindow() {
    		return Instance.Iframe.contentWindow;
    	}
    }
    

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

    • Параметр WithParentContext – это свойство позволяет передавать родительский object id в дочернее окно;
    • Второй параметр – EntityCode (ObjectTypeCode) объекта, среди записей которого производится поиск. Если Вы хотите, чтобы представление обновлялась автоматически, Вы должны определить это значение;
    • Параметры FetchXml, LayoutXml, EntityName и DefaultQueryId мы получили в первом пункте с помощью Расширенного поиска – просто подставьте их и если нужно подкорректируйте (можно и динамически);
    • Измените номер вкладки на ту, где у Вас расположен iFrame – это необходимо для динамической регенерации содержимого при переходе на эту вкладку.
  4. Открываем форму (Контакта), переходим на только что созданную вкладку и смотрим…




В данном примере был рассмотрен исключительно статически Fetch-запрос. Но ни что не мешает генерировать его динамически с помощью JavScript в зависимости от условий Вашей бизнес-логики. Например, можете при формировании запроса расширенного поиска указать отбор Контактов, у которых родительским Контактом является каким-либо строго определенным. А затем на онлоаде вместо жестко заданного GUID’а динамически его подставлять с помощью crmForm.ObjectId. Что в итоге позволит Вам ообразить только те записи, которые связаны с текущей. Тоже самое касается и динамического вывода (LayoutXml).

Более подробно про формирование Fetch-запросов можете посмотреть в статьях FetchXML и FetchXML Builder.

Комментарии (22)
  • DK 27.11.2009

    У Вас очень полезный блог! Спасибо!
    Вопрос по этому посту: а как можно как нибудь java-скриптом поменять предопределенный GUID связанной записи в фэтч-запросе на GUID лукапа, который вынесен на форму. Лукап на форме и условие в фэтч-запросе — одна и та же сущность. Может у Вас ссылка есть, где почитать про это.

  • slivka_83 27.11.2009

    Пожалуйста 🙂

    Фетч запросаом нельзя! Он только возвращает записи!
    Можно SOAP запросом, например так http://msdn.microsoft.com/en-us/library/cc677074.aspx
    Можете также восопользоваться готовой библиотекой: http://mmcrm.ru/?p=790

  • DK 27.11.2009

    Жаль, что нельзя… Просто у Вас написано «вместо жестко заданного GUID’а динамически его подставлять с помощью…» Я было подумал, что можно как-то скриптом поменять этот самый GUID в фэтч-запросе здесь: (return ‘<fetch……value="{D45C5FBC-FCDF-DE11-BFFB-00235ACF9EFE}"……). Чтобы на айфрейме был список только связанных записей. Спасибо! Буду копать.

  • slivka_83 27.11.2009

    Стоп!
    Тогда я не понял вопроса! Я думал Вы хотите с помощью фетч-запроса изменить какую-то запись (связанную с данной).
    Опишите тогда более подробно (и желательно структурировано) вопрос?

  • DK 27.11.2009

    Извините, я наверное непонятно написал. Я начинающий 🙂
    Конструкцию из Вашего поста я повесил на OnLoad формы объекта task (задача). В расширенном поиске получил объекты kbarticle (статья) с фильтром по связанному объекту subject (тема). Фетч-запрос выглядит так: . На форму объекта task (задача) выведен лукап объекта subject (тема) через кастомную связь. Задумка была такая: при изменении лукапа subject (тема) менять список «статей» в айфрейме, которые связаны с выбранной «темой». Но я не знаю как скриптом поменять значение {c481ad27-93df-de11-944c-00235acf9efe} в фэтч-запросе значением со связанного лукапа subject (тема). Вот и спросил, может подскажете куда копать.

  • DK 27.11.2009

    Все получилось! Я кавычки не те ставил…

  • Татьяна 27.11.2009

    Добрый день! У Вас на сайте очень много полезного за это отдельное спасибо!!!
    А можно получить контакт человека у кого можно будет консультироваться по реализации оброботчиков событий, просто жизнено необходимо. Заранее спасибо!!!

    С Уважением,
    Татьяна!

  • Виктор 27.11.2009

    В форме account в onLoad я добавил fetch-запрос на activities, относящиеся к этому account:

    Что-то не работает. Пожалуйста, ответьте на вопросы:

    1) Можно ли убрать uiname и uitype?
    2) Нужно ли к «crmForm.ObjectId»
    добавлять «.DataValue»?
    3) Нужны ли какие-нибудь кавычки?
    4) Что еще не так в моем примере?
    5) В вашем примере значение QueryID также берется из результата начального JavaScript-а?

  • slivka_83 27.11.2009

    Боюсь движок блога поглатил Ваш код 🙂 пришлите весь Ваш пример в виде файла мне на почту 🙂

  • Виктор 27.11.2009

    К сожалению, не знаю Вашей почты и не могу ее найти на сайте.

    Попробую закинуть код вот так:

    condition attribute=»regardingobjectid» operator=»eq» uiname=crmForm.all.name.DataValue uitype=»account» value=crmForm.ObjectId.DataValue

  • slivka_83 27.11.2009

    И так не очень получилось 🙂 мыло slivkа_83@mail.ru

  • Николай 27.11.2009

    Задумка такая: при изменении лукапа ПРОДУКТ (продукт для коммерческого предложения) вытаскивать этот продукт только из другого прайс листа в айфрейме. Но я не знаю как динамически изменить id продукта в запросе да и вообще запрос. Вот и спросил, может подскажете куда копать. использую:
    function GetFrameSource(tabSet)
    {
    if (crmForm.ObjectId != null) {
    var oId = crmForm.ObjectId;
    var oType = crmForm.ObjectTypeCode;
    var security = crmFormSubmit.crmFormSubmitSecurity.value;
    return «areas.aspx?oId=» + oId + «&oType=» + oType + «&security=» + security + «&tabSet=» + tabSet;
    }
    else {
    return «about:blank»;
    }
    }
    crmForm.all.IFRAME_MRC.src = «ссылка на продукт из прайс листа»

  • slivka_83 27.11.2009

    вопрос не совсем понятен… что значит «вытаскивать этот продукт только из другого прайс листа в айфрейме»? что именно Вы хотите отобразить а айфпейме?

  • Николай 27.11.2009

    есть продукт для коммерческого предложения, там выбираем существующий и выбираем из лукапа продукт. Подтягивается информация того прайс-листа, который указан в самом КП. Мне нужно вытащить фрейм, в котором был бы позиция определенного прайс-листа, в которой был бы указан тот же самый продукт!
    Это нужно для сравнения цен данного Продукта для КП с определенным прайс — листом!
    Ну как?

  • Vitaly 27.11.2009

    Здравствуйте!

    Возникла проблема при использовании этого кода, решить которую не получается никак. Фрейм находится на второй вкладке. При переходе на эту вкладку вызывается функция Instance.Refresh = function().
    Внутри неё, при получении документа фрейма (m_iframeDoc = getIframeDocument();) возникает ошибка.

    Instance.Iframe.contentWindow.document;
    А именно при обращении к атрибуту document.

    Причину возникновения определить не смог. Если есть мысли, поделитесь пожалуйста) Код хорош, только не совсем рабочий получился(

  • slivka_83 27.11.2009

    Добрый день.

    А Вы в коде указали что Ваш код iFrame находится на второй вкладке? 🙂

  • Vitaly 27.11.2009

    Такие детали, как указание на какой вкладке, проверял много раз) всё указано. С этими моментами проблем возникнуть не могло.

    Создаётся впечатление, что при возникновении события onreadystatechange (при переходе на вкладку с моим фреймом) содержимое фрейма не успевает подгрузиться. При доступе к содержимому возникает ошибка.

    Такое ощущение, что стадия readyState != ‘complete’ не является последней(( Хотя это маловероятно..

    Барахлит именно этот асинхронный подгруз содержимого. Что делать не знаю..

  • slivka_83 27.11.2009

    >Такое ощущение, что стадия readyState != ‘complete’ не является последней(( Хотя это маловероятно..

    Да да да 🙂 что-то такое припоменаю 🙂 я уже с таким раньше сталкивался 🙂 если не изменяет память то действие readyState != ‘complete’ происходит 3!!! раза (а может и больше) 🙂 вообщем нада счетчик поставить чтобы код срабатывал только на третий раз 🙂

  • Илья 27.11.2009

    Добрый день. столкнулся со следующей проблемой:

    в iframe с результатами расширенного поиска есть стандартная кнопка «Создать». Я попытался повесть на неё нужный мне код (создание связанного объекта). Проблема в том, что событие появляется в DOM (любой, например alert(«test»)), а кнопка реально не срабатывает. т.е. код не выполняется при нажатии на кнопку

  • slivka_83 27.11.2009

    Добрый день 🙂 боюсь что никогда раньше не сталкивался с такой кастомизацие… 🙂

  • Василий 27.11.2009

    Добрый день.
    Столькнулся с точно такой же проблемой, котору описал Vitaly 15th Май 2011 — когда iFrame находится не на первой вкладке.
    Подскажите, кто-нибудь решил уже эту проблему?

  • Vitaly 27.11.2009

    Я перенёс iframe на первую вкладку в итоге, т.к. нужно всё быстрее делать было. Но сейчас могу дать совет (как возможное решение)
    Помести код обработчиков событий смены статуса в try/catch блок. и он сам определит когда возможно провести загрузку тела документа.

    function RefreshOnReadyStateChange() {
    try{
    Instance.Refresh();
    } catch(e) {}
    }

    Также и второй обработчик. Думаю, что это должно сработать)

*

code