Monday, 30 May 2016

AngularJS: How do I create cascading lists?

I was looking for a cascading list solution and I found one here that almost gave me what I needed. I added a few tweaks to get my desired result.

UPDATE: Be very careful with the items that are used in the filter. The filter is a string based comparison, so performing a filter on 'ID=1' will also return items for ID=10,ID=11 etc.

My solution was to create two new properties: filterId and filterParentId. These are set to '<placeholder>ID<placeholder>'. For example, ###1###. You can then filter

So, instead of searching for 1, you could search for ##1##, which would remove the unwanted results.

My code is as follows:

html:
            <select class="form-control"
                    ng-model="selectedParentItem"
                    ng-change="parentchanged()"
                    ng-options="p.displayName for p in parentItems"></select>

            <select class="form-control"
                    ng-model="selectedChildItem"
                    ng-disabled="!selectedParentItem"
                    ng-options="c.displayName for c in childItems | filter:{filterParentId: selectedParentItem.filterId}"></select>

            <select class="form-control"
                    ng-model="selectedGrandChildItem"
                    ng-disabled="!selectedChildItem"
                    ng-options="g.displayName for g in grandChildItems | filter:{filterParentId: selectedChildItem.filterParentId}"></select>

js:
    $scope.parentchanged = function ()
    {
        $scope.selectedGrandChildItem = undefined;
    }

    $scope.parentItems = [
        {
            "id": 0,
            "displayName": "parent 00",
            "filterId": ##0##
        },
        {
            "id": 1,
            "displayName": "parent 01",
            "filterId": ##1##
        },
        {
            "id": 2,
            "displayName": "parent 02"
            "filterId": ##2##
        }
    ];

    $scope.childItems = [
        {
            "id": 0,
            "displayName": "child0 of 00",
            "parentId": 0,
            "filterParentId": ##0##
        },
        {
            "id": 1,
            "displayName": "child1 of 00",
            "parentId": 0,
            "filterParentId": ##0##
        },
        {
            "id": 2,
            "displayName": "child2 of 00",
            "parentId": 0,
            "filterParentId": ##0##
        },
        {
            "id": 3,
            "displayName": "child0 of 01",
            "parentId": 1,
            "filterParentId": ##1##
        },
        {
            "id": 4,
            "displayName": "child1 of 01",
            "parentId": 1,
            "filterParentId": ##1##
        },
        {
            "id": 5,
            "displayName": "child0 of 02",
            "parentId": 2,
            "filterParentId": ##2##
        }
    ];

    $scope.grandChildItems = [
    {
        "id": 0,
        "displayName": "grandChild0 of 00",
        "parentId": 0,
         "filterParentId": ##0##
    },
    {
        "id": 1,
        "displayName": "grandChild1 of 00",
        "parentId": 0,
         "filterParentId": ##0##
    },
    {
        "id": 2,
        "displayName": "grandChild2 of 00",
        "parentId": 0,
         "filterParentId": ##0##
    },
    {
        "id": 3,
        "displayName": "grandChild0 of 01",
        "parentId": 1,
         "filterParentId": ##1##
    },
    {
        "id": 4,
        "displayName": "grandChild1 of 01",
        "parentId": 1,
         "filterParentId": ##1##
    },
    {
        "id": 5,
        "displayName": "grandChild0 of 02",
        "parentId": 2,
         "filterParentId": ##2##
    }
    ];


SharePoint Search: How do I limit a search query to a specific site collection?

The url needs to be as follows (as per this entry):

http://localhost/_api/search/query?querytext='test+path:"http://localhost/subsite/"'

The C# code the create the querystring is as follows:

string searchRestUrl = "/_api/search/query?querytext='" + text + "+%2b+path:\"" + HttpUtility.UrlEncode(SiteUrl) + "\"'&rowlimit=5";

where SiteUrl is the full url for my site.

Wednesday, 25 May 2016

Web API: Why am a getting a '500' error when accessing my REST endpoints for javascript?

For me, the solution was found by doing the following:

1. Add a 'Route' annotation above the endpoints

   [Authorize]
    public class MyAPIController : ApiController
    {
        [HttpPost]
        [Route("api/MyAPI/MyPostEndPoint")]
        public void MyPostEndPoint()
       {
       }

       [HttpGet]
       [Route("api/MyAPI/MyGetEndPoint")]
       public void MyGetEndPoint()
       {
       }
    }

2. When calling the endpoints from the js factory, my POST call we different to my GET command:

function executeGet(url, success, failure) {
            $http.get(url, {
                headers: { 'Authorization': 'Blah' }
            }).then(
            function (data) {
                return success(data);
            }, function (err) {
                return failure(err);
            });
        }

        function executePost(url, success, failure) {
            var req = {
                method: 'POST',
                url: url,
                headers: {
                    'Authorization': 'Blah']
                },
                data: { test: 'test' }
            };

            $http(req).then(function(data){
                return success(data);
                }, 
                function (err) {
                    return failure(err);
                }
            );
        }

Monday, 23 May 2016

SharePoint: How can I improve the search relevance of a list item? (How do I use RecordPageClick?)

In order to improve the relevance of a list item, I learnt about RecordClick.

Thankfully, I found the following code on Technet to resolve the problem. I have duplicated it here.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security;
using System.Threading.Tasks;
using Microsoft.SharePoint.Client;
using Microsoft.SharePoint.Client.Search;
using Microsoft.SharePoint.Client.Search.Query;
using System.IO;

namespace SPOnlinePageCallback2
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ClientContext clientContext = new ClientContext("https://xxxxxxx.sharepoint.com/"))
            {

                SecureString passWord = new SecureString();
                foreach (char c in "XXXXXXXX".ToCharArray()) passWord.AppendChar(c);
                clientContext.Credentials = new SharePointOnlineCredentials("xxxxxx@xxxxxx.onmicrosoft.com", passWord);

               
                StringBuilder sbOut = new StringBuilder();



            string resultTitleToClick = "tt";
            string resultUrlToClick = "https://xxxxxx.sharepoint.com/Lists/clist1/DispForm.aspx?ID=4";

            string immediacyQueryString = "tt";

           
            KeywordQuery keywordQuery = new KeywordQuery(clientContext);
            keywordQuery.RowLimit = 50;
            keywordQuery.QueryText = immediacyQueryString;
            keywordQuery.TrimDuplicates = true;
            keywordQuery.EnableStemming = true;
            keywordQuery.StartRow = 0;

            SearchExecutor searchExecutor = new SearchExecutor(clientContext);
            ClientResult<ResultTableCollection> resultTables = searchExecutor.ExecuteQuery(keywordQuery);

            clientContext.ExecuteQuery();
           
            //pageImpressionId_partitionBucket_LCID (1131_1820_1033)
            //NOTE: pageImpressionId is the search result page Id that it will change every time
            string pageInfo = resultTables.Value.Properties["piPageImpression"].ToString();

            string clickType = "Result";


            string immediacySourceId = resultTables.Value.Properties["SourceId"].ToString();
                //Microsoft.SharePoint.Client.Search.Query.
            ResultTable relevantResults = resultTables.Value.Where(
                result => result.TableType == KnownTableTypes.RelevantResults).FirstOrDefault(result => result.QueryRuleId == "00000000-0000-0000-0000-000000000000");

            List<IDictionary<string, object>> resultRows = (List<IDictionary<string, object>>)relevantResults.ResultRows;


            //int clickResultBlockId = (int)relevantResults.Properties["piPageImpressionBlockType"];
            int blockType = (int)relevantResults.Properties["piPageImpressionBlockType"];

            int subResultIndex = 0;

            //var clickThroughResult = resultRows[5];
            //string clickResultId = (string)relevantResults.Properties["piSearchResultId"];

            Dictionary<string, object> clickThroughResult = null;
            foreach(Dictionary<string, object> resultRow in resultRows)
            {
                string resultTitle = (string)resultRow["Title"];
                string resultUrl = (string)resultRow["Path"];
                if ((resultTitle.ToLower() == resultTitleToClick.ToLower()) && (resultUrl.ToLower() == resultUrlToClick.ToLower()))
                {
                    clickThroughResult = resultRow;
                    break;
                }
            }
            //var clickThroughResult = resultRows[1];
            if (clickThroughResult != null)
            {
                string clickedResultId = (string)clickThroughResult["piSearchResultId"];
                string immediacyTitle = (string)clickThroughResult["Title"];
                string immediacyUrl = (string)clickThroughResult["Path"];

               
                searchExecutor.RecordPageClick(pageInfo, clickType, blockType, clickedResultId, subResultIndex, immediacySourceId, immediacyQueryString, immediacyTitle, immediacyUrl);
                clientContext.ExecuteQuery();
               
                sbOut.Append(clickedResultId + "\r\n");
                sbOut.Append(immediacyTitle + "\r\n");
                sbOut.Append(immediacyUrl + "\r\n");
                sbOut.Append("Clicked");
            }

            int resultCount = resultRows.Count;
            Console.Write(sbOut.ToString());

                Console.ReadLine();
            }
        }
    }
}

Sunday, 8 May 2016

SharePoint 2010: Create a folder in Javascript

Here is some simple Javascript to create a folder and a sub folder.


CreateFolder($scope, // success function(){ CreateFolder2($scope) } );

function CreateFolder($scope, success){
var clientContext;
    var oWebsite;
    var oList;
    var itemCreateInfo;

    clientContext = new SP.ClientContext.get_current();
    oWebsite = clientContext.get_web();
    oList = oWebsite.get_lists().getByTitle("Report");

    itemCreateInfo = new SP.ListItemCreationInformation();
    itemCreateInfo.set_underlyingObjectType(SP.FileSystemObjectType.folder);
    itemCreateInfo.set_leafName("Top Folder");
    this.oListItem = oList.addItem(itemCreateInfo);
    this.oListItem.update();

    clientContext.load(this.oListItem);
    clientContext.executeQueryAsync(
        Function.createDelegate(this, successHandler),
        Function.createDelegate(this, errorHandler)
    );

    function successHandler() {
       //alert('success');
  console.log("success");
  return success();
    }

    function errorHandler() {
      // alert('fail');
 return success();
    }
}

function CreateFolder2($scope){
var clientContext;
    var oWebsite;
    var oList;
    var itemCreateInfo;

    clientContext = new SP.ClientContext.get_current();
    oWebsite = clientContext.get_web();
    oList = oWebsite.get_lists().getByTitle("Report");

    itemCreateInfo = new SP.ListItemCreationInformation();
    itemCreateInfo.set_underlyingObjectType(SP.FileSystemObjectType.folder);
    itemCreateInfo.set_leafName("Top Folder/test2");
    this.oListItem = oList.addItem(itemCreateInfo);
    this.oListItem.update();

    clientContext.load(this.oListItem);
    clientContext.executeQueryAsync(
        Function.createDelegate(this, successHandler),
        Function.createDelegate(this, errorHandler)
    );

    function successHandler() {
       alert('success');
    }

    function errorHandler() {
      alert('fail');
    }
}

Sunday, 1 May 2016

AngularJS: Why is my HTML not rendering correctly?

I was recently faced with the problem that my solution required HTML to be rendered. Not a problem - bind it with ng-bind-html and all is good.

Or is it?

This solution falls on its head when we have embedded 'syle' in the HTML.

<div class="ExternalClass2F49307A49BF4FE3B48CB68350A1A9C6"><span style="color:rgb(0, 128, 0)">--My Sample Text in the RichText  Editor in SP2010--</span><div><br /></div>
<div><br /></div></div>

The solution is to used $sce (Strict Contextual Escaping) to format the output.

Controller:
$scope.trustAsHtml = function(string) {
    return $sce.trustAsHtml(string);
};

DOM/HTML:
<div data-ng-bind-html="trustAsHtml(myHtmlString)"></div>