Geeks With Blogs

News

Hello,
My name is David Jacobus I am a former teacher of Computer Science and Mathematics, who is now working full time as a SharePoint Consultant! I have raced off-road motorcycles for 30+ years! I have fallen in love with Glamis and the Dune experience the last few years! My friends like to say: Jake, with age comes the cage! I suppose that is because at Glamis I use a Razor XP 900! Which has a full roll cage!


David Jacobus SharePoint Consultant

I have been thinking this through since my last post on Knockout.   I have always been in favor of using SharePoint for List CRUD operations instead of building CRUD into custom applications/web parts.   What I mean is that let the user use the built-in New Form and Edit Form to add/edit items instead of building this capability in a custom web part or Single Page Application (SPA).  So if you are not going to add this functionality but you are not on the list view then you need to reference these pages (Newform\EditIform) within your web part and to keep you application from refreshing the page then you need to call these pages with the SharePoint Modal Framework.   This actually complicates MVVM slightly, as add/edit/delete is not accomplished by View Model but a combination of SharePoint and the View Model.  I do not think the solution I am putting forward is viable for large lists as it retrieves all the list items.  I think this can and should be modified for large lists.   I have been building SharePoint web parts for many years and for most this solution will suffice.   What I wanted to do is use JQuery DataTables to show the data and Knockout binding in the HTML.  To do this I had to find an adapter to bind Knockout to DataTables.  I found it on the DataTables site.   referenced CogShift / Knockout.Extensions.  I found it to work very well.   I used a Custom Action to add JQuery and Knockout to the page as general scripts and then referenced my specific scripts on the HTML page itself yellow highlight, co.js, cog.utils.js and knockotDTBindings.js are the knockout DataTable adapters. jquery.dataTables.min.js is JQuery Datatables and finally dataTables.js is my MVVM Javascipt  in MVVM parlance this is the View:

 <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>Announcements</title>
    <link type="text/css" rel="stylesheet" href="../css/ThemeRoller.css" />
    <script type="text/javascript" src="../Scripts/cog.js"></script>
    <script type="text/javascript" src="../Scripts/cog.utils.js"></script>
    <script type="text/javascript" src="../Scripts/knockoutDTBindings.js"></script>
    <script type="text/javascript" src="../Scripts/jquery.dataTables.min.js"></script>
    <script type="text/javascript" src="../Scripts/dataTable.js"></script>
</head>
<body>
    <div class='liveExample'>
              <table id='sample2' data-bind="dataTable: { dataSource : items, rowTemplate: 'sample2RowTemplate',
             options: {
                aoColumns: [
                        { sTitle: 'Id', mDataProp:'ID' },
                        { sTitle: 'Title', mDataProp:'Title' },
                        { sTitle: 'Description', mDataProp:'Body' },
                        { sTitle:'Expires',  mDataProp:'Expires' },
                        { sTitle:'Edit'  ,   mDataProp:'Expires' },
                        { sTitle:'Delete'  ,   mDataProp:'Expires' }
                        ]}}" class="display"  >
                <thead> 
                </thead>
                <tbody></tbody>
            </table>
            <script id="sample2RowTemplate" type="text/html">
                <td> <label data-bind="text: ID" /></td>
                <td> <label data-bind="text: Title" /></td>
                <td> <label data-bind="text: Body" /></td>
                <td> <label data-bind="text: Expires" /></td>
                <td><button data-bind="click: $root.EditAnnouncment" class="but">Edit</button></td>
                <td><button data-bind="click: $root.deleteAnnouncement" class="but">Delete</button></td>
            </script>
        <button data-bind="click: $root.addNew" class="but"> New Announcement </button>
</div>
</body>
</html>

Notice that DataTables have some specific setup and property names.  The Row Template is standard knockout data binding thanks to the knockout adapters!  Below is the  Model and ViewModel for this page dataTable.js:

  ExecuteOrDelayUntilScriptLoaded(retrieveListItems, "sp.js");
var context;
var collListItem;
var web;
var listRootFolder;
var oList;
//retieve the list items from the host web
function retrieveListItems() {
    context = SP.ClientContext.get_current();
    web = context.get_web();
    oList = context.get_web().get_lists().getByTitle('Announcements');
    var camlQuery = new SP.CamlQuery();
    camlQuery.set_viewXml(
        '<View><Query><Where><Geq><FieldRef Name=\'ID\'/>' +
        '<Value Type=\'Number\'>1</Value></Geq></Where></Query>' +
        '<RowLimit>50</RowLimit></View>'
    );
    collListItem = oList.getItems(camlQuery);
    listRootFolder = oList.get_rootFolder();
    context.load(listRootFolder);
    context.load(web);
    context.load(collListItem);
    context.executeQueryAsync(onQuerySucceeded, onQueryFailed);
}
//Model object
var Announcement = function (id, title, body, expires, url) {
    var self = this;
    self.ID = ko.observable(id)
    self.Title = ko.observable(title)
    self.Body = ko.observable(body)
    self.Expires = ko.observable(expires)
    self.Url = url;
}
//View model
var ann = new TableViewModel()
function TableViewModel() {
    var self = this;
    self.items = ko.observableArray([]);
    self.EditAnnouncment = function (data) {
        showModalCallBack(ko.unwrap(data.Title), data.Url);
        //use the call back to update the data
    };
    self.deleteAnnouncement = function (data) {
        context = SP.ClientContext.get_current();
          var oTable = $('#sample2').dataTable();
          var item = context.get_web().get_lists().getByTitle('Announcements').getItemById(data.ID);
         context.load(item);
         //delete the selected item
         item.deleteObject();
         context.executeQueryAsync(function() {
         });
         self.clear();
         self.addAll();
    };
       self.clear = function () {
        self.items.removeAll();
    };
 
    self.addAll = function () {
        var oTable = $('#sample2').dataTable();
        RefreshTable('#sample2', ann);
    };
       self.addNew = function () {
        self.clear();
        var url = web.get_serverRelativeUrl() + "/Lists/Announcements/NewForm.aspx"
        showModalCallBack("New Announcement", url);
        //this will run before the dialog closes so we will need a callback
        //when the modal closes the callback will be executed which refreshes the data from SharePoint
    };
 
}
 
function onQuerySucceeded(sender, args) {
    var listItemEnumerator = collListItem.getEnumerator();
    while (listItemEnumerator.moveNext()) {
        var oListItem = listItemEnumerator.get_current();
        var url = web.get_serverRelativeUrl() + "/Lists/Announcements"
        ann.items.push(new Announcement(oListItem.get_item('ID'), oListItem.get_item('Title'), remove_tags(oListItem.get_item('Body')), oListItem.get_item('Expires'), url + "/EditForm.aspx?ID=" + oListItem.get_item('ID')));
    }
    ko.applyBindings(ann);
}
//announcements have a rich text element (Body)
function remove_tags(html) {
    return jQuery(html).text();
}
function onQueryFailed(sender, args) {
    alert('Request failed. ' + args.get_message() +
        '\n' + args.get_stackTrace());
}
function getQueryStringParameter(paramToRetrieve) {
    var params = document.URL.split("?").length > 1 ?
        document.URL.split("?")[1].split("&") : [];
    var strParams = "";
    for (var i = 0; i < params.length; i = i + 1) {
        var singleParam = params[i].split("=");
        if (singleParam[0] == paramToRetrieve)
            return singleParam[1];
    }
}
function showModal(title, url) {
    var options = SP.UI.$create_DialogOptions();
    options.title = title;
    //options.width = 900;
    //options.height = 600;
    autosize=true;
    options.url = url;
    SP.UI.ModalDialog.showModalDialog(options);
}
 
function showModalCallBack(title, url) {
    var options = SP.UI.$create_DialogOptions();
    options.title = title;
    //options.width = 900;
    //options.height = 600;
    autosize = true;
    options.url = url;
    options.dialogReturnValueCallback = Function.createDelegate(null, CloseCallback);
    SP.UI.ModalDialog.showModalDialog(options);
}
 
// Dialog callback
function CloseCallback(result, target) {
    if (result == SP.UI.DialogResult.OK) {
        // Run OK Code
        RefreshTable('#sample2', ann);
    }
    if (result == SP.UI.DialogResult.cancel) {
        // Run Cancel Code
    }
}
//needed to refresh the datatable after a deletion out of SharePoint normally would be redundant
//with a MVVM approach but we are using sharepoint to delete/edit/add
function RefreshTable(tableId, data) {
    context = SP.ClientContext.get_current();
    web = context.get_web();
    oList = context.get_web().get_lists().getByTitle('Announcements');
    var camlQuery = new SP.CamlQuery();
    camlQuery.set_viewXml(
        '<View><Query><Where><Geq><FieldRef Name=\'ID\'/>' +
        '<Value Type=\'Number\'>1</Value></Geq></Where></Query>' +
        '<RowLimit>50</RowLimit></View>'
    );
    collListItem = oList.getItems(camlQuery);
    listRootFolder = oList.get_rootFolder();
    context.load(listRootFolder);
    context.load(web);
    context.load(collListItem);
    //didn't want to rebind knockout
    context.executeQueryAsync(onQueryRefreshSucceeded, onQueryRefreshFailed);
 
        table = $(tableId).dataTable();
        oSettings = table.fnSettings();
 
        table.fnClearTable(this);
 
        for (var i = 0; i < ann.items.length; i++) {
            table.oApi._fnAddData(oSettings, ann.items[i]);
        }
 
        oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
        table.fnDraw();
    
}
//these two are needed as I don't want to use  ko.applyBindings(ann); more than once.
function onQueryRefreshSucceeded(sender, args) {
    var listItemEnumerator = collListItem.getEnumerator();
    while (listItemEnumerator.moveNext()) {
        var oListItem = listItemEnumerator.get_current();
        var url = web.get_serverRelativeUrl() + "/Lists/Announcements"
        ann.items.push(new Announcement(oListItem.get_item('ID'), oListItem.get_item('Title'), remove_tags(oListItem.get_item('Body')), oListItem.get_item('Expires'), url + "/EditForm.aspx?ID=" + oListItem.get_item('ID')));
    }
}
function onQueryRefreshFailed(sender, args) {
    alert('Request failed. ' + args.get_message() +
        '\n' + args.get_stackTrace());
}

 

The Model and ViewModel are colored above showing how simple MVVM is.  The rest of the script is just getting data out of and into SharePoint.  Because we are using the Modal Dialog Framework to edit and add new items we needed a callback version to run our retrieval of list items when the modal is closed.  thus when the modal closes the callback reloads the table via the RefreshTable method.   That is all there is to it!   So here is a simple Client Object Model to get data into and out of SharePoint.  The whole solution can be modified by changing the Model, say to an event object and the ViewModel to correspond.  In my Last post, I showed how easy it was to retrieve list items.  Now we have the complete CRUD capability.   In my thinking this application can be packaged up in a sandbox solution with an aspx page put into the Site Assets library linking to the html page via a CEWP on the page all  contained the solution.  Just tell the users the URL and we have an SPA up and running.  What is nice about this is we can change the view and not worry about the data say we don’t want DataTables but just a normal table or list, easy in a few minutes.   I didn’t style this with any effort as the purpose of this post was the complete CRUD stack.

Here is a the test page on my development server:

image

 

 

Here is the complete code for this solution:  code

 

I might add this is a great tool for viewing large lists and search the list.  If not needing CRUD but just using the view the search allows a fast search of the list items.

Posted on Saturday, January 11, 2014 11:59 PM SharePoint | Back to top


Comments on this post: SharePoint 2010 Announcements List Knockout MVVM full CRUD

# re: SharePoint 2010 Announcements List Knockout MVVM full CRUD
Requesting Gravatar...
Hello, thank you for sharing great information, can you also share the full code?
Left by ms on Dec 23, 2015 6:26 AM

Your comment:
 (will show your gravatar)


Copyright © David Jacobus | Powered by: GeeksWithBlogs.net