Monday, August 18, 2014

Resizing 2011 entity forms

This is a small issue, and will be a non-issue once everyone move to 2013.
Until then if you need to control the size of your entity forms here is what you would expect to work.

//Resize to screen: 
xrmForm.FitToWindow();

// your SDK resource 
function XrmForm() {
     var xfrm = this;
     
  xfrm.ResizeTo = xfrm.FitToWindow = function () {
        window.resizeTo(arguments[0] || screen.availWidth, 
                        arguments[1] || screen.availHeight);
        return frm;
    }
}

var xrmForm = new XrmForm();


This would work in IE but won’t work in chrome. The reason for this is that chrome has strict rules regarding from where resizeTo works. Since you’re resizing the
window of an IFRAME (edit.aspx – this is where you script runs) and not the main window it won’t resize.
To overcome this obstacle you need to resize the window at the top.

The following will work in all browsers.
//your SDK resource
function XrmForm() {
     var xfrm = this;
    //resize top window
  xfrm.ResizeTo = xfrm.FitToWindow = function () {
        window.top.resizeTo(arguments[0] || screen.availWidth, 
                            arguments[1] || screen.availHeight);
        return frm;
    }
}

var xrmForm = new XrmForm();
Changed window.resizeTo > window.top.resizeTo

Sample Usage
//your entity.js resource

//Resize to screen: 
xrmForm.FitToWindow();
//Resize to give width and height
xrmForm.ResizeTo(800, 600);


Of course in 2013 everything opens in the same browser window (tab) so again this would be a non-issue.
I personally don’t like not being able to choose how my apps behaves. I also here the same voices from others in my radius.
My thoughts are that the CRM team need to create different themes for different mediums and not impose the tablet/mobile look on office applications.

Cheers



Friday, August 15, 2014

Disabling the Lookup View Picker in CRM 2013


One of the shortcomings of lookup controls in CRM 2011 and 2013 is that you can’t disable the View Picker once you enter a new custom view. Although you can set it as default and put it first on the list, users can still select records related to other views.



When 2011 was introduces it seemed that this missing SDK ability was no more than a glitch.
Unfortunately it’s still an issue in 2013 and yet again we are required to find undocumented features to enforce this type of business rule.

Luckily both 2011 and 2013 has this feature built into the product. We just needed to find a way to activate it without hindering existing CRM functionality.

In order to handle all the lookup internals, supported and unsupported, I’ve created an XrmLookupField wrapper. The wrapper enables me to manipulate the control with ease and I suggest you do the same in your code.

There is one thing you need to remember about this feature. Once you disable the View Picker you can’t add custom views to it. You must enable the View Picker before you add new customs views and disable it again if you so require.

If you take a closer look at the AddLockedView method in the code sample you’ll notice that this is exactly what I’m doing before adding a new custom view.
i.e. EnableViewPicker(); > AddCustomView() > DisableViewPicker()

This is how we did it in 2011
Note: This XrmLookupField example only includes methods that are relevant to this post.

//SDK wrapper for lookup field 2011
function XrmLookupField(sId) {
    var xlf = this;
    //control instance
    xlf.Ctl = Xrm.Page.getContorl(sId);
    //dom instance
    xlf.Dom = document.getElementById(sId);
    //jquery instance
    xlf.$ = $(xlf.Dom);

    //use that to disable the view picker
    xlf.DisableViewPicker = function () { 
        xlf.SetParameter("disableViewPicker", "1"); 
    }
    //use that to enable the view picker
    xlf.EnableViewPicker = function () { 
        xlf.SetParameter("disableViewPicker", "0");  
    }
    //set undocumented attributes
    xlf.SetParameter = function (sName, vValue) { 
        xlf.$.attr(sName, vValue);  
    }   
    //add locked view
    xlf.AddLockedView = function (sViewId, sEntityName, sViewDisplayName, sFilterXml, sFilterLayout) {
        //first enable the view picker
        xlf.EnableViewPicker();
        //add the custom view (last parameter set the view as default)
        xlf.Ctl.addCustomView(sViewId, sEntityName, sViewDisplayName, sFilterXml, sFilterLayout, true);
        //lock the view picker
        xlf.DisableViewPicker();
    }
    //create new guid
    xlf.NewGuid = function () {
        var d = new Date().getTime();
        var guid = '{xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx}'.replace(/[xy]/g, function (c) {
            var r = (d + Math.random() * 16) % 16 | 0;
            d = Math.floor(d / 16);
            return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16);
        });
        return guid;
    }
}


And here is a usage example. Note that I’m using the dynamic FetchXml builder to construct my Layouts and FetchXml queries.
You can read more about it here or use regular xml in your code.

//entity onload js
var myLookup1;

//call this function from OnLoad handler
function OnCrmPageLoad() {
    myLookup1 = new XrmLookupField("new_lookup1");
    myLookup1.AddLockedView(
        //sViewId
        myLookup1.NewGuid() ,
        //sEntityName
        "account",
        //sViewDisplayName
        "My Locked Custom View",
        //sFilterXml
        fetch()
            .entity("account")
                .attribute("name")
                    .filter()
                        .condition("name" , fetch.op.Eqaul, "My Company")
        .toString(),
        //sFilterLayout
        layout(1, "name", "accountid")
            .column("name", 200)
        .toString()
    );
}


CRM 2013 introduced some interesting changes. Controls now have a double layout state, one for printing or a read-only state and another for inline editing.
This also means that the built-in feature that controls the View Picker changed.
Luckily, now that we have our XrmLookupField wrapper, we only need to make slight modification in order to make this feature work again and apply it in all our forms.

Following are the required XrmLookupFIeld additions for 2013
I added a comment above each addition to make it noticeable



//SDK wrapper for lookup field 2013
function XrmLookupField(sId) {
    var xlf = this;
    //control instance
    xlf.Ctl = Xrm.Page.getContorl(sId);
    //dom instance
    xlf.Dom = document.getElementById(sId);
    //jquery instance
    xlf.$ = $(xlf.Dom);
 
    /* 2013 addition --- Inline Control instance --- */
    xlf.$i = $("#" + sId + "_i");

    //use that to disable the view picker
    xlf.DisableViewPicker = function () { 
        /* 2013 addition --- The attribute capitalization changed */
        xlf.SetParameter("disableviewpicker", "1"); 
    }
    //use that to enable the view picker
    xlf.EnableViewPicker = function () { 
        /* 2013 addition --- The attribute capitalization changed */
        xlf.SetParameter("disableviewpicker", "0");  
    }
    
    //set undocumented attributes
        xlf.SetParameter = function (sName, vValue) { 
        xlf.$.attr(sName, vValue);  

        /* 2013 addition --- Also change the inline contorl value */
        xlf.$i.attr(sName, vValue);
    }   
    
    //add locked view
    xlf.AddLockedView = function (sViewId, sEntityName, sViewDisplayName, sFilterXml, sFilterLayout) {
        //first enable the view picker
        xlf.EnableViewPicker();
        //add the custom view (last parameter set the view as default)
        xlf.Ctl.addCustomView(sViewId, sEntityName, sViewDisplayName, sFilterXml, sFilterLayout, true);
        //lock the view picker
        xlf.DisableViewPicker();
    }
    //create new guid
    xlf.NewGuid = function () {
        var d = new Date().getTime();
        var guid = '{xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx}'.replace(/[xy]/g, function (c) {
            var r = (d + Math.random() * 16) % 16 | 0;
            d = Math.floor(d / 16);
            return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16);
        });
        return guid;
    }
}

And finally , the usage is exactly the same as above saving us a tone of work 
Fill free to comment

Cheers,
Adi

Tuesday, August 12, 2014

detachCloseAlert 2013

Although the detach Close Alert method exist in 2013 code it doesn’t work.
Instead you should use the following peace of code.

//this seems to work only in chrome
Mscrm.RefreshPageHandler.set_forceNavigationAway(true);
//this seems to work both in IE and chrome 
Mscrm.ReadFormUtilities.set_forceNavigationAway(true);

If you need to check the value of the force Navigation flag you can use:

Mscrm.RefreshPageHandler.get_forceNavigationAway(true);

If you what to change the actual text that appear inside the close alert you can change the
Following variable LOCID_FORMS_SAVE_CONFIRM_TITLE. e.g.

LOCID_FORMS_SAVE_CONFIRM_TITLE  = "There are unsaved changes ... "

The downside to this is that you can only assign a static string to LOCID_FORMS_SAVE_CONFIRM_TITLE.
This of course is totally unsupported so use it at your own risk.

Cheers,


Friday, August 8, 2014

Changing fields style 2013

Changing Style properties might seem basic to MSCRM 'outsiders' but that’s ok cause they haven’t met Anne and Ted from MS support.

Once they get accustomed to un-suppor-ted concepts and accept the risk, it’s up to us to make it a smooth journey.

Let’s assume we need to change the fields color, background-color, border and font properties.
Although it seems easy, get yourself ready for a bumpy ride.

The simplest most direct course of action would be to use a JQUERY selector
(e.g. #new_myattribute) to match a single html construct that applies to all field types.
Luckily both CRM 2011 and 2013 support the attribute.id + '_d' container (e.g. new_myattribute_d) which makes it simple to apply the same styling rules to all controls.

After some exploration it appears that changing the background-color (e.g. background-color: red) and border (border: 1px solid green) rules are the safest way to go since current CRM behavior doesn’t override these changes between field tabs and form saves.

The most problematic controls, as you might have guest by now, are controls that open other windows or apps such as ticker, url, email, phone (2013) and lookup.
CRM will override some of the styling rule you apply to these controls when you tab in and out of the fields. For example changing the font color will revert to its original color after you blur out of the field.


Finally , some controls are more complex (HTML wise) and require different Selectors in order to apply styling rules. To overcome this obstacle we can use an OR selector and build a few selectors that match all html constructs. Again, these changes will most likely be overridden by CRM so I didn’t implement them in my solution.

I haven’t tried all CSS styling options to see what lasts and what not. I leave it up to you to experiment further.

following is a simple 2013 field styler.
It has 3 methods.
The border and background methods that use 1 type of selector ('#new_attr1_d') and the css method
That use a different selector (i.e. '#new_attr_d div')

function styler(contorlId) {
    var sr = {},
        sel1 = ["#", contorlId , "_d"],
        sel2 = sel1.concat(" DIV");
    //change border style
    sr.border = function(){
        return applyStyle.call($(sel1.join("")), 
                               "border" , arguments[0]);
    }
    //change background style
    sr.background = function(){
        return applyStyle.call($(sel1.join("")), 
                               "background" , arguments[0]);
    }
    //change all other styles
    sr.css = function(){
        return applyStyle.apply($(sel2.join("")), arguments);
    }
    
    function applyStyle() {
        if (this.length) {   
            switch(arguments.length) {
                case 1: 
                    this.css(arguments[0]); 
                    break;
                case 2: 
                    this.css(arguments[0],arguments[1]); 
                    break;
            }
        }
        return sr;
    }    
    return sr;
}


See usage of styler inside the for loop.
The styler function accepts the field's id e.g. styler("new_attr1")
and returns a literal object which you can use to style against your field.

//test fields
var all = 
   ["new_phone1", "new_url1", "new_ticker1", 
    "new_string1", "new_optionset1", "new_float1", 
    "new_number1", "new_email1", "new_datetime1",
    "new_datetime2", "new_decimal1", "new_bool1", 
    "new_currency1", "new_account1", "new_memo1" , 
    "new_textarea1"];

//test the styler on all field types
for (var i = 0; i < all.length; i++) {
    //test disabled fields
    //xrmPage.getControl(all[i]).setDisabled(true);

    styler(all[i])
        .border("1px solid red")
        .background("gray")
        .css({
                textDecoration: 'underline',
                color: 'navy'
         });
}
One 2013 improvement that is worth mentioning is that all fields have two presentation states:
1. An inline value state which is what you see when you enter the field
2. A print state that takes over once field editing is done.
This concept actually allows for styling of controls that you could not style in 2011 such as disabled Option Sets.
A few tips for dessert:
1. When changing the background be sure to pick a light color since changing font-color won’t stick in all fields.
2. Put the styler in your SDK web resource so it’s available in all your forms.
Feel free to comment. Cheers,

Monday, August 4, 2014

Working with Multiple Forms

Role forms are by far one of the most underestimated feature in CRM 2011/13.
Here are the reasons why I really like this cool feature:
1. Significantly reduces the load on your forms.
2. Allows you to better express business processes. Forms are minified and contain only fields that need to take part in a particular record state.
3. Allows control over who can see each form and thus facilitates the business workflow.
4. Allows for simple creation of wizard like applications.

With that said, I found that coding the form selector item needed a bit of work and as usual wrapped it in a way that enables me to navigate between forms with ease.

The first thing I did , as with other SDK objects, is wrap the form item in a specialized object called XrmFormItem. The XrmFormItem accepts both form id and name (or index) and does a double search to ensure that the form is found.

function XrmFormItem(oFormId) {
    if (!oFormId) return;

    var xfItem = this;
    var oForm = formSelector.items.get(oFormId) ||
        formSelector.items.get(function(item, index) {
            return item.getLabel() == oFormId;
        })[0];

    xfItem.Exists = function() {
        return !!oForm;
    };

    if (!xfItem.Exists()) return xfItem;
    xfItem.FormId = oForm.getId();
    //get form name
    xfItem.GetLabel = function() {
        return oForm.getLabel();
    }
    //navigate to a different form
    xfItem.Navigate = function() {
        if (formSelector.getCurrentItem().getLabel() != xfItem.GetLabel())
            oForm.navigate();
    }

    xfItem.toString = function() {
        return xfItem.GetLabel();
    }
}

Secondly, I created XrmForm methods to encapsulate all the logic involved in manipulating the forms.
Note: This is a partial implementation of XrmForm. For complete implementation click here.

function XrmForm() {
    var xfrm = this;
    // new additions for form selector and role forms
    xfrm.GetForms = function() {
            var aItems = [];
            var oArg = arguments[0];
            switch (typeof(oArg)) {
                case "string":
                case "number":
                    //get form by label or id or index
                    aItems[0] = new XrmFormItem(oArg);
                    break;
                case "function":
                    // get items that match a custom function
                    formSelector.items.get(function(item, index) {
                        var bResult = oArg(item, index);
                        if (bResult)
                            aItems.push(new XrmFormItem(item.getId()));
                        return bResult;
                    });
                    break;
                case "undefined":
                    //get all forms 
                    formSelector.items.get(function(item, index) {
                        aItems.push(new XrmFormItem(item.getId()));
                        return true;
                    });
                    break;
            }
            return aItems;
        }
    //get form by label or id or index
    xfrm.GetForm = function() {
            if (xfrm.HasMultipleForms())
                return xfrm.GetForms(arguments[0])[0];
            return null;
        }
    //debug forms
    xfrm.DisplayAvailableForms = function() {
            alert((function() {
                if (!xfrm.HasMultipleForms())
                    return "Only One form exists";
                var summary = "";
                forms = xfrm.GetForms();
                for (var f in forms) summary += forms[f].GetLabel() + "\n";
                return summary;
            })());
        }
    //navigate to another form
    xfrm.NavigateTo = function(frmLabel) {
        if (frmLabel && xfrm.HasMultipleForms()) {
            try {
                var xfItem = xfrm.GetForm(frmLabel);
                xfItem.Exists() && xfItem.Navigate();
            } catch (e) {}
        }
        return xfrm;
    }
   //true if more than 1 form exists for the current user 
    xfrm.HasMultipleForms = function() {
        return formSelector.getCurrentItem() != null;
    }
}

Now, all that remains is to navigate between role forms depending on specific scenarios e.g.

function OnCrmLoad() {
    //debug
    xrmForm.DisplayAvailableForms();

    var processTest = new XrmBooleanField("new_myformselectorbit");
    processTest.IsTrue() // getValue() === true;
        ? xrmForm.NavigateTo("First Role Form Name") 
        : xrmForm.NavigateTo("Secodnd Role Form Name");
}

Note: if the form becomes dirty CRM will present a dialog requesting save / discard changes so the
Best approach is to navigate between forms before it becomes dirty or by disabling all fields before navigation.

Fill free to add comments,
Cheers


Building FetchXml Dynamically

The FetchXml query language plays an important role in CRM client side javascript development.
We use it to query information from CRM, add custom lookup views and present custom views in our iframes and subgrids.

Here is an example of using FetchXml while building a FetchViewer to display custom grid inside an iframe. As you can see combining the query in such a fashion is cluttering and hard to understand.

Note: for a complete usage of FetchViewer click here.
function LoadAccounts() {
window.fetchAccounts = new FetchViewer("IFRAME_accounts");
   fetchAccounts.FetchXml  = getFetchXml();
   fetchAccounts.LayoutXml = getLayoutXml();
   fetchAccounts.Entity    = "account";
   fetchAccounts.QueryId   = "{00000000-0000-0000-00AA-000010001001}";
   fetchAccounts.RegisterOnTab(0); 
}

function getFetchXml() {
   return '<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="true">' + 
          '<entity name="account"><attribute name="name"/><attribute name="address1_city"/>' + 
          '<attribute name="primarycontactid"/><attribute name="telephone1"/>' +
          '<attribute name="accountid"/><order attribute="name" descending="false"/>' + 
          '<filter type="and"><condition attribute="ownerid" operator="eq-userid"/>' +
          '<condition attribute="statecode" operator="eq" value="0"/></filter>' +
          '<link-entity name="contact" from="contactid" to="primarycontactid" visible="false" link-type="outer" alias="accountprimarycontactidcontactcontactid">' +
          '<attribute name="emailaddress1"/></link-entity></entity></fetch>';
}

function getLayoutXml(){
   return '<grid name="resultset" object="1" jump="name" select="1" icon="1" preview="1">' + 
          '<row name="result" id="accountid"><cell name="name" width="300" />' + 
          '<cell name="telephone1" width="100" /><cell name="address1_city" width="100" />' +
          '<cell name="primarycontactid" width="150" />'+
          '<cell name="accountprimarycontactidcontactcontactid.emailaddress1" width="150" disableSorting="1" />' + 
          '</row></grid>';
}

I personally don’t like the idea of integrating Xml into javascript code and find it much more appealing to write queries on the fly. IMO, writing FetchXml directly, especially for this type of task, has obvious advantages, such as:
1. Code stays clean and minified containing only the important parts of the query,
making the query simple to read and understand.
2. Ability to write any type of query supported by the language and not being restricted by Advanced find designer limitation
i.e. using all type of operators, creating aggregate and groupby queries and so forth.

Now, let’s change the example above and use Dynamic FetchXml Builder

function LoadAccounts() {

window.fetchAccounts = new FetchViewer("IFRAME_test1");
   fetchAccounts.FetchXml  = getFetchXml();
   fetchAccounts.LayoutXml = getLayoutXml();
   fetchAccounts.Entity    = "account";
   fetchAccounts.QueryId   = "{00000000-0000-0000-00AA-000010001001}";
   fetchAccounts.RegisterOnTab(0); 
}

function getFetchXml(){
   var fetchXml = 
    
    fetch(true) //true - distinct
      .entity("account") 
          .attributes("name","address1_city", "primarycontactid" , "telephone1", "accountid")
          .order("name", fetch.order.Asc)
          .filter()
              .condition("ownerid", fetch.op.Current_User)
              .condition("statecode", fetch.op.Equal, 0)
          .link ({
              entityName : "contact",
              to: "primarycontactid",
              from: "contactid",
              type: fetch.link.outer,
              alias: "accountprimarycontactidcontactcontactid"
          })
            .attribute("emailaddress1");
            
    return fetchXml.toString();
}

function getLayoutXml(){
   retrun layout("1", "name", "accountid")
               .column("name", 300)
               .column("telephone1", 100)
               .column("address1_city", 100)
               .column("primarycontactid", 150)
               .column("accountprimarycontactidcontactcontactid.emailaddress1", 150)
               toString();
}

To make my wish come to live I’ve crafted the following library.
It enables you to simply and effectively create FetchXml and LayoutXml without leaving the IDE.
If you wish to integrate it in your code simply create a new web resource in CRM and add it to your js.

Here is the library (to select all the code: press double click and ctrl + c)

function fetchBase(pub, prv, docElem) {
    pub.toString = function () {
        return prv.serialize(docElem);
    }

    prv.serialize = function (xmlNode) {
        if (typeof (window.XMLSerializer) !== "undefined") {
            return (new window.XMLSerializer()).serializeToString(xmlNode);
        }
        else if (typeof (xmlNode.xml) !== "undefined") {
            return xmlNode.xml;
        }
        return "";
    }
}

function layout(otc, jumpAttr, primaryKey) {
    var l = {}, docElem, row;
    var xmlDoc = (function (sXml) { if (window.DOMParser) { parser = new DOMParser(); return parser.parseFromString(sXml, "text/xml"); } else { xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); xmlDoc.async = false; xmlDoc.loadXML(sXml); xmlDoc = xmlDoc.firstChild; return xmlDoc; } })
    ('<grid name="resultset" object="0" jump="" select="1" icon="1" preview="1"/>');

    l.column = function (attr, width) {
        var col = xmlDoc.createElement("cell");
        col.setAttribute("name", attr);
        col.setAttribute("width", width);
        row.appendChild(col);
        return l;
    }

    var private = {
        init: function () {
            docElem = xmlDoc.documentElement;
            docElem.setAttribute("object", otc);
            docElem.setAttribute("jump", jumpAttr);

            row = xmlDoc.createElement("row");
            row.setAttribute("name", "result");
            row.setAttribute("id", primaryKey);
            docElem.appendChild(row);
            fetchBase(l, private, docElem);
        }
    }

    private.init();
    return l;
}

function fetch(o) {
    var f = {}, docElem, entElem, cur, iAlias = 0

    var filters = { flt: 0, flt2: 0, flt3: 0, flt4: 0, flt5: 0 }
    var links = { lnk: 0, lnk2: 0, lnk3: 0, lnk4: 0 }

    var xmlDoc = (function (sXml) { if (window.DOMParser) { parser = new DOMParser(); return parser.parseFromString(sXml, "text/xml"); } else { xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); xmlDoc.async = false; xmlDoc.loadXML(sXml); xmlDoc = xmlDoc.firstChild; return xmlDoc; } })
    ('<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false"/>');

    f.entity = function (name) {
        if (docElem.childNodes.length) throw "entity node already exists";
        if (!name) throw "missing entity name";
        cur = entElem = xmlDoc.createElement("entity");
        cur.setAttribute("name", name);
        docElem.appendChild(cur);
        return f;
    }

    f.count = function (attributeName, alias) { return private.aggregate(attributeName, alias, "count"); }
    f.avg = function (attributeName, alias) { return private.aggregate(attributeName, alias, "avg"); }
    f.max = function (attributeName, alias) { return private.aggregate(attributeName, alias, "max"); }
    f.min = function (attributeName, alias) { return private.aggregate(attributeName, alias, "min"); }
    f.sum = function (attributeName, alias) { return private.aggregate(attributeName, alias, "sum"); }
    f.countcolumn = function (attributeName, alias, distinct) {
        if (typeof (alias) === "boolean") {
            distinct = alias;
            alias = null;
        }
        return private.aggregate(attributeName, alias, "countcolumn", distinct);
    }

    f.order = function (attr, ord) {
        if (!entElem) throw "missing entity element";
        if (!attr) throw "attribute name is missing";
        var o = xmlDoc.createElement("order");
        o.setAttribute("attribute", attr);
        o.setAttribute("descending", (ord || "asc") === "desc");
        entElem.appendChild(o);
        return f;
    }

    f.all = f.attribute = function (name) {
        private.attribute(name);
        return f;
    }

    f.attributes = function () {
        var args = Array.isArray(arguments[0]) ? arguments[0] : Array.prototype.slice.call(arguments, 0);
        for (var i = 0; i < args.length; i++)
            private.attribute(args[i]);
        return f;
    }

    f.filter = function (type)  { return private.filter(type, "flt", entElem); }
    f.filter2 = function (type) { return private.filter(type, "flt2", filters.flt); }
    f.filter3 = function (type) { return private.filter(type, "flt3", filters.flt2); }
    f.filter4 = function (type) { return private.filter(type, "flt4", filters.flt3); }
    f.filter5 = function (type) { return private.filter(type, "flt5", filters.flt4); }

    f.condition = function (attr, operator, value) {
        if (!attr) throw "condition attribute is missing";
        if (!operator) throw "condition operator is missing";
        var c = xmlDoc.createElement("condition");
        cur.appendChild(c);
        c.setAttribute("attribute", attr);
        c.setAttribute("operator", operator);
        if (typeof (value) !== "undefined") {
            if (operator !== "in" && !Array.isArray(value)) {
                c.setAttribute("value", getValue(value));
            }
            else {
                c.setAttribute("operator", fetch.op.In);
                for (var i = 0; i < value.length; i++) {
                    var v = xmlDoc.createElement("value");
                    var t = xmlDoc.createTextNode(getValue(value[i]));
                    c.appendChild(v);
                    v.appendChild(t);
                }
            }
        }

        function getValue(value) {
            if (!value.IsXrmObject) 
                return value;
            if (value.IsLookup())
                return value.GetValueId();
            else if (value.IsDateObject)
                return value.ToFormat("yyyy-MM-dd");
            return value.Get();
        }

        return f;
    }

    f.groupBy = function (attrName, alias, dateGrouping, bUserTimezone) {
        var a, args = arguments[0], argLen = arguments.length;

        if (typeof (arguments[0]) !== "object") {
            return f.groupBy({
                userTimezone: argLen != 4 ? undefined : bUserTimezone,
                dateGrouping: private.isDateGrouping(alias) ? alias : dateGrouping,
                alias: argLen != 4 ? attrName : alias,
                attrName: attrName
            });
        }

        if (!args.attrName) throw "group by attribute is missing";

        a = private.attribute(args.attrName);
        args.alias && a.setAttribute("alias", args.alias);
        args.dateGrouping && a.setAttribute("dategrouping", args.dateGrouping);
        typeof (args.userTimezone) === "boolean" && a.setAttribute("usertimezone", !!args.userTimezone);
        a.setAttribute("groupby", true);
        return f;
    }

    f.link = function (entityName, to, alias, from, type, intersect) {
        return private.link(entityName, to, from, alias, "lnk", entElem, type, intersect);
    }
    f.link2 = function (entityName, to, alias, from, type, intersect) {
        return private.link(entityName, to, from, alias, "lnk2", links.lnk, type, intersect);
    }
    f.link3 = function (entityName, to, alias, from, type, intersect) {
        return private.link(entityName, to, from, alias, "lnk3", links.lnk2, type, intersect);
    }
    f.link4 = function (entityName, to, alias, from, type, intersect) {
        return private.link(entityName, to, from, alias, "lnk4", links.lnk3, type, intersect);
    }

    var private = {
        init: function () {
            docElem = xmlDoc.documentElement;
            switch (arguments.length) {
                case 1:
                    if (typeof (o) === "boolean" || typeof (o) === "undefined")
                        docElem.setAttribute("distinct", o);
                    else if (typeof (o) === "object") {
                        o.distinct && docElem.setAttribute("distinct", o.distinct);
                        o.output && docElem.setAttribute("output-format", o.output);
                        o.mapping && docElem.setAttribute("mapping", o.mapping);
                        o.version && docElem.setAttribute("version", o.version);
                        o.count && docElem.setAttribute("count", o.count);
                    }
                    else if (typeof (o) === "number") {
                        docElem.setAttribute("count", o);
                    }
                    break;
                case 2:
                    docElem.setAttribute("distinct", arguments[0]);
                    docElem.setAttribute("count", arguments[1]);
                    break;
            }
            fetchBase(f, private, docElem);
        },
        link: function (entityName, to, from, alias, li, par, type, intersect) {
            var args = arguments[0];
            if (typeof (args) === "object") {
                return private.link(args.entityName,
                args.to, args.from, args.alias, li, par, args.type, args.intersect);
            }

            if (!entityName) throw "missing link entity name";
            if (!to) throw "link entity to attribute is required";
            alias = alias || ("link" + (++iAlias));
            cur = links[li] = xmlDoc.createElement("link-entity");
            cur.setAttribute("name", entityName);
            from && cur.setAttribute("from", from);
            cur.setAttribute("to", to);
            cur.setAttribute("alias", alias);
            type && cur.setAttribute("link-type", type);
            intersect && cur.setAttribute("intersect", intersect);
            par.appendChild(cur);
            return f;
        },
        filter: function (type, fi, par) {
            type = type || "and";
            if (!par) throw "missing entity element";
            par = cur.nodeName === "entity" ? par : cur;
            cur = filters[fi] = xmlDoc.createElement("filter");
            cur.setAttribute("type", type);
            par.appendChild(cur);
            return f;
        },
        aggregate: function (attributeName, alias, type, distinct) {
            if (!cur) throw "missing entity element";
            if (!attributeName) throw "attribute name is missing"
            docElem.setAttribute("distinct", false);
            docElem.setAttribute("aggregate", true);
            var a = xmlDoc.createElement("attribute");
            a.setAttribute("name", attributeName);
            a.setAttribute("aggregate", type);
            a.setAttribute("alias", alias || type + (++iAlias))
            distinct && a.setAttribute("distinct", distinct);
            cur.appendChild(a);
            return f;
        },
        attribute: function (name) {
            var a;
            if (!cur) throw "missing entity/link-entity element";
            if (cur.nodeName !== "entity" && cur.nodeName !== "link-entity")
                throw "can't add attributes to current parent node";
            a = xmlDoc.createElement(!name ? "all-attributes" : "attribute");
            name && a.setAttribute("name", name);
            cur.appendChild(a);
            return a;
        },
        isDateGrouping: function (value) {
            return (!!dateGroupingMap[value]);
        }
    }

    fetch.link = {
        Inner: "inner",
        Outer: "outer"
    }

    fetch.order = {
        Desc: "desc",
        Asc: "asc"
    }

    fetch.dateGrouping = {
        Day: "day",
        Week: "week",
        Month: "month",
        Quarter: "quarter",
        Year: "year",
        Fiscal_Year: "fiscal-year",
        Fiscal_Period: "fiscal-period"
    }

    var dateGroupingMap = {
        day: "Day",
        week: "Week",
        month: "Month",
        quarter: "Quarter",
        year: "Year",
        "fiscal-year": "Fiscal_Year",
        "fiscal-period": "Fiscal_Period"
    }

    fetch.op = {
        Equal: "eq",
        In: "in",
        Neq: "neq",
        Not_Equal: "ne",
        Greater_Than: "gt",
        Greater_Equal: "ge",
        Less_Equal: "le",
        Less_Than: "lt",
        Like: "like",
        Not_Like: "not-like",
        Not_In: "not-in",
        Between: "between",
        Not_Between: "not-between",
        Null: "null",
        Not_Null: "not-null",
        Yesterday: "yesterday",
        Today: "today",
        Tomorrow: "tomorrow",
        Last_7_Days: "last-seven-days",
        Next_7_Days: "next-seven-days",
        Last_Week: "last-week",
        This_Week: "this-week",
        Next_Week: "next-week",
        Last_Month: "last-month",
        This_Month: "this-month",
        Next_Month: "next-month",
        On: "on",
        On_OR_Before: "on-or-before",
        On_OR_After: "on-or-after",
        Last_Year: "last-year",
        This_Year: "this-year",
        Next_Year: "next-year",
        Last_X_Hours: "last-x-hours",
        Next_X_Hours: "next-x-hours",
        Last_X_Days: "last-x-days",
        Next_X_Days: "next-x-days",
        Last_X_Weeks: "last-x-weeks",
        Next_X_Weeks: "next-x-weeks",
        Last_X_Months: "last-x-months",
        Next_X_Months: "next-x-months",
        Older_Than_X_Months: "olderthan-x-months",
        Last_X_Years: "last-x-years",
        Next_X_Years: "next-x-years",
        Current_User: "eq-userid",
        Not_Current_User: "ne-userid",
        Equal_User_Teams: "eq-userteams",
        Equal_User_OR_User_Teams: "eq-useroruserteams",
        Equal_Bu: "eq-businessid",
        Not_Equal_Bu: "ne-businessid",
        Equal_User_LCID: "eq-userlanguage",
        This_Fiscal_Year: "this-fiscal-year",
        This_Fiscal_Period: "this-fiscal-period",
        Next_Fiscal_Year: "next-fiscal-year",
        Next_Fiscal_Period: "next-fiscal-period",
        Last_Fiscal_Year: "last-fiscal-year",
        Last_Fiscal_Period: "last-fiscal-period",
        Last_X_Fiscal_Years: "last-x-fiscal-years",
        Last_X_Fiscal_Periods: "last-x-fiscal-periods",
        Next_X_Fiscal_Years: "next-x-fiscal-years",
        Next_X_Fiscal_Periods: "next-x-fiscal-periods",
        In_Fiscal_Year: "in-fiscal-year",
        In_Fiscal_Period: "in-fiscal-period",
        In_Fiscal_Period_And_Year: "in-fiscal-period-and-year",
        In_OR_Before_Fiscal_Period_And_Year: "in-or-before-fiscal-period-and-year",
        In_OR_After_Fiscal_Period_And_Year: "in-or-after-fiscal-period-and-year",
        Begins_With: "begins-with",
        Not_Begins_With: "not-begin-with",
        Ends_With: "ends-with",
        Not_Ends_With: "not-end-with"
    }

    private.init.apply(this, arguments);
    return f;
}
Here are a few usage examples taken from MSDN. Example 1: In the following example, the FetchXML statement retrieves all accounts:
<fetch mapping='logical'> 
   <entity name='account'>
      <attribute name='accountid'/> 
      <attribute name='name'/> 
</entity>
</fetch>
fetch()
   .entity("account")
   .attributes("accountid", "name").toString();
Example 2: In the following example, the FetchXML statement retrieves all accounts where the last name of the owning user is not equal to Cannon:
<fetch mapping='logical'>
   <entity name='account'> 
      <attribute name='accountid'/> 
      <attribute name='name'/> 
      <link-entity name='systemuser' to='owninguser'> 
         <filter type='and'> 
            <condition attribute='lastname' operator='ne' value='Cannon' /> 
          </filter> 
      </link-entity> 
   </entity> 
</fetch>  
fetch()
    .entity("account").attributes("accountid", "name")
    .link("systemuser", "owninguser")
       .filter()
           .condition("lastname", fetch.op.Not_Equal, "Cannon")
    .toString();
Example 3: Fetch the average of estimatedvalue for all opportunities.
<fetch distinct='false' mapping='logical' aggregate='true'> 
    <entity name='opportunity'> 
       <attribute name='estimatedvalue' alias='estimatedvalue_avg' aggregate='avg' /> 
    </entity> 
</fetch>
fetch(false)
    .entity("opportunity")
    .avg("estimatedvalue", "estimatedvalue_avg").toString()
Example 4: Fetch multiple aggregate values within a single query.
<fetch distinct='false' mapping='logical' aggregate='true'> 
    <entity name='opportunity'> 
       <attribute name='opportunityid' alias='opportunity_count' aggregate='count'/> 
       <attribute name='estimatedvalue' alias='estimatedvalue_sum' aggregate='sum'/> 
       <attribute name='estimatedvalue' alias='estimatedvalue_avg' aggregate='avg'/> 
    </entity> 
</fetch>
fetch(false).entity("opportunity")
          .count("opportunityid", "opportunity_count")
          .sum("estimatedvalue", "estimatedvalue_sum")
          .avg("estimatedvalue", "estimatedvalue_avg")
  .toString();
Example 5: Fetch a list of users with a count of all the opportunities they own using groupby.
<fetch distinct='false' mapping='logical' aggregate='true'> 
    <entity name='opportunity'> 
       <attribute name='name' alias='opportunity_count' aggregate='countcolumn' /> 
       <attribute name='ownerid' alias='ownerid' groupby='true' /> 
    </entity> 
</fetch>
fetch(false)
      .entity("opportunity")
         .countcolumn("name", "opportunity_count")
         .groupBy("ownerid")
   .toString(); 
Let’s assume you need to add if else conditioning to example number 4 above. Since the fetchxml document is built internally you can easily apply the statement as follows:
function aggregate(type){
    var f = fetch(false).entity("opportunity");
    
    if (type === "count")
          f.count("opportunityid", "opportunity_count");
    else if (type === "sum")
          f.sum("estimatedvalue", "estimatedvalue_sum");
    else
          f.avg("estimatedvalue", "estimatedvalue_avg");
          
  return  f.toString();
}
//assuming you have a built in Fetch method on XrmForm
xrmForm.Fetch(aggregate("sum"));

I’ll provide more references in the near future. If you have any questions regarding the library and usage fill free to ask. Cheers,

Saturday, August 2, 2014

Display Custom Fetch in Grid

I wrote this a few years back. It enables you to display a custom FetchXml query inside a CRM grid.
I made a few modifications to support 2011 , IE and chrome.

Note: This version requires jquery and XrmForm as web resources.
If you don’t want to use the entire XrmForm object (see here) you can replace the function calls to XrmForm with original CRM SDK functions.

Here are the XrmForm methods being utilized in FetchViewer.
For an updated XrmForm click here

//companyname_SDK.js
function XrmForm() {
    //this is a partial implementation of XrmForm
    var userAgent = window.navigator.userAgent;
    xfrm.IsIE = userAgent.indexOf("MSIE") != -1;
    xfrm.IsChrome = userAgent.indexOf("Chrome") != -1;
    
    xfrm.GetCrmUrl =
    xfrm.GetClientUrl = 
    xfrm.GetServerUrl = function() {
        return context.getClientUrl 
            ? context.getClientUrl()+ "/" 
            : context.getServerUrl();
    }
    xfrm.GetHost = function () {
        return location.protocol + "//" + location.host + "/";
    }

}

function FetchViewer(iframeId) {
    var fev = this;
    var vform, m_iframeTab, m_iframeDoc;
    var fetchDataUrl = xrmForm.GetCrmUrl() + 
                        "AdvancedFind/fetchData.aspx";

    fev.Entity = "";
    fev.Iframe = null;
    fev.FetchXml = "";
    fev.QueryId = "";
    fev.LayoutXml = "";

    fev.RegisterOnTab = function(tabIndex) {
        fev.Iframe = document.getElementById(iframeId);

        if (!fev.Iframe)
            return alert("Iframe " + iframeId + " is undefined");

        m_iframeDoc = getIframeDocument();
        m_iframeDoc.body.innerHTML = [
          "<table height='100%' width='100%' style='cursor:wait'>",
            "<tr>", 
              "<td valign='middle' align='center'>", 
                "<img alt='' src='", xrmForm.GetHost(), 
                          "/_imgs/processing_loader.gif'/>", 
                "<b>Loading View...</b>", 
              "</td>", 
            "</tr>",
           "</table>"
        ].join("");

        parseInt("0" + tabIndex) == 0 
           ? fev.Refresh() 
           : $(fev.Iframe).bind("load", RefreshOnReadyStateChange);
    }

    function RefreshOnReadyStateChange() {
        if (fev.Iframe.contentWindow.document.readyState != 'complete')
            return;
        fev.Refresh();
    }

    fev.Refresh = function() {

        if (!fev.Iframe)
            return alert("Iframe " + iframeId + " is undefined");

        m_iframeDoc = getIframeDocument();
        $(fev.Iframe).unbind("load", RefreshOnReadyStateChange);

        xrmForm.IsIE ? createIEForm() : createChromeForm();
        $(fev.Iframe).bind("load", OnViewReady);
    }

    function createIEForm() {
        var create = m_iframeDoc.createElement;
        var append1 = m_iframeDoc.appendChild;
        vform = create("<FORM name='vform' method='post'>");

        var append2 = vform.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(vform);

        vform.action = fetchDataUrl;
        vform.FetchXml.value = fev.FetchXml;
        vform.LayoutXml.value = fev.LayoutXml;
        vform.EntityName.value = fev.Entity;
        vform.DefaultAdvFindViewId.value = fev.QueryId;
        vform.ViewType.value = 1039;
        vform.submit();
    }

    function createChromeForm() {
        vform = $("<FORM name='vform' id='vform' method='post'>");
        $(vform).append($("<INPUT type='hidden' name='FetchXml'>"));
        $(vform).append($("<INPUT type='hidden' name='LayoutXml'>"));
        $(vform).append($("<INPUT type='hidden' name='EntityName'>"));
        $(vform).append($("<INPUT type='hidden' name='DefaultAdvFindViewId'>"));
        $(vform).append($("<INPUT type='hidden' name='ViewType'>"));
        $(m_iframeDoc.body).append(vform);

        vform = $('#vform', m_iframeDoc.body)[0];
        vform.action = fetchDataUrl;
        vform.FetchXml.value = fev.FetchXml;
        vform.LayoutXml.value = fev.LayoutXml;
        vform.EntityName.value = fev.Entity;
        vform.DefaultAdvFindViewId.value = fev.QueryId;
        vform.ViewType.value = 1039;
        vform.submit();
    }

    function OnViewReady() {
        if (fev.Iframe.contentWindow.document.readyState != 'complete')
            return;

        fev.Iframe.style.border = 0;
        $(fev.Iframe).unbind("load", OnViewReady);
        m_iframeDoc = getIframeDocument();
        m_iframeDoc.body.scroll = "no";
        m_iframeDoc.body.style.padding = "0px";
    }

    function getIframeDocument() {
        return fev.Iframe.contentWindow.document;
    }
}

var xrmForm = new XrmForm();

The actual values of FetchXml , LayoutXml, EntityName and QueryId can be extracted using advanced find:
1.The FetchXml can be downloaded easily using advanced find toolbar button.
2.Other values need to be extracted using the following instructions:
    a.Open Advance find in chrome.
    b.Build your FetchXml query, add desired columns and run it once.
    c.Go back to the query designer and Press F12 to open chrome dev tools
    d.Press Esc to open the console if it’s not yet opened.
    e.Enter the following lines and press enter:
      i.For LayoutXml – document.all.LayoutXml.value
      ii.For EntityName – document.all.EntityName.value
      iii.For QueryId – document.all. DefaultAdvancedFindViewId
f.You can also extract FetchXml using this line:
    i.document.all.FetchXml.value
    g.Paste values in your code (see usage example)

Usage example:
fAccounts = new FetchViewer("IFRAME_accounts");
   fAccounts.FetchXml  = "Paste FetchXml  here";
   fAccounts.LayoutXml = "Paste LayoutXml here";
   fAccounts.Entity    = "account";
   fAccounts.QueryId   = "{00000000-0000-0000-00AA-000010001001}";
   fAccounts.RegisterOnTab(0); //IFRAME ON THE DEFAULT TAB



Coding Navigation items

In my former posts I generally discussed about creating wrappers for CRM built-in objects and gave basic examples. I think the best way to make sense of it all is to provide a more concrete example.

Our "defendant", this time, will be the Navigation Item object.
I chose it mainly, since it has a small and simple API implementation.

Following are the steps we’re going to take to achieve our goal:
1. Create an object that receives a navigation item id or index and creates a wrapper.
2. Create matching methods for each built-in CRM method.
3. Add magic to our XrmForm so it carries the weight against our wrapper object.
This will make our coding against CRM navigation item even simpler.

Note: jquery library is required.

// navigation item wrapper object
function XrmNavItem(oNavId) {
    if (!oNavId)
        return;

    var xItem = this,
        oItem = nav && nav.items.get(oNavId);

    xItem.Exists = function() {return !!oItem;}
    if (!xItem.Exists()) return xItem;

    xItem.GetId = function() {return oItem.getId();}
    xItem.GetLabel = function() {return oItem.getLabel();}
    xItem.IsVisible = function() {return oItem.getVisible();}
    xItem.IsHidden = function() {return !xItem.IsVisible();}
    xItem.Focus = function() {oItem.setFocus();return xItem;}
    xItem.SetLabel = function(sName) {oItem.setLabel(sName);return xItem;}
    xItem.Hide = function() {oItem.setVisible(false);return xItem;}
    xItem.Show = function() {oItem.setVisible(true);return xItem;}
    xItem.toString = function() {return xItem.GetId();}
}

Note: The following is a partial implementation of XrmForm and contains methods only relevant to this post. For an updated XrmForm click here.

function XrmForm() {
    //...new additions, see complete XrmForm Implementation here
    
    //return multiple nav items
    xfrm.GetNavItems = function() {
        var aItems = [], i,
            args = $.IsArray(arguments[0]) 
              ? arguments[0] 
              : arguments;
        
        for(i = 0; i < args.length ; i++) {
            switch (typeof(args[i])) {
                case "string":
                case "number":
                    //find item by label or index
                    aItem.push(new XrmNavItem(args[i]));
                case "function":
                    //find item by custom function. 
                    nav && nav.items.get(function(item, index) {
                        var bResult = args[i](item, index);
                        if (bResult)
                            aItem.push(new XrmNavItem(item.getId()));
                        return bResult;
                    });
                    break;
            }
        }
        
        if (!i) {
            //get all navigation items
            nav && nav.items.get(function(item, index) {
                aItem.push(new XrmNavItem(item.getId()));
                return true;
            });
        }
        
        return aItem;
    }
    //return a single nav item
    xfrm.GetNavItem = function () { 
        return xfrm.GetNavItems(arguments[0])[0]; 
    }
    //hide item or items
    xfrm.HideNavItems = xfrm.HideNavItem = function() {
        private.applyCommand(
            xfrm.GetNavItems.apply(xfrm, arguments),
            'Hide');
        return xfrm;
    }
    //show item or items
    xfrm.ShowNavItems = xfrm.ShowNavItem = function() {
        private.applyCommand(
            xfrm.GetNavItems.apply(xfrm, arguments),
            'Show');
        return xfrm;
    }
    
    //display all nav item ids and names (for debugging)
    xfrm.DebugNavItems = function() {
        var items = xfrm.GetNavItems(),
            summary = "";
            
        for (var i in items)
            summary += items[i].GetLabel() + " - " + 
                       items[i].GetId() + "\n";
        alert(summary);
    }    
    
    var private = {
        applyCommand : function(args,command,param) {
            var item, i;
            for(i = 0 ; i < args.length ; i++) {
                item = args[i]; 
                item.Exists() && item[command](param);
            }   
        }
    }
}

var xrmForm = new XrmForm();
Some benefits of using the XrmForm concept and Wrapper objects are: 1. The method names are easy to remember. 2. The methods are straight forward and easy to code. 3. This type of coding keeps your business logic clean and understandable. Here are some usage examples.
//alert nav item names and ids
xrmForm.DebugNavItems();

//example 1 - hide all items 
xrmForm.HideNavItems();

//example 2 - show 3 item using item id or index
xrmForm.ShowNavItems("item1 id", 2 , "item3 id");

//example 3 - hide array containing 3 items
xrmForm.HideNavItems(["item1 id", 2 , "item3 id"]);

//example 4 - hide item 1,2 and show 3
xrmForm.HideNavItem("item1 id")
       .HideNavItem("Item2 id")
       .ShowNavItem(3) //by index

//example 5 - get item, if it's hidden show it.
var navItem1 = xrmForm.GetItemNav("item1 id");
navItem1.IsHidden() && navItem1.Show();
       
//example 6 - manipulate item 1
xrmForm.GetNavItem("item id 1")
       .SetLabel("new Lable for item 1")
       .Show()
       .Focus();
Cheers

Friday, August 1, 2014

Building an XrmForm


If you followed my first post you probably noticed that XrmForm plays an important and centralized role in the code.

While writing another interesting post about building dynamic FetchXml it came to me that repeating the same code samples each time is cluttering,
so I decided to write a post that is all about XrmForm and refer to it in Future articles whenever it’s in use.

This post is a work in progress. More methods will be added as more code samples become available.

//companyname_sdk.js web resource
var xrmPage = Xrm.Page;
var data = xrmPage.data;
var ui = xrmPage.ui;
var context = xrmPage.context;
var nav = ui && ui.navigation;
var formSelector = ui && ui.formSelector;

function XrmForm() {
    var xfrm = this,
        EMPTY_GUID = "{00000000-0000-0000-0000-000000000000}",
        userAgent = window.navigator.userAgent;

    xfrm.IsIE = userAgent.indexOf("MSIE") != -1;
    xfrm.IsChrome = userAgent.indexOf("Chrome") != -1;
    //get a query string value by param name
    xfrm.GetQSParam = function(name) {
        var p, aq = location.search.substring(1).split('&'),
            ap = [];
        for (var i = 0; i < aq.length; i++) {
            p = aq[i].split('=');
            ap[p[0]] = decodeURIComponent(p[1]);
        }
        return ap[name];
    }
    //fit window to screen or supplied width and height 
    xfrm.ResizeTo = xfrm.FitToWindow = function() {
        window.resizeTo(arguments[0] || screen.availWidth, 
                        arguments[1] || screen.availHeight);
        return xfrm;
    }
    //apply a command on all attributes
    xfrm.All = function(sMethod, oValue) {
        var attrs, func;
        if (!data) return;

        attrs = data.entity.attributes;
        func = typeof(oValue) != "undefined" 
           ? function(attribute, index) {
                 attribute[sMethod](oValue);
             } 
           : function(attribute, index) {
                 attribute[sMethod]();
           };

        attrs.forEach(func);
    }
    
    xfrm.IsDirty = function() {
        return data && data.entity.getIsDirty();
    }
    
    xfrm.GetUserId = function() {
        return context.getUserId();
    }
         
    xfrm.GetCrmUrl =
    xfrm.GetClientUrl = 
    xfrm.GetServerUrl = function() {
        return context.getClientUrl 
            ? context.getClientUrl() + "/"
            : context.getServerUrl();
    }
    
    xfrm.GetUserLcid = function() {
        return context.getUserLcid();
    }
    
    xfrm.GetUserRoles = function() {
        return context.getUserRoles();
    }
    
    xfrm.GetId = xfrm.GetRecordId = function() {
        return data && data.entity.getId() || EMPTY_GUID;
    }
    
    xfrm.Save = function() {
        data && data.entity.save(arguments[0] || null);
    }
    
    xfrm.SaveAndClose = function() {
        xfrm.Save("saveandclose");
    }
    
    xfrm.SaveAndNew = function() {
        xfrm.Save("saveandnew");
    }
    
    xfrm.HideRecordSet = function() {
        $('#recordSetToolBar').hide();
        return xfrm;
    }
    
    xfrm.ShowRecordSet = function() {
        $('#recordSetToolBar').show();
        return xfrm;
    }
    // close form , bSuppress - true to avoid dirty check
    xfrm.Close = function(bSuppress) {
        bSuppress && xfrm.All("setSubmitMode", "never");
        ui && ui.close();
    }
    
    frm.HideNavigation = function () {
        $('#crmNavBar').parent()
            .css("left", "0.1px")
            .hide()
            .prev()
            .css({ left: "0px", right: "0px" });
        return xfrm;
    }
    //entity name e.g. account, contact
    xfrm.EntityName = data && data.entity.getEntityName();
    //form type e.g. CREATE, UPDATE ...
    xfrm.Type = ui && ui.getFormType();
    
    /*
       SDK Additions: Controling Navigation items.
       Require: XrmNavItem wrapper object
       see: http://totbcrm.blogspot.co.il/2014/08/coding-navigation-items.html
       for more details.
    */

    //return multiple nav items
    xfrm.GetNavItems = function() {
        var aItems = [], i,
            args = $.IsArray(arguments[0]) 
              ? arguments[0] 
              : arguments;
         
        for(i = 0; i < args.length ; i++) {
            switch (typeof(args[i])) {
                case "string":
                case "number":
                    //find item by label or index
                    aItem.push(new XrmNavItem(args[i]));
                case "function":
                    //find item by custom function. 
                    nav && nav.items.get(function(item, index) {
                        var bResult = args[i](item, index);
                        if (bResult)
                            aItem.push(new XrmNavItem(item.getId()));
                        return bResult;
                    });
                    break;
            }
        }
         
        if (!i) {
            //get all navigation items
            nav && nav.items.get(function(item, index) {
                aItem.push(new XrmNavItem(item.getId()));
                return true;
            });
        }
         
        return aItem;
    }
    //return a single nav item
    xfrm.GetNavItem = function () { 
        return xfrm.GetNavItems(arguments[0])[0]; 
    }
    //hide item or items
    xfrm.HideNavItems = xfrm.HideNavItem = function() {
        private.applyCommand(
            xfrm.GetNavItems.apply(xfrm, arguments),
            'Hide');
        return xfrm;
    }
    //show item or items
    xfrm.ShowNavItems = xfrm.ShowNavItem = function() {
        private.applyCommand(
            xfrm.GetNavItems.apply(xfrm, arguments),
            'Show');
        return xfrm;
    }
     
    //display all nav item ids and names (for debugging)
    xfrm.DebugNavItems = function() {
        var items = xfrm.GetNavItems(),
            summary = "";
             
        for (var i in items)
            summary += items[i].GetLabel() + " - " + 
                       items[i].GetId() + "\n";
        alert(summary);
    }    
     
    var private = {
        applyCommand : function(args,command,param) {
            var item, i;
            for(i = 0 ; i < args.length ; i++) {
                item = args[i]; 
                item.Exists() && item[command](param);
            }   
        }
    }
}

var xrmForm = new XrmForm(); 
Possible Usage
//example 1:
xrmForm.HideRecordSet().HideNavigation();
//example 2:
if (xrmForm.IsDirty())
    xrmForm.SaveAndClose();
//example 3:
alert(xrmForm.Type)
Yes! this is just the basics but you can clearly see that utilization is short and simple. You may also notice how the code reuses it own methods like in the xrmForm.Close method. Cheers

Containing CRM client side Beast

CRM client SDK evolved considerably since its early days as the product team slowly fill in the gaps between desired and available.
The most noticeable change IMO is the shift from simple to a more complex ("namespaced") programming interface.
In this Keep it short and simple (KISS) world this type of long programming style is a big "ouch" or an itch that needs to be contained.

Here are a few coding concepts and principles you should consider before you start fighting the beast:
1. "Flatten the tree" (AMAP) - Write object shortcuts to make your code shorter ,more readable and hopefully more manageable.

e.g.
var xrmPage = Xrm.Page;
var data = xrmPage.data;
var ui = xrmPage.ui;
var context = xrmPage.context;
var nav = ui && ui.navigation;
var formSelector = ui && ui.formSelector;
//see usage below ...

2. "Simple Entry point" - Create a simple object that contains all the important functions / methods you usually utilize in CRM SDK.
Instantiate that object in your SDK (see #3) so it’s available to your form automatically.

e.g.
// XrmForm contains all useful functions
function XrmForm() {
        var xfrm = this;
        // method - get view port hight
        xfrm.Height = function() {
                return ui && ui.getViewPortHeight();
            }
            //method - get view port width
        xfrm.Width = function() {
                return ui && ui.getViewPortWidth();
            }
            //method - refresh ribbon
        xfrm.RefreshRibbon = function() {
                ui && ui.refreshRibbon();
                return xfrm;
            }
            //property – holds the entity name
        xfrm.EntityName = data && data.entity.getEntityName();
    }

// at the bottom of the file
var xrmForm = new XrmForm();

// in your form you can write
xrmForm.RefreshRibbon();
//or
alert(xrmForm.EntityName);

There are many advantages to this approach, to name a few:
1. A centralized SDK web resource is easily upgradable. If the API changes again, like it did in 2011, you only need to adjust and repair this single file.
2. Enables you to continually build your library capabilities upon existing improvements.
As you can see in my examples adding a new method to your specialized objects is very easy.

3. "Clear separation" - Create a single web resource (e.g. companyname_SDK.js) that contains all the technical stuff your forms require in order to implement business logic.
Your forms logic should ideally be written against your own KISS API implementation of CRM SDK.

4. "Building blocks" - Use available libraries such as jquery , odata and fetch in order to build a more robust API.
here is an example of using jquery in your code
function XrmForm() {
        var xfrm = this;
        //hide form recordset bar
        xfrm.HideRecordSet = function() {
                $('#recordSetToolBar').hide();
                return xfrm;
            }
            //show from recrodset bar
        xfrm.ShowRecordSet = function() {
                $('#recordSetToolBar').show();
                return xfrm;
            }
            //hide left navigation 
        xfrm.HideNavigation = function() {
            $('#crmNavBar').parent()
               .css("left", "0.1px")
               .hide().prev()
               .css({
                   left: "0px",
                   right: "0px"
                });
            return xfrm;
        }
    }

//bottom of file
var xrmForm = new XrmForm();

//in your forms you can write
xrmForm.HideRecrodSet().HideNavigation();

Note: although you can hide the left navigation using customizations, using the above technique allows you to code against the form selector and navigate between role forms.

5. "Control the beast" – Wrap SDK objects and centralize their functionality under your own specialized objects. This way you can easily add functionality that is still missing in CRM.

e.g. adding missing Toggle method on a Boolean field
function XrmBooleanField(sId, iCtlIndex) {
    if (sId == undefined)
        return;

    var xbAttr = this;

    xbAttr.Toggle = function() {
        xbAttr.Set(!xbAttr.Get());
        return xbAttr;
    }

    xbAttr.Set = function(bValue, bDefault) {
        //setValue implementation …
    }

    xbAttr.Get = function() {
        //getValue implementation …
    }
}

//form usage 
var myBit = new XrmBooleanField("new_attr1");
myBit.Toggle();

6. "Look for patterns" – Utilize coding patterns such as chaining or cascading to make your code shine.

function XrmBooleanField(sId, iCtlIndex) {
    if (sId == undefined)
        return;

    var xbAttr = this;

    xbAttr.Toggle = function() {
        xbAttr.Set(!xbAttr.Get());
        return xbAttr;
    }

    xbAttr.Set = function(bValue, bDefault) {}
    xbAttr.Get = function() {}

    xbAttr.Require = function() {
        // implement setRequiredLevel …
        return xbAttr;
    }
}

//in your forms you can chain your methods like so …:
var myBit = new XrmBooleanField("new_attr2");
myBit.Require().Toggle();

This is just a small and hopefully tasty appetizer … more CRM frenzy in future posts …