CHUVASH.eu

CHunky Universe of Vigourous Astonishing SHarepoint :)

Tag Archives: javascript

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?

Convert any web app to a SharePoint app

convert-app-001

Have you noticed that you can right-click a web application project in Visual Studio and convert it to a provider hosted app? Well why not? Basically your own website and a SharePoint manifest is all what you need for a provider hosted app.

convert-app-002

This discovery today made me think about all legacy web apps out there that can be converted to SharePoint apps.  Traditionally we had to add plain links to external applications or embed them into an IFrame by hardcoding it in an .aspx page or a Page Viewer WebPart.

A web application that should be converted to a SharePoint app can be any web app, not only asp.net web site.

For a year ago, I had a little nodejs project to try out mongodb and knockout.js: Anvaska which I published as a heroku app:

Now I want to try to convert this to a SharePoint app. I am satisfied when:

  1. A full page app is rendering Anvaska
  2. The SharePoint app renders the chrome control (suiteBar) if the app runs within a SharePoint context
1. A full page app

To create a link to Anvaska is simple. I only have to add the link in the manifest file:

Anvaska
http://anvaska.herokuapp.com/?StandardTokens

convert-app-003

But when I click on the app, there is a problem:

convert-app-004

A “POST” to this site? Why? The reason is the application page called appredirect which makes a “POST” call:

  • /_layouts/15/appredirect.aspx?instance_id={F9049494-42D0-4077-9F34-88A35B7271B9}

In the hive you can see why. The redirect page uses a form and POST method:

convert-app-005

<form id="frmRedirect" action="<%= SPHttpUtility.HtmlEncode(SPHttpUtility.UrlPathEncode(RedirectLaunchUrl, false)) %>" method="post">
 <input type="hidden" name="SPAppToken" value="<%= SPHttpUtility.HtmlEncode(AppToken) %>" />
 <input type="hidden" name="SPSiteUrl" value="<%= SPHttpUtility.HtmlEncode(Web.Url) %>" />
 <input type="hidden" name="SPSiteTitle" value="<%= SPHttpUtility.HtmlEncode(Web.Title) %>" />
 <input type="hidden" name="SPSiteLogoUrl" value="<%= SPHttpUtility.HtmlEncode(Web.SiteLogoUrl) %>" />
 <input type="hidden" name="SPSiteLanguage" value="<%= SPHttpUtility.HtmlEncode(Web.UICulture.Name) %>" />
 <input type="hidden" name="SPSiteCulture" value="<%= SPHttpUtility.HtmlEncode(System.Threading.Thread.CurrentThread.CurrentUICulture.Name) %>" />
 <input type="hidden" name="SPRedirectMessage" value="<%= SPHttpUtility.HtmlEncode(RedirectMessage) %>" />
 <input type="hidden" name="SPErrorCorrelationId" value="<%= SPHttpUtility.HtmlEncode(ErrorCorrelationId) %>" />
 <input type="hidden" name="SPErrorInfo" value="<%= SPHttpUtility.HtmlEncode(ErrorInfo) %>" />
</form>

Just of curiosity, I tried to change method=”post” to method=”get” and the app worked. Of course you never should change any SharePoint built-in controls or pages. So there must be another solution. The asp.net web sites are okay with the external POST calls, but pages like wordpress, *.herokuapp.com don’t allow external POST redirects.

The anvaska application uses nodejs, expressjs. To solve this issue in this particular application I can do a redirect in express js:

var express = require("express");
var app = express();
app.post('/', function(req, res){
    res.redirect(req.url);
});
app.listen(process.env.PORT || "8080");

SharePoint Matryoshka

Russian Nested Doll: Matryoshka

To overcome this issue with the POST verb, a nested iframe can be used. Recently, I wrote a post about “Provider Hosted First Approach” where I presented the original idea of Thomas Deutsch. The implementation is an SPAppIframe which covers the whole html body. In an app part it would cause a nested iframe. But it would work.

<WebPartPages:AllowFraming ID="AllowFraming" runat="server" />

<html>
    <head>
        <title>JSDEV - App Part</title>
        <style type="text/css">
            html, body {
                overflow:hidden;
            }
        
            body {
                margin:0px;
                padding:0px;
            }
         
            iframe {
                border:0px;
                height:100%;
                width:100%;
            }
        </style>
    </head>

    <body>
        <SharePoint:SPAppIFrame ID="SPAppIFrame1" 
            runat="server" 
            src="http://localhost:9000/#?SPHostUrl={HostUrl}&amp;SPAppWebUrl={AppWebUrl}&amp;SPLanguage={Language}&amp;SPClientTag={ClientTag}&amp;SPProductNumber={ProductNumber}" 
            frameborder="0">
        </SharePoint:SPAppIFrame>
    </body>
</html>
2. SharePoint Chrome Control (suiteBar)

To add a SharePoint Chrome Control is easy. Just follow this MSDN article:

Add a javascript file to your project: spapp-chrome.js and refer to it from your website page. That’s it. Here is the screenshot:

convert-app-006

Summary

With a little effort, any legacy web application can be converted into a SharePoint app and be part of a bigger intranet, can be added by users in the sites where it is meaningful rather than just adding links to all existing external applications. These SharePoint apps could even interact with SharePoint and even have appwebs if it makes sense. What do you think? Let me know.

Make javascript code work with Minimal Download Strategy Part 2

I have a newer blog post about MDS, that provides a much simpler solution. Please check it before reading further.


 

mds_007
Minimal Download Strategy (MDS) is an important feature in SharePoint 2013. It lets you download only a page delta, only changes. There is still issues with the MDS and custom scripts and almost no documentation on msdn or technet. In this blog post I want to learn more about adjusting custom scripts for MDS. As in my previous post, I want to take a real problem and try to solve it. The goal is to find a solution, not nececerilly the most optimal solution, at least for now.

Global Navigation and MDS

Recently I read in the Waldek Mastykarz’ blog about how to create a Global Navigation using Managed Metadata and knockout.js. These are two posts which cover this solution, where you can learn a lot from:

I wanted to try it out in my environment and test it in an MDS-enabled site. As a first step I configured it in a site without MDS. There were no problems to get it working in a site without MDS, except some changed I had to make (The final code is available on my github repository):

  • sp.js had to be loaded, because it wasn’t loaded. I added: SP.SOD.executeFunc(“sp.js”, “SP.Utilities.Utility”, …)
  • The script didn’t need to load the navigation in modal dialogs and other views with hidden suiteLinksBox

I added this to the Mavention.GlobalNavigation.init to determine if the element with id suiteLinksBox was hidden and if so, just finish the function:

var suiteLinksBox = document.getElementById("suiteLinksBox");
if (suiteLinksBox && suiteLinksBox.offsetHeight === 0
                  && suiteLinksBox.offsetWidth === 0) {
	//suiteLinksBox is hidden, no need to load any navigation items
	return;
}

Enabling Minimal Download Strategy

The Global Navigation works! Now it is time to enable Minimal Download Strategy. My site is a team site, so I can go to “Manage Site Features” and enable it:
mds_008

And… it stopped working, of course. Now the actual work begins. While troubleshooting, I discovered this:

  • MasterUrl and CustomMasterUrl have to be the same Master in order to work, otherwise MDS fails
  • with MDS knockout is not loaded, the Mavention.GlobalNavigation does not run
  • Moving the code outside the SharePoint:AjaxDelta control in the masterpage doesn’t affect anything

As previously, I tried to create a module and register it with RegisterModuleInit with no success. Maybe I’ve overseen something, but RegisterModuleInit doesn’t work in my environment.

Then I tried to wrap the whole Mavention.GlobalNavigation into a function and called it $_global_Mavention_GlobalNavigation, as it is in callout.js:

mds_002

I added $_global_Mavention_GlobalNavigation() in the end of the javascript file and I added this line of code in the master page. Unfortunately, no success. The code still didn’t run with MDS, literally, it didn’t run at all.

Breakthrough

I tried almost everything. The code started to run in MDS after these changes in the masterpage:

  • the plain <script> tags were converted to <SharePoint:ScriptLink> tags
  • In the masterpage I ran the $_global_Mavention_GlobalNavigation() again

Fortunately it started to run the code, but there was another error: ko is not defined.

Allright. I still had knockout in a plain <script> tag. So I converted it to a ScriptLink and I had to download a copy of knockout.js and provision it to the Style Library. That didn’t help much. After that I actually did the same thing with knockout as with the Mavention.GlobalNavigation code, I put everything in a function and called it $_global_knockout and invoked in the end of the script and in the script in the master page.

mds_009

The masterpage contains the following code now:

<!-- START Mavention Global Navigation -->
<div>
<ul class="ms-core-suiteLinkList" data-bind="foreach: globalMenuItems">
			<span>
				<span data-bind="text: title"></span>
				<!-- ko if: location.href.slice(0, url.length) == url || (location.href.slice(0, 8) === 'https://' && location.href.slice(0, url.replace(':443/', '/').length) == url.replace(':443/', '/')) -->
				<span class="ms-suitenav-caratBox" id="Suite_ActiveLinkIndicator_Clip">
					<img class="ms-suitenav-caratIcon" id="Suite_ActiveLinkIndicator" src="/_layouts/15/images/spcommon.png?rev=23" />
				</span>
				<!-- /ko -->
			</span></ul>
</div>
<SharePoint:ScriptLink Language="javascript"
	Name="~site/Style Library/Mavention/knockout.js" runat="server"
	OnDemand="False" LoadAfterUI="True"
	Localizable="false"/>
<SharePoint:ScriptLink Language="javascript"
	Name="~site/Style Library/Mavention/Mavention.GlobalNavigation.js" runat="server"
	OnDemand="False" LoadAfterUI="True"
	Localizable="false"/>
<SharePoint:ScriptBlock runat="server">
	$_global_knockout();
	$_global_Mavention_GlobalNavigation();
	Mavention.GlobalNavigation.init('Managed Metadata Service', 'd604c487-0119-4c9e-8b2a-b7c10cf058d6');
</SharePoint:ScriptBlock>
<!-- END Mavention Global Navigation -->

The final code is available on my github repository.

$_global_

There is a convention in SharePoint for naming these functions. But it is not necessary to use it. We can call anything. The only thing is to think about is to call it in the end of a script and in the page. Why $_global_ convention does work is this part of init.js:

NotifyEventAndExecuteWaitingJobs(eventName);
if (typeof g_MinimalDownload != 'undefined' && Boolean(g_MinimalDownload) && typeof RegisterModuleInit != 'undefined') {
    var lastSlashPos = scriptFileName.lastIndexOf('/');

    if (-1 != lastSlashPos) {
        scriptFileName = scriptFileName.substring(lastSlashPos + 1);
    }
    var lastdotPos = scriptFileName.lastIndexOf('.');
    var funcName = null;

    if (-1 == lastdotPos)
        funcName = scriptFileName;
    else
        funcName = scriptFileName.substring(0, lastdotPos);
    funcName = funcName.replace(/\./g, '_');
    var funcPattern = new RegExp("^[A-Za-z0-9_\\-\$]+$");

    if (Boolean(funcPattern.exec(funcName))) {
        funcName = "$_global_" + funcName;
        var initFuncName = eval("'undefined' != typeof (" + funcName + ") ? " + funcName + " : null");

        if (null != initFuncName) {
            RegisterModuleInit(scriptFileName, initFuncName);
        }
    }
}

Summary

In this post I have had a goal to try out a good javascript solution and try to make it work with MDS. I have used Waldek Mastykarz’ code for rendering a Global Navigation. The navigation is rendered in the masterpage through a custom script that loads data from a term store.

  • To make a custom javascript code, and this Global Navigation example particularly, work with MDS, we need:
  • Create a function (not an anonymous) and call it directly in the end of the script file
  • Use a ScriptLink instead of a plain script tag
  • Rewrite your javascript libraries like your custom javascript files

I want to learn more about the MDS feature and adjusting custom javascript code. So it maybe will be a part three.

Make javascript code work with Minimal Download Strategy Part 1

I have a newer blog post about MDS, that provides a much simpler solution. Please check it before reading further.


mds_001

This is a part 1 of the blog post about Minimal Download Strategy and javascript adjustments for user code. What I initially thought should be enough for one post, is not enough, so I see it as a part 1. I wrote this post after I had read Chris O’Brien’s post about JSLink Here I want investigate how we can get his accordion list view working with MDS.

Minimal Dowload Strategy or MDS is a new feature in SharePoint 2013. By now, if you read this post, you already know about it. The simplest way to see if MDS is enabled on your site, you can recognize it on the “ugly” urls. I don’t think they are so ugly. But it is a matter of taste and habit.

No matter if you like MDS or not, MDS is enabled on many site templates and is a huge step towards a faster, more responsive architecture in SharePoint, I would say, towards the Single Page Application concept in SharePoint (but it is a long way to go).

We have to keep the MDS in mind, when we write our customizations in javascript. SharePoint 2013 loves javascript and the probability is high that you write a lot of javascript. If it doesn’t work with MDS, your code breaks and the user doesn’t see the functionality, or the site owner must disable the Minimal Download Strategy feature. I wouldn’t like to have disabling of an improvement feature as a prerequisite for my code.

In this blog post I want to dig into the techniques for getting the javascript code working with MDS. For a while ago I read a wonderful blog post in Chris O’Brien’s blog:

There he describes how JSLink works and how much you can change a standard XSLTListViewWebPart. Chris creates a jQuery UI Accordion view for his list view. As an issue he mentions the MDS.

Here I want to take Chris’ code and adjust it for MDS. My goal is to change as little as possible to find the most important steps for MDS. So I’ll continue where he has finished.

My colleages who have debugged the MDS a lot, gave me a tip: $_global. The SharePoint 2013 internally uses function inside the files which starts with $_global:

mds_002

Here we have callout.js/callout.debug.js The function is called $_global and _ and the filename callout = $_global_callout. Then the function is invoked directly in the end of the file. It is a different story than the anonymous self executing funcitons we’ve seen before.

When I search the hive folder with grepWin tool, I find 148 files containing “$_global”:

mds_003

I rewrote the the code into one wrapper function and invoked in the end of file:


// function to setup JSLink templates
function $_global_AccordionListView() {
    // function to process an accordion item..
    window.COB = window.COB || {};
    window.COB.accordionItem = {
        customItemHtml: function (ctx) {
            var accordionItemHtml = "</pre>
<h3>" + ctx.CurrentItem.Title + "</h3>
<pre>
";
            accordionItemHtml += "</pre>
<div>" + ctx.CurrentItem.AccordionItemDescription + "</div>
<pre>
";
            return accordionItemHtml;
        }
    };

    var overrideCtx = {};
    overrideCtx.Templates = {};

    overrideCtx.Templates.Header = "</pre>
<div id="\&quot;accordion\&quot;">";
 overrideCtx.Templates.Item = window.COB.accordionItem.customItemHtml;
 overrideCtx.Templates.Footer = "</div>
<pre>
";

    overrideCtx.BaseViewID = 1;
    overrideCtx.ListTemplateType = 11000;

    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideCtx);

    $(document).ready(function() {
	    // It seems SharePoint inserts a script tag in an inconvenient place that breaks jQuery UI's accordion, so let's remove it!
		// (N.B. further testing recommended for production)..
		$("#accordion").find('#scriptBodyWPQ2').remove();

		$("#accordion").width('70%');
		$("#accordion").accordion();
	});
}

$global_AccordionListView();

Unfortunately, it didn’t help. There was still no accordion. But wait, is it just the accordion that isn’t created. Indeed. The JSLink itself works. We can see it in the markup:

mds_004

Strange, maybe there was no need for rewriting the code in this case. I changed back all the javascript code to see the markup. It is the right markup. Then the problem is the accordion initialization, or the $(document).ready. Then I thought about the SharePoint-function for that: _spBodyOnLoadFunctionNames and rewrote the $(document).ready:

function onReady() {
    $("#accordion").find('#scriptBodyWPQ2').remove();
    $("#accordion").width('70%');
    $("#accordion").accordion();
}

_spBodyOnLoadFunctionNames.push("onReady");

When I deployed it, it worked… It doesn’t seem like it is the whole solution. It is too simple. Well, it is the solution for the Accordion List View. By putting the accordion initialization code into _spBodyOnLoadFunctionNames we ensure that SharePoint runs it even on pages with MDS. As the name tells us: OnLoad. This appends the code to the onload function which runs after the $(document).ready. It means the time before the text becomes an accordion is longer.

Other cases

Allright, the actual jslink works pretty fine with MDS, except the accordion. But if we hadn’t the jQuery UI Accordion, there wouldn’t be a need to make change to the javascript code. There must be other cases where we need to adjust our javascript code. In the meanwhile I discovered a couple of files in the hive folder which use RegisterModuleInit function:

mds_005

After a quick search I found this:

Sridhar writes in his post, you have to have your javascript code in a function, then call the function inside a RegisterModuleInit. It is strange. Chris O’Brien’s example makes almost the same thing as Sridhar, it changes the display with JSLink. But there was no need for RegisterModuleInit.

More investigation will be in part 2.

Thanks to my colleagues Christopher, Björn and Martin for giving me tips and discussing it with me.

REST API: Add a plain text file as an attachment to a list item

plain-text-attachment
SharePoint 2013 REST API has been enhanced and extended. The old _vti_bin/listdata.svc is still there, but the new api for working with lists and list items is much more and obviously a part of a bigger api: _api/web/lists

Yesterday I saw an interesting question on SharePoint StackExchange:

The instructions in the MSDN resource are not so detailed, the cannot be. The guy who asked the question did as it stood in the examples. But sometimes solutions for SharePoint need some small adjustments 🙂

Here is the simplest code to create an attachment in plain text for a list item 1 in the list called List1 in the root web. That’s it. But it works:

var content = "Hello, this text is inside the file created with REST API";
var digest = $("#__REQUESTDIGEST").val();
var composedUrl = "/_api/web/lists/GetByTitle('List1')/items(1)/AttachmentFiles/add(FileName='readme.txt')";
$.ajax({
    url: composedUrl,
    type: "POST",
    data: content,
    headers: {        
        "X-RequestDigest": digest
    }
})

This example is of course just for demonstration. It uses only hard-coded values. But it shows how simple it is to create a list item attachment using SharePoint 2013 REST API and “upload” plain text asynchronously to the server.

Not only plain text (update 2013-03-05)

Of course, uploading only a plain text isn’t enough. The same person Fedor Shihantsov came with an additional question and solved it: Upload a non-text file. Somehow jQuery ajax didn’t work. SP.RequestExecutor worked:

$(document).ready( dofunc );

function dofunc() {
    var control = document.getElementById("ufile");
    control.addEventListener("change", fdocattach, false);
}

var file;
var contents;

function fdocattach(event) {
    var i = 0,
    files = event.srcElement.files,
    len = files.length;

    for (; i < len; i++) {
        console.log("Filename: " + files[i].name);
        console.log("Type: " + files[i].type);
        console.log("Size: " + files[i].size + " bytes");
    }
        

    if (files.length > 0) {
        file = files[0];
        fileName = file.name;

        var reader = new window.FileReader();
        reader.onload = fonload;

        reader.onerror = function(event) {
            console.error("File reading error " + event.target.error.code);
        };
        reader.readAsArrayBuffer(file);
    }       
    return false;
}

function _arrayBufferToBase64(buffer) {
    var binary = '';
    var bytes = new window.Uint8Array(buffer);
    var len = bytes.byteLength;
    for (var i = 0; i < len; i++) {
        binary += String.fromCharCode(bytes[i]);
    }
    return binary;
}

function fonload(event) {
    contents = event.target.result;
    $.getScript("/_layouts/15/SP.RequestExecutor.js", fonload2);
}

function fonload2() {
    var contents2 = _arrayBufferToBase64(contents);

    var createitem = new SP.RequestExecutor("/");
    createitem.executeAsync({
        url: "/_api/web/lists/GetByTitle('List1')/items(1)/AttachmentFiles/add(FileName='" + file.name + "')",
        method: "POST",
        binaryStringRequestBody: true,
        body: contents2,
        success: fsucc,
        error: ferr,
        state: "Update"
    });

    function fsucc(data) {
        alert('success');
    }

    function ferr(data) {
        alert('error\n\n' + data.statusText + "\n\n" + data.responseText);
    }
}

I found another example of SP.RequestExecutor: Calling SharePoint search using REST (e.g. from JavaScript or an app)

SharePoint Modal Dialog as AngularJS directive

ng-sp-modal

It has already become a series of posts in my blog about the combination of AngularJS and SharePoint:

And it will be more. Some of them are a pure angular stuff, some of them are really for SharePoint.

In this post post I’ll show how to create a directive for a sharepoint modal dialog (SP.UI.ModalDialog). There is already a modal dialog implementation in the Angular UI project, but it uses the bootstrap modal dialog. But it custom attributes for angular”>is not that hard to create an own directive for showing sharepoint modal dialogs. I’ve called the new directive ng-sp-modal (ngSpModal). Here is this:

var ngSpModal = function() {

	link: function (scope, element, attrs, controller) {
		var dialog,
			defaults = { html: element.get(0), showClose: false },
			getOptions = function () {
				var options = scope.$eval(attrs.ngSpModal);
				return angular.extend(defaults, options);
			};
			fireSpModal: function (value) {
				if (value) {
					var opts = getOptions();
					dialog = SP.UI.ModalDialog.showModalDialog(opts);
				} else {
					dialog &&  dialog.close();
				}
			};
		// Watch for changes to the directives options
		scope.$watch(attrs.ngShow, fireSpModal, true);
	}

	return {
		require: '?ngShow',
		link: link
	};
};

window.myApp = angular.module('myApp', ['ui.directives']);

myApp.directive('ngSpModal', [ngSpModal]);

The new ng-sp-modal directive depends on ng-show. When your expression or value for ng-show for your html area is true, then a sharepoint modal dialog will open. This is achieved through a $watch command. The html area which is used to create will be copied to the modal dialog. I have used it for forms. To use it, just place this directive into your html element that you want be shown in a modal dialog. Here is a simple example to achieve what it is shown in the screenshot pasted above.

<div ng-sp-modal="modalOptions" ng-show="formShown">
    Here is the html markup for your modal dialog
    <input type="button" ng-click="formShown = false" value="Close modal">
</div>
<input type="button" ng-click="formShown= true" value="Open modal">

Here is the very simple angular controller to let it work:

function someCtrl($scope) {
	$scope.modalOptions = {
		height: 100,
		width: 300,
		title: "Yeah"
	};
}

JavaScript Localization in SharePoint

Yesterday Waldek Mastykarz published a cool post: Globalizing JavaScript in SharePoint 2013. This is a very cool technique to localize your client code in javascript and reuse your resx files in Server Side and Client Side. This is actually not new for SharePoint 2013 despite it has become more needed with the huge client focus in the new SharePoint. I have used this in SharePoint 2010 for a long time. In my blog post: ScriptResx.ashx in SharePoint I told about that technique. What I didn’t know that you can define your javascript namespace directly in the resx file. Waldek wrote in his comment that SP.Publishing.Resources.en-US.resx automatically are SP.Publishing.Resources in javascript. That was not the case for my own localization files. A simple look at SP.Publishing.Resources.en-US.resx helped:

scriptresx

  <!-- 
    Whether this .resx could be read by scriptResx.ashx handler. Only a file
    marked with scriptResx:true could be returned to client.
  -->
  <resheader name="scriptResx">
    <value>true</value>
  </resheader>
  <!-- the full name of the JavaScript class.  -->
  <resheader name="classFullName">
    <value>SP.Publishing.Resources</value>
  </resheader>

This results in:

_EnsureJSNamespace('SP.Publishing');

So what we have to do for our custom resx file is to add classFullName resheader:

  <resheader name="classFullName">
    <value>Takana.Res</value>
  </resheader>
Вула Чăвашла

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