CHUVASH.eu

CHunky Universe of Vigourous Astonishing SHarepoint :)

Category Archives: javascript

Disabling a column in Quick Edit

jsgrid-015
In my project I have a column called Request Status. This column is not shown in any forms, meaning users should not edit, because it is controlled through the app. Nevertheless it is editable in the Quick Edit.

Yesterday I wrote about jsgrid in my blog. Now comes more. Today I’ll share a little practical solution how one can disable editing a field in Quick Edit.

The field is edited in jsgrid, but to disable it, we only have set the property called AllowGridEditing to false on our column (not even touching the heavy jsgrid api). We can do in the OnPreRender event in our Client Side Rendering (CSR) registration. Having the context object we have access to the Fields (ContextInfo.ListSchema.Field):

(function () {
    var overrideContext = {};
    overrideContext.Templates = overrideContext.Templates || {};
    overrideContext.Templates.OnPreRender = function(ctx) {
    	var statusField = ctx.ListSchema.Field.filter(function(f) {
    		return f.Name === 'Request_x0020_Status';
    	});
    	if (statusField) {
    		statusField[0].AllowGridEditing = false;
    	}
    }
    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideContext);
})();

Another way is to implement the display form:

(function () {
    var view = function (ctx, field) {
        if (ctx.inGridMode) {
            field.AllowGridEditing = false;
        }
        return window.RenderFieldValueDefault(ctx);
    };
    var overrideContext = {};
    overrideContext.Templates = overrideContext.Templates || {};
    overrideContext.Templates.Fields = {
        'Request_x0020_Status': {
            'View': view
        }
    };
    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideContext);
})();

JSGrid Basics

JSGrid is the javascript framework in SharePoint used in Quick Edit View (previously Datasheet View). There are a few very good blog posts on this topic (See below in “Sources”). Nevertheless the fact is that jsgrid and working with quick edit from a developer’s perspective is a huge undiscovered area. Articles I have seen are intended for advanced developers. The goal with my post today is to outline the very basics of working with JSGrid. When you know the basics you will be more comfortable to discover and try out more.

The example I want to show is a jsgrid code for a “VerySimpleColumn”. The source code can be found on gist.github.com: https://gist.github.com/mirontoli/838d60df76107fac56a0 To focus on jsgrid, I assume you have knowledge and some experience of working with jslink, which is related to jsgrid.

A word of caution before we start

JSGrid is an undocumented part of SharePoint javascript “ecosystem”, neither it is a part of the official SharePoint javascript api. So actually we should not use it. On the other hand JSGrid indicates something that is more like a full-fledged javascript api because:

  1. It seems to be carefully prepared (all possible situations are covered)
  2. The api is human-readable. The events and properties are called OnBeginEdit, OnEndEdit, Focus, BindToCell and so on (opposed to the properties in the SP.Microfeed.js like $v_1, $v_2)
  3. It follows many best-practices for handling the UI in javascript, eventhough some constructs are clumsy, e.g. using absolute-positioned overlays on top of original table cells while editing a cell value.

So to me it seems quite okay to use it in real applications, but we have to live with the risk that the jsgrid api will be changed without any notice to us developers.

Set up a site column with a custom jslink

The first step is to set up everything so we can start discovering jsgrid. It is just a walkthrough, not a detailed explanation. If you are eager to look at jsgrid, jump directly to that section. In this example we’ll apply a jslink to a custom site column. The very same jslink will be used in jsgrid, too.

First of all add a new Site Column, call it VerySimpleColumn. The type is Multiple lines of text:

jsgrid-001

Let us put it in a new group: Tolle Columns (beautiful, huh?)

jsgrid-002

 

Three lines of plain text. Nothing extravagant.

jsgrid-003

After that we can verify that the column exists. Fine.

jsgrid-004

 

Now we have to update the jslink property for the new column. Make it in an app:

jsgrid-005

Now add a custom list, call it “TolleList”:

jsgrid-006

 

In the List Settings add the new site column, then you’ll see this:

jsgrid-007

 

Now upload an empty js file to the Style Library, call it field.jslink.verysimplecolumn.js:

jsgrid-008

 

It goes so fast 🙂 Now it is time to implement some jslink code

Writing jslink

I’ve been thinking a while. What example could I use for that jslink? I want it to be a very simple example, so we don’t need to concentrate on actual rendering logic. You can see sophisticated examples that I listed below in “Sources”. Here we will be using this example: We’ll append a text to the field value: “This field is fully controlled by jslink”. This text should be visible on all forms, but it is not a part of the actual field value. Well here it is:

jsgrid-009

The code should appear simple for us who have written at least some jslink code.

(function () {
    function verySimpleView(ctx, field) {
        var wrapper = 'This field is fully controlled by jslink<hr><span>{0}</span>';
        var value = ctx.CurrentItem[ctx.CurrentFieldSchema.Name] || "";
        return String.format(wrapper, value);
    }
    
    function verySimpleNewAndEdit(ctx) {
        var wrapper = 'This field is fully controlled by jslink<br><input type="text" id="{0}" value"{1}"/>';
        var id = 'tolle-' + new Date().getTime();
        var formCtx = SPClientTemplates.Utility.GetFormContextForCurrentField(ctx);
 
        formCtx.registerGetValueCallback(formCtx.fieldName, function () {
            var input = document.getElementById(id);
            return input.value;
        });
        var value = ctx.CurrentItem[ctx.CurrentFieldSchema.Name] || "";
        var html = String.format(wrapper, id, value);
        return html;
    }
    var overrideContext = {};
 
    overrideContext.Templates = overrideContext.Templates || {};
    overrideContext.Templates.Fields = {
        'VerySimpleColumn': {
            'View': verySimpleView,
            'DisplayForm': verySimpleView,
            'EditForm': verySimpleNewAndEdit,
            'NewForm': verySimpleNewAndEdit
        }
    };
 
    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideContext);
})();

This is how it looks like in the DispForm:

jsgrid-010

JSGrid. First Step: Disable the field in Quick Edit

Now we are ready to handle the Quick Edit. What we’ll do first is to disable editing our column. Why? Let’s say we have some complicated logic for rendering and editing of our field, we don’t the quick edit override it. So before we start implementing the QuickEdit part of the field, we want to disable it. Here, perhaps, ends the implementation of Quick Edit for some business cases.

jsgrid-011

To determine the Quick Edit we can use the propery of ther Render Context called inGridMode. The Quick Edit list uses the “View” template:

function handleGridMode(ctx, field) {
    field.AllowGridEditing = false;
}
function verySimpleView(ctx, field) {
    var wrapper = 'This field is fully controlled by jslink<hr><span>{0}</span>';
    var value = ctx.CurrentItem[ctx.CurrentFieldSchema.Name] || "";
    if (ctx.inGridMode) {
        handleGridMode(ctx, field);
    }
    return String.format(wrapper, value);
}

Wiring up a jsgrid EditControl

When our code works in all forms and the values are not “damaged” in the Quick Edit View, then the next step is to create an Edit Control that is used by the jsgrid “engine”. The very minimal Edit Control object is as follows. It contains a few event listeners and properties:

{
    SupportedWriteMode: SP.JsGrid.EditActorWriteType.LocalizedOnly,
    SupportedReadMode: SP.JsGrid.EditActorReadType.LocalizedOnly,
    BindToCell: function () { console.log("tolle BindToCell"); },
    OnCellMove: function () { console.log("tolle OnCellMove"); },
    Focus: function () { console.log("tolle Focus"); },
    OnBeginEdit: function () { console.log("tolle OnBeginEdit"); },
    OnEndEdit: function () { console.log("tolle OnEndEdit"); },
    Unbind: function () { console.log("tolle Unbind"); },
    Dispose: function () { console.log("tolle Dispose"); }
}

This edit control is created and returned in a function that is called: createVerySimpleColumnGridEditControl.

var createVerySimpleColumnGridEditControl = function (gridContext, cellControl) {
    return {
        SupportedWriteMode: SP.JsGrid.EditActorWriteType.LocalizedOnly,
        SupportedReadMode: SP.JsGrid.EditActorReadType.LocalizedOnly,
        BindToCell: function () { console.log("tolle BindToCell"); },
        OnCellMove: function () { console.log("tolle OnCellMove"); },
        Focus: function () { console.log("tolle Focus"); },
        OnBeginEdit: function () { console.log("tolle OnBeginEdit"); },
        OnEndEdit: function () { console.log("tolle OnEndEdit"); },
        Unbind: function () { console.log("tolle Unbind"); },
        Dispose: function () { console.log("tolle Dispose"); }
    }
};

Now we have to register our Edit Control when jsgrid is ready. We have to write a callback function and invoke SP.GanttControl.WaitForGanttCreation. Inside the callback function we register our Edit Control for our field: SP.JsGrid.PropertyType.Utils.RegisterEditControl

function handleGridMode(ctx, field) {
    window.SP.SOD.executeOrDelayUntilScriptLoaded(function () {
        window.SP.GanttControl.WaitForGanttCreation(function (ganttChart) {
            var verySimpleColumn = null;
            var editId = "EDIT_TOLLE_VERYSIMPLEFIELD";
            var columns = ganttChart.get_Columns();

            for (var i = 0; i < columns.length; i++) {
                if (columns[i].columnKey == "VerySimpleColumn") {
                    verySimpleColumn = columns[i];
                    break;
                }
            }
            if (verySimpleColumn) {
                verySimpleColumn.fnGetEditControlName = function (record, fieldKey) {
                    return editId;
                };

                window.SP.JsGrid.PropertyType.Utils.RegisterEditControl(editId, function (gridContext, cellControl) {
                    return createVerySimpleColumnGridEditControl(gridContext, cellControl);
                }, []);
            }

        });
    }, "spgantt.js");
}

In our first version of the Edit Control we only log the event names to the web browser console. It works.

jsgrid-012

Implementing the actual editing

When working on this I did small changes to these events and tried it out in the web browser. I’d recommend it to you, too. Trying out is the best way of learning. Here comes the functions that we need to have to implement:

  • createVerySimpleColumnGridEditControl (“constructor”). Here we initialize the edit control
  • bindToCell. Here we get the cellContext that we save as a “private” variable on that Edit Control object. The cell context is needed to get and set the field value.
  • focus. Here we define what element should be focused. In this case we forward the focus event to the actual input
  • onBeginEdit. Here we show the Edit Control and make it editable.
  • onEndEdit. Here we save the field value and hide the edit control.

jsgrid-013

Some notes on the Edit Control and Events

The Edit Control has a “container” – a html element that contains the representation of the field in edit mode. The Edit Control Container is an overlay – an absolutely-positioned element that exists outside the actual listview. We must set the position and we are responsible for hiding it when the field is not edited:

container.style.cssText = 'visibility:hidden;position:absolute;top:0;left:0;background:#ffffff;';

We also need to set the dimensions of the edit control container. It is quite easy using the information from cellContext:

var bindToCell = function(ctx) {
    cellContext = ctx;
    //An input is put as an overlay. 
    //We have to set the width and height so that it takes the whole cell place
    container.style.minWidth = cellContext.cellWidth + 'px';
    container.style.width = cellContext.cellWidth + 'px';
    container.style.height = cellContext.cellHeight + 'px';
    console.log("tolle BindToCell");
};

In OnBeginEdit we have to show the container, and in OnEndEdit we have to hide it

//OnBeginEdit
cellContext.Show(container);
//OnEndEdit
cellContext.Hide(container);

We have to save the value using the cellContext.

var value = input.value;
cellContext.SetCurrentValue({
    localized: value
});

Final code

(function () {
    var editWrapper = '<span>This field is fully controlled by jslink</span><br><input type="text" id="{0}" value"{1}"/>';
    var createVerySimpleColumnGridEditControl = function (gridContext, gridTextInputElement) {
        var cellContext, inEdit, html, container, input, id;
        id = "tolle-" + new Date().getTime();
        html = String.format(editWrapper, id, "");
        container = document.createElement("div");
        container.innerHTML = html;
        input = container.getElementsByTagName("input")[0];
        container.style.cssText = 'visibility:hidden;position:absolute;top:0;left:0;background:#ffffff;';
        gridContext.parentNode.appendChild(container);
        var bindToCell = function(ctx) {
            cellContext = ctx;
            //An input is put as an overlay. We have to set the width and height so that it takes the whole cell place
            container.style.minWidth = cellContext.cellWidth + 'px';
            container.style.width = cellContext.cellWidth + 'px';
            container.style.height = cellContext.cellHeight + 'px';
            console.log("tolle BindToCell");
        };
        var onCellMove = function () { console.log("tolle OnCellMove"); };
        var focus = function(eventInfo) {
            input.focus();
            console.log("tolle Focus", eventInfo);
        };
        var onBeginEdit = function (eventInfo) {
            inEdit = true;
            var currentValue = cellContext.originalValue.localized;
            if (currentValue) {
                input.value = currentValue;
            }
            cellContext.Show(container);
            console.log("tolle OnBeginEdit");
            this.Focus(eventInfo);
        };
        var onEndEdit = function() {
            cellContext.Hide(container);
            inEdit = false;
            var value = input.value;
            cellContext.SetCurrentValue({
                localized: value
            });
            console.log("tolle OnEndEdit");
        };

        var unbind = function() { console.log("tolle Unbind"); };
        var dispose = function () { console.log("tolle Dispose"); }

        return {
            SupportedWriteMode: window.SP.JsGrid.EditActorWriteType.LocalizedOnly,
            SupportedReadMode: window.SP.JsGrid.EditActorReadType.LocalizedOnly,
            BindToCell: bindToCell,
            OnCellMove: onCellMove,
            Focus: focus,
            OnBeginEdit: onBeginEdit,
            OnEndEdit: onEndEdit,
            Unbind: unbind,
            Dispose: dispose
        }
    };

    function handleGridMode(ctx, field) {
        window.SP.SOD.executeOrDelayUntilScriptLoaded(function () {
            window.SP.GanttControl.WaitForGanttCreation(function (ganttChart) {
                var verySimpleColumn = null;
                var editId = "EDIT_TOLLE_VERYSIMPLEFIELD";
                var columns = ganttChart.get_Columns();

                for (var i = 0; i < columns.length; i++) {
                    if (columns[i].columnKey == "VerySimpleColumn") {
                        verySimpleColumn = columns[i];
                        break;
                    }
                }
                if (verySimpleColumn) {
                    verySimpleColumn.fnGetEditControlName = function (record, fieldKey) {
                        return editId;
                    };

                    window.SP.JsGrid.PropertyType.Utils.RegisterEditControl(editId, function (gridContext, cellControl) {
                        return createVerySimpleColumnGridEditControl(gridContext, cellControl);
                    }, []);
                }

            });
        }, "spgantt.js");
    }


    function verySimpleView(ctx, field) {
        var wrapper = 'This field is fully controlled by jslink<hr><span>{0}</span>';
        var value = ctx.CurrentItem[ctx.CurrentFieldSchema.Name] || "";
        if (ctx.inGridMode) {
            handleGridMode(ctx, field);
        }
        return String.format(wrapper, value);
    }
    
    function verySimpleNewAndEdit(ctx) {
        var id = 'tolle-' + new Date().getTime();
        var formCtx = window.SPClientTemplates.Utility.GetFormContextForCurrentField(ctx);

        formCtx.registerGetValueCallback(formCtx.fieldName, function () {
            var input = document.getElementById(id);
            return input.value;
        });
        var value = ctx.CurrentItem[ctx.CurrentFieldSchema.Name] || "";
        var html = String.format(editWrapper, id, value);
        return html;
    }
    var overrideContext = {};

    overrideContext.Templates = overrideContext.Templates || {};
    overrideContext.Templates.Fields = {
        'VerySimpleColumn': {
            'View': verySimpleView,
            'DisplayForm': verySimpleView,
            'EditForm': verySimpleNewAndEdit,
            'NewForm': verySimpleNewAndEdit
        }
    };

    window.SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideContext);
})();


Summary

Well, jsgrid is quite complicated. I have tried to keep it as simple and minimalistic as possible. When we can these basics, we can move on and master advanced examples where we can deliver high value to business that loves Quick Edit 🙂

Sources

Interesting, Anton, Andrey and me are born in Sovyet and are interested in JSGrid. Coincidence? 🙂

AppLoader Concept for SharePoint apps

In this post I want to share an unusual, nevertheless interesting conceptual idea of loading content from SharePoint 2013 apps on many pages. The original awesome concept was proposed and developed by my colleague Martin Villysson at Bool.

The problem we are trying to solve

SharePoint apps are great to extend functionality in SharePoint and integrate other systems (full page apps available through Site Contents), they also provide tools to enrich the default SharePoint experience by App Parts (Client Web Parts) and Custom Actions (additional menus).

One of the biggest shortcomings of that model is the need to add app parts on all pages where it is needed. Let’s say, we want to have some app parts present on every single page in our whole SharePoint tenancy, to provide a consistent look and feel (e.g. navigation, notifications). Traditionally, on premises, we have added user controls in our customized master page. In SharePoint Online that is impossible. The complicated workaround is to add those client web parts (app parts) on every page, be it manually or by automating it (powershell or app). It will require updating all pages. Nevertheless, it will not work on Out-of-the-box application pages (pages from layouts folder). It becomes even more unacceptable when you realize that your app must be added as an app instance on every single site (SPWeb) in your tenancy. 

Towards a solution

Allright, we don’t want to have thousands of app instances of the same app. What we can do is to use Tenant scoped apps (Tenant Apps). Then we’ll need only one app instance. But wait, app parts from a tenant app are only available in the parent site (HostWeb), meaning – App Catalog. That’s not good. So what Martin found in the SharePoint internal javascript code is using of _layouts/15/TenantAppInfo.ashx, a http handler that provides information about all Tenant Apps and their custom actions.That’s how the idea of the AppLoader was born.

Vesa Juvonen

After we had created a working Proof-of-Concept of the AppLoader concept, I met Vesa Juvonen at the SharePoint Conference in Las Vegas and introduced this idea to him (although I didn’t call it AppLoader). He liked it although he pointed out that this TenantAppInfo.ashx is an internal utility only in SharePoint and it is not supported by Microsoft. That’s correct. There is even almost no information about it on the Internet. But I got a feeling of Microsoft that they are willing to hear feedback and improve the product. Vesa encouraged me also to blog about it. So now I am telling about this idea. I hope to hear feedback about it. Unfortunately I cannot share the source code of the working Proof-of-Concept solution.

AppLoader Concept in colors

The AppLoader Concept is quite simple. Look at this picture:

apploader-concept

 

The solution contains a custom Master Page (blue) that references a javascript file called apploader.js (red). This file initializes the whole process. Tenant Apps (green) are the apps that an administrator has installed in the App Catalog and deployed to the whole tenancy. TenantAppInfo.ashx (black) is a handy but officially unsupported OOB service utility (http handler) that returns a json-formatted list of all Tenant Apps (green). AppLoader (red) receives the app list (black) and renders it on the Page (blue) inside new iframes (red). The page a user has navigated to can be any page (wiki page, publishing page, application page, really any page).

To summarize the colors in the diagram: red is our javascript code, green are all the tenant apps and their content, black is the utility and its output, blue is a sharepoint page and its underlying component (master page).

The steps in the AppLoader process:

  1. Make an ajax request to TenantAppInfo.ashx using XHR (XmlHttpRequest)
  2. Receive the app list
  3. For every app information, render app part, or inject css and javascript references. 

 

Reading what to render on the page

You probably have already have tried to navigate to _layouts/15/TenanAppInfo.ashx while reading this post, I know you are curious. Then you’ve noticed that there is no information about app parts. So you may ask: how do we know what app parts to render and where to put them in the page, and how do we know what resources (css and javascript files) to inject on the page. Well there is no information about it in the apps list. But if you have an app with custom actions you’ll see that they are listed in this json-formatted list we receive from the TenantAppInfo.ashx. So the solution is the brilliant idea of my colleage Martin to define custom actions in the app. CustomActions contain a ActionUrl. The ActionUrl points to the url to render (app part page) or to inject (javascript or css file). The apploader.js reads the ActionUrl in the Custom Actions for every app information and takes action upon it (rendering an app part iframe, or injecting a javascript or css file). That’s it. 

Usage and Limitations

This bright idea takes advantages from a huge SharePoint API (that contains a lot of good but not supported parts) to make using of apps in Client Application Model solutions more pragmatic and still provide a consistent design and behavior. By consistent design I mean same parts like additional navigation, notifications etc in the whole Intranet. The AppLoader renders and injects whatever you have rolled out to your whole tenancy (Tenant Scoped Apps) and that on every page (!). It also improves the perceived performance of the page load, because it renders app parts (iframes) after the main page has been loaded preventing freezing of the page. 

There are of course some limitations in the AppLoader Concept. Today we cannot rely on the TenanAppInfo.ashx API (because it is not supported and future updates can break solutions). We have to define our own custom actions in the apps. That means we can only use our own apps, it will hardly work with the apps installed from the Office Store. On the other hand, your customer will not want to have generic apps from the Office Store to be a part of every page on their intranet.

Using CAML with SharePoint REST API

Do you prefer REST over CSOM as I do? I’ll skip the whys. Andrew Connell put it already in wrtiting so nicely. Well, if you do prefer REST, then you must have discovered some shortcomings of REST, or its incompleteness compared to CSOM. I think of:

  1. Inability to filter items based on multivalued taxonomy fields
  2. Inability to filter items based on user fields where user is added through a group, rather than directly, e.g. AssignedTo=[Me] combined with a SharePoint group.

In such situations I was forced to use CSOM. Until yesterday. Yesterday I learned that we can actually use CAML queries in REST requests.

 

This enables using REST in all situations. The REST API is still developed and many features are added. Maybe a particular operation that needs a CAML query today, can be supported in the core REST API and can be easily refactored then.

But until then, we can use CAML queries in REST requests. Here are the important things about it:

  • A REST request with a CAML query is always a POST request
  • A REST request with a CAML query has always to have X-RequestDigest http header (actually because it is a POST request)
  • A REST request with a CAML query should always have the attached CAML query in the request body (and not in a query string). We don’t want to mess with long urls, do we?
  • A REST request with a CAML query must have the http header “Content-Type: application/json;odata=verbose” unless you use xml in the request body.
Needed HTTP Headers in REST requests

HTTP headers you have to provide in REST requests with CAML queries

You can use jQuery or SP.RequestExecutor to make an ajax call. The REST endpoint is:

_api/web/Lists/GetByTitle('<your list title>')/GetItems

The request body (if you use json, and I bet, you do) is in this format:

{ "query" :
   {"__metadata": 
      { "type": "SP.CamlQuery" }
      , "ViewXml": "<YOUR CAML QUERY>" 
   }
}

Here is the boilerplate for a REST request with a CAML Query:

function getDataWithCaml(listName, caml) {
    var endpoint = "/_api/web/lists/GetByTitle('" 
        + listName + "')/GetItems";
    var requestData = { "query" :
           {"__metadata": 
              { "type": "SP.CamlQuery" }
              , "ViewXml": caml
           }
        };
    return jQuery.ajax({
        url: endpoint,
        method: "POST",
        data: requestData,
        headers: {
            "X-RequestDigest": $("#__REQUESTDIGEST").val(),
            "Accept": "application/json; odata=verbose",
            "Content-Type": "application/json; odata=verbose"
        }
    });
}

This function is just an example. It has no error handling, and it takes for granted that your list is in the root site for on your (sub-)domain (“/”). So take it as an example only.

Here is how the function can be invoked

var caml = "<View><Query><Where><Or><Eq><FieldRef Name='AssignedTo' /><Value Type='Integer'><UserID/></Value></Eq><Membership Type='CurrentUserGroups'><FieldRef Name='AssignedTo' /> </Membership></Or></Where></Query></View>";
getDataWithCaml("Tasks", caml);

Count lines of code with PowerShell

Today I got a question:

How many lines of code are there in our SharePoint solution?

After a little search, I found that PowerShell is really a nice tool to count lines of code:

I wanted to count lines for different types of code:

  1. Code Behind written in C#, the files have .cs file extension
  2. JavaScript code (except jQuery, angular or knockout frameworks)
  3. PowerShell files (.ps1 and psm1)
  4. Xml files (all the SharePoint .xml files)

Here is the powershell code that counts lines of code:

# go to the solution folder
cd <solution directory>

#count lines in .cs files
ls -include *.cs -recurse | select-string . | measure | select count

#count lines in our .js files
ls -include *.js -recurse `
    -exclude *min.js, jquery*, _*, jsrender*, CamlBuilder*, knockout* `
  | select-string . `
  | measure `
  | select Count

#count lines in our powershell scripts
ls -include *.xml -recurse | select-string . | measure | select count

#count lines in our powershell scripts
ls -include *.ps1, *.psm1 -recurse | select-string . | measure | select count

Just a curious fact, I can’t tell you how many lines of code we have in our solution, but I can reveal the proportions. If I used the flexible box model in css3, it would look like this:

lines of code

There are as many lines of code written in javascript as it is in C#. The main reason that for the big js code base are the SharePoint hosted apps. The PowerShell scripts are as big the javascript code base. Xml files are 4 times bigger than C# code, and it is even bigger than the sum of all lines of code written in C#, JavaScript and PowerShell. It isn’t strange that xml is dominating, almost everything in SharePoint is defined in xml.

Fortunately, there are less cases where you have to write raw xml in Visual Studio 2012/2013 and SharePoint 2013.

How does it look in your project? What language is dominating in your SharePoint project?

Debugging OOB SharePoint. Unable to post comments on SharePoint blogs (SP2013 June CU)

I have had a strange bug. The comment text box in a OOB SharePoint 2013 blog doesn’t appear. It only says: “There are no comments for this post.” In this blog post I’ll tell you how I found the bug and I’ll show you how you can temporarily bring the commenting to life.

comments-not-working-001

I have had luck. While it doesn’t work on the Test Environment, it does actually work on my development machine. The comments text box is rendered as an OnPostRender action in the blog comments display template. After debugging the javascript in hours and comparing the two environments, I just could confirm that displaytemplates are the same. There are two main javascript files that are involved:

  • clienttemplates.js
  • sp.ui.blogs.js

So it must some differences elsewhere. The build versions of the environments are

  • Working Environment:  15.0.4420.1017 (SharePoint 2013 RTM)
  • Not-working Environment: 15.0.4517.1005 (SharePoint 2013 June CU)

No errors are thrown, even on the blog where it doesn’t work. It is because of the permissive try-catch in this function that wraps rendering of the comment text box:

function CallFunctionWithErrorHandling(fn, c, erv, execCtx) {
    if (SPClientRenderer.IsDebugMode(c)) {
        return fn();
    }
    try {
        return fn();
    }
    catch (e) {
        if (c.Errors == null)
            c.Errors = [];
        try {
            e.ExecutionContext = execCtx;
            if (Boolean(SPClientRenderer.AddCallStackInfoToErrors) && typeof execCtx == "object" && null != execCtx) {
                execCtx.CallStack = ULSGetCallstack(CallFunctionWithErrorHandling.caller);
            }
        }
        catch (ignoreErr) { }
        c.Errors.push(e);
        return erv;
    }
}

This is where the error occurs but doesn’t appear in the script console. Here is the error:

ExecutionContext: Object
message: "Syntax error, unrecognized expression: #{B808453A-141A-4091-8467-B6915B8B3E99}-{6C3BF638-43F2-4F12-A297-DEF18635540E}-CommentContainer"
stack: "Error: Syntax error, unrecognized expression: #{B808453A-141A-4091-8467-B6915B8B3E99}-{6C3BF638-43F2-4F12-A297-DEF18635540E}-CommentContainer
    at Error ()
    at Function.st.error (jquery-1.9.1.min.js:4:10926)
    at ft (jquery-1.9.1.min.js:4:17138)
    at wt (jquery-1.9.1.min.js:4:19823)
    at Function.st (jquery-1.9.1.min.js:4:6140)
    at b.fn.extend.find (jquery-1.9.1.min.js:4:20941)
    at b.fn.b.init (jquery-1.9.1.min.js:3:1126)
    at b (jquery-1.9.1.min.js:3:206)
    at /ScriptResource.axd:344:53
    at _foreach (ScriptResource.axd:33:17)"

What? jQuery error? Why?

It turns out that jQuery is used to get the html element for the comments area in the server with June CU, but not in the Server with SharePoint 2013 RTM.

To get the reference to the comments area, it invokes the $get method, which invokes Sys.get which in some cases invokes the jQuery (if present)

$get implementation in SP 2013 June CU

These are the functions which can be found in the SharePoint 2013 June CU (15.0.4517.1005)

$get = $type.getElementById = function DomElement$getElementById(id, element) {
    /// <summary locid="M:J#Sys.UI.DomElement.getElementById">Finds an element by id.</summary>
    /// <param name="id" type="String">The id of the element to find.</param>
    /// <param name="element" domElement="true" optional="true" mayBeNull="true"></param>
    /// <returns domElement="true" mayBeNull="true">The element, or null if it was not found.</returns>
    var e = Function._validateParams(arguments, [
        {name: "id", type: String},
        {name: "element", mayBeNull: true, domElement: true, optional: true}
    ]);
    if (e) throw e;
    return Sys.get("#" + id, element || null);
}

 

        get: function get(selector, context) {
            /// <summary>Queries the DOM for a single DOM element.</summary>
            /// <validationOptions enabled="false" />
            /// <param name="selector">
            /// Selector for a DOM element based on id (#&lt;id>), class (.&lt;name>), or tag name (&lt;tagname>).
            //// More complex selectors may be used if jQuery is loaded.
            /// If multiple elements match the selector, the first one is returned.
            /// </param>
            /// <param name="context" optional="true" mayBeNull="true">An element, array of elements, or Sys.UI.TemplateContext to restrict the query within.</param>
            /// <returns>The matching element, or null if none match.</returns>
            return (context && typeof(context.get) === "function") ?
                context.get(selector) :
                this._find(selector, context, true);
        },

 

_find: function _find(selector, context, single, filter) {
    ...
    else if (window.jQuery) {
        if (!filter) {
            found.push.apply(found, jQuery(selector, context).get());
        }
        if (includeSelf) {
            found.push.apply(found, jQuery(context).filter(selector).get());
        }
    }
    ...
},

older (but working) $get implementation in SharePoint 2013 RTM

These are two versions which can be found in the older SharePoint Release: RTM (15.0.4420.1017)

var $get = Sys.UI.DomElement.getElementById = function Sys$UI$DomElement$getElementById(id, element) {
    /// <summary locid="M:J#Sys.UI.DomElement.getElementById" />
    /// <param name="id" type="String"></param>
    /// <param name="element" domElement="true" optional="true" mayBeNull="true"></param>
    /// <returns domElement="true" mayBeNull="true"></returns>
    var e = Function._validateParams(arguments, [
        {name: "id", type: String},
        {name: "element", mayBeNull: true, domElement: true, optional: true}
    ]);
    if (e) throw e;
    if (!element) return document.getElementById(id);
    if (element.getElementById) return element.getElementById(id);
    var nodeQueue = [];
    var childNodes = element.childNodes;
    for (var i = 0; i < childNodes.length; i++) {
        var node = childNodes[i];
        if (node.nodeType == 1) {
            nodeQueue[nodeQueue.length] = node;
        }
    }
    while (nodeQueue.length) {
        node = nodeQueue.shift();
        if (node.id == id) {
            return node;
        }
        childNodes = node.childNodes;
        for (i = 0; i < childNodes.length; i++) {
            node = childNodes[i];
            if (node.nodeType == 1) {
                nodeQueue[nodeQueue.length] = node;
            }
        }
    }
    return null;
}

It doesn’t even go to Sys.get and doesn’t even try to use jQuery.

The id of the comment are is a combination of two guids and -CommentContainer. jQuery fails to select an element with this id:
This error happens when you try to select this html element with jQuery in the web console:

comments-not-working-002

Temporary solution

The solution is to prevent using jQuery for this element selection. Hopefully new updates of SharePoint will address this issue. Until then we have to tweak the $get method. To do it very carefully, we can copy the old $get function and call it $get_asFoundInRTM and copy the new $get and call it $get_asFoundInJuneCU

The $get function has to rewritten like that:

$get_asFoundInJuneCU = $get

$get = function () {
    var id = arguments[0];
    return /\{/.test(id)
        ? $get_asFoundInRTM.apply(null, arguments)
        : $get_asFoundInJuneCU.apply(null, arguments);
}

This javascript code has to be loaded after two ScriptResource.axd files. Please be very careful if you have to implement this temporary fix. Probably it is better to wait for an official Microsoft bug fix.

 

UPDATE 2014-03-27

I have discovered that in SP1 it is already solved.

Log to ULS using javascript

uls_001

The more javascript code is produced in SharePoint solutions, the more need we have to log information and possible errors to a central logging  place in SharePoint: ULS. This blog post is about logging to ULS from javascript.

For a while ago I read a blog post:

The author @avishnyakov mentions the ability log to ULS from javascript. I want to dive deeper.

ULS.enable = true
ULSOnError("Hello from javascript", location.href, 0);

What this function actually does, is that it calls a web service called _vti_bin/diagnostics.asmx

We can follow the function in the init.debug.js

function ULSOnError(msg, url, line) {
    return ULSSendExceptionImpl(msg, url, line, ULSOnError.caller);
}

ULSOnError invokes ULSSendExceptionImpl:

function ULSSendExceptionImpl(msg, url, line, oCaller) {
    if (Boolean(ULS) && ULS.enable) {
        ULS.enable = false;
        window.onerror = ULS.OriginalOnError;
        ULS.WebServiceNS = "http://schemas.microsoft.com/sharepoint/diagnostics/";
        try {
            ULS.message = msg;
            if (url.indexOf('?') != -1)
                url = url.substr(0, url.indexOf('?'));
            ULS.file = url.substr(url.lastIndexOf('/') + 1);
            ULS.line = line;
            ULS.teamName = "";
            ULS.originalFile = "";
            ULS.callStack = '<stack>\n' + ULSGetCallstack(oCaller) + '</stack>';
            ULS.clientInfo = '<client>\n' + ULSGetClientInfo() + '</client>';
            ULSSendReport(true);
        }
        catch (e) { }
    }
    if (Boolean(ULS) && Boolean(ULS.OriginalOnError))
        return ULS.OriginalOnError(msg, url, String(line));
    else
        return false;
}

ULSSendExceptionImpl invokes ULSSendReport:

function ULSSendReport(async) {
    ULS.request = new XMLHttpRequest();
    ULS.request.onreadystatechange = ULSHandleWebServiceResponse;
    ULS.request.open("POST", ULSGetWebServiceUrl(), async);
    ULS.request.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
    ULS.request.setRequestHeader("SOAPAction", ULS.WebServiceNS + "SendClientScriptErrorReport");
    ULS.request.send('<?xml version="1.0" encoding="utf-8"?>' 
        + '<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">' 
        + '<soap:Body>' 
        + '<SendClientScriptErrorReport xmlns="' + ULS.WebServiceNS + '">' 
        + '<message>' + ULSEncodeXML(ULS.message) + '</message>' 
        + '<file>' + ULSEncodeXML(ULS.file) + '</file>' 
        + '<line>' + String(ULS.line) + '</line>' 
        + '<stack>' + ULSEncodeXML(ULS.callStack) + '</stack>' 
        + '<client>' + ULSEncodeXML(ULS.clientInfo) + '</client>' 
        + '<team>' + ULSEncodeXML(ULS.teamName) + '</team>' 
        + '<originalFile>' + ULSEncodeXML(ULS.originalFile) + '</originalFile>' 
        + '</SendClientScriptErrorReport>' 
        + '</soap:Body>' + '</soap:Envelope>');
}

Alternatives

An alternative way is to create an own handler that is invoked through an ajax request and that implement an own logic for writing logs to ULS. You can see a neat solution provided by Albert Jan-Shot:

Not to be confused with SP.ULS.log

There is another utility function for logging: SP.ULS.log, this function logs messages in a new browser window. In my opinion, it is a substitute for console.log, but the good thing is that you have to manually enable it in order to see:
log-001

 

Logging from an app

If you are developing a sharepoint app, you can log app errors easily by using:

SP.Utilities.Utility.logCustomAppError(
       SP.ClientContext.get_current(), errorMessage);

javascript: Alert Me on a Page

alertme-001
Recently I needed to add an Alert Me link on Pages. Alert Me is a well known SharePoint functionality for notifying users about changes in list or list items. It is availabe in OOB SharePoint as a command in Ribbon if you go to a list view:

alertme-002

When you click on this ribbon command, SharePoint opens a modal dialog and takes you to layouts page: SubNew.aspx. To open a modal dialog and load a page is not a rocket science. So a custom “Alert Me” link is doable.

As the first step I copied the html markup from the ribbon and adjusted it a little bit.

<span class="ms-cui-img-16by16 ms-cui-img-cont-float" 
   style="margin-right: 2px;">
   <img alt="" src="/_layouts/15/1033/images/formatmap16x16.png?rev=23" 
       style="top: -295px; left: -19px;margin-top: 2px;">
</span>
<a href="javascript:takana.alertMe();">Alert Me</a>

Then the javascript code which gets the List ID and Page ID is very simple because this information is there in the magic _spPageContextInfo:

var takana = window.takana || {};
takana.alertMe = function () {
    var url = String.format("{0}/{1}/SubNew.aspx?List={2}&ID={3}"
        , _spPageContextInfo.webAbsoluteUrl
        , _spPageContextInfo.layoutsUrl
        , encodeURI(_spPageContextInfo.pageListId)
        , _spPageContextInfo.pageItemId);
    OpenPopUpPage(url);
}

This code will open a modal dialog in exactly the same way as the ribbon command in OOB SharePoint and let you subscribe to changes on that page. In this code I use String.format which is available on SharePoint pages and _spPageContextInfo which has existed since SharePoint 2010 and has been extended with more useful information about the current context.

javascript: Remove illegal characters in url

Illegal characters

Recently I needed to create valid urls for pages using javascript. Also this time I thought it must be some out-of-the-box code for that in SharePoint. The first thing I came up  was the “Add page” dialog.  I found that the actual dialog was in the _layouts virtual folder:

/_layouts/15/CreatePublishingPageDialog.aspx

Bingo, it does the validation in the client side. This is the responsible javascript function that resides directly in the page:

function UpdateUrl() {
  LoadTermContextInfo();
  var hiddenPageUrlLabelExtensionClientId = "<%=hiddenPageUrlLabelExtension.ClientID%>";
  var hiddenPageUrlLabelExtension = document.getElementById(hiddenPageUrlLabelExtensionClientId);
  var pageNamePreviewUrlLabelClientId = "<%=pageNamePreviewUrlLabel.ClientID%>";
  var pageNamePreviewUrlLabel = document.getElementById(pageNamePreviewUrlLabelClientId);
  if( pageNamePreviewUrlLabel != null ) {
    var nameInputTextBoxClientId = "<%=nameInput.ClientID%>";
    var nameInputTextBox = document.getElementById(nameInputTextBoxClientId);
    var allowSpaces = false;
    if( GetInnerText(hiddenPageUrlLabelExtension) != "" ) {
      var suggestUrlValue = "";
      for (var i=0; i < nameInputTextBox.value.length; i++) {
         var currentChar = nameInputTextBox.value.charAt(i);
         if (IndexOfIllegalCharInUrlLeafName(currentChar) == -1 
			&& !(currentChar == ' ' && allowSpaces == false) 
			&& currentChar != '.' && currentChar != '+') {
           suggestUrlValue += currentChar;
         }
         else if (currentChar == ' ' 
			|| currentChar == '+' 
			|| (currentChar == '.' && i > 0 && i < (nameInputTextBox.value.length - 1))) {
          suggestUrlValue += '-';
        }
      }
      UpdatePreviewUrl( suggestUrlValue );
    } else {
      if( g_timerId != 0 ) {
        window.clearTimeout(g_timerId);
      }
      g_timerId = window.setTimeout(OnFriendlyUrlNameChanged, 500);
    }
  }
}

This function iterates through all the characters in the page url and removes the illegal characters. The space, plus sign and the dot become a hyphen. To determine if a character is illegal, it relies on another javascript function called: IndexOfIllegalCharInUrlLeafName. This function can be found in the init.js or init.debug.js:

function IndexOfIllegalCharInUrlLeafName(strLeafName) {
    for (var i = 0; i < strLeafName.length; i++) {
        var ch = strLeafName.charCodeAt(i);

        if (strLeafName.charAt(i) == '.' && (i == 0 || i == strLeafName.length - 1))
            return i;
        if (ch < 160 && (strLeafName.charAt(i) == '/' || !LegalUrlChars[ch]))
            return i;
    }
    return -1;
}

This function checks a char against an array of all characters: LegalUrlChars from the same file: init.js.

ill_002

To use this UpdateUrl function, we have to remove the references to the fields from the Add Page Dialog. Of course, we won’t overwrite this original function, we don’t want to break the SharePoint OOB functionality. Here is how my new function looks like:

var takana = {};
function takana.updateUrl(url) {
  var allowSpaces = false;
  if( url )
  {
    var suggestUrlValue = "";
    var length = url.length;
    for (var i=0; i < length; i++)     {
       var currentChar = url.charAt(i);
       if (IndexOfIllegalCharInUrlLeafName(currentChar) == -1
            && !(currentChar == ' ' && allowSpaces == false)
            && currentChar != '.' && currentChar != '+') {
         suggestUrlValue += currentChar;
       }
       else if (currentChar == ' '
            || currentChar == '+'
            || (currentChar == '.'
                     && i > 0
                   && i < (nameInputTextBox.value.length - 1))) {
         suggestUrlValue += '-';
      }
    }
  }
}

Server Side

For those of you who want run this operation on the server, here is the code written in C#:

//" # % & * : < > ? \ / { } ~ |
var illegalChars = @"[""#%&\*:\<\>\?\\\/\{\}~\|]";

pageName = Regex.Replace(pageName, illegalChars, string.Empty);

var punctuation = @"[\s\.;\+]";
pageName = Regex.Replace(pageName, punctuation, "-");

//remove "--"
pageName = Regex.Replace(pageName, @"\-{2,}", "-");

pageName = string.Format("{0}.aspx", pageName);

//do it like the built-in dialog, lower case
pageName = pageName.ToLower();

Don’t forget to leave a comment if you find this post useful.

The CDN concept in SharePoint

How many instances of jquery are there in your SharePoint farm?

 Get-SPWebApplication http://dev `
  | Select -Expand Sites `
  | Select -Expand AllWebs `
  | Select -Expand Lists `
  | Select -Expand Items `
  | ? { $_.Url -match "jquery.*.js" } `
  | select Name, Url

Have you more than two (jquery and jquery-ui), then you have too much. You can save much place and performance by using Content Delivery Network (CDN) links for the resources like javascript, css, fonts and icons. Consider those Content Delivery Networks:

CDN for custom resources

But can we benefit from this CDN concept for our custom resources? I think so, if your farm has ten thousands of site collections, and you deploy javascript files, css files in the Style Library, it would be great to eliminate resource duplicates. So my thougt is to deploy resources to one place. It could be:

  • An external web application static.contoso.com like many web applications do
  • A dedicated site collection for resources cdn.contoso.com with Anonymous access
The CDN concept in Office 365

Have you noticed that Office 365 uses cdn links for almost all SharePoint javascript files that traditionaly were referenced from the _layouts folder

cdn-001

With this I want to raise a question. What do you think about the CDN concept within SharePoint? Have you used it? Have you plans to have it?

Вула Чăвашла

VulaCV - Чăвашла вулаттаракан сайт

Discovering SharePoint

And going crazy doing it

Bram de Jager - Coder, Speaker, Author

Office 365, SharePoint and Azure

SharePoint Dragons

Nikander & Margriet on SharePoint

Cameron Dwyer

Office 365, SharePoint, Azure, OnePlace Solutions & Life's Other Little Wonders

paul.tavares

Me and My doings!

Share SharePoint Points !

By Mohit Vashishtha

Jimmy Janlén "Den Scrummande Konsulten"

Erfarenheter, synpunkter och raljerande om Scrum från Jimmy Janlén

Aryan Nava

DevOps, Cloud and Blockchain Consultant

SPJoel

SharePoint for everyone

SharePointRyan

Ryan Dennis is a SharePoint Solution Architect with a passion for SharePoint and PowerShell

SharePoint 2020

The Vision for a Future of Clarity

Aharoni in Unicode

Treacle tarts for great justice

... And All That JS

JavaScript, Web Apps and SharePoint

blksthl

Mostly what I know about SharePoint - CommunicoCuspis

SharePointDiver

SharePoint på ren svenska