Tuesday, June 17, 2014

Search documents from a Document library by calling List.asmx webservice via JavaScript

Context: The requirement from the customer was to provide a search facility on the welcome page of the site where they can find documents by adding a keyword or text. The customer doesn’t want to use the Search service provided by SharePoint and wants the dedicated component only applied to a single document library where he has thousands of documents stored.

Solution: The solution that I proposed is to create a content editor webpart which would be binded to an html page and javascript files. I used JavaScript to call the list.asmx webservice and the following code that I wrote to accomplish this task.
function SearchDocs() {

    var soapEnv =
            "" +
             "" +
             "" +
             "ListName" +
             "" +
             "" +
             "TRUE" +
             "" +
             "TRUE" +
             " " +
             " " +
             " " +
             " " +
             "" + $('#txtExactPhrase').val() + "" +
             "" + $('#txtExactPhrase').val() + " " +
             " " +
             " " +
             " " +
             " " +
             " " +
             " " +
             " " +
             " " +
             " " +
             "";


    $.ajax({
        url: "http://sharepointsite/_vti_bin/lists.asmx",
        type: "POST",
        dataType: "xml",
        data: soapEnv,
        complete: RenderResults,
        contentType: "text/xml; charset=\"utf-8\""
    });


function RenderResults(xData, status) {
    var hdrTable = "";

    var rowTable = "";
    $(xData.responseXML).find("z\\:row").each(function () {
        var arrPath = $(this).attr("ows_FileRef").split(';#');
        var docname = ExtractValue($(this).attr("ows_FileLeafRef"), 1, ';#');
        var datelastmodified = ExtractValue($(this).attr("ows_Last_x0020_Modified"), 1, ';#');
        datelastmodified = moment(datelastmodified).format('MM/DD/YYYY HH:mm');

        var type = "";
        if ($(this).attr("ows_DocIcon") != null) {
            type = $(this).attr("ows_DocIcon");
        }
        else if ($(this).attr("ows_HTML_x0020_File_x0020_Type") != null) {
            type = "Document set";
        }
        rowTable = rowTable + "";

    });
    var renderTable = hdrTable + rowTable + "
TypeTitleNameLast Modified Date
" + type + "" + $(this).attr("ows_Title") + "" + docname + "" + datelastmodified + "
"; $("#divRenderResults").empty(); $("#divRenderResults").append(renderTable); } }
The following is how the search content editor works when a keyword is searched.


Wednesday, June 4, 2014

SharePoint: Read Word document from document libray, make changes and save as a new Word document in a Document set using C#.Net.

Context: I had to read a word template document consisting of content controls from a document library. The next step, I had to do is to read properties of the document set and place data into the content controls. I then have to create a new document based on the word template and changes and later save into the document.


Solution: I used OpenXml to read the word document from the document libarary. Later I made the changes by replacing the values in the XElement objects. Once I have the changes made in the memory, I created a new word document using Microsoft.SharePoint Api(Server object model) and save it into the respective document set. The following is the code that I wrote to achieve this task.



private void ReadTemplateDoc(SPFile spFile, string folderPath)
        {
            byte[] byteArray = spFile.OpenBinary();
            using (MemoryStream mem = new MemoryStream())
            {
                mem.Write(byteArray, 0, (int)byteArray.Length);

                using (WordprocessingDocument wordDoc =
                    WordprocessingDocument.Open(mem, true))
                {
                    XNamespace w = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
                    XDocument xdoc = wordDoc.MainDocumentPart.Annotation();

                    using (StreamReader sr = new StreamReader(wordDoc.MainDocumentPart.GetStream()))
                    using (XmlReader xr = XmlReader.Create(sr))
                        xdoc = XDocument.Load(xr);
                    wordDoc.MainDocumentPart.AddAnnotation(xdoc);
                    XElement bodyElement = xdoc
                                            .Element(w + "document")
                                            .Element(w + "body");
                    IEnumerable contentControlsTags = bodyElement.Descendants(w + "sdt");

                    foreach (XElement contentControlsTag in contentControlsTags)
                    {
                        string strSpFldName = contentControlsTag.Element(w + "sdtPr").Element(w + "tag").Attribute(w + "val").Value.ToString();
                        string strValue = contentControlsTag.Element(w + "sdtContent").Element(w + "r").Element(w + "t").Value.ToString();
                        //Save the metadata into Templates
                        if (hashtableMetaData[strSpFldName] != null)
                        {
                            string[] separator = { ";#" };
                            string valueMetaData = Utility.GetStringBySplit(hashtableMetaData[strSpFldName].ToString(), 1, separator);
                            contentControlsTag.Element(w + "sdtContent").Element(w + "r").Element(w + "t").SetValue(valueMetaData);
                        }
                    }

                    wordDoc.MainDocumentPart.PutXDocument();

                    string filePath = string.Empty;
                    string fileName = string.Empty;

                    using (SPWeb currentWeb = SPContext.Current.Web)
                    {
                        currentWeb.AllowUnsafeUpdates = true;
                        SPList spList = currentWeb.Lists[listId];
                        string documentSetName = spList.GetItemById(itemId).Name;
                        fileName = documentSetName + " - MetaDataReport.docx";
                        filePath = itemUrl + "/" + fileName;
                        spFile.ParentFolder.Files.Add(filePath, mem, true);
                        currentWeb.AllowUnsafeUpdates = false;
                        //Page.ClientScript.RegisterOnSubmitStatement(typeof(Page), "closePage", "window.onunload = CloseWindow();");
                    }

                    Page.Response.Clear();
                    string htmlMessage = "alert('Metadata report generated named as : " + fileName + "');";
                    this.Page.Response.Write("");
                    Page.Response.End();

                  
                }
            }
        }


public class Utility
    {
        public static string GetStringBySplit(string stringWords, int returnPostion, string[] splitChar)
        {
            string[] split = stringWords.Split(splitChar, StringSplitOptions.RemoveEmptyEntries);
            if (split.Length > 1)
                return split[returnPostion];

            return stringWords;
        }
    }
SharePoint: Read Metadata(properties) values of a document set in a document library using C#.Net.


Solution: I used methods from sever object model(Microsoft.SharePoint Api) to read a particular document set as I had list id and item id(document set id). Once I got the document set object, I used hashtable to contain fields and its values. I used that hashtable for further processing. I hope this would help some one...Cheers…J

Hashtable hashtableMetaData = null;

 private void ReadDocumentSet()
        {
            //Get the document library based on list ID
            using (SPWeb currentWeb = SPContext.Current.Web)
            {
                SPList spList = currentWeb.Lists[listId];
                //Get the document set based on item id
                DocumentSet documentSet = DocumentSet.GetDocumentSet(spList.GetItemById(itemId).Folder);

                SPListItem spListItem = documentSet.Item;

                //Collect MetaData
                hashtableMetaData = new Hashtable();

                string str = string.Empty;
                foreach (SPField field in spListItem.Fields)
                {
                    if (spListItem[field.Title] == null)
                    {
                        hashtableMetaData[field.Title] = null;
                    }
                    else
                    {
                        hashtableMetaData[field.Title] = spListItem[field.Title].ToString();
                    }
                }

            }
        }
SharePoint: Show Application page as ‘popup’ when an item(Custom Action) clicked from Document set’s ECB(Edit control block).

Context: The requirement of the customer was to make an option available in the edit control block of the document set from where a specific word document will be generated.

Solution: To accomplish that I had to write the server side code (C#.Net) using an application page. I had to read a word document using OpenXml sdk and further create a new document in the document set via C#.Net. The solution that I proposed is to add a new custom action in the edit control block of a document set. I passed the important information such as ListId, ItemId in the query string while calling the application page. When the user clicks a particular item in the EDC of a document set, an application page will be called and server side (C#.Net) code written will be executed.


 The following snapshots will further help you understand what I am talking about;

Once the user click the 'Generate Metadata Document', the server side code will be executed and the application page will be shown as popup window.




  
    
  


As I want to make the custom action to be part of a document set edit control block(ECD), so I used registrationtype='contenttype' and registrationid='0x0120D520' which is the base document set content type id. The url consist of the application page along with the query string. The querystring is used on the application page to identify the list and the list item using C#.Net.

Tuesday, June 3, 2014

SharePoint: Call new document content type popup page of a document library on button click.

Context: Customer wants to have a button on the home which will be used to open the new document. If the customer wants to create a new document or a document set, he has to navigate to the document library and later clicking the documents tab and selecting a new Document option. In our scenario, the document library consist of documents sets and a document set consist of a content type.

Solution: The solution is based on the content editor webpart. The webpart refers to the html page from the SiteAssets document library. In order to get the Url of the popup new document page, go to the document library settings -> Advance settings-> Launch forms in Dialog (No). After these settings, click again the new document and you can get the url of the popup page. Use that url in your code for invoking the default popup page. Also do remember to revert the settings to Launch forms in Dialog (Yes), once u get the url.

After getting the url, the following code I wrote in a html file to call new document popup;




 
  
        
        
 
 
 
 

Later I deployed the html file in the Asset library. Once I had the html file, I added a content editor webpart on the Welcome page of the site and referred the html file..Thats it..Happy coding..J

Monday, June 2, 2014

SharePoint 2013/2010: Generate custom graphical workflows from SharePoint list data via JSOM & JSPlumb ApI

Context: Customer wants to visualize his data in a graphical workflow as he is working in quick Edit mode with a SharePoint List.


Solution: I used JSOM and JS Plumb API to accomplish this task. I found JSPlumb (JavaScript graphic API) as an excellent API to accomplish my task. It is very well documented and one can find very nice and interactive examples at their site. So the scenario would be whenever the customer leaves the SharePoint list row, I am refreshing the workflow and the customer immediately sees the changes that he has made in the list. 
I created a page and added the List alongside with the content editor webpart and then referenced the html page. The following is the snapshot how it looks like;



The following is another snapshot of how the SharePoint list is automated to work with the graphical workflow based content editor webpart;

The following code I wrote to achieve this task;
First of all there is a html file which consist of some divs and referencing js + css files from Site Assets;














The Sharepoint.js consist of following line of codes. An important idea here to understand is that workflow is generated everytime from the scratch by calling retreiveListItems function once the user click a row i.e. tr. The other line of codes are related to the JSOM and describing how to retrieve the SharePoint list data using JSOM;

$(window).load(function () {    
    $(document).ready(function () {             
        jsPlumb.ready(function () {
            makeSetup();
            retrieveListItems();

            $('tr').click(function () {
                retrieveListItems();
            });
        });
    });
});


function retrieveListItems() {
    
    
    var clientContext = new SP.ClientContext.get_current();
    var oList = clientContext.get_web().get_lists().getByTitle('Course schedule');

    var camlQuery = new SP.CamlQuery();
    var strCamlQuery = '';

    strCamlQuery += '' +
                    '' +
                    '' +
                    '' +
                    '' +
                    '' +                    
                    '';

    camlQuery.set_viewXml(strCamlQuery);
    this.collListItem = oList.getItems(camlQuery);
    clientContext.load(collListItem);    
    clientContext.executeQueryAsync(Function.createDelegate(this, this.onQuerySucceeded), Function.createDelegate(this, this.onQueryFailed));

}

function onQuerySucceeded(sender, args) {
    
    var listItemInfo = '';

    var listItemEnumerator = collListItem.getEnumerator();
    makeSetup();
    while (listItemEnumerator.moveNext()) {
        var oListItem = listItemEnumerator.get_current();
        //oListItem.get_item('Task_x0020_Type').get_lookupValue()
        
        var arrayListItem = [oListItem.get_id(), oListItem.get_item('Title'), oListItem.get_item('Task_x0020_Type'), oListItem.get_item('Publish_x0020_Date'), oListItem.get_item('Due_x0020_Date')];        
        collectInformation(arrayListItem);

    }

    drawConnectors();

}

function onQueryFailed(sender, args) {
    alert("Failed");
    alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
} + args.get_stackTrace());

The purpose of Dynamic.js is generate the windows(divs) using the JSPlumb library in a sequential workflow. The following are the lines of codes that I wrote to generate sequential workflow;
var propertiesArray = []; //All Divs(Window) Array
var connectorArray = []; //JSPlumb Connector Array
var windowCounter = 0;
var currentWindowType = 'Default';
var top = 10;
var left = 30;


function makeSetup() {
    jsPlumb.deleteEveryEndpoint();  // Delete all Endpoints
    removeAllWindowsOnCanvas(); // Delete all Windows
        
    $("
Start").insertAfter("#assessmentJsPlumb").parent(); propertiesArray = []; propertiesArray.push({ 'windowID': 'windowStart', 'title': 'Start', 'type': 'Default', 'publishDate': "01/01/2013", 'dueDate': "01/02/2013" }); $(document).tooltip(); top = 10; left = 30; } function removeAllWindowsOnCanvas() { for (var i = 0; i < propertiesArray.length; i++) { $("#" + propertiesArray[i]['windowID']).remove(); } } //Collect all the assessment items information into the propertiesArray Array Object. function collectInformation(arrayListItem) { var windowID = "window-" + arrayListItem[0]; //Format Dates arrayListItem[3] = arrayListItem[3].format("mm/dd/yyyy"); //Format Publish Date arrayListItem[4] = arrayListItem[4].format("mm/dd/yyyy"); //Format Due Date propertiesArray.push({ 'windowID': windowID, 'title': arrayListItem[1], 'type': arrayListItem[2], 'publishDate': arrayListItem[3], 'dueDate': arrayListItem[4] }); //, , 'dueDate': $("#txtDueDate").val(), , 'Marks': $("#txtMarks").val() } //Calculate the positions of the window and add it. function addWindowsToCanvas() { for (var i = 1; i < propertiesArray.length; i++) { if (i + 1 >= propertiesArray.length) { left = 30; createWindows(i); top = top + 10; } else { if (propertiesArray[i]['dueDate'] == propertiesArray[i + 1]['dueDate']) { left = left - 10; createWindows(i); for (var j = i + 1 ; j < propertiesArray.length; j++) { if (propertiesArray[i]['dueDate'] == propertiesArray[j]['dueDate']) { left = left + 20; top = top - 5; i = j; createWindows(j); } else { i = j - 1; left = 30; top = top + 10; break; } } } else { createWindows(i); top = top + 10; } } } } //Add the windows on the canvas function createWindows(index) { var tooptip = "Publish date: " + propertiesArray[index]['publishDate'] + " Due date: " + propertiesArray[index]['dueDate']; var window = "
"; var previndex = index - 1; var prvWindowID = propertiesArray[previndex]['windowID']; //Create a new div (window) $(window).insertAfter("#" + prvWindowID); // Make the new div draggable jsPlumb.draggable($(".window")); } /* JsPlumb Methods */ //Draw the JsPlumb connectors between the windows. function drawConnectors() { jsPlumb.ready(function () { //jsPlumb.detachEveryConnection(); propertiesArray.sort(function (a, b) { var a1 = a.dueDate, b1 = b.dueDate; if (a1 == b1) return 0; return a1 > b1 ? 1 : -1; }); addWindowsToCanvas(); //jsPlumb.removeEveryEndpoint(); for (var i = 0; i < propertiesArray.length; i++) { for (var j = i + 1; j < propertiesArray.length; j++) { if (propertiesArray[i]['dueDate'] != propertiesArray[j]['dueDate']) { connectorArray.push( jsPlumb.connect({ source: propertiesArray[i]['windowID'], target: propertiesArray[j]['windowID'], anchor: "Continuous", connector: ["StateMachine", { curviness: 1 }], maxConnections: 1 }) ); for (var k = j + 1; k < propertiesArray.length; k++) { if (propertiesArray[j]['dueDate'] == propertiesArray[k]['dueDate']) { connectorArray.push( jsPlumb.connect({ source: propertiesArray[i]['windowID'], target: propertiesArray[k]['windowID'], anchor: "Continuous", connector: ["StateMachine", { curviness: 1 }], maxConnections: 1 }) ); } else { break; } } break; } } } }); } jsPlumb.ready(function () { // helper method to generate a color from a cycle of colors. var curColourIndex = 1, maxColourIndex = 24, nextColour = function () { var R, G, B; R = parseInt(128 + Math.sin((curColourIndex * 3 + 0) * 1.3) * 128); G = parseInt(128 + Math.sin((curColourIndex * 3 + 1) * 1.3) * 128); B = parseInt(128 + Math.sin((curColourIndex * 3 + 2) * 1.3) * 128); curColourIndex = curColourIndex + 1; if (curColourIndex > maxColourIndex) curColourIndex = 1; return "rgb(" + R + "," + G + "," + B + ")"; }; // setup some defaults for jsPlumb. jsPlumb.importDefaults({ Endpoint: ["Dot", { radius: 2 }], HoverPaintStyle: { strokeStyle: "#42a62c", lineWidth: 2 }, ConnectionOverlays: [ ["Arrow", { location: 1, id: "arrow", length: 14, foldback: 0.8 }] ] }); jsPlumb.bind("connection", function (info) { info.connection.setPaintStyle({ strokeStyle: "blue", lineWidth: 3 }); }); });
Note: It is not the actual solution but the foundation from where I started building the bigger solution. Please do let me know if you have any comments and if you need help... I hope it would help someone. Enjoy & Cheers…J