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,