Wednesday, September 17, 2008

Cloning an Entity Part 3 – using Ajax

This is a complementary post to my two previous post regarding how to clone an entity using JavaScript.The main idea, as the title suggests, is to build a soap envelope utilizing the create message gather the entity values and send them to CrmService.Create web method. The method returns a create result of the newly created (cloned) entity which is used to create a valid entity url
e.g. edit.aspx?id={cloned entity id}.


I also included a dynamic popup similar to ms yellow customization popup since this is a server side operation. If an error is returned then the popup inner message is changed and displays the error information.


I added a simple prompt (OnCrmPageLoad) instead of an ISV button. You can check the “cloning an entity using JavaScript” for details on how to implement the button.


if you don’t want to include the popup in your solution , should you choose this method for cloning, then simply set the ajaxCloner.Progress.Visible to false. If you need to clone specific fields then use the AddField method. If you don’t use the method the cloner will send all non null fields to CrmService.Create. Finally use the Clone method to rap up the process.


I bet some of you are asking your self why not send the entity id and selected attributes names to a custom web method and do every thing there. The answer is that you can. The major advantage this method has to offer over a custom web method is that it saves you a Query(expression) to crm since the entity values are already available to you. On the other hand you're sending more information over the network


This might seem a lot, i hope you finod this to be self explanatory.
I added general summery remarks on top of each functional code block and direct links to ms sdk files above.









/* simulate ISV button */
function OnCrmPageLoad()
{
if( prompt("Execute?") == "ok" )
CloneEntity();
}

/*
Call this method from the ISV button.
Create a new cloner, set whether to show a progress popup.
Set the popup default message.
Add specific candidate fields (optional). Otherwise all the form is cloned
Start the cloning process
*/
function CloneEntity()
{
var ajaxCloner = new AjaxCloner();
ajaxCloner.Progress.Visible = true;
ajaxCloner.Progress.Message = "Cloning Account";
//ajaxCloner.AddField(“firstname”);
ajaxCloner.Clone();
}

/*
Cloner class.
Sets the progress popup default values.
AddField – add new candidate fields to be cloned
Clone – Start the cloning process
*/
function AjaxCloner()
{
var Instance = this;

Instance.Progress = new ProgressPopup();
Instance.Progress.Visible = false;
Instance.Progress.Message = "Cloning...";

Instance.Request = null;
Instance.Fields = null;

Instance.AddField = function( fieldId )
{
if( Instance.Fields == null )
Instance.Fields = [];

var field = document.getElementById(fieldId);
if( field ) Instance.Fields[fieldId] = field;
}

Instance.Clone = function()
{
Instance.Progress.Show();
var list = Instance.Fields == null ? crmForm.all : Instance.Fields;
var 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 += GenerateAuthenticationHeader();
xml += "<soap:Body>";
xml += "<Create xmlns=\"http://schemas.microsoft.com/crm/2007/WebServices\">";
xml += "<CreateDuplicatesOptionalParameter><value>false</value></CreateDuplicatesOptionalParameter>";
xml += "<PersistInSyncOptionalParameter><value>false</value></PersistInSyncOptionalParameter>";
xml += "<entity xsi:type='" + crmForm.ObjectTypeName + "'>";
for( var item in list )
{
var field = list[item];
if( field.DataValue == null || !field.req ) continue;

switch( field.tagName )
{
case "IMG": //Lookup
if( field.lookupstyle == "multi" )
{
xml += "<" + field.id + ">";
for( var i = 0 ; i < field.DataValue.length ; i++ )
{
xml += "<activityparty>";
xml += "<partyid type='" + field.DataValue[i].typename + "'>"
xml += field.DataValue[i].id;
xml += "</partyid>";
xml += "</activityparty>";
}
xml += "</" + field.id + ">";
}
else
{
xml += "<" + field.id + " type='" + field.DataValue[0].typename + "'>"
xml += field.DataValue[0].id;
xml += "</" + field.id + ">";
}
break;
case "TABLE": //DateTime
xml += "<" + field.id + ">";
xml += field.InnerText;
xml += "</" + field.id + ">";
break;
default: //All the rest
if( field.type == "hidden" || field.id == "statuscode")
continue;
xml += "<" + field.id + ">";
xml += field.DataValue;
xml += "</" + field.id + ">";
break;
}
}
xml += "</entity>";
xml += "</Create>";
xml += "</soap:Body>";
xml += "</soap:Envelope>";

Instance.Request = CreateXmlHttp();
Instance.Request.open("POST", "/mscrmservices/2007/crmservice.asmx", true );
Instance.Request.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
Instance.Request.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/Create");
Instance.Request.onreadystatechange = OnCloneEnd;
Instance.Request.send(xml);
}

/*
Cloning callback.
Checks the request status. If it’s ready and the request succeeded then
Extract the cloned entity id, build a valid entity url, close the popup and open a new window
*/
function OnCloneEnd()
{
if( Instance.Request.readyState != 4 )
return;
if( Instance.Request.status != 200 )
return HandleError();

var resultId = Instance.Request.responseXML.selectSingleNode("//CreateResult").text;
var url = location.pathname + "?id=" + resultId;
var features = "top=" + screenTop + ",";
features += "left=" + screenLeft + ",";
features += "width=" + document.body.offsetWidth + ",";
features += "height=" + document.body.offsetHeight;

Instance.Progress.Hide();
window.open ( url , "" , features );
}

/*
If the request generated an error then display the error to the user.
*/
function HandleError()
{
var sHTML = Instance.Request.responseXML.text;
if( Instance.Progress )
Instance.Progress.UpdateMessage( sHTML );
}

/*
This class represents the progress popup object.

*/
function ProgressPopup()
{
var Popup = this;
var Control = window.createPopup();

Popup.Message = "";
Popup.Visible = false;

Popup.Show = function()
{
if( !Popup.Visible ) return;

bulidPopup();
Popup.Control.show(( window.document.body.clientWidth - 250 )/2,
( window.document.body.clientHeight - 160 )/2,
250, 160, document.body);
}

Popup.Hide = function()
{
if( !Popup.Visible ) return;
Popup.Control.hide();
}

Popup.UpdateMessage = function( html )
{
if( !Popup.Visible ) return;
Popup.Control.document.all.msgDiv.innerHTML = getPopupHTML(html);
}

function bulidPopup()
{
if( Popup.Control ) return;

Popup.Control = window.createPopup();

with( Popup.Control.document )
{
createStyleSheet("/" + ORG_UNIQUE_NAME + "/_common/styles/global.css.aspx?lcid=" + USER_LANGUAGE_CODE);
createStyleSheet("/" + ORG_UNIQUE_NAME + "/_common/styles/fonts.aspx?lcid=" + USER_LANGUAGE_CODE);

var outerHTML = "<DIV id='msgDiv' class='actionMsgBox' style='width:100%;height:100%;backgroundColor:#ffffee;border:2px solid #000000;'>"
var msgDiv = createElement(outerHTML);
body.appendChild(msgDiv);
msgDiv.innerHTML = getPopupHTML(Popup.Message);
}
}

function getPopupHTML( innerText )
{
var popupHTML = "<table width=100% height=100%>";
popupHTML += "<tr>";
popupHTML += "<td style='font: bold 15px;text-align: center; vertical-align: middle; cursor: wait; color: #000099'>";
popupHTML += innerText;
popupHTML += "</td>";
popupHTML += "</tr>";
popupHTML += "</table>";

return popupHTML;
}
}
}

window.CloneEntity = CloneEntity;

OnCrmPageLoad();

9 comments:

Anonymous said...

I try to clone a service activity,but it seems that resource field and customer field are not cloned.

Anonymous said...

I tried to clone an incident, but the only message was an error

Adi Katz said...

Added support for multi lookup.
Good Luck

Chris Brown said...

Hi Adi,

Thanks for the code. Is there a way to make this AJAX code work for cloning SalesOrderDetail?

Thanks,

Chris

Jodi said...

This worked great in the onload of my custom entity form. How can I clone the related entities (1:N)

thanks
Jodi

Adi Katz said...

Chris, I have not tried it on the SalesOrderDetail entity. Did you? If so, what error are you getting?

Adi Katz said...

Jodi. Related records can only be duplicated using a server side operation. This type of activity is not suited for client side.

Anonymous said...

Adi,
I am trying to implement this code. I get a error 0x80040239 - date time format for 26-08-2010 is invalid..... What can I do to aviod this error or how can I correct this..
Thanks,

René

Anonymous said...

Did you figure out the error : 0x80040239? Get the same error, if I change the timeformat in options, it works, but my users uses a different language.