Created
April 8, 2013 11:43
-
-
Save RainerAtSpirit/5336223 to your computer and use it in GitHub Desktop.
PromiseDemo: Using SPServiceAlpha as data provider for a Durandal app
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<section> | |
<h2 class="page-title" data-bind="text: title"></h2> | |
<div class="row-fluid"> | |
<div class="span6"> | |
<label>Select a list</label> | |
<select data-bind="options: lists, optionsText: 'name', value: activeList"></select> | |
</div> | |
<div class="span6 well"> | |
<h3 data-bind="text: activeList().listName"></h3> | |
<div data-bind="text: activeList().description"></div> | |
</div> | |
</div> | |
<!--ko compose: activeList--><!--/ko--> | |
</section> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*global define, ko, L_Menu_BaseUrl*/ | |
define(['./list', 'durandal/viewModel', 'services/logger', 'services/spdata'], | |
function (List, viewModel, logger, spdata) { | |
var webUrl = ko.observable(); | |
var lists = ko.observableArray([]); | |
return { | |
title: 'Promises demo', | |
webUrl: webUrl, | |
lists: lists, | |
activeList: viewModel.activator().forItems(lists), | |
activate: activate | |
}; | |
//#region Internal Methods | |
function activate() { | |
var self = this; | |
var webUrl = ''; | |
// Checking for edge case when running in top level site collection | |
webUrl = (typeof L_Menu_BaseUrl !== 'undefined' && L_Menu_BaseUrl !== '') ? L_Menu_BaseUrl : '../'; | |
self.webUrl(webUrl); | |
logger.log('Activatíng List View', null, 'list', true); | |
return $.when(spdata.getListCollection({ webUrl: webUrl })).then(function (store) { | |
var listDropDown = []; | |
$.each(store, function (key, obj) { | |
if (!obj.Hidden) { | |
listDropDown.push(new List({ | |
name: obj.Title, | |
webUrl: self.webUrl(), | |
listName: obj.Title, | |
// optional fields for detail information block | |
description: obj.Description | |
}) | |
); | |
} | |
}); | |
// | |
self.lists(listDropDown); | |
}); | |
} | |
//#endregion | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div> | |
<div class="row-fluid"> | |
<div class="span6"> | |
<h4>paging</h4> | |
<div> | |
<select data-bind="value: _pageSize, options: [10, 20, 50]"></select> | |
Page: <span data-bind=" text: page"></span> | |
</div> | |
Page Size: | |
<div class="btn-group"> | |
<button class="btn" data-bind="css: { disabled: !hasPrevious() }, click: previous ">← previous</button> | |
<button class="btn" data-bind="css: { disabled: !hasNext() }, click: next ">next →</button> | |
</div> | |
</div> | |
<div class="span6"> | |
<h4>sorting</h4> | |
<div data-bind="foreach: _sort()"> | |
<!-- Todo: convert to Durandal compose --> | |
<select data-bind="options: $parent.fields, optionsText: 'DisplayName', optionsValue: 'StaticName', value: field"></select> | |
<!--<select data-bind="options: ['ID', 'Title', 'Modified'], value: field"></select> | |
--> | |
<select data-bind="options: ['asc', 'desc'], value: dir"></select> | |
</div> | |
</div> | |
</div> | |
<h4>Result</h4> | |
<table class="table table-striped table-bordered table-hover"> | |
<thead> | |
<tr> | |
<td>ID</td> | |
<td>Title</td> | |
<td>Modified</td> | |
</tr> | |
</thead> | |
<tbody data-bind="foreach: listItems"> | |
<tr> | |
<td data-bind="text: ID"></td> | |
<td data-bind="text: Title"></td> | |
<td data-bind="text: Modified"></td> | |
</tr> | |
</tbody> | |
</table> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*global define, ko */ | |
define(['services/spdata'], | |
function (spdata) { | |
var Sort = function (options) { | |
var self = this; | |
this.field = ko.observable(options.field); | |
this.dir = ko.observable(options.dir); | |
}; | |
var ctor = function (options) { | |
var self = this; | |
// Preserve constructor options | |
self.options = options || {}; | |
this.listInfo = {}; | |
this.pagingHistory = ['']; | |
this.fields = []; | |
this.viewFields = []; | |
// ko observables | |
this.ListItemCollectionPositionNext = ko.observable(''); | |
this.listItems = ko.observableArray([]); | |
this.fetching = ko.observable(true); | |
this.page = ko.observable(1); | |
this.ItemCount = ko.observable(); | |
this._pageSize = ko.observable(); | |
this._sort = ko.observableArray([]); | |
// ko computed | |
this.hasPrevious = ko.computed(function () { | |
return (self.page() > 1); | |
}); | |
this.hasNext = ko.computed(function () { | |
return (self.ListItemCollectionPositionNext() !== ''); | |
}); | |
this.init(options); | |
// refresh and fetch new data on observable change | |
$.each(['_pageSize', '_sort'], function (idx, obj) { | |
self[obj].subscribe(function (val) { | |
self.refresh(); | |
}); | |
}); | |
}; | |
ctor.prototype.init = function (options) { | |
//Todo: Check mandatory parameter | |
this.name = options.name; | |
this.webUrl = options.webUrl; | |
this.listName = options.listName; | |
this.description = options.description || 'Default description'; | |
this._pageSize(options.pageSize || 10); | |
this.viewFields = options.viewFields || ['Title', 'Editor']; | |
// Do NOT call sort(option.sort) here as it would trigger a refresh | |
options.sort = options.sort || { field: 'ID', dir: 'asc' }; | |
}; | |
ctor.prototype.refresh = function () { | |
this.pagingHistory = ['']; | |
this.page(1); | |
this.fetch(); | |
}; | |
ctor.prototype.key = function () { | |
var path = this.webUrl.replace(/^\/+|\/+$/g, ''); | |
return '/' + path.toLowerCase() + '/' + this.listName.toLowerCase(); | |
}; | |
ctor.prototype.fetch = function () { | |
var self = this; | |
spdata.getListItems(spdata.getListItemsOptions(this), this.listInfo.SPXmlToJsonMap).then(function (json) { | |
//ko observables | |
self.listItems(json.data); | |
self.ListItemCollectionPositionNext(json.ListItemCollectionPositionNext); | |
self.ItemCount = (json.ItemCount); | |
self.pagingHistory.push(json.ListItemCollectionPositionNext); | |
}); | |
}; | |
ctor.prototype.next = function () { | |
if (this.ListItemCollectionPositionNext() === '') { | |
return false; | |
} | |
this.page(this.page() + 1); | |
this.fetch(); | |
}; | |
ctor.prototype.previous = function () { | |
var spliceValue = 0; | |
if (this.pagingHistory.length < 3) { | |
return false; | |
} | |
this.page(this.page() - 1); | |
// GetListItems method calculates ListItemPositionNext on each request, so we remove entries from pagingHistory | |
spliceValue = (this.page() - this.pagingHistory.length); | |
this.pagingHistory.splice(spliceValue); | |
this.fetch(); | |
}; | |
ctor.prototype.sort = function (options) { | |
var self = this; | |
var sortArray = []; | |
// ToDo: Check normalisation | |
options = $.isArray(options) ? options : [options]; | |
$.each(options, function (idx, obj) { | |
var sort = new Sort(obj); | |
// Setting up change event | |
$.each(['field', 'dir'], function (idx, obj) { | |
sort[obj].subscribe(function (val) { | |
self.refresh(); | |
}); | |
}); | |
sortArray.push(sort); | |
}); | |
this._sort(sortArray); | |
}; | |
// Durandal viewmodel function | |
ctor.prototype.activate = function () { | |
var self = this; | |
var getModelOptions = { | |
listName: this.listName, | |
webUrl: this.webUrl, | |
key: this.key() | |
}; | |
// Step 1: Create a Json list model based on SPServices 'GetList' method | |
// spdata.getModel() returns either a promise or a cached result -> use $.when() to resolve | |
// see http://lostechies.com/derickbailey/2012/03/27/providing-synchronous-asynchronous-flexibility-with-jquery-when/ | |
$.when(spdata.getModel(getModelOptions)).then(function (data) { | |
// Todo: Move to viemmodel init method and call self.init(data) instead | |
//store produced json | |
self.listInfo = data; | |
$.each(data.fields, function (prop, obj) { | |
if (!obj.Hidden) { | |
if (typeof obj.Sortable === 'undefined') { | |
self.fields.push(obj); | |
return; | |
} | |
if (obj.Sortable) { | |
self.fields.push(obj); | |
return; | |
} | |
} | |
}); | |
// apply inital sort options | |
self.sort(self.options.sort); | |
// Step 2: Fetch the actual data | |
//self.fetch(); | |
}); | |
// Expose the listModel globally | |
sp3.dataSources[this.key()] = this; | |
}; | |
ctor.prototype.deactivate = function () { | |
delete sp3.dataSources[this.key()]; | |
}; | |
return ctor; | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*global define, sp3 */ | |
define(['./logger'], | |
function (logger) { | |
var getListCollection = function (options) { | |
if (sp3.webs[options.webUrl]) { | |
logger.log('cached ListCollection', sp3.webs[options.webUrl], 'spdata.getListCollection()', true); | |
return sp3.webs[options.webUrl]; | |
} | |
var getListCollectionPromise = $().SPServices({ | |
operation: "GetListCollection", | |
webURL: options.webUrl | |
}); | |
return $.when(getListCollectionPromise) | |
.then(function (data) { | |
logger.log('resolve getListCollectionPromise', mapGetListCollection2Json(data), 'spdata.getListCollection', true); | |
// Store for caching purposes | |
sp3.webs[options.webUrl] = mapGetListCollection2Json(data); | |
return sp3.webs[options.webUrl]; | |
}) | |
.fail(function (data) { | |
logger.logError('Error', data); | |
}); | |
}; | |
var getModel = function (options) { | |
// Return cached result if available | |
if (sp3.models[options.key]) { | |
logger.log('cached model', sp3.models[options.key], 'spdata.getModel()', true); | |
return sp3.models[options.key]; | |
} | |
var getListPromise = $().SPServices({ | |
operation: "GetList", | |
listName: options.listName, | |
webURL: options.webUrl | |
}); | |
return $.when(getListPromise) | |
.then(function (data) { | |
logger.log('resolve getListPromise', mapGetListResult2Json(data), 'spdata.getModel()', true); | |
return mapGetListResult2Json(data); | |
}) | |
.fail(function (data) { | |
logger.logError('Error', data); | |
}); | |
}; | |
var getListItems = function (options, SPXmlToJsonMap) { | |
var getListItemsPromise = $().SPServices(options); | |
SPXmlToJsonMap = SPXmlToJsonMap || {}; | |
// Step 2: GetListItems using SPXmlToJsonMap if available | |
return $.when(getListItemsPromise) | |
.then(function (data) { | |
var $data = $(data); | |
var json = { | |
ItemCount: parseInt($data.SPFilterNode("rs:data").attr('ItemCount'), 10) || 0, | |
ListItemCollectionPositionNext: $data.SPFilterNode("rs:data").attr('ListItemCollectionPositionNext') || '', | |
data: $data.SPFilterNode("z:row").SPXmlToJson({ | |
mapping: SPXmlToJsonMap, | |
includeAllAttrs: true, | |
removeOws: false | |
}) | |
}; | |
logger.log('resolve getListItemsPromise', json, 'spdata.getListItems()', true); | |
return json; | |
}) | |
.fail(function (data) { | |
logger.logError('Error', data); | |
}); | |
}; | |
var getListItemsOptions = function (options) { | |
var CAMLQueryOptions = createQueryOptions({ | |
page: options.page(), | |
pagingHistory: options.pagingHistory | |
}); | |
var CAMLQuery = createCAMLQuery({ | |
sortExpression : options._sort() | |
}); | |
var CAMLViewFields = createCAMLViewFields({ | |
viewFields: options.viewFields | |
}); | |
return { | |
operation: "GetListItems", | |
async: false, | |
webURL: options.webUrl, | |
listName: options.listName, | |
CAMLQuery: CAMLQuery, | |
CAMLRowLimit: options._pageSize(), | |
CAMLViewFields: CAMLViewFields, | |
CAMLQueryOptions: CAMLQueryOptions | |
}; | |
}; | |
return { | |
getListCollection: getListCollection, | |
getListItems: getListItems, | |
getListItemsOptions: getListItemsOptions, | |
getModel: getModel | |
}; | |
//#region Internal Methods | |
function createCAMLViewFields(options) { | |
var result = []; | |
var viewFields = options.viewFields || ['Title']; | |
result.push('<ViewFields>'); | |
$.each(viewFields, function (idx, field) { | |
result.push('<FieldRef Name="'); | |
result.push(field); | |
result.push('" />'); | |
}); | |
result.push('</ViewFields>'); | |
return result.join(''); | |
} | |
function createCAMLQuery(options) { | |
var query = []; | |
query.push('<Query>'); | |
// sorting | |
if (options.sortExpression.length > 0) { | |
query.push('<OrderBy>'); | |
// create Caml sort expression | |
$.each(options.sortExpression, function (index, sortObj) { | |
sortDir = (sortObj.dir() === 'asc'); | |
query.push('<FieldRef Name="' + sortObj.field() + '" Ascending="' + sortDir + '"/>'); | |
}); | |
query.push('</OrderBy>'); | |
} | |
// Todo filtering | |
query.push('</Query>'); | |
return query.join(''); | |
} | |
function createQueryOptions(options) { | |
var PID = ''; | |
var queryOptions = []; | |
// Todo: expose other QueryOptions | |
queryOptions.push('<QueryOptions>'); | |
queryOptions.push('<DateInUtc>False</DateInUtc>'); | |
queryOptions.push('<ExpandUserField>True</ExpandUserField>'); | |
if (options.page > 1) { | |
PID = options.pagingHistory[options.page - 1].replace(/&/g, '&'); | |
} | |
queryOptions.push('<Paging ListItemCollectionPositionNext="' + PID + '"/>'); | |
queryOptions.push('</QueryOptions>'); | |
return queryOptions.join(''); | |
} | |
function mapGetListCollection2Json(data) { | |
var store = {}; | |
$(data).SPFilterNode('List').each(function (idx, obj) { | |
var propStore = {}; | |
var storeID; | |
// See http://stackoverflow.com/questions/828311/how-to-iterate-through-all-attributes-in-an-html-element | |
for (var i = 0; i < obj.attributes.length; i++) { | |
var attrib = obj.attributes[i]; | |
if (attrib.specified) { | |
if (attrib.name === 'Title') { | |
storeID = attrib.value.toLowerCase(); | |
} | |
// mapping of text values that are stored as mixed case TRUE false | |
if (attrib.value.toLowerCase() === 'false') { | |
propStore[attrib.name] = false; | |
} | |
else if (attrib.value.toLowerCase() === 'true') { | |
propStore[attrib.name] = true; | |
} | |
else { | |
propStore[attrib.name] = attrib.value; | |
} | |
} | |
} | |
storeID = (propStore.WebFullUrl + '/' + storeID).toLowerCase(); | |
store[storeID] = propStore; | |
}); | |
return store; | |
} | |
function mapGetListResult2Json(data, Xml2JsonMap) { | |
var listInfo = {}; | |
var SPXmlToJsonMap = listInfo.SPXmlToJsonMap = {}; | |
var storeKey; | |
// Xml2JsonMap configuration | |
// Filternode: XML element used as filter | |
// PropertyID: XML attribute used as property. false store properties on root object | |
Xml2JsonMap = Xml2JsonMap || { | |
fields: { FilterNode: 'Field', PropertyID: 'StaticName' }, | |
info: { FilterNode: 'List', PropertyID: false } | |
}; | |
$.each(Xml2JsonMap, function (key, val) { | |
var store = listInfo[key] = {}; | |
$(data).SPFilterNode(val.FilterNode).each(function (idx, obj) { | |
var propStore = {}; | |
var storeID; | |
// See http://stackoverflow.com/questions/828311/how-to-iterate-through-all-attributes-in-an-html-element | |
for (var i = 0; i < obj.attributes.length; i++) { | |
var attrib = obj.attributes[i]; | |
if (attrib.specified) { | |
if (attrib.name === val.PropertyID) { | |
storeID = attrib.value; | |
store[storeID] = {}; | |
} | |
// mapping of text values that are stored as mixed case TRUE false | |
if (attrib.value.toLowerCase() === 'false') { | |
propStore[attrib.name] = false; | |
} | |
else if (attrib.value.toLowerCase() === 'true') { | |
propStore[attrib.name] = true; | |
} | |
else { | |
propStore[attrib.name] = attrib.value; | |
} | |
} | |
} | |
if (val.PropertyID && storeID) { | |
store[storeID] = propStore; | |
} | |
else if (!val.PropertyID) { | |
listInfo[key] = propStore; | |
} | |
}); | |
}); | |
// storeKey match logic of ctor.prototype.key = function () {... | |
storeKey = (listInfo.info.WebFullUrl + '/' + listInfo.info.Title).toLowerCase(); | |
// Store cached result in global sp3.models using storeKey | |
sp3.models[storeKey] = listInfo; | |
// Add SPXmlToJson mapping | |
$.each(listInfo.fields, function (name, properties) { | |
SPXmlToJsonMap['ows_' + name] = { | |
mappedName: name, | |
objectType: properties.Type | |
}; | |
}); | |
return listInfo; | |
} | |
//#endregion | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment