Last active
December 27, 2015 11:59
-
-
Save purtuga/7322504 to your computer and use it in GitHub Desktop.
Proposed fix to Issue #9 of SPWidgets
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
/** | |
* @fileOverview jquery.SPWidgets.js | |
* jQuery plugin offering multiple Sharepoint widgets that can be used | |
* for creating customized User Interfaces (UI). | |
* | |
* @version 20131106052852 | |
* @author Paul Tavares, www.purtuga.com, paultavares.wordpress.com | |
* @see http://purtuga.github.com/SPWidgets/ | |
* | |
* @requires jQuery.js {@link http://jquery.com} | |
* @requires jQuery-ui.js {@link http://jqueryui.com} | |
* @requires jquery.SPServices.js {@link http://spservices.codeplex.com} | |
* | |
* Build Date: ptavares:November 06, 2013 05:28 PM | |
* Version: 20131106052852 | |
* | |
*/ | |
;(function($){ | |
// Local pointer to jQuery given on input | |
var jQuery = $; | |
// Need a shim because we insert styles into head | |
document.head || (document.head = document.getElementsByTagName('head')[0]); | |
(function(){ | |
"use strict"; | |
/*jslint nomen: true, plusplus: true */ | |
/** | |
* Namespace for all properties and methods | |
* @name pt | |
* @namespace pt | |
* @memberOf jQuery | |
*/ | |
try { | |
if (!$.pt) { | |
$.pt = {}; | |
} | |
} catch (e) { | |
$.pt = {}; | |
} | |
if ($.pt._cache === undefined) { | |
/** | |
* Local cache of data that is unlikely to change during | |
* the live of the page. | |
*/ | |
$.pt._cache = {}; | |
} | |
$.SPWidgets = {}; | |
$.SPWidgets.version = "20131106052852"; | |
$.SPWidgets.defaults = {}; | |
/** | |
* Given an XML message as returned by the Sharepoint WebServices API, | |
* this method will check if it contains an error and return a boolean | |
* indicating that. | |
* | |
* @return {Boolean} true|false | |
* | |
*/ | |
$.fn.SPMsgHasError = function() { | |
var spErrCode = $(this).find("ErrorCode"), | |
response = false; | |
if ( !spErrCode.length ) { | |
if ( $(this).find("faultcode").length ) { | |
return true; | |
} else { | |
return false; | |
} | |
} | |
spErrCode.each(function(){ | |
if ( $(this).text() !== "0x00000000" ) { | |
response = true; | |
return false; | |
} | |
}); | |
return response; | |
}; /* $.fn.SPMsgHasError() */ | |
/** | |
* Given a sharepoint webservices response, this method will | |
* look to see if it contains an error and return that error | |
* formated as a string. | |
* | |
* @return {String} errorMessage | |
* | |
*/ | |
$.fn.SPGetMsgError = function(){ | |
var xMsg = $(this), | |
error = "", | |
spErr = xMsg.find("ErrorCode"), | |
count = 0; | |
if (!spErr.length) { | |
spErr = xMsg.find("faultcode"); | |
} | |
if (!spErr.length) { | |
return ""; | |
} | |
// Loop through and get all errors. | |
spErr.each(function(){ | |
var thisErr = $(this); | |
if ( thisErr.text() !== "0x00000000" ) { | |
count += 1; | |
error += "(" + count + ") " + thisErr.text() + ": " + | |
thisErr.parent().children().not(thisErr).text() + "\n"; | |
} | |
}); | |
error = count + " error(s) encountered! \n" + error; | |
return error; | |
}; /* $.fn.SPGetMsgError() */ | |
/** | |
* An extreemly lightweight template engine for replacing | |
* tokens in the form of {{name}} with values from an object | |
* or a list (array) of objects | |
* | |
* @param {Object} tmplt | |
* @param {Object} data | |
* | |
* @return {String} templated filled out | |
* | |
*/ | |
$.SPWidgets.fillTemplate = function(tmplt, data) { | |
var opt = {},i,j,x,y,item, tokenVal; | |
// If user used an object to define input param, then parse that now | |
if (typeof tmplt === "object" && arguments.length === 1) { | |
data = tmplt.data; | |
tmplt = tmplt.tmplt; | |
} | |
opt.response = ""; | |
opt.template = ( typeof tmplt !== "string" | |
? String($("<div/>").append(tmplt).html()) | |
: tmplt | |
); | |
opt.tokens = opt.template.match(/(\{\{.*?\}\})/g); | |
if (!$.isArray(data)) { | |
data = [ data ]; | |
} | |
if (opt.tokens !== null) { | |
for(x=0,y=data.length; x<y; x++){ | |
item = opt.template; | |
for(i=0,j=opt.tokens.length; i<j; i++){ | |
opt.tokens[i] = opt.tokens[i].replace(/[\{\{\}\}]/g, ""); | |
tokenVal = data[x][ opt.tokens[i] ] || ''; | |
if ($.isFunction(tokenVal)) { | |
tokenVal = tokenVal(); | |
} | |
item = item.replace("{{" + opt.tokens[i] + "}}", tokenVal); | |
} | |
opt.response += item; | |
} | |
} | |
return opt.response; | |
}; //end: $.SPWidgets.fillTemplate() | |
/** | |
* Parses a Sharepoint lookup values as returned by webservices | |
* (id;#title;#id;#Title) into an array of objects. | |
* | |
* @param {String} v | |
* Lookup items string as returned by SP webservices. | |
* | |
* @return {Array} | |
* Array of objects. Each object has two keys; title and id | |
*/ | |
$.SPWidgets.parseLookupFieldValue = function(v) { | |
var r = [], | |
a = String(v).split(';#'), | |
total = a.length, | |
i, n, t; | |
if (v === undefined) { | |
return r; | |
} | |
for (i=0; i<total; i++){ | |
n = a[i]; | |
i++; | |
t = a[i]; | |
if (n || t) { | |
r.push({ id: n, title: t }); | |
} | |
} | |
return r; | |
}; //end: $.SPWidgets.parseLookupFieldValue | |
/** | |
* Given an array of CAML matches, this method will wrap them all in a | |
* Logical condition (<And></And> or a <Or></Or>). | |
* | |
* @param {Object} options | |
* Options for the call. See below. | |
* @param {String} options.type | |
* Static String. The type of logical condition that | |
* the 'values' should be wrapped in. Possible values | |
* are 'AND' or 'OR'. Default is 'AND'. | |
* @param {Array options.values | |
* The array of String elements that will be | |
* join into caml Logical condition. | |
* @param {Function} [options.onEachValue=null] | |
* A function to process each items in the 'values' | |
* array. Function must return the value that should | |
* be used instead of the one found in the array. Use | |
* it to define the xml around each value | |
* (ex. <Eq><FieldRef>...</Eq>). | |
* Function is given 1 input param - the item currently | |
* being processed (from the 'values' input param). | |
* | |
* @return {String} logical Query as a single string. | |
* | |
* @example | |
* $.SPWidgets.getCamlLogical({ | |
* type: "or", | |
* values: [ | |
* "<Eq><FieldRef Name='Title' /><Value Type='Text'>Test</Value></Eq>", | |
* "<Eq><FieldRef Name='Title' /><Value Type='Text'>Test1</Value></Eq>", | |
* "<Eq><FieldRef Name='Title' /><Value Type='Text'>Test2</Value></Eq>", | |
* "<Eq><FieldRef Name='Title' /><Value Type='Text'>Test3</Value></Eq>", | |
* "<Eq><FieldRef Name='Title' /><Value Type='Text'>Test4</Value></Eq>" | |
* ] | |
* }) | |
* | |
* | |
* Concatenate multiple calls to getCamlLogical(): | |
* | |
* $.SPWidgets.getCamlLogical({ | |
* type: "or", | |
* values: [ | |
* "<Eq><FieldRef Name='ID' /><Value Type='Text'>10</Value></Eq>", | |
* "<Eq><FieldRef Name='ID' /><Value Type='Text'>15</Value></Eq>", | |
* $.SPWidgets.getCamlLogical({ | |
* type: "and", | |
* values: [ | |
* "west", | |
* "east" | |
* ], | |
* onEachValue: function(loc){ | |
* return "<Neq><FieldRef Name='Region'/><Value Type='Text'>" + | |
* loc + "</Value></Neq>"; | |
* } | |
* }) | |
* ] | |
* }) | |
* | |
*/ | |
$.SPWidgets.getCamlLogical = function(options){ | |
// FIXME: BUG: getCamlLogical() currently alters values array given on input. | |
var o = $.extend( | |
{}, | |
{ type: "AND", | |
values: [], | |
onEachValue: null | |
}, | |
options), | |
tagOpen = "<And>", | |
tagClose = "</And>", | |
logical = "", | |
total = 0, | |
last = 0, | |
haveFn = false, | |
i; | |
o.type = String(o.type).toUpperCase(); | |
if (!$.isArray(o.values)) { | |
o.values = [o.values]; | |
} | |
if (o.type !== "AND") { | |
tagOpen = "<Or>"; | |
tagClose = "</Or>"; | |
} | |
logical = tagOpen; | |
total = o.values.length; | |
last = (total - 1); | |
haveFn = $.isFunction(o.onEachValue); | |
if (total < 2){ | |
logical = ""; | |
} | |
for ( i=0; i<total; i++){ | |
if (haveFn) { | |
logical += String(o.onEachValue(o.values[i])).toString(); | |
} else { | |
logical += String(o.values[i]).toString(); | |
} | |
if ((last - i) > 1){ | |
logical += $.SPWidgets.getCamlLogical( | |
$.extend({}, o, { | |
values: o.values.slice((i + 1), (total - i)) | |
}) | |
); | |
break; | |
} | |
} | |
if (total > 1){ | |
logical += tagClose; | |
} | |
return logical; | |
};// $.SPWidgets.getCamlLogical() | |
/** | |
* Returns a date string in the format expected by Sharepoint | |
* Date/time fields. Usefull in doing filtering queries. | |
* | |
* Credit: Matt (twitter @iOnline247) | |
* {@see http://spservices.codeplex.com/discussions/349356} | |
* | |
* @param {Date} [dateObj=Date()] | |
* @param {String} [formatType='local'] | |
* Possible formats: local, utc | |
* | |
* @return {String} a date string. | |
* | |
*/ | |
$.SPWidgets.SPGetDateString = function( dateObj, formatType ) { | |
formatType = String(formatType || "local").toLowerCase(); | |
dateObj = dateObj || new Date(); | |
function pad( n ) { | |
return n < 10 ? '0' + n : n; | |
} | |
var ret = ''; | |
if (formatType === 'utc') { | |
ret = dateObj.getUTCFullYear() + '-' + | |
pad( dateObj.getUTCMonth() + 1 ) + '-' + | |
pad( dateObj.getUTCDate() ) + 'T' + | |
pad( dateObj.getUTCHours() ) + ':' + | |
pad( dateObj.getUTCMinutes() )+ ':' + | |
pad( dateObj.getUTCSeconds() )+ 'Z'; | |
} else { | |
ret = dateObj.getFullYear() + '-' + | |
pad( dateObj.getMonth() + 1 ) + '-' + | |
pad( dateObj.getDate() ) + 'T' + | |
pad( dateObj.getHours() ) + ':' + | |
pad( dateObj.getMinutes() )+ ':' + | |
pad( dateObj.getSeconds() ); | |
} | |
return ret; | |
}; //end: $.SPWidgets.SPGetDateString() | |
/** | |
* Parses a date string in ISO 8601 format into a Date object. | |
* Date format supported on input: | |
* 2013-09-01T01:00:00 | |
* 2013-09-01T01:00:00Z | |
* 2013-09-01T01:00:00Z+05:00 | |
* | |
* @param {String} dateString | |
* The date string to be parsed. | |
* | |
* @return {Date|Null} | |
* If unable to parse string, a Null value will be returned. | |
* | |
* @see {https://github.com/csnover/js-iso8601} | |
* Method was developed using some of the code from js-iso8601 | |
* project on github by csnover. | |
* | |
*/ | |
$.SPWidgets.parseDateString = function(dateString) { | |
var dtObj = null, | |
re, dtPieces, i, j, numericKeys, minOffset; | |
if (!dateString) { | |
return dtObj; | |
} | |
// let's see if Date.parse() can do it? | |
// We append 'T00:00' to the date string case it is | |
// only in format YYYY-MM-DD | |
dtObj = Date.parse( | |
( dateString.length === 10 | |
? dateString + "T00:00" | |
: dateString | |
) | |
); | |
if (dtObj) { | |
return new Date(dtObj); | |
} | |
// Once we parse the date string, these locations | |
// in the array must be Numbers. | |
numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ]; | |
// Define regEx | |
re = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/; | |
// dtPieces: | |
// [0] | |
// [1] YYYY | |
// [2] MM | |
// [3] DD | |
// [4] HH | |
// [5] mm | |
// [6] ss | |
// [7] msec | |
// [8] Z | |
// [9] +|- | |
// [10] Z HH | |
// [11] Z mm | |
dtPieces = dateString.match(re); | |
if( !dtPieces ){ | |
return dtObj; | |
} | |
for(i=0,j=numericKeys.length; i<j; i++){ | |
dtPieces[numericKeys[i]] = ~~dtPieces[numericKeys[i]]; | |
} | |
// Month is "zero" based | |
--dtPieces[2]; | |
// Date specifed UTC Format? | |
if (dtPieces[8] === 'Z') { | |
// do we need to calculate offset to minutes? | |
if (dtPieces[9] !== undefined) { | |
minOffset = dtPieces[10] * 60 + dtPieces[11]; | |
if (dtPieces[9] === '+') { | |
minOffset = (- minOffset); | |
} | |
dtPieces[5] += minOffset; | |
} | |
dtObj = new Date( | |
Date.UTC( | |
dtPieces[1], | |
dtPieces[2], | |
dtPieces[3], | |
dtPieces[4], | |
dtPieces[5], | |
dtPieces[6], | |
dtPieces[7] | |
) | |
); | |
// Else: Date was did not seem to be UTC. Do local. | |
} else { | |
dtObj = new Date( | |
dtPieces[1], | |
dtPieces[2], | |
dtPieces[3], | |
dtPieces[4], | |
dtPieces[5], | |
dtPieces[6], | |
dtPieces[7] | |
); | |
} | |
return dtObj; | |
}; //end: $.SPWidgets.parseDateString() | |
/** | |
* Make a set of element the same height by taking the height of | |
* the longest element. | |
* | |
* @param {HTMLElement|Selector|jQuery} ele - Set of elements | |
* @param {Interger} [pad=0] - Number of pixels to add on to the height | |
* | |
* @return {Object} ele (input param) is returned | |
* | |
*/ | |
$.SPWidgets.makeSameHeight = function(ele, pad) { | |
var h = 0, | |
e = $(ele); | |
e.each(function(){ | |
var thisEle = $(this).css("height", ""); | |
if (h < thisEle.outerHeight(true)) { | |
h = thisEle.outerHeight(true); | |
} | |
}); | |
if (h > 0) { | |
if (pad) { | |
h += pad; | |
} | |
e.height(h); | |
} | |
return ele; | |
}; // end: $.SPWidgets.MakeSameHeight() | |
/** | |
* Escapes html code. Characters that are escaped include | |
* <, > and &. These are converted to the HTML safe | |
* characters. This method can also be used to escape XML. | |
* | |
* @param {Object} xmlString | |
* The html code to be escaped. | |
* | |
* @return {String} | |
* html escaped | |
* | |
*/ | |
$.SPWidgets.escapeXML = function(xmlString) { | |
if ( typeof xmlString !== "string" ) { | |
return ""; | |
} | |
return xmlString | |
.replace(/&/g,'&') | |
.replace(/</g,'<') | |
.replace(/>/g,'>') | |
.replace(/'/g,"'") | |
.replace(/"/g,"""); | |
}; /* $.SPWidgets.escapeXML() */ | |
/** | |
* Un-escapes html code. Characters that are un-escaped include | |
* "<", ">" "'", """ and "&". These are | |
* converted to <, >, ', " and & | |
* | |
* @param {Object} xmlString | |
* The html code to be un-escaped. | |
* | |
* @return {String} | |
* html string escaped. | |
* | |
*/ | |
$.SPWidgets.unEscapeXML = function(xmlString){ | |
if ( typeof xmlString !== "string" ) { | |
return ""; | |
} | |
return xmlString | |
.replace(/</g,'<') | |
.replace(/>/g,'>') | |
.replace(/&/g,'&') | |
.replace(/'/g,"'") | |
.replace(/"/g,'"'); | |
}; /* $.SPWidgets.unEscapeXML() */ | |
/** | |
* Returns information about the runtime as it applies | |
* to SPWidgets. | |
* | |
* @return {Object} info | |
* | |
*/ | |
$.SPWidgets.getRuntimeInfo = function() { | |
// Class | |
function Info() { | |
this.SPWidgets = $.SPWidgets.version; | |
this.jQuery = ($.fn.jquery || '?'); | |
this.jQueryUI = '?'; | |
this.jQueryUICss = "?"; | |
this.SPServices = "?"; | |
return this | |
} | |
Info.prototype.asString = function() { | |
var me = this, | |
resp = "", | |
prop; | |
for (prop in me) { | |
if (me.hasOwnProperty(prop)) { | |
resp += "[ " + prop + " = " + me[prop] + " ] " | |
} | |
} | |
return resp; | |
}; //end: asString() | |
var info = new Info(), | |
$testObj = $('<div style="position:fixed;width:100px;left:-1000px;"/>') | |
.appendTo("body"), | |
testInfo = ''; | |
try { | |
info.jQueryUI = jQuery.ui.version; | |
} catch(e){} | |
try { | |
info.SPServices = $().SPServices.Version(); | |
} catch(e){ | |
if ($.fn.SPServices) { | |
info.SPServices = "loaded"; | |
} | |
} | |
// Check if jQuery ui css loaded | |
testInfo = $testObj.css("background-image"); | |
$testObj.addClass('ui-widget-header'); | |
if ($testObj.css("background-image") !== testInfo) { | |
info.jQueryUICss = 'loaded'; | |
} | |
$testObj.remove(); | |
return info; | |
}; //end: $.SPWidgets.getRuntimeInfo() | |
/** | |
* Returns the SharePoint version number. This is accomplished by | |
* looking for the SP namespace and if it is define, parsing the | |
* SP.ClientSchemeversions value. | |
* | |
* @param {Boolean} returnExternal | |
* If true, then the external version (ex. 2007, 2010) is | |
* returned. Default is to return the internal version number | |
* (ex. 12, 14) | |
* | |
* @return {String} | |
* | |
*/ | |
$.SPWidgets.getSPVersion = function(returnExternal) { | |
// Some approaches below taken from: | |
// http://sharepoint.stackexchange.com/questions/74978/can-i-tell-what-version-of-sharepoint-is-being-used-from-javascript | |
var versionMap = { | |
12: '2007', | |
14: '2010', | |
15: '2013' | |
}, | |
version = 12; | |
if (typeof SP !== "undefined") { | |
version = 14; | |
if (SP.ClientSchemaVersions) { | |
if (SP.ClientSchemaVersions.currentVersion) { | |
version = parseInt(SP.ClientSchemaVersions.currentVersion); | |
} | |
} else { | |
version = parseInt(_spPageContextInfo.webUIVersion); | |
if (version === 4) { | |
version = 14; | |
} | |
} | |
} | |
if (returnExternal) { | |
version = versionMap[version] || version; | |
} | |
return version | |
}; //end: $.SPWidgets.getSPVersion(); | |
})(jQuery); /** *********** END: $.SPWidgets common */ | |
/** | |
* Displays data from a list in Kan-Ban board using a specific column from | |
* that list. Column (at this point) is assume to be a CHOICE type of field. | |
* | |
* Dependencies: | |
* | |
* - jQuery-UI Draggable | |
* | |
* | |
* BUILD: Paul:November 04, 2013 07:09 PM | |
*/ | |
;(function($){ | |
"use strict"; | |
/*jslint nomen: true, plusplus: true */ | |
/*global SPWidgets */ | |
/** | |
* @class Baard | |
*/ | |
var Board = {}; | |
/** @property {Boolean} */ | |
Board.initDone = false; | |
/** @property {Integer} The max number of columns that can be built (not displayed) */ | |
Board.maxColumns = 20; | |
/** | |
* Board widget default options. | |
*/ | |
$.SPWidgets.defaults.board = { | |
list: '', | |
field: '', | |
CAMLQuery: '<Query></Query>', | |
CAMLViewFields: '', | |
fieldFilter: null, | |
optionalLabel: '(none)', | |
template: null, | |
webURL: $().SPServices.SPGetCurrentSite(), | |
showColPicker: false, | |
colPickerLabel: "Columns", | |
colPickerVisible: [], | |
colPickerCloseLabel: "Close", | |
colPickerApplyLabel: "Apply", | |
colPickerCheckLabel: "Check-Uncheck All", | |
colPickerTotalLabel: "Selected.", | |
colPickerMaxColMsg: "Can not exceed 10 columns!", | |
colPickerMinColMsg: "Mininum of 2 required!", | |
onGetListItems: null, | |
onPreUpdate: null, | |
onBoardCreate: null | |
}; | |
/** | |
* Given a selector, this method will insert a Kan-Ban board inside | |
* of it with data retrieved from a specific list. | |
* This widget will retrieve the List definition upon first call | |
* using SPServices and setting cache = true. In some implementations | |
* it may be desirable to get these defintions ahead of calling this | |
* widget so that a cached version is used. | |
* | |
* @param {Object} options | |
* | |
* @param {String} options.list | |
* The list name or UID. | |
* | |
* @param {String} options.field | |
* The field from the List from where the board should | |
* be built from. This field should be either of type | |
* CHOICE or LOOKUP. | |
* | |
* @param {String|Function} [options.CAMLQuery="<Query></Query>"] | |
* String with CAML query to be used against the list | |
* to filter what is displayed or a function that will | |
* provide the list of items (an array). If defining | |
* a Function, it will be given two input parameter: | |
* 1) a function that must be called and be given the | |
* array of items. | |
* 2) The options defiend on input to this widget. | |
* The user defined function will be given a scope | |
* (this keyword) of the html element it was bound to. | |
* Example: | |
* options.CAMLQuery = '<Query><Where>\ | |
* <FieldRef Name="Project" />\ | |
* <Value Type="Text">Latin America</Value>\ | |
* </Where></Query>'; | |
* --or-- | |
* options.CAMLQuery = function(sendResults) { | |
* //get items from DB | |
* sendResults([...]); | |
* } | |
* | |
* @param {String} [options.CAMLViewFields=""] | |
* String in CAML format with list of fields to be | |
* returned from the list when retrieving the rows | |
* to be displayed on the board. | |
* | |
* @param {String} [options.fieldFilter=""] | |
* A string with either a comma delimetered list of | |
* column values to show if field is of CHOICE type; | |
* or a string with a CAML query to filter field values, | |
* if field is of type Lookup | |
* | |
* @param {String} [options.optionalLabel="(none)"] | |
* The string to be used as the State column header when | |
* field from where Board was built is optional in the | |
* List. | |
* | |
* @param {String|Function} [options.template="<div></div>"] | |
* The HTML template that will be used to for displaying | |
* items on the board. The HTML can be defined with tokens | |
* in the format of {{Column_Internal_Name}}. | |
* When defining a Function, it will be called with | |
* a context of the board Html Element container and be | |
* given two input params: | |
* 1. Item data object | |
* 2. Null || jQuery object | |
* | |
* Example: | |
* | |
* function(listItemObj, $ItemUI){ | |
* // this = jQuery - the container of the board. | |
* } | |
* | |
* @param {String} [options.webURL=$().SPServices.SPGetCurrentSite()] | |
* The WebURL for the list. | |
* | |
* @param {Boolean} [options.showColPicker=false] | |
* If true, the column picker option will be displayed | |
* on the page. Allows user to pick which column are | |
* visible/hidden. | |
* Note: This option is automatically turned to True | |
* if the number of columns available is greater than | |
* 10. | |
* | |
* @param {Array} [options.colPickerVisible=[]] | |
* The list of board columns that should be visible. Used | |
* only when showColPicker is true. | |
* | |
* @param {String} [options.colPickerLabel="Columns"] | |
* The label for the column picker button. | |
* | |
* @param {String} [options.colPickerCloseLabel="Close"] | |
* The label for the column picker pop-up close button | |
* | |
* @param {String} [options.colPickerCheckLabel="Apply"] | |
* Label for the Check all/uncheck all | |
* | |
* @param {String} [options.colPickerApplyLabel="Apply"] | |
* The label for the column picker pop-up apply button | |
* | |
* @param {String} [options.colPickerMaxColMsg="Can not exceed 10 columns!"] | |
* Message to display when more than 10 columns were selected | |
* | |
* @param {String} [options.colPickerMinColMsg="Minimum of 2 required!"] | |
* Message to display when less then 2 columsn were selected | |
* | |
* @param {String} [options.colPickerTotalLabel="Selected."] | |
* The label for the number of column selected text on | |
* the column picker popup. | |
* | |
* @param {Function} [options.onGetListItems=null] | |
* Callback function to be called after data has been | |
* retrieved from the 'list'. Function will be given a | |
* scope (this) of the selection they used on input to | |
* this method and two input parameters: | |
* An Array of Objects with the list of rows returned | |
* from the List, and | |
* A jQuery object representing the entire xml document | |
* response. Example: | |
* | |
* onGetListItems: function(items, xmlResponse){ | |
* //this = jQuery element container selction | |
* } | |
* | |
* @param {Function} [options.onPreUpdate=null] | |
* Callback function to be called just prior to a List Item | |
* update. The callback will have a scope of the item being | |
* updated and be given 2 parameters: | |
* 1) the event object, | |
* 2) the item (DOM element) that triggered the event and | |
* 3) a data object with information/methods for the current | |
* item/widget binding. The object will include two | |
* attributes that will impact the updates: | |
* data.updates - An array of updates that will be made. | |
* The array will have, to start, the update to the | |
* state that was triggered by the move in the board. | |
* Additional updates can be added. | |
* Format will be identical to what SPServices uses: | |
* ["field", "value"]. Example: | |
* data.updates.push(["Title", "New title here"]); | |
* | |
* data.updatePromise - A jQuery.Promise that represents | |
* the update that will be made. This can be used to | |
* bind on additional functionality. The queued functions | |
* will be given the following as input: | |
* | |
* updatePromise.then(function(newItemObj, oldItemObj, xData){ | |
* // this = jQuery of the row container on the board | |
* }) | |
* | |
* - The update item object (as returned by SP) | |
* - current item object (the one used to display the item on the board) | |
* - XML Document of the response from SP (xData) | |
* | |
* The function should return a boolean indicating whether the | |
* update should be canceled. True will cancel the update. | |
* Example: | |
* | |
* onPreUpdate: function(ev, item, data){ | |
* //this = jQuery element container selction | |
* } | |
* | |
* @param {Function} [options.onBoardCreate=null] | |
* Function triggered after board is initially created. | |
* See spwidget:boardcreate even for parameters that | |
* will be given to function. | |
* | |
* | |
* @return {jQuery} this | |
* | |
* | |
* @example | |
* | |
* $("#boardContainer").SPShowBoard({ | |
* list: "Tasks", | |
* field: "Status" | |
* }); | |
* | |
* | |
* EVENTS TRIGGERED BY THIS PLUGIN: | |
* | |
* spwidget:boardchange, | |
* spwidget:boardcreate - Events triggered anytime a change happens | |
* in the board or when the board is first created. | |
* Event is provided 3 parameters | |
* 1) the event object, | |
* 2) the item (DOM element) that triggered | |
* the event and | |
* 3) a data object with information/methods for the current | |
* item/widget binding. The objects's .updates attribute | |
* will contain an array of array's with the updates that | |
* will be made to the item. | |
* The function's 'this' | |
* variable will point to the column element that | |
* received the new item. | |
* | |
* Example: | |
* | |
* ele.on("spwidget:boardchange", function(ev, item, data){ | |
* // this = ele; | |
* }) | |
* | |
* spwidget:boarditemadd - Event triggered when new items are added to the | |
* board (ex. from a refresh). Event will be given | |
* the following input params: | |
* 1) the event object (jquery) | |
* 2) the item (DOM element) that triggered | |
* the event and | |
* 3) a data object with information/methods for the current | |
* item/widget binding. The objects's .itemsModified attribute | |
* will contain an array of Objects that were added. | |
* | |
* spwidget:boarditemremove - Event triggered when items are removed from the | |
* board (ex. from a refresh). Event will be given | |
* the following input params: | |
* 1) the event object (jquery) | |
* 2) the board container (DOM element) | |
* 3) a data object with information/methods for the current | |
* item/widget binding. The objects's .itemsModified attribute | |
* will contain an array of Objects that were removed. | |
* | |
* spwidget:boardColumnChange - Event triggered when columns on the board are changed. | |
* Event will be given the following input params: | |
* 1) jQuery: the event object (jquery) | |
* 2) jQuery: the board container (DOM element) | |
* 3) Object of Column Names currently visible. Key is internal | |
* static name, while value is the external visible name. | |
* For boards created from CHOICE values, key and value is | |
* the same. | |
* | |
* $("#board") | |
* .on( | |
* "spwidget:boardColumnChange", | |
* function($board, columnsObj){ | |
* //this = $board object | |
* }) | |
* | |
* | |
* | |
* | |
* AVAILABLE METHODS: | |
* | |
* refresh - Refreshes the data in the Board by retrieving the data | |
* from the list again. During a refresh, existing board | |
* items (the html element in DOM) is not actually deleted | |
* and recreated if it already exists, but re-used. It is | |
* important to note this specially if a custom template | |
* function was defined as an input param. | |
* | |
* $().SPShowBoard("refresh"); | |
* | |
* redraw - Redraws the board without pulling in data from the list. | |
* Column heights will be normalized and jQuery UI's sortable | |
* widget will be refreshed. | |
* | |
* $().SPShowBoard("redraw"); | |
* | |
* setVisible - Sets which Board columns should be visible. Method takes | |
* as input an array of board column values (the visible name) | |
* | |
* $().SPShowBoard("setVisible", ['Not Started', 'Completed']); | |
* | |
* | |
* // TODO: Destroy method (must remove all event bindings) | |
* // TODO: move method - moves an item on the board (identified by ID) to | |
* a different state | |
* | |
* | |
*/ | |
$.fn.SPShowBoard = function(options){ | |
// TODO: need to determine how to page large datasets. | |
// If initialization was not done yet, then do it now. | |
// if the global styles have not yet been inserted into the page, do it now | |
if (!Board.initDone) { | |
Board.initDone = true; | |
if (Board.styleSheet !== "") { | |
$('<style type="text/css">' + "\n\n" + | |
Board.styleSheet + "\n\n</style>" ) | |
.prependTo("head"); | |
} | |
} | |
// Capture original set of input arguments. | |
var args = arguments; | |
// Attach the board to each element | |
return this.each(function(){ | |
var ele = $(this), | |
isMethod = (typeof options === "string"), | |
hasBoard = ele.hasClass("hasSPShowBoard"), | |
opt = null, | |
method = '', | |
board = null; | |
// if this element alraedy has a board on it, and | |
// options is not a string, then exit. | |
if ( hasBoard && !isMethod ) { | |
return this; | |
// Handle METHODS | |
} else if (isMethod && hasBoard && !ele.hasClass("loadingSPShowBoard")) { | |
method = options.toLowerCase(); | |
board = ele.data("SPShowBoardOptions"); | |
//*** REFRESH ***\\ | |
if (method === "refresh") { | |
board._getListItems().then(function(){ | |
board.showItemsOnBoard({ refresh: true }); | |
}); | |
//*** REDRAW ***\\ | |
} else if (method === "redraw") { | |
board.setBoardColumnHeight(); | |
//*** SETVISIBLE ***\\ | |
} else if (method === "setvisible") { | |
if (board.showColPicker) { | |
board.setUserDefinedVisibleCol( args[1] ); | |
} | |
}//end: if(): methods | |
return this; | |
}//end: if() | |
// If this element is already loading the UI, exit now | |
if (ele.hasClass("loadingSPShowBoard")) { | |
return this; | |
} | |
// Define this Widget instance | |
opt = $.extend({}, | |
$.SPWidgets.defaults.board, | |
options, | |
{ | |
ele: ele, | |
states: [], // List of states | |
statesMap: {}, // Map of State->object in states[] | |
tmpltHeader: '', // Header template | |
tmpltState: '', // State item template | |
statesCntr: '', // DOM element where rows are located | |
headersCntr: '', // DOM element where headers are located | |
listItems: [], // Array with items from the list. | |
initDone: false, | |
formUrls: null, // Object with url's to form. Used by opt.getListFormUrl() | |
isStateRequired: true, | |
maxColumnVisible: 10, | |
showNumberOfColumns: 10, // number of columns shown on the board | |
/** | |
* Populates the opt.stats and opt.statesMap by | |
* pulling info. from the List definition | |
* | |
* @return {jQuery.Promise} | |
* Success, promise get resolved with a scope of 'opt' and | |
* receives the xData and status variables | |
* Failure, promise gets resolved with cope of 'ele' and | |
* received a string with the error, xData and Status. | |
* | |
*/ | |
getBoardStates: function(){ | |
return $.Deferred(function(dfd){ | |
// Get List information (use cached if already done in prior calls) | |
// and get list of States to build | |
$().SPServices({ | |
operation: "GetList", | |
listName: opt.list, | |
cacheXML: true, | |
async: false, | |
webURL: opt.webURL, | |
completefunc : function(xData, status) { | |
// FIXME: need to handle errors | |
// if (resp.hasSPError()) { | |
// spAgile.logMsg({ | |
// type: "error", | |
// msg: resp.getSPError() | |
// }); | |
// return null; | |
// } | |
var resp = $(xData.responseXML), | |
f = resp.find("Fields Field[StaticName='" + opt.field + "']"); | |
// If we did not find the field by internal name, try external. | |
// If we found it by Display name, then lets change the | |
// field value... We need internal name for referencing | |
// item column values. | |
if (!f.length) { | |
f = resp.find("Fields Field[DisplayName='" + opt.field + "']"); | |
if (!f.length) { | |
dfd.rejectWith( | |
ele, | |
[ 'Field (' + opt.field + ') not found in list definition!', | |
xData, status ]); | |
return; | |
} | |
opt._origField = opt.field; | |
opt.field = f.attr("StaticName"); | |
} | |
// store if field is required | |
if ( f.attr("Required") === "FALSE" ) { | |
opt.isStateRequired = false; | |
} | |
switch(f.attr("Type").toLowerCase()) { | |
// CHOICE COLUMNS | |
case "choice": | |
// Should there be a blank column? | |
if ( !opt.isStateRequired ) { | |
opt.states.push({ | |
type: 'choice', | |
title: opt.optionalLabel, | |
name: opt.optionalLabel | |
}); | |
opt.statesMap[""] = opt.states[opt.states.length - 1]; | |
} | |
if (opt.fieldFilter) { | |
opt.fieldFilter = opt.fieldFilter.split(/\,/); | |
} | |
f.find("CHOICES CHOICE").each(function(i,v){ | |
var thisChoice = $(this).text(); | |
// if there i sa filter and this column | |
// is not part of it, exit now | |
if (opt.fieldFilter) { | |
if (!$.grep(opt.fieldFilter, function(e){ return (e === thisChoice); }).length) { | |
return; | |
} | |
} | |
// If we reached a max column number, exit here. | |
if (i >= Board.maxColumns){ | |
try { console.log( | |
"SPWIDGETS:BOARD:Warning: Can only build a max of " + | |
Board.maxColumns + " columns!"); | |
} catch(e){ } | |
return false; | |
} | |
opt.states.push({ | |
type: 'choice', | |
title: thisChoice, // extenal visible | |
name: thisChoice // internal name | |
}); | |
// Store State value in mapper (use internal name) | |
opt.statesMap[thisChoice] = opt.states[opt.states.length - 1]; | |
}); | |
dfd.resolveWith(opt, [xData, status]); | |
break; | |
// LOOKUP COLUMNS | |
case "lookup": | |
if ( !opt.fieldFilter ) { | |
opt.fieldFilter = "<Query></Query>"; | |
} | |
// Query the lookup table and get the rows that | |
// should be used to build the states | |
$().SPServices({ | |
operation: "GetListItems", | |
listName: f.attr("List"), | |
async: true, | |
cacheXML: true, | |
CAMLQuery: opt.fieldFilter, | |
webURL: opt.webURL, | |
CAMLRowLimit: Board.maxColumns, | |
CAMLViewFields: | |
'<ViewFields><FieldRef Name="' + | |
f.attr("ShowField") + | |
'" /></ViewFields>', | |
completefunc: function(xData, status){ | |
// Process Errors | |
if (status === "error") { | |
dfd.rejectWith( | |
ele, | |
[ 'Communications Error!', xData, status ]); | |
return; | |
} | |
var resp = $(xData.responseXML); | |
if ( resp.SPMsgHasError() ) { | |
dfd.rejectWith( | |
ele, | |
[ resp.SPGetMsgError(), xData, status ]); | |
return; | |
} | |
// Should there be a blank column? | |
if ( !opt.isStateRequired ) { | |
opt.states.push({ | |
type: 'lookup', | |
title: opt.optionalLabel, // extenal visible | |
name: "" // internal name | |
}); | |
opt.statesMap[""] = opt.states[opt.states.length - 1]; | |
} | |
// Loop thorugh all rows and build the | |
// array of states. | |
resp.SPFilterNode("z:row").each(function(i,v){ | |
// If we reached a max column number, exit here. | |
if (i >= Board.maxColumns){ | |
try { console.log( | |
"SPWIDGETS:BOARD:Warning: Can only build a max of " + | |
Board.maxColumns + " columns!"); | |
} catch(e){ } | |
return false; | |
} | |
var thisRow = $(this), | |
thisId = thisRow.attr("ows_ID"), | |
thisTitle = thisRow.attr( "ows_" + f.attr("ShowField") ), | |
thisName = thisId + ";#" + thisTitle; | |
opt.states.push({ | |
type: 'lookup', | |
title: thisTitle, // Extenal visible | |
name: thisName // internal name | |
}); | |
// Store State value in mapper (use internal name) | |
opt.statesMap[thisName] = opt.states[opt.states.length - 1]; | |
}); | |
dfd.resolveWith(opt, [xData, status]); | |
return; | |
} //end: completefunc | |
}); | |
break; | |
// DEFAULT: Type on the column is not supported. | |
default: | |
dfd.rejectWith( | |
ele, | |
[ 'Field (' + opt.field + | |
') Type (' + f.attr("Type") + | |
') is not supported!', | |
xData, | |
status ]); | |
break; | |
} | |
return; | |
}//end: completefunc() | |
});//end: spservices | |
}) | |
.promise(); | |
}, //end: getBoardStates() | |
/** | |
* Retrieves the items from the list for display on the board. | |
* Method return a promise whose input param is an array of | |
* object. | |
* | |
* @param {object} options | |
* | |
* @return {jQuery.Promise} jQuery promise | |
* | |
*/ | |
_getListItems: function(){ | |
return $.Deferred(function(dfd){ | |
/** | |
* Resolves the Deferred object. | |
* | |
* @param {jQuery|Function} rawResponse | |
* Raw response from teh call to get data. | |
* is passed along to the user's onGetListItems() | |
* callback. | |
*/ | |
function resolveDeferred(rawResponse) { | |
// If a callback was defined for onGetListItems, | |
// then call it now | |
if ($.isFunction(opt.onGetListItems)) { | |
opt.onGetListItems.call( | |
ele, | |
opt.listItems, | |
rawResponse | |
); | |
} | |
dfd.resolveWith(ele, [opt.listItems]); | |
} //end: resolveDeferred() | |
// If CAMLQuery is a function, then call user' | |
// data retrieval method. | |
if ($.isFunction( opt.CAMLQuery )) { | |
opt.CAMLQuery.call( | |
ele, | |
function(items){ | |
if ($.isArray(items)) { | |
opt.listItems = items; | |
resolveDeferred( opt.CAMLQuery ); | |
} | |
}, | |
options ); | |
// ELSE, opt.CAMLQuery is not a function... | |
// call GetListItems operation. | |
} else { | |
$().SPServices({ | |
operation: "GetListItems", | |
listName: opt.list, | |
async: true, | |
CAMLQuery: opt.CAMLQuery, | |
CAMLRowLimit: 0, // FIXME: SP data should be paged?? | |
CAMLViewFields: opt.CAMLViewFields, | |
webURL: opt.webURL, | |
completefunc: function(xData, status){ | |
// Process Errors | |
if (status === "error") { | |
dfd.rejectWith( | |
ele, | |
[ 'Communications Error!', xData, status ]); | |
return; | |
} | |
var resp = $(xData.responseXML); | |
if ( resp.SPMsgHasError() ) { | |
dfd.rejectWith( | |
ele, | |
[ resp.SPGetMsgError(), xData, status ]); | |
return; | |
} | |
// Store the list of items | |
opt.listItems = resp | |
.SPFilterNode("z:row") | |
.SPXmlToJson({ | |
includeAllAttrs: true | |
}); | |
resolveDeferred( resp ); | |
}//end: completefunc() | |
});//end: SPServices | |
} //end: else: do SPServices call | |
}).promise(); | |
}, //end: _getListItems() | |
/** | |
* Given an ID, this method will return the data object | |
* for that item - the element retrieved during for | |
* display on the board. | |
* | |
* @param {String|Interger} | |
* | |
* @return {Object} Item Object | |
* | |
*/ | |
getBoardItemDataObject: function(itemId){ | |
var itemObject = null, | |
x,y,id; | |
if (itemId) { | |
itemId = String(itemId); | |
for(x=0,y=opt.listItems.length; x<y; x++){ | |
id = opt.listItems[x].ID; | |
if ($.isFunction(id)) { | |
id = opt.listItems[x].ID(); | |
} | |
id = String(id); | |
if (itemId === id) { | |
itemObject = opt.listItems[x]; | |
x = y + y; | |
} | |
} | |
} | |
return itemObject; | |
}, // end: pageSetup.getBoardItemDataObject | |
/** | |
* Shows the List items on the board. | |
* | |
* @param {Object} [options] | |
* | |
* @param {Array} [options.rows=opt.listItems] | |
* The rows to display on tehe board. Default | |
* to list stored in opt.listItems. | |
* | |
* @param {Boolean} [options.refresh=false] | |
* If true, then items currently on the board | |
* will not be erased; only new items will be | |
* added and invalid item will be removed. | |
* | |
* @param {Boolean} [options.doBoardInsert=true] | |
* When true, the items created will be inserted | |
* into the board widget. Set to false if doing it | |
* elsewhere. | |
* | |
* @return {Object} itemsForBoard | |
* An object with state=string of html for | |
* insertion into the Board. | |
* | |
*/ | |
showItemsOnBoard: function(options){ | |
// console.time("Board.ShowItemsOnBoard()"); | |
var thisOpt = $.extend({}, { | |
rows: opt.listItems, | |
refresh: false, | |
doBoardInsert: true | |
}, options), | |
newItems = [], | |
delItems = [], | |
chgItems = [], | |
itemsForBoard = {}, // each state as the key... string of html as value | |
boardItemStr = "", | |
boardItemCntr = null, | |
thisRowState = null, | |
thisRowID = null, | |
evData = null, | |
thisListRow = null, | |
x,y; | |
/** | |
* Creates a new items using the given template | |
* and returns a string of that new items. | |
* | |
* @param {Object} itemDataObj - The item's object. | |
* @param {jQUery} $uiELe - The UI container. | |
* | |
* @return {String} new item html | |
* | |
*/ | |
function createNewItem(itemDataObj, $uiEle) { | |
var newItem = "", | |
itemId = null, | |
css = ""; | |
// Caller defined a function for item template? | |
if ($.isFunction(opt.template)) { | |
newItem = opt.template.call( | |
ele, itemDataObj, $uiEle); | |
if (newItem) { | |
newItem = String(newItem); | |
} | |
// ELSE: Caller did not define function for template | |
} else { | |
newItem = $.SPWidgets.fillTemplate(opt.template, thisListRow ); | |
} | |
// If we have a UI element already and a new item was created | |
// insert it directly into the UI element. | |
if ($uiEle !== undefined && newItem !== "") { | |
$uiEle.html(newItem); | |
// Else, we have no UI Element... If the new Item is not | |
// blank, then create a new item for insertion. | |
} else if (newItem !== "") { | |
// Accomodate possible knockout objects | |
itemId = itemDataObj.ID; | |
if ($.isFunction(itemDataObj.ID)) { | |
itemId = itemDataObj.ID(); | |
} | |
// Store this item to be added to the board in bulk | |
if ( itemsForBoard[thisRowState] === undefined ) { | |
itemsForBoard[thisRowState] = ""; | |
} | |
if (opt.initDone && thisOpt.refresh) { | |
css += " spwidget-temp"; | |
} | |
itemsForBoard[thisRowState] += | |
'<div class="spwidget-board-state-item ui-state-default ui-corner-all' + | |
css + '" data-id="' + itemId + '">' + newItem + '</div>'; | |
} | |
return newItem; | |
} //end: ------> createNewItem() | |
// If refresh is false, then erase all items | |
// currently in the board | |
if (!thisOpt.refresh) { | |
for(x=0,y=opt.states.length; x<y; x++){ | |
opt.states[x].headerTotalEle.html("0"); | |
opt.states[x].dataEle.empty(); | |
} | |
} | |
// console.time("Board.ShowItemsOnBoard().each(rows)"); | |
// Populate each row into its respective column | |
for(x=0,y=thisOpt.rows.length; x<y; x++){ | |
thisListRow = thisOpt.rows[x]; | |
// Get this row's State and ID. | |
// Accomodate possible knockout objects | |
thisRowState = thisListRow[opt.field] || ""; | |
thisRowID = thisListRow.ID; | |
if ($.isFunction(thisRowState)) { | |
thisRowState = thisListRow[opt.field](); | |
} | |
if ($.isFunction(thisRowID)) { | |
thisRowID = thisRowID(); | |
} | |
// If this state value is on the board (as a column), | |
// Then proced to build the item into the board. | |
if (opt.statesMap[thisRowState]) { | |
// If not a refresh, then the entire UI is being | |
// rebuilt. We'll be working with Strings. | |
if (thisOpt.refresh === false) { | |
// if INIT is done (true), then capture this as a new | |
// item on the board (for events) | |
if (opt.initDone) { | |
newItems.push(thisListRow); | |
} | |
createNewItem(thisListRow); | |
// ELSE, must be doing a Refresh and these | |
// items could already exist on the board. | |
} else { | |
// Try to find this row in the board | |
boardItemCntr = opt.statesCntr | |
.find( "div[data-id='" + thisRowID + "']" ); | |
// If item was NOT found on the board, then | |
// we're adding it now. | |
if ( !boardItemCntr.length ) { | |
// if INIT is done (true), then capture this as a new | |
// item on the board (for events) | |
if (opt.initDone) { | |
newItems.push(thisListRow); | |
} | |
createNewItem(thisListRow); | |
// Else, item was found on the Board. | |
} else { | |
// Add a temporary class to the item, so that we | |
// know a little later (below) that this is still | |
// a valid item | |
boardItemCntr.addClass("spwidget-temp"); | |
// Check if it should be moved to a new STate (column) | |
if (boardItemCntr.closest("div.spwidget-board-state") | |
.data("boardstate") !== thisRowState | |
) { | |
boardItemCntr.appendTo(opt.statesMap[thisRowState].dataEle); | |
chgItems.push(thisListRow); | |
} | |
// Refresh the UI for the item with the new data | |
createNewItem(thisListRow, boardItemCntr); | |
} | |
} //end: if(): is it refresh? | |
} //end: if(): Does the state appear on the board? | |
} //end: for() - each thisOpt.rows[] | |
// console.timeEnd("Board.ShowItemsOnBoard().each(rows)"); | |
// should we update the board? | |
if (thisOpt.doBoardInsert) { | |
// console.time("Board.ShowItemsOnBoard().InsertIntoDOM"); | |
for (x in itemsForBoard) { | |
if ( itemsForBoard.hasOwnProperty(x) && itemsForBoard[x] !== "" ) { | |
opt.statesMap[x].dataEle.append( itemsForBoard[x] ); | |
} | |
} | |
// Update the board headers with the totals | |
opt.updBoardHeaders(); | |
// Add the mouseover hover affect. | |
$.pt.addHoverEffect(ele.find(".spwidget-board-state-item")); | |
// console.timeEnd("Board.ShowItemsOnBoard().InsertIntoDOM"); | |
} | |
// If initialization is done and board is being | |
// refreshed, then check to see if any items are | |
// no longer valid | |
if (opt.initDone && thisOpt.refresh) { | |
opt.statesCntr.find("div.spwidget-board-state-item") | |
.not("div.spwidget-temp").each(function(){ | |
delItems.push( | |
opt.getBoardItemDataObject( $(this).data("id") ) | |
); | |
$(this).remove(); | |
}) | |
.end() | |
.removeClass("spwidget-temp"); | |
} | |
// If initialization was done already, then | |
// trigger events and refresh jQuery UI widget | |
if (opt.initDone) { | |
// Refresh sortable widget if init was already done | |
opt.statesCntr.find("div.spwidget-board-state") | |
.sortable("refresh") | |
.end() | |
.disableSelection(); | |
opt.setBoardColumnHeight(); | |
// Get a new event object | |
evData = opt.getEventObject(); | |
// Trigger events if init has already been done | |
if (newItems.length) { | |
evData.itemsModified.length = 0; | |
evData.itemsModified.push(newItems); | |
ele.trigger( | |
"spwidget:boarditemadd", | |
[ ele, $.extend( {}, evData ) ] ); | |
} | |
if (delItems.length) { | |
evData.itemsModified.length = 0; | |
evData.itemsModified.push(delItems); | |
ele.trigger( | |
"spwidget:boarditemremove", | |
[ ele, $.extend( {}, evData ) ] ); | |
} | |
// Push both updates and removals to the eventObject | |
evData.itemsModified.length = 0; | |
evData.itemsModified.push.apply(evData.itemsModified, newItems); | |
evData.itemsModified.push.apply(evData.itemsModified, delItems); | |
evData.itemsModified.push.apply(evData.itemsModified, chgItems); | |
// Trigger event if anything has changed. | |
if (evData.itemsModified.length) { | |
ele.trigger("spwidget:boardchange", [ ele, evData ]); | |
} | |
}//end: if(): initDone == true | |
// console.timeEnd("Board.ShowItemsOnBoard()"); | |
return itemsForBoard; | |
}, //end: opt.showItemsOnBoard() | |
/** | |
* Updates the board headers with the total number of | |
* items under each state column | |
* | |
* @param {options} [options] | |
* @param {String|} [options.state=null] The state to be updated | |
* | |
*/ | |
updBoardHeaders: function(options) { | |
var thisOpt = $.extend({}, { | |
state: null | |
}, options ), | |
x,y; | |
// Specific state | |
if (thisOpt.state) { | |
// FIXME: Need to implement functionality | |
// ALL States | |
} else { | |
for( x=0,y=opt.states.length; x<y; x++ ){ | |
opt.states[x].headerTotalEle | |
.html( | |
opt.states[x].dataEle.children().length | |
); | |
} | |
} | |
}, //end: opt.updBoardHeaders() | |
/** | |
* Returns an object with data about the board that can | |
* be used to pass along to events. | |
* | |
* @param {jQuery|HTMLElement} [uiItemEle] | |
* The board item to initiate the event object from. | |
* | |
* @return {Object} | |
* | |
*/ | |
getEventObject: function(uiItemEle){ | |
if (!uiItemEle) { | |
uiItemEle = opt.statesCntr.find("div.spwidget-board-state-item:first"); | |
} | |
uiItemEle = $(uiItemEle); | |
var evObj = { | |
/** @property {Object} evObj.stateTotal A map of state name to total number of items */ | |
stateTotals: {}, | |
/** @property {Integer} itemTotal The total number of items in the board, across all states. */ | |
itemTotal: 0, | |
/** @property {String} evObj.currentState The state name */ | |
currentState: uiItemEle.closest("div.spwidget-board-state") | |
.data("boardstate"), | |
/** @property {Object} evObj.itemObj The individual board item data */ | |
itemObj: ( opt.getBoardItemDataObject( uiItemEle.data("id") ) || {} ), | |
/** @property {Array} evObj.itemsModified The list of objects representing the modified items */ | |
itemsModified: [] | |
}, | |
x,j; | |
// Build totals | |
for( x=0,j=opt.states.length; x<j; x++ ){ | |
evObj.itemTotal += evObj.stateTotals[opt.states[x].name] = Number( opt.states[x].headerTotalEle.text() ); | |
} | |
return evObj; | |
}, //end: opt.getEventObject() | |
/** | |
* Returns the url (full url) for the requested form | |
* of the List. | |
* | |
* @param {String} type | |
* A static string value indicating the type | |
* of form to return. Valid values include | |
* 'EditForm', 'DisplayForm' and 'NewForm' | |
* | |
* @return {String} | |
* The url to the list form. | |
* | |
*/ | |
getListFormUrl: function(type) { | |
type = String(type).toLowerCase(); | |
function loadFormCollection() { | |
$().SPServices({ | |
operation: "GetFormCollection", | |
listName: opt.list, | |
webURL: opt.webURL, | |
cacheXML: true, | |
async: false, | |
completefunc: function(xData, Status) { | |
// Need to check for errors? | |
$(xData.responseXML) | |
.find("Form") | |
.each(function(){ | |
var $thisForm = $(this); | |
opt.formUrls[ String($thisForm.attr("Type")).toLowerCase() ] = | |
opt.webURL + "/" + $thisForm.attr("Url"); | |
}); | |
} //end: completefunc | |
}); | |
} //end: loadFormCollection() | |
if (opt.formUrls === null) { | |
opt.formUrls = {}; | |
loadFormCollection(); | |
} | |
return ( opt.formUrls[type] || "" ); | |
}, // end: opt.getListFormUrl() | |
/** | |
* Sets the class on the board based on the number | |
* of columns displayed. | |
* | |
* @param {Integer} colCount | |
* Number of columns. If not defined, then this | |
* method will loop through opt.states to determine | |
* what is visible | |
* | |
* @return {Object} opt | |
*/ | |
setBoardColumnClass: function(colCount) { | |
var $colCntr = opt.headersCntr.add(opt.statesCntr); | |
colCount = parseInt( colCount ); | |
if (!colCount || colCount < 2) { | |
colCount = 0; | |
$.each(opt.states, function(i, colDef){ | |
if (colDef.isVisible) { | |
colCount++; | |
} | |
}); | |
} | |
if (opt.showNumberOfColumns === colCount) { | |
return opt; | |
} | |
// Add the new class... | |
$colCntr.addClass("spwidget-states-" + colCount); | |
if (opt.showNumberOfColumns) { | |
$colCntr.removeClass( | |
"spwidget-states-" + opt.showNumberOfColumns); | |
} | |
opt.showNumberOfColumns = colCount; | |
return opt; | |
}, //end: opt.setBoardColumnClass() | |
/** | |
* Sets up the button for the Column picker. | |
*/ | |
setupColumnPicker: function(){ | |
var $colCntr = ele.find(".spwidget-board-column-list-cntr"), | |
$colList = $colCntr.find("div.spwidget-board-column-list"), | |
$colFooter = $colCntr.children("div.ui-state-default:last"), | |
Picker = { | |
$totalCntr: $colCntr.find("span.spwidget-board-column-total") | |
}; | |
/** | |
* SEts the total selected on the picker and returns | |
* that total to the caller. | |
* | |
* @return {Integer} | |
*/ | |
Picker.setTotalSelected = function(){ | |
var total = Picker.getSelected().length; | |
Picker.$totalCntr.html(total); | |
return total; | |
}; //end: Picker.setTotalSelected() | |
/** | |
* Returns a jQuery object with the list of columns | |
* selected by the user (anchors <a>) | |
* | |
* @return {jQuery} | |
*/ | |
Picker.getSelected = function() { | |
return $colList.find("a.ui-state-highlight"); | |
}; //end: Picker.getSelected() | |
/** | |
* Shows a message on the picker | |
* | |
*/ | |
Picker.showMessage = function(msg) { | |
$('<div class="spwidget-board-msg ui-state-error ui-corner-all">' + | |
msg + '</div>') | |
.appendTo($colFooter) | |
.fadeOut(8000, function(){ | |
$(this).remove(); | |
}); | |
}; | |
/** | |
* Sets the currently displayed columns on the picker | |
*/ | |
Picker.setSelected = function() { | |
var $columns = $colList.find("a"); | |
$.each(opt.states, function(i, colDef){ | |
var $thisCol = $columns.filter( | |
"[data-board_col_index='" + i + "']" ); | |
if (colDef.isVisible) { | |
Picker.selectColumn($thisCol, false); | |
} else { | |
Picker.selectColumn($thisCol, true); | |
} | |
}); | |
Picker.setTotalSelected(); | |
}; //end: Picker.setSelected() | |
/** | |
* Sets the columns (an <a> anchor) to either | |
* selected or not selected | |
* | |
* @param {HTMLElement} colEle | |
* Single html element or an array of elements | |
* | |
* @param {Boolean} unSelect | |
* If true, then the column, regardless of its | |
* current display setting, will be un-selected. | |
* | |
* @return {HTMLElement} anchor | |
*/ | |
Picker.selectColumn = function(colEle, unSelect){ | |
$(colEle).each(function(){ | |
var $a = $(this), | |
$icon = $a.find(".ui-icon"); | |
if ($a.hasClass("ui-state-highlight") || unSelect) { | |
if (unSelect !== false) { | |
$icon.removeClass("ui-icon-check"); | |
$a.removeClass("ui-state-highlight"); | |
} | |
} else { | |
$icon.addClass("ui-icon-check"); | |
$a.addClass("ui-state-highlight"); | |
} | |
}); | |
return colEle; | |
}; //end: Picker.selectColumn() | |
/** | |
* CHanges the board columns and makes only those selected | |
* on the COlumn Picker visible. A set of colunsn (the <a> | |
* element on the picker) can also be given on input, which | |
* will be used as the set to make visible, regardless of | |
* their state on the picker. | |
* | |
* @param {jQuery} $selected | |
* | |
* @return {undefined} | |
*/ | |
Picker.setVisibleColumns = function($selected){ | |
if (!$selected) { | |
$selected = Picker.getSelected(); | |
} | |
var colNum = $selected.length; | |
// Apply columns | |
$.each(opt.states, function(i, colDef){ | |
if ($selected.filter( | |
"[data-board_col_index='" + i + "']" | |
).length | |
) { | |
if (colDef.isVisible === false) { | |
colDef.isVisible = true; | |
colDef.dataEle.css("display", ""); | |
colDef.headerEle.css("display", ""); | |
} | |
} else { | |
colDef.isVisible = false; | |
colDef.dataEle.css("display", "none"); | |
colDef.headerEle.css("display", "none"); | |
} | |
}); | |
opt.setBoardColumnClass(colNum); | |
// Adjust the board columns height | |
opt.setBoardColumnHeight(); | |
}; //end: Picker.setVisibleColumns() | |
/** | |
* Given an array of visible column names, this method | |
* will make that set of columns visible. | |
* Columns are first validated to ensure they exist, | |
* and the min/max limits are also honored. | |
* | |
*/ | |
Picker.setUserDefinedVisibleCol = | |
opt.setUserDefinedVisibleCol = function(colList){ | |
var count = 0, | |
selector = ""; | |
if (!$.isArray(colList)) { | |
return; | |
} | |
// Build the jQUery selector for the set of columns | |
// that should be made visible. This selector is used | |
// to get a set of elements (columns) from the Picker | |
// that will then drive which columns are visible. | |
$.each(colList, function(i,columnName){ | |
// loop through the Array of states looking | |
// for this column. Once found, build the | |
// jquery selector for it and exit loop | |
$.each(opt.states, function(i, state){ | |
if (state.title === columnName) { | |
count++; | |
if (count > 1) { | |
selector += ","; | |
} | |
selector += "a[data-board_col_name='" + | |
state.name + | |
"']"; | |
return false; | |
} | |
}); | |
// if we reached the MAX allowed number | |
// of visible columns, then break loop. | |
if (count >= opt.maxColumnVisible) { | |
return false; | |
} | |
}); | |
// if we have at least 2 columns, then make only the | |
// requested set visible | |
if (count >= 2) { | |
Picker.setVisibleColumns($colList.find(selector)); | |
Picker.triggerEvent(); | |
} | |
}; //end: Picker.setUserDefinedVisibleCol() and opt.setUserDefinedVisibleCol() | |
/** | |
* Triggers a spwidget:boardColumnChange event on the board. | |
* This is done only if initiazliation has been done. | |
*/ | |
Picker.triggerEvent = function() { | |
var columns = []; | |
if (opt.initDone) { | |
$.each(opt.statesMap, function(key,defObj){ | |
if (defObj.isVisible){ | |
columns.push(defObj.title); | |
} | |
}); | |
opt.ele.trigger( | |
"spwidget:boardColumnChange", | |
[ opt.ele, columns ] ); | |
} | |
}; //end: PIcker.triggerEvent() | |
// ----------------- [ setup ] ------------------ | |
// Setup Picker apply button | |
$colCntr.find("button[name='apply']") | |
.button({ | |
label: opt.colPickerApplyLabel, | |
icons: { | |
secondary: "ui-icon-circle-check" | |
} | |
}) | |
.on("click", function(ev){ | |
var $selected = Picker.getSelected(), | |
colNum = $selected.length; | |
// validate | |
if (colNum > opt.maxColumnVisible) { | |
Picker.showMessage(opt.colPickerMaxColMsg) | |
return; | |
} else if (colNum < 2) { | |
Picker.showMessage(opt.colPickerMinColMsg) | |
return; | |
} | |
// Hide container | |
$colCntr.hide(); | |
Picker.setVisibleColumns($selected); | |
Picker.triggerEvent(); | |
}); | |
// Setup Picker CHECK button | |
$colCntr.find("button[name='check']") | |
.attr("title", opt.colPickerCheckLabel) | |
.button({ | |
text: false, | |
icons: { | |
primary: "ui-icon-check" | |
} | |
}) | |
.on("click", function(ev){ | |
var $sel = Picker.getSelected(); | |
if ($sel.length) { | |
Picker.selectColumn($sel, true); | |
} else { | |
Picker.selectColumn( $colList.find("a") ); | |
} | |
Picker.setTotalSelected(); | |
}); | |
// Setup Picker Close button | |
$colCntr.find("button[name='close']") | |
.attr("title", opt.colPickerCloseLabel) | |
.button({ | |
text: false, | |
icons: { | |
primary: "ui-icon-circle-close" | |
} | |
}) | |
.on("click", function(ev){ | |
$colCntr.hide(); | |
}); | |
// Setup the columns button | |
ele.find("div.spwidget-board-settings") | |
.css("display", "") | |
.find("div.spwidget-board-settings-columns") | |
.each(function(){ | |
var $btn = $(this); | |
$btn.button({ | |
label: opt.colPickerLabel, | |
icons: { | |
secondary: "ui-icon-triangle-1-s" | |
} | |
}) | |
.on("click.SPWidgets", function(){ | |
if ($colCntr.is(":visible")) { | |
$colCntr.hide(); | |
} else { | |
Picker.setSelected(); | |
$colCntr.show() | |
.position({ | |
my: "left top", | |
at: "left bottom", | |
of: $btn | |
}); | |
} | |
}); | |
return false; | |
}); | |
// Setup the Column choices in the popup | |
$colList.each(function(){ | |
var $cntr = $(this), | |
columns = ""; | |
$.each(opt.states, function(i,colDef){ | |
columns += '<a href="javascript:" data-board_col_index="' + | |
i + '" data-board_col_name="' + colDef.name + | |
'"><span class="ui-icon ui-icon-minus"></span>' + | |
'<span>' + colDef.title + '</span></a>'; | |
}); | |
$cntr.html(columns); | |
return false; | |
}) | |
.on("click", "a", function(){ | |
Picker.selectColumn(this); | |
Picker.setTotalSelected(); | |
}); | |
// Set the label of the Total | |
$colCntr.find("span.spwidget-board-column-total-label") | |
.html( opt.colPickerTotalLabel ); | |
// If user defined colPickerVisible on input, then | |
// make only those items visible | |
if ($.isArray(opt.colPickerVisible) && opt.colPickerVisible.length) { | |
Picker.setUserDefinedVisibleCol(opt.colPickerVisible); | |
} //end: if() Have opt.colPickerVisible? | |
}, //end: opt.setupColumnPicker() | |
/** | |
* Sets the height on the board header and | |
* data columns so that they are all equal. | |
* | |
*/ | |
setBoardColumnHeight: function() { | |
if (opt.statesCntr.is(":visible")) { | |
$.SPWidgets.makeSameHeight( | |
opt.statesCntr.find("div.spwidget-board-state:visible"), | |
20 ); | |
} | |
if (opt.headersCntr.is(":visible")) { | |
$.SPWidgets.makeSameHeight( | |
opt.headersCntr.find("div.spwidget-board-state:visible"), | |
0 ); | |
} | |
} // end: opt.setBoardCOlumnHeight() | |
});//end: $.extend() set opt | |
//---------------------------------------------------------- | |
//----------------[ Initialize this instance ]-------------- | |
//---------------------------------------------------------- | |
// Check for Required params | |
if ( !opt.list || !opt.field ) { | |
ele.html("<div>SPWidgets:Board [ERROR] Missing required input parameters!</div>"); | |
return this; | |
} | |
// Store instance object and mark element "loading" | |
ele.addClass("loadingSPShowBoard").data("SPShowBoardOptions", opt); | |
// get board states from the table definition | |
opt.getBoardStates().then(function(){ | |
// If user did not define CAMLViewFields or the definition | |
// by the user did not include the Board column then either | |
// define it here or add on to the set. | |
if (opt.CAMLViewFields === "") { | |
opt.CAMLViewFields = | |
'<ViewFields>' + | |
'<FieldRef Name="ID" />' + | |
'<FieldRef Name="Title" />' + | |
'<FieldRef Name="' + opt.field + '" />' + | |
'</ViewFields>'; | |
} else if (opt.CAMLViewFields.indexOf(opt.field) < 0){ | |
opt.CAMLViewFields = opt.CAMLViewFields.replace( | |
/\<\/ViewFields\>/i, | |
'<FieldRef Name="' + | |
opt.field + '" /></ViewFields>' | |
); | |
} | |
// Populate the element with the board template | |
ele.html($(Board.htmlTemplate).filter("div.spwidget-board")); | |
// Get a copy of the state column for both headers and values | |
opt.tmpltHeader = $("<div/>") | |
.append( | |
ele.find("div.spwidget-board-headers-cntr div.spwidget-board-state").clone() | |
).html(); | |
opt.tmpltState = $("<div/>") | |
.append( | |
ele.find("div.spwidget-board-states-cntr div.spwidget-board-state") | |
) | |
.html(); | |
// Set the number of columns to display | |
// If less then 11, then set it to that number | |
if (opt.states.length <= opt.maxColumnVisible) { | |
opt.showNumberOfColumns = opt.states.length; | |
// ELSE, must be higher than 10... Force columnsPicker. | |
} else { | |
opt.showColPicker = true; | |
} | |
// Get pointers to the containers in the UI | |
opt.statesCntr = ele | |
.find("div.spwidget-board-states-cntr") | |
.addClass( | |
"spwidget-states-" + | |
opt.showNumberOfColumns | |
) | |
.empty(); | |
opt.headersCntr = ele | |
.find("div.spwidget-board-headers-cntr") | |
.addClass( | |
"spwidget-states-" + | |
opt.showNumberOfColumns | |
) | |
.empty(); | |
// Build the board columns | |
$.each(opt.states, function(i,v){ | |
v.headerEle = $(opt.tmpltHeader).appendTo(opt.headersCntr) | |
.attr("data-boardstate", v.name) | |
.attr("data-boardindex", i) | |
.html(v.title); | |
v.dataEle = $(opt.tmpltState).appendTo(opt.statesCntr) | |
.attr("data-boardindex", i) | |
.attr("data-boardstate", v.name); | |
// Create the header element that holds the total | |
v.headerTotalEle = $('<span> [<span class="spwidget-state-item-total">0</span>]</span>') | |
.appendTo(v.headerEle) | |
.find("span.spwidget-state-item-total"); | |
// Create variable to track if column is visible | |
v.isVisible = true; | |
// If the index is greater than 9 (10 columns) then Column | |
// must be hidden - only support 10 columns. | |
if (i > (opt.maxColumnVisible - 1) ) { | |
v.headerEle.css("display", "none"); | |
v.dataEle.css("display", "none"); | |
v.isVisible = false; | |
} | |
}); //end: .each() | |
// Insert element to clear float elements | |
$(opt.headersCntr,opt.statesCntr) | |
.append('<div style="clear:both;"></div>'); | |
// If showColPicker is true, then show the column selector | |
if (opt.showColPicker === true) { | |
opt.setupColumnPicker(); | |
} | |
// Create listeners on the board. | |
ele | |
// Bind function to sortable events so that headers stay updated | |
.on("sortreceive sortremove", function(ev, ui){ | |
opt.updBoardHeaders(); | |
$(ui.item).removeClass("ui-state-hover"); | |
}) | |
// On Sortreceive: update item | |
.on("sortreceive", function(ev, ui){ | |
var evData = opt.getEventObject(ui.item), | |
dfd = $.Deferred(), | |
itemId = ''; | |
// Handle possibly the itemObject being a knockout object | |
if ($.isFunction(evData.itemObj.ID)) { | |
itemId = evData.itemObj.ID(); | |
} else { | |
itemId = evData.itemObj.ID; | |
} | |
// Make the update to the state in SP | |
evData.updates = []; // Format = SPService UpdateListItems | |
evData.updatePromise = dfd.promise(); | |
evData.updates.push([ opt.field, evData.currentState ]); | |
// TODO: need to normalize evData by adding values to itemsModified | |
// Call any onPreUpdate event. If TRUE (boolean) is returned, | |
// update is canceled. Note that the UI is not updated to | |
// reflect a canceled update (ex. item is not moved back to | |
// original position) | |
if ($.isFunction(opt.onPreUpdate)) { | |
if (opt.onPreUpdate.call(ui.item, ev, ui.item, evData) === true) { | |
return this; | |
} | |
} | |
// If no updates to make, exit here. | |
if (!evData.updates.length) { | |
return this; | |
} | |
// Make update to SP item | |
$().SPServices({ | |
operation: "UpdateListItems", | |
listName: opt.list, | |
async: true, | |
ID: itemId, | |
valuepairs: evData.updates, | |
webURL: opt.webURL, | |
completefunc: function(xData, status){ | |
// Process Errors | |
if (status === "error") { | |
dfd.rejectWith( | |
ele, | |
[ 'Communications Error!', xData, status ]); | |
return; | |
} | |
var resp = $(xData.responseXML), | |
row = null; | |
if ( resp.SPMsgHasError() ) { | |
dfd.rejectWith( | |
ele, | |
[ resp.SPGetMsgError(), xData, status ]); | |
return; | |
} | |
row = $(xData.responseXML).SPFilterNode("z:row") | |
.SPXmlToJson({includeAllAttrs: true}); | |
$(ev.target).trigger( | |
"spwidget:boardchange", [ ui.item, evData ] ); | |
dfd.resolveWith(ev.target, [row[0], evData.itemObj, xData]); | |
}//end: completefunc() | |
}); | |
}) // end: ele.on("sortreceive") | |
// Buind event to catch board actions | |
.on("click", "a.spwidgets-board-action", function(ev){ | |
var $actionEle = $(ev.currentTarget), | |
action = String( | |
$actionEle | |
.data("spwidgets_board_action") | |
) | |
.toLowerCase(), | |
gotoUrl = "", | |
thisPageUrl = $.pt.getEscapedUrl(window.location.href); | |
// TODO: enhance to open item in dialog (SP2010) if that feature is on | |
switch (action) { | |
case "edit-item": | |
gotoUrl = opt.getListFormUrl("EditForm"); | |
break; | |
case "view-item": | |
gotoUrl = opt.getListFormUrl("DisplayForm"); | |
break; | |
} //end: switch() | |
window.location.href = gotoUrl + | |
"?ID=" + $actionEle.data("spwidgets_id") + | |
"&Source=" + thisPageUrl; | |
return this; | |
}); //end: ele.on() | |
// If no template was defined, use default | |
if (opt.template === null) { | |
opt.template = $( Board.htmlTemplate ) | |
.filter("div.spwidget-item-template"); | |
} | |
// Retrieve the items from the List and then | |
// Display items retrieved on the board | |
opt._getListItems() | |
.then(function(){ | |
opt.showItemsOnBoard(); | |
// Make the columns "sortable" | |
opt.statesCntr.find("div.spwidget-board-state").each(function(){ | |
var thisState = $(this); | |
thisState.sortable({ | |
connectWith: thisState.siblings(), | |
containment: ele, | |
cursor: "move", | |
tolerance: "pointer", | |
opacity: ".80", | |
placeholder: "ui-state-highlight spwidget-board-placeholder", | |
forcePlaceholderSize: true, | |
remove: function(ev, ui){ | |
opt.setBoardColumnHeight(); | |
}//end: remove() | |
}); | |
}); | |
// Make text inside the states container un-selectable. | |
opt.statesCntr.disableSelection(); | |
opt.initDone = true; | |
opt.setBoardColumnHeight(); | |
// remove temp class and add the hasSPShowBoard to it. | |
ele.addClass("hasSPShowBoard") | |
.removeClass("loadingSPShowBoard"); | |
// Call any user defined callback and trigger create event | |
if ($.isFunction(opt.onBoardCreate)) { | |
opt.onBoardCreate.call(ele, opt.getEventObject()); | |
} | |
$(ele).trigger( | |
"spwidget:boardcreate", | |
[ ele, opt.getEventObject() ] ); | |
}); | |
}) //end: .then() (get board states) | |
.fail(function(failureMsg, xData, status){ | |
ele.append('<div class="ui-state-error"><p>' + failureMsg + '</p></div>'); | |
}); //end: .fail() (get board states) | |
return this; | |
});//end: return .each() | |
};//end: $.fn.SPShowBoard() | |
/** | |
* @property | |
* Stores the Style sheet that is inserted into the page the first | |
* time SPShowBoard() is called. | |
* Value is set at build time. | |
*/ | |
Board.styleSheet = "/** \n" | |
+ " * Stylesheet for the Board widget\n" | |
+ " * \n" | |
+ " * BUILD: September 07, 2013 - 03:52 PM\n" | |
+ " */\n" | |
+ "div.spwidget-board {\n" | |
+ " width: 100%;\n" | |
+ " position: relative;\n" | |
+ "}\n" | |
+ "\n" | |
+ "div.spwidget-board div.spwidget-board-headers,\n" | |
+ "div.spwidget-board div.spwidget-board-headers-cntr,\n" | |
+ "div.spwidget-board div.spwidget-board-states-cntr, \n" | |
+ "div.spwidget-board div.spwidget-board-states {\n" | |
+ " width: 100%;\n" | |
+ "}\n" | |
+ "\n" | |
+ "div.spwidget-board div.spwidget-board-state {\n" | |
+ " width: 49%;\n" | |
+ " float: left;\n" | |
+ " margin: .1%;\n" | |
+ " padding: .2%;\n" | |
+ " overflow: auto;\n" | |
+ "}\n" | |
+ "\n" | |
+ "div.spwidget-board div.spwidget-board-headers-cntr {\n" | |
+ " border: none;\n" | |
+ "}\n" | |
+ "div.spwidget-board div.spwidget-board-headers-cntr div.spwidget-board-state {\n" | |
+ " text-align: center;\n" | |
+ " font-weight: bold;\n" | |
+ " font-size: 1.1em;\n" | |
+ " overflow: hidden;\n" | |
+ " word-wrap: break-word;\n" | |
+ "}\n" | |
+ "div.spwidget-board div.spwidget-board-states div.spwidget-board-state {\n" | |
+ " margin-bottom: 1em;\n" | |
+ " min-height: 10em;\n" | |
+ "}\n" | |
+ "\n" | |
+ "div.spwidget-board div.spwidget-board-state div.spwidget-board-state-item {\n" | |
+ " padding: .2em;\n" | |
+ " margin: .5em .2em;\n" | |
+ " font-weight: normal;\n" | |
+ " cursor: move;\n" | |
+ " overflow: auto;\n" | |
+ "}\n" | |
+ "div.spwidget-board div.spwidget-board-state-item div.spwidget-board-item-actions{\n" | |
+ " margin-top: .2em;\n" | |
+ " padding: .2em .5em;\n" | |
+ " overflow: hidden;\n" | |
+ "}\n" | |
+ "div.spwidget-board .spwidget-board-placeholder {\n" | |
+ " height: 3em;\n" | |
+ "}\n" | |
+ "\n" | |
+ "/** Setting container */\n" | |
+ "div.spwidget-board-settings {\n" | |
+ " font-size: .8em;\n" | |
+ " margin: .2em;\n" | |
+ "}\n" | |
+ "div.spwidget-board-settings div.spwidget-board-column-list-cntr {\n" | |
+ " z-index: 5;\n" | |
+ " position: absolute;\n" | |
+ "}\n" | |
+ "div.spwidget-board-settings div.spwidget-board-column-list-cntr > div {\n" | |
+ " padding: .2em;\n" | |
+ "}\n" | |
+ "div.spwidget-board-settings div.spwidget-board-column-list-cntr > div:first-child,\n" | |
+ "div.spwidget-board-settings div.spwidget-board-column-list-cntr > div:last-child {\n" | |
+ " text-align: right;\n" | |
+ "}\n" | |
+ "div.spwidget-board-settings div.spwidget-board-column-list {\n" | |
+ " width: 20em;\n" | |
+ " height: 17em;\n" | |
+ " overflow: auto;\n" | |
+ " position: relative\n" | |
+ "}\n" | |
+ "div.spwidget-board-settings div.spwidget-board-column-list-cntr .spwidget-board-msg {\n" | |
+ " position: absolute;\n" | |
+ " top: 1px;\n" | |
+ " left: 1px;\n" | |
+ " padding: .2em;\n" | |
+ "}\n" | |
+ "div.spwidget-board-settings div.ui-state-default {\n" | |
+ " position: relative;\n" | |
+ "}\n" | |
+ "div.spwidget-board-settings div.spwidget-board-column-list > a {\n" | |
+ " display: block;\n" | |
+ " margin: .2em;\n" | |
+ " padding: .2em;\n" | |
+ "}\n" | |
+ "div.spwidget-board-settings div.spwidget-board-column-list > a > span.ui-icon {\n" | |
+ " display: inline-block;\n" | |
+ "}\n" | |
+ "\n" | |
+ "/* Number of Columns (96 % #columns)\n" | |
+ " * Currently support 10 columns. \n" | |
+ " */\n" | |
+ "div.spwidget-board .spwidget-states-3 div.spwidget-board-state {\n" | |
+ " width: 32.4%;\n" | |
+ "}\n" | |
+ "div.spwidget-board .spwidget-states-4 div.spwidget-board-state {\n" | |
+ " width: 24%;\n" | |
+ "}\n" | |
+ "div.spwidget-board .spwidget-states-5 div.spwidget-board-state {\n" | |
+ " width: 19.1%;\n" | |
+ "}\n" | |
+ "div.spwidget-board .spwidget-states-6 div.spwidget-board-state {\n" | |
+ " width: 15.8%;\n" | |
+ "}\n" | |
+ "div.spwidget-board .spwidget-states-7 div.spwidget-board-state {\n" | |
+ " width: 13.4%;\n" | |
+ "}\n" | |
+ "div.spwidget-board .spwidget-states-8 div.spwidget-board-state {\n" | |
+ " width: 11.6%;\n" | |
+ "}\n" | |
+ "div.spwidget-board .spwidget-states-9 div.spwidget-board-state {\n" | |
+ " width: 10.2%;\n" | |
+ "}\n" | |
+ "div.spwidget-board .spwidget-states-10 div.spwidget-board-state {\n" | |
+ " width: 9.1%;\n" | |
+ "}\n"; | |
//_HAS_BOARD_CSS_TEMPLATE_ | |
/** | |
* @property | |
* Stores the HTML template for each Board widget. | |
* Value is set at build time. | |
*/ | |
Board.htmlTemplate = "<div class=\"spwidget-board\">\n" | |
+ " <div class=\"spwidget-board-settings\" style=\"display:none;\">\n" | |
+ " <div class='spwidget-board-settings-columns'>Columns</div>\n" | |
+ " <div class=\"spwidget-board-column-list-cntr ui-widget-content ui-corner-all\" style=\"display: none\">\n" | |
+ " <div class=\"ui-state-default\">\n" | |
+ " <span>\n" | |
+ " <span class=\"spwidget-board-column-total\"></span> \n" | |
+ " <span class=\"spwidget-board-column-total-label\">Selected.</span>\n" | |
+ " </span>\n" | |
+ " <button type=\"button\" name=\"check\" title=\"Check-Uncheck All\">Check</button>\n" | |
+ " <button type=\"button\" name=\"close\" title=\"Close\">Close</button>\n" | |
+ " </div>\n" | |
+ " <div class=\"spwidget-board-column-list\">\n" | |
+ " </div>\n" | |
+ " <div class=\"ui-state-default\">\n" | |
+ " <button type=\"button\" name=\"apply\">Apply</button>\n" | |
+ " </div>\n" | |
+ " </div>\n" | |
+ " </div>\n" | |
+ " <div class=\"spwidget-board-headers\">\n" | |
+ " <div class=\"spwidget-board-headers-cntr ui-widget-content ui-corner-all\">\n" | |
+ " <div class=\"spwidget-board-state ui-widget-content ui-corner-all\"></div>\n" | |
+ " <div style=\"clear:both;\"></div>\n" | |
+ " </div>\n" | |
+ " </div>\n" | |
+ " <div style=\"clear:both;\"></div>\n" | |
+ " <div class=\"spwidget-board-states\">\n" | |
+ " <div class=\"spwidget-board-states-cntr\">\n" | |
+ " <div class=\"spwidget-board-state ui-widget-content ui-corner-all\"></div>\n" | |
+ " <div style=\"clear:both;\"></div>\n" | |
+ " </div>\n" | |
+ " </div>\n" | |
+ " <div style=\"clear:both;\"></div>\n" | |
+ "</div>\n" | |
+ "<div class=\"spwidget-item-template\">\n" | |
+ " <div>\n" | |
+ " <div>#{{ID}}: {{Title}}</div>\n" | |
+ " <div class=\"ui-state-active ui-corner-all spwidget-board-item-actions\">\n" | |
+ " <a class=\"spwidgets-board-action\" href=\"javascript:\" title=\"View Item\" data-spwidgets_id=\"{{ID}}\" data-spwidgets_board_action=\"view-item\"><img src=\"/_layouts/images/icgen.gif\" border=\"0\"/></a>\n" | |
+ " <a class=\"spwidgets-board-action\" href=\"javascript:\" title=\"Edit Item\" data-spwidgets_id=\"{{ID}}\" data-spwidgets_board_action=\"edit-item\"><img src=\"/_layouts/images/CMSEditSourceDoc.GIF\" border=\"0\"/></a>\n" | |
+ " </div>\n" | |
+ " </div>\n" | |
+ "</div>\n"; | |
//_HAS_BOARD_HTML_TEMPLATE_ | |
})(jQuery); | |
/** | |
* Widget that turn an input field into a lookup field. The | |
* field will store only the ID's (one or more) for the items | |
* that the user picks. | |
* THe user, however, is presented with the existing items | |
* and has the ability to Remove them and add new ones. | |
* | |
* BUILD: Paul:November 04, 2013 07:09 PM | |
* | |
*/ | |
;(function($){ | |
"use strict"; | |
/*jslint nomen: true, plusplus: true */ | |
/*global SPWidgets */ | |
/** | |
* Namespace for pickSPUser specific methods. | |
* @name Lookup | |
* @class Namespace for lookup Field plugin | |
*/ | |
var Lookup = { | |
_islookupFieldCssDone: false, | |
_isLookupbodyEventDone: false | |
}; | |
// Default options | |
$.SPWidgets.defaults.LookupField = { | |
list: '', | |
allowMultiples: true, | |
inputLabel: '', | |
inputPlaceholder: 'Type and Pick', | |
readOnly: false, | |
exactMatch: true, | |
uiContainer: null, | |
selectFields: ['Title'], | |
filter: '', | |
filterFields: ['Title'], | |
filterOrderBy: '', | |
template: '<div>{{Title}} <span class="spwidgets-item-remove">[x]</span></div>', | |
listTemplate: '{{Title}}', | |
listHeight: 0, | |
onItemAdd: null, | |
onItemRemove: null, | |
onReady: null, | |
msgNoItems: "", | |
maxResults: 50, | |
minLength: 2, | |
hideInput: true, | |
padDelimeter: false, | |
showSelector: false | |
}; | |
/** | |
* | |
* Converts the selection into a Sharepoint Lookup Field. | |
* | |
* @param {Object} options | |
* | |
* @param {String} options.list | |
* List name from where lookup will be done. | |
* | |
* @param {Boolean} [options.allowMultiples=true] | |
* Set to false if wanting only 1 item to be referenced. | |
* | |
* @param {String} [options.inputLabel=""] | |
* The label for the input field. | |
* | |
* @param {String} [options.inputPlaceholder="Type and Pick"] | |
* The value to be used in the Input Field placeholder | |
* attribute (HTML5 attribute) | |
* | |
* @param {Boolean} [options.exactMatch=true] | |
* If set to false, then the text entered by the user will | |
* be parsed into individual keywords and a search will be | |
* done on those instead. | |
* | |
* @param {Boolean} [options.readOnly=false] | |
* If true, field is displayed as readonly. | |
* | |
* @param {Selector|Object} [options.uiContainer=null] | |
* The container where the UI widget should be inserted. | |
* Default is directly after the input field | |
* | |
* @param {Array} options.selectFields=["Title"] | |
* Array of field names (internal names) that should be | |
* returned. ID is also used when the input value by the | |
* user is an integer. | |
* | |
* @param {String} [options.filter=""] | |
* Any additional filter criteria (in CAML format) to be | |
* added to the query when retrieving the Lookup values | |
* from the list. | |
* Example: | |
* <Contains> | |
* <FieldRef Name="Title" /> | |
* <Value Type="Text">New</Value> | |
* </Contains> | |
* | |
* @param {String} [options.filterOrderBy=''] | |
* The OrderBy (sort) CAML string used when retrieving values | |
* from the List. | |
* Example: | |
* <OrderBy> | |
* <FieldRef Name="Title" Ascending="TRUE"/> | |
* </OrderBy> | |
* | |
* @param {Array} [options.filterFields=["Title"]] | |
* Array of fields name (internal names) that will be used | |
* to filter data against. | |
* Example: | |
* options.filterFields=[ | |
* "Title", | |
* "Description", | |
* "Notes" | |
* ] | |
* | |
* @param {String} [options.template="..."] | |
* The template to be used for displaying the item once selected. | |
* Use the following format for item Field placeholders | |
* {{fieldInternalName}}. When defining HTML, an element containing | |
* a call of 'spwidgets-item-remove' will be used to remove the item | |
* from the selected list. | |
* Example: | |
* options.template='<div>{{Title}} [<span class="spwidgets-item-remove">x</span>]</div>', | |
* | |
* @param {String} [options.listTemplate="..."] | |
* The template to be used for displaying the items displayed as | |
* suggestion (autocomplete values). | |
* Use the following format for item Field placeholders | |
* {{fieldInternalName}}. Example: {{Title}} | |
* | |
* @param {Number} [options.listHeight=0] | |
* The height to be set on the Autocomplete suggesion box. | |
* Use this value when there is a chance for allot of values | |
* to be returned on a query. | |
* | |
* @param {Boolean} [options.padDelimeter=false] | |
* If true, then an extra delimeter (;#) will be inserted at | |
* the begining of the stored value. | |
* Example: ;#;#5;# (normal would be: 5;#) | |
* | |
* @param {Function} [options.onReady=null] | |
* Triggered after the LookupField has been setup. This is | |
* triggered either after completing the UI setup, or if the | |
* field already had pre-defined values, after retrieving that | |
* data and displaying it. | |
* Function will be given a scope of the original selector | |
* (the field) as well as the following input params: | |
* 1) widget container (jQuery) | |
* Example: | |
* onReady: function(widgetCntr){ | |
* //this=original selector to where the widget was bound | |
* } | |
* | |
* @param {Function} [options.onItemAdd=null] | |
* Function that will be called when adding a new item reference | |
* to the list of currently picked item. This method could, if | |
* necessary remove the new item from the UI (ex. due to some | |
* custom validation rule). | |
* The function will be given a scope of the bound area (the | |
* input field) as well as two input parameters: | |
* 1) A jQuery object representing the new item | |
* on the UI and | |
* 2) An object with the item's information | |
* Example: | |
* onItemAdd: function($newItemSelection, itemObject, widgetCntr){ | |
* //this=original selector to where the widget was bound | |
* } | |
* | |
* @param {Function} [options.onItemRemove=null] | |
* Function that is called when items are removed. Return Boolean | |
* false will cancel the removal of the items. | |
* Function is given the list of items on the UI, an array of | |
* objects that represent the row data structure (as retrieved from | |
* SP) and the Widget container on the page | |
* Example: | |
* onItemRemove: function($items, itemObjects, $widgetCntr ){ | |
* //this=bound element | |
* } | |
* | |
* @param {String} [options.msgNoItems=""] | |
* Message to be displayed when no items are selected. Set this | |
* to null/blank if wanting nothing to be displayed, which will | |
* result in only the input selection field being displayed. | |
* | |
* @param {Integer} [options.maxResults=50] | |
* Max number of results to be returned as the user types the filter | |
* | |
* @param {Integer} [options.minLength=2] | |
* The minimum length before the autocomplete search is triggered. | |
* | |
* @param {Boolean} [options.hideInput=true] | |
* Option used only when allowMultiples is false. It will hide | |
* the input field once a value has been selected. Only way to | |
* get it displayed again is to remove existing selected item. | |
* | |
* @param {Boolean} [options.hideInput=false] | |
* If true, then an icon will be displayed to the right of the | |
* selection input field that displays a popup displaysing all | |
* values currently in the lookup List. | |
* | |
* | |
* @return {jQuery} Selection | |
* | |
* | |
* | |
* Methods: | |
* | |
* jQuery(ele).SPLookupField("method", <action>, <options>) | |
* | |
* clear Clears all items currently reference. | |
* Usage: | |
* $(ele).SPLookupField("method", "clear"); // clears all | |
* $(ele).SPLookupField("method", "clear", 5); // clear ID=5 | |
* $(ele).SPLookupField("method", "clear", [5, 123455]); // clear ID=5 and 123455 | |
* | |
* | |
* add Adds a lookup value to the widget. (does not clear existing) | |
* Usage: | |
* $(ele).SPLookupField("method", "add", "45;#test;#234;#test 2") | |
* | |
* | |
*/ | |
$.fn.SPLookupField = function(options) { | |
// if the global styles have not yet been inserted into the page, do it now | |
if (!Lookup._islookupFieldCssDone) { | |
Lookup._islookupFieldCssDone = true; | |
$('<style type="text/css">' + "\n\n" + | |
Lookup.styleSheet + | |
"\n\n</style>") | |
.prependTo("head"); | |
} | |
// Store the arguments given to this function. Used later if the | |
// user is trying to execute a method of this plugin. | |
var arg = arguments; | |
// Initiate each selection as a Lookup element | |
this.each(function(){ | |
var ele = $(this); | |
// TODO: may need to change code below if going to bind to other types of fields (like select) | |
// FIXME: when allowing textarea, need to ensure that its definition is textonly (no HTML) | |
if ( ( !ele.is("input") && !ele.is("textarea") ) | |
|| ele.hasClass("hasLookupSPField") | |
){ | |
// if the first argument is a string, and this is an input | |
// field, then process methods | |
if (typeof options === "string" && ele.is("input")) { | |
var o = ele.data("SPWidgetLookupFieldUI").data("SPWidgetLookupFieldOpt"); | |
// METHOD | |
if (options.toLowerCase() === 'method') { | |
var cmd = String(arg[1] || '').toLowerCase(); | |
var cmdOpt = arg[2]; | |
// ====> ACTION: clear | |
if (cmd === "clear") { | |
if (!$.isArray(cmdOpt)) { | |
if (cmdOpt) { | |
cmdOpt = [ cmdOpt ]; | |
} else { | |
cmdOpt = []; | |
} | |
} | |
// If we have no ID, then blank them all out. | |
if (!cmdOpt.length) { | |
Lookup.removeItem( | |
o, | |
o._selectedItemsCntr.find("div.spwidgets-item") | |
); | |
// Else, we must have an id defined. Parse that | |
// and remove only those items. | |
} else { | |
(function(){ | |
// find all the ID's in the UI | |
var $rmItems = $(); | |
$.each(cmdOpt, function(i, id){ | |
$rmItems = $rmItems.add( | |
o._selectedItemsCntr | |
.find("div.spwidgets-item-id-" + id) | |
); | |
}); | |
// Remove them. | |
Lookup.removeItem(o, $rmItems); | |
})(); | |
} | |
// ====> ACTION: add | |
} else if (cmd === "add") { | |
Lookup.addItem(o, cmdOpt); | |
} | |
}//end: options === method | |
} | |
// Exit | |
return this; | |
} | |
//------------------------------------- | |
// CREATE THE WIDGET ON THE PAGE. | |
//------------------------------------- | |
// Options for this element | |
var o = $.extend( | |
{}, | |
$.SPWidgets.defaults.LookupField, | |
options, | |
{ | |
_ele: ele.css("display", "none").addClass("hasLookupSPField") | |
} | |
); | |
/** | |
* Displays items selected by the user on the UI and updates | |
* the original input element if necessary. | |
* | |
* @params {Array|Object} items | |
* An object or array of objects with the rows | |
* to be shown as slected. Object contains the row | |
* metadata as retrieved from Sharepoint and used on | |
* the autocomplete widget | |
* @params {Boolean} [doNotStoreIds=false] | |
* If true, then the IDs of the items that will be | |
* shown as selected will not be added to the input | |
* field. Good for when initially displaying data | |
* that is defined in the intput field | |
* (ex. when the widget is first bound) | |
* | |
*/ | |
o.showSelectedItems = function(items, doNotStoreIds) { | |
var itemCntr = o._selectedItemsCntr.css("display", ""), | |
itemList = [], | |
wasUpdated = false; | |
// If this is the first item, empty container | |
if ( !itemCntr.find("div.spwidgets-item").length | |
|| o.allowMultiples === false | |
) { | |
itemCntr.empty(); | |
} | |
// If input is an array, then use that to iterate over. | |
if ( $.isArray(items) ) { | |
itemList = items; | |
// Else, the input must not be an array (assume single object) | |
// Add it as an item in the defiend array. | |
} else { | |
itemList.push(items); | |
} | |
// Loop through each item to be shown as selected | |
$.each(itemList, function(i, item){ | |
// If this item is not yet displayed, then add it now | |
if (!itemCntr.find("div.spwidgets-item-id-" + item.ID).length) { | |
// Create the new item UI and append it to the | |
// display area. | |
var thisItemUI = | |
$('<div class="spwidgets-item spwidgets-item-id-' + item.ID + | |
'" data-spid="' + item.ID + '" style="display:none">' + | |
$.SPWidgets.fillTemplate(o.template, item) + | |
'</div>' | |
) | |
.appendTo( itemCntr ) | |
.find(".spwidgets-item-remove") | |
.on("click.SPWidgets", function(ev){ | |
Lookup.removeItem(o,this); | |
}) | |
.end(); | |
// If an onAddItem event was defined, AND the storage | |
// of the ID are is not being bypassed, then then run it now | |
if ($.isFunction(o.onItemAdd) && doNotStoreIds !== true) { | |
o.onItemAdd.call(o._ele, thisItemUI, item, o._cntr); | |
} | |
// If item is still present in the selction list | |
// then continue on to add its ID to the input field | |
// which is used to store it in the DB. | |
// We check this here because the .onItemAdd() event | |
// could have removed it from the UI | |
if ( itemCntr.find("div.spwidgets-item-id-" + item.ID).length > 0 ) { | |
wasUpdated = true; | |
// Show the new item on the page. | |
thisItemUI.fadeIn("slow").promise().then(function(){ | |
$(this).css("display", ""); | |
}); | |
// Store this item's ID in the input field | |
if (doNotStoreIds !== true) { | |
o.storeItemIDs(item.ID, o.allowMultiples); | |
} | |
// If allowMultiples is false, then check if the input field | |
// should be hidden | |
if (o.allowMultiples === false && o.hideInput === true) { | |
o._lookupInputEleCntr.css("display", "none"); | |
} | |
} //end: if() is item still in the UI (after .onItemAdd()) | |
} //end: if(): item already displayed? | |
});//end: .each() item | |
// If readOnly = true, then remove the "delete item" | |
// link from the elements | |
if (o.readOnly) { | |
o._cntr.find(".spwidgets-item-remove").remove(); | |
} | |
// if an update was made, then trigger the change() event on the | |
// original input element. | |
if (wasUpdated) { | |
o._ele.trigger("change"); | |
} | |
};//end: o.showSelectedItems() | |
/** | |
* Stores the ID's of the selected items in the | |
* input field that this widget was bound to. | |
* | |
* @param {Array|String} ids | |
* @param {Boolean} [append=false] | |
* | |
*/ | |
o.storeItemIDs = function(ids, append) { | |
// Store item in input field, by appending this new | |
// item to the end of the existing data in the input. | |
var newItemValue = $.trim( o._ele.val() ), | |
isPadDone = false; | |
// If ID's not an array, then converted to one and | |
// assign its value to the new array. | |
if ( !$.isArray(ids) ) { | |
ids = [ ids ]; | |
} | |
// If append is not true, then erase whatever | |
// data might be there now. | |
if (append !== true) { | |
newItemValue = ""; | |
} | |
// Loop through all element and add them to the string | |
// that will be used to update the input field. | |
$.each( ids, function( i, thisID ){ | |
if (thisID){ | |
// If existing input is blank and padDelimeter is | |
// true, then add an extra delimeter to the begening of the | |
// string. | |
if (newItemValue.length < 1 && o.padDelimeter === true && !isPadDone ) { | |
newItemValue += ";#"; | |
isPadDone = true; | |
} | |
// If data is already in the input field, then add | |
// delimeter to end of the data. | |
if (newItemValue.length > 0) { | |
newItemValue += ";#"; | |
} | |
newItemValue += thisID + ";#"; | |
// TODO: Support for having the Title also be saved - similar to SP | |
// Does the .Title value need to be escaped | |
} | |
}); | |
// Store the values back on the input element. | |
o._ele.val(newItemValue); | |
};//end: o.storeItemIDs() | |
/** | |
* Looks at the input field where this widget was bound to | |
* and displays the items (rows) that are currently stored | |
* there in the widget. | |
* | |
* @param {Object} options | |
* @param {Boolean} [options.aysnc=true] | |
* | |
* @return {jQuery.Deferred} | |
* A deferred because based on those values in the input | |
* calls will be made to the server to retrieve their data. | |
* Deferred is resolved with a scope of the intance object | |
* (o) and given two input params: xData, Status.. Note that | |
* these could be null if input was not set | |
*/ | |
o.showCurrentInputSelection = function(options) { | |
return $.Deferred(function(dfd){ | |
var opt = $.extend({}, { | |
async: true | |
}, options), | |
items = $.SPWidgets.parseLookupFieldValue(o._ele.val()); | |
if (!items.length) { | |
dfd.resolveWith(o, [null, null]); | |
return; | |
} | |
$().SPServices({ | |
operation: "GetListItems", | |
async: opt.async, | |
listName: o.list, | |
CAMLQuery: '<Query><Where>' + | |
$.SPWidgets.getCamlLogical({ | |
type: 'OR', | |
values: items, | |
onEachValue: function(n){ | |
var s = ""; | |
if (n.id) { | |
s = "<Eq><FieldRef Name='ID'/>" + | |
"<Value Type='Counter'>" + | |
n.id + "</Value></Eq>"; | |
} | |
return s; | |
} | |
}) + | |
'</Where></Query>', | |
CAMLViewFields: "<ViewFields>" + | |
o._selectFields + "</ViewFields>", | |
CAMLRowLimit: 0, | |
completefunc: function(xData, status) { | |
// Display the items. | |
var arrayOfCurrentItems = $(xData.responseXML) | |
.SPFilterNode("z:row") | |
.SPXmlToJson({ | |
includeAllAttrs: true, | |
removeOws: true | |
}); | |
// Add to autocomplete cache | |
o.addToAutocompleteCache(arrayOfCurrentItems); | |
o.showSelectedItems( arrayOfCurrentItems, true ); | |
dfd.resolveWith(o, [xData, status]); | |
return; | |
}//end: completefunc() | |
}); //end: SPSErvices | |
}) //end: deferred() | |
.promise(); | |
}; //end: o.showCurrentInputSelection() | |
/** | |
* Checks the cache object (o._autocompleteCache), which is | |
* used to store the objects of data used by the Autocomplete | |
* function, for an object matching the ID provided on input. | |
* | |
* @param {String} itemId | |
* | |
* @return {null|Object} | |
* | |
*/ | |
o.getItemObjectFromCache = function(itemId) { | |
var itemObj = null; | |
$.each(o._autocompleteCache, function(key, rows){ | |
$.each(rows, function(i, row){ | |
if (row.ID == itemId){ | |
itemObj = row; | |
return false; | |
} | |
}); | |
if (itemObj !== null) { | |
return false; | |
} | |
}); | |
return itemObj; | |
}; //end: o.getItemObjectFromCache() | |
/** | |
* Add a new row or rows to the autocomplete | |
* cache. Cache token will be each row ID. | |
*/ | |
o.addToAutocompleteCache = function(rows){ | |
if (!$.isArray(rows)) { | |
rows = [rows]; | |
} | |
$.each(rows, function(i, row){ | |
if (!o._autocompleteCache[row.ID]) { | |
o._autocompleteCache[row.ID] = []; | |
} | |
o._autocompleteCache[row.ID].push( row ); | |
}); | |
}; //end: o.addToAutocommpleteCache(); | |
//--------------------------------------------------- | |
// START BUILD THIS INSTANCE | |
//--------------------------------------------------- | |
// Create the UI container and store the options object in the input field | |
o._cntr = $(Lookup.htmlTemplate) | |
.find(".spwidgets-lookup-cntr").clone(1); | |
// Insert the widget container into the UI | |
if (o.uiContainer === null) { | |
o._cntr.insertAfter(o._ele); | |
} else { | |
o._cntr.appendTo($(o.uiContainer)); | |
} | |
// Define references to the different elements of the UI | |
o._selectedItemsCntr = o._cntr.find("div.spwidgets-lookup-selected"); | |
o._lookupInputEleCntr = o._cntr.find("div.spwidgets-lookup-input"); | |
o._lookupInputEle = o._lookupInputEleCntr | |
.find("input[name='spwidgetLookupInput']"); | |
o._ignoreKeywordsRegEx = (/^(of|and|a|an|to|by|the|or)$/i); | |
o._cntr.data("SPWidgetLookupFieldOpt", o); | |
o._ele.data("SPWidgetLookupFieldUI", o._cntr); | |
// If showSelector is false, remove the option from the UI... | |
// FIXME: maybe we realy want to hide it? case the option is changed later? | |
if (!o.showSelector){ | |
o._cntr.find('.spwidget-lookup-selector-showhide,.spwidget-lookup-selector-cntr').remove(); | |
// Else, bind methods for handling the selector. | |
} else { | |
o._selectorCntr = o._cntr.find("div.spwidget-lookup-selector-cntr"); | |
o._queryInitDone = false; | |
o._cntr.find(".spwidget-lookup-selector-showhide") | |
.on("click", function(ev){ | |
if (o._selectorCntr.is(":visible")) { | |
o._selectorCntr.css("display", "none"); | |
} else { | |
o._selectorCntr | |
.css("display", "block") | |
.position({ | |
my: "left top", | |
at: "left bottom", | |
of: o._lookupInputEle | |
}); | |
if (!o._queryInitDone) { | |
o._queryInitDone = true; | |
Lookup.doSelectorDataInit(o); | |
} | |
} //end: if/else(): how/hide | |
}); | |
o._selectorCntr | |
.find("button[name='close']") | |
.button({ | |
text: false, | |
icons: { | |
primary: "ui-icon-circle-close" | |
} | |
}) | |
.click(function(){ | |
o._selectorCntr.css("display", "none"); | |
}); | |
// If user focuses on the Input field (autocomplete), | |
// then hide the selector if visible | |
o._lookupInputEle.on("focus", function(ev){ | |
if (o._selectorCntr.is(":visible")) { | |
o._selectorCntr.css("display", "none"); | |
} | |
}); | |
} //end: else(): ShowSelector is true | |
// If an input label was defined, then set it, else, remove input label | |
if (o.inputLabel) { | |
o._cntr.find("div.spwidgets-lookup-input label") | |
.empty() | |
.append(o.inputLabel); | |
} else { | |
o._cntr.find("div.spwidgets-lookup-input label").remove(); | |
} | |
// insert placeholder | |
if (o.inputPlaceholder) { | |
o._lookupInputEleCntr | |
.find("input") | |
.attr("placeholder", o.inputPlaceholder); | |
} | |
// Hide the ADD input field if we're in readonly mode | |
if (o.readOnly === true) { | |
o._lookupInputEleCntr.css("display", "none"); | |
o._cntr.find("div.spwidget-lookup") | |
.addClass("spwidget-lookup-readyonly"); | |
} | |
// Convert the list of fields to CAML | |
o._selectFields = ""; | |
$.each(o.selectFields, function(i, f){ | |
o._selectFields += "<FieldRef Name='" + f + "'/>"; | |
}); | |
// Get the token names from the text template | |
o._templateTokens = String(o.template).match(/(\$\{.*?\})/g); | |
if (o._templateTokens == null) { | |
o._templateTokens = []; | |
} | |
$.each(o._templateTokens, function(i, thisToken){ | |
o._templateTokens[i] = thisToken.replace(/[\$\{\}]/g, ""); | |
}); | |
// Bind an Autocomplete to the ADD input of the Lookup widget | |
// Cache is kept by [searchTerm]: ArrayOfObjects (rows from DB) | |
var cache = o._autocompleteCache = {}; | |
o._cntr.find("div.spwidgets-lookup-input input") | |
.autocomplete({ | |
minLength: 2, | |
appendTo: o._cntr, | |
/** | |
* Add format to the pick options and set height | |
* if it was defined on input. | |
*/ | |
open: function(ev, ui){ | |
$(this).autocomplete("widget") | |
.each(function(){ | |
if (o.listHeight > 0) { | |
$(this).css("height", o.listHeight + "px"); | |
} | |
return false; | |
}); | |
// TODO: need to create a class to place a border around suggestion. | |
// then, add to the above: .find("a").addClass("classname here") | |
}, | |
/** | |
* Searches for the data to be displayed in the autocomplete choices. | |
*/ | |
source: function(request, response){ | |
request.term = $.trim(request.term); | |
// If search term is in cache, return it now | |
var termCacheName = String($.trim(request.term)).toUpperCase(); | |
if (termCacheName in cache) { | |
response(cache[termCacheName]); | |
return; | |
} | |
cache[termCacheName] = []; | |
var filterItems = []; | |
// If search term contains only digits, then do a search on ID | |
var term = String(request.term); | |
if ( term.match(/\D/) === null | |
&& term.match(/\d/) !== null) { | |
filterItems.push( | |
"<Eq><FieldRef Name='ID'/>" + | |
"<Value Type='Counter'>" + | |
term + "</Value></Eq>" ); | |
// Else, search all Fields defined by the caller for the term | |
} else { | |
var keywords = [request.term]; | |
if (!o.exactMatch) { | |
keywords = String(request.term).split(/ /); | |
} | |
// For each search field, build the search using AND logical | |
for (var n=0,m=o.filterFields.length; n<m; n++){ | |
var fieldFilters = []; | |
for (var i=0,j=keywords.length; i<j; i++){ | |
if (!o._ignoreKeywordsRegEx.test(keywords[i])) { | |
fieldFilters.push( | |
"<Contains><FieldRef Name='" + o.filterFields[n] + "'/>" + | |
"<Value Type='Text'>" + keywords[i] + "</Value></Contains>" ); | |
} | |
} | |
filterItems.push($.SPWidgets.getCamlLogical({ | |
values: fieldFilters, | |
type: "AND" | |
})); | |
} | |
} | |
// Build the query using OR statements | |
var camlFilter = $.SPWidgets.getCamlLogical({ | |
values: filterItems, | |
type: "OR" | |
}); | |
// If caller defined extra REQUIRED criteria, then | |
// build it into the query. | |
if (o.filter) { | |
camlFilter = $.SPWidgets.getCamlLogical({ | |
values: [camlFilter, o.filter], | |
type: "AND" | |
}); | |
} | |
// Find the items based on the user's input | |
$().SPServices({ | |
operation: "GetListItems", | |
listName: o.list, | |
async: true, | |
CAMLQuery: '<Query><Where>' + camlFilter + '</Where>' + | |
o.filterOrderBy + '</Query>', | |
CAMLRowLimit: o.maxResults, | |
CAMLViewFields: "<ViewFields>" + o._selectFields + "</ViewFields>", | |
completefunc: function(xData, status){ | |
$(xData.responseXML).SPFilterNode("z:row").each(function(){ | |
var thisEle = $(this); | |
var thisDt = thisEle.SPXmlToJson({includeAllAttrs: true})[0]; | |
thisDt.value = ""; | |
thisDt.label = $.SPWidgets.fillTemplate(o.listTemplate, thisDt ); | |
// Add to cache | |
cache[termCacheName].push(thisDt); | |
}); | |
// Return response | |
response(cache[termCacheName]); | |
} | |
}); | |
},//end:source() | |
/** | |
* Event bound to an autocomplete suggestion. | |
* | |
* @param {jQuery} ev - jQuery event. | |
* @param {Object} u - An object containing the element generated above | |
* by the <source> method that represents the item | |
* that was selected. | |
*/ | |
select: function(ev, u){ | |
o.showSelectedItems(u.item); | |
}//end: event: select() | |
})//end:autocomplete | |
/** | |
* ON enter key, if value is less than the minLength, then | |
* Force a search. We pad the query string with spaces so | |
* that it gets pass the autocomplete options set during setup. | |
*/ | |
.on("keyup.SPWidgets", function(ev){ | |
if (ev.which != 13 ) { return; } | |
var v = $(ev.target).val(); | |
if (v) { | |
if (String(v).length < o.minLength) { | |
$(ev.target).autocomplete("search", v + " "); | |
} | |
} | |
}); | |
// If the input field has values, then parse them and display them | |
if (o._ele.val()) { | |
o.showCurrentInputSelection() | |
.then(function(xData, status){ | |
// Call onReady function if one was defined. | |
if ($.isFunction(o.onReady)) { | |
o.onReady.call(o._ele, o._cntr); | |
} | |
}); | |
// ELSE, input was blank. Trigger onReady if applicable. | |
} else { | |
if ($.isFunction(o.onReady)) { | |
o.onReady.call(o._ele, o._cntr); | |
} | |
} // end: if() | |
return this; | |
}); | |
return this; | |
};//end: $.fn.SPLookupField() | |
/** | |
* Removes an item or array of item from the selection. | |
* The html element is removed from UI and the input | |
* element is updated to not contain that ID | |
* | |
* @memberOf Lookup.lookupField | |
* | |
* @param {Object} o | |
* @param {Object} htmlEle | |
* A jQuery selection of elements to remove. | |
* | |
* @return {Object} Lookup | |
*/ | |
Lookup.removeItem = function(o, htmlEle) { | |
var e = $(htmlEle).closest('div.spwidgets-item'), | |
cntr = o._selectedItemsCntr, | |
store = []; | |
// If an onItemRemove param was defined, then trigger it now | |
// Use the store[] array to temporarly store the item IDs that | |
// will be removed. This is used to provide it to the callback. | |
if ($.isFunction(o.onItemRemove)) { | |
e.each(function(){ | |
store.push( | |
o.getItemObjectFromCache( $(this).data("spid") ) | |
); | |
}); | |
if (o.onItemRemove.call(o._ele, e, store, o._cntr) === false){ | |
return Lookup; | |
} | |
store = []; | |
} | |
// Hide the item the user removed from the UI | |
e.fadeOut("fast").promise().then(function(){ | |
e.remove(); | |
// If AllowMultiple is false and msgNoItem is false | |
// then hide the selected items container | |
if ( !o.msgNoItems | |
&& ( o.allowMultiples === false | |
|| ( o.allowMultiples === true | |
&& cntr.find("div.spwidgets-item").length < 1 | |
) | |
) | |
) { | |
cntr.css("display", "none"); | |
} | |
// If allowMultiples is false, and hideInput is true, then make sure | |
// it is visible again | |
if ( o.allowMultiples === false && o.hideInput === true ) { | |
o._lookupInputEleCntr.css("display", ""); | |
} | |
// If a message was defined for no items selected, | |
// then show it now. | |
if ( cntr.find("div.spwidgets-item").length < 1 && o.msgNoItems ) { | |
cntr.append("<div>" + o.msgNoItems + "</div>"); | |
} | |
// Get a new list of items to store | |
cntr.find("div.spwidgets-item").each(function(){ | |
store.push($(this).data("spid")); | |
}); | |
// Focus on the autocomplete field. | |
o._lookupInputEleCntr.find("input").focus(); | |
// remove the item and trigger a change event | |
o.storeItemIDs( store ); | |
o._ele.change(); | |
}); | |
return Lookup; | |
};//end:Lookup.removeItem() | |
/** | |
* Adds items to the Lookup widget. Method is used with the | |
* "add" method on this widget. | |
* Takes a string of values in format id;#title (title optional) | |
* and adds them to the input element and then calls the | |
* Inst.showCurrentInputSelection() method to display them. | |
* | |
* @param {Object} Inst The instance object for the widget | |
* @param {String} strItems The sting of items to add. | |
* | |
* @return {Object} Inst | |
*/ | |
Lookup.addItem = function(Inst, strItems) { | |
if (!strItems || typeof strItems !== "string") { | |
return Inst; | |
} | |
var newVal = Inst._ele.val(); | |
if (newVal === "" && Inst.padDelimeter === true) { | |
newVal += ";#"; | |
} | |
if (newVal) { | |
newVal += ";#"; | |
} | |
newVal += strItems; | |
Inst._ele.val(newVal); | |
Inst.showCurrentInputSelection(); | |
return Inst; | |
}; //end: Lookup.addItem() | |
/** | |
* Initializes the Selector with data from the List. | |
* | |
* @param {Object} Inst | |
* The widget instance object. | |
* | |
* @return {Object} Inst | |
* | |
*/ | |
Lookup.doSelectorDataInit = function(Inst) { | |
var opt = { | |
$resultsCntr: Inst._selectorCntr | |
.find("div.spwidget-lookup-selector-item-cntr"), | |
nextPageToken: '', | |
isLoading: false, | |
hasMorePages: true, | |
$lastPage: $(), | |
queryXml: ( | |
Inst.filter | |
? '<Query><Where>' + Inst.filter + | |
'</Where>' + Inst.filterOrderBy + '</Query>' | |
: '<Query>' + Inst.filterOrderBy + '</Query>' | |
) | |
}; | |
// If the global listner is not yet setup, do it now | |
if (!Lookup._isLookupbodyEventDone) { | |
Lookup._isLookupbodyEventDone = true; | |
$("body").on("click", function(ev){ | |
var $ele = $(ev.target), | |
$allSelectors = $("div.spwidget-lookup-selector-cntr:visible"), | |
$clickArea = null; | |
if ($allSelectors.length) { | |
$clickArea = $ele.closest("div.spwidget-lookup-selector-cntr"); | |
if (!$clickArea.length && $ele.is(".spwidget-lookup-selector-showhide")) { | |
$clickArea = $ele.parent().find("div.spwidget-lookup-selector-cntr"); | |
} | |
$allSelectors.not($clickArea).hide(); | |
} | |
}); | |
} | |
/** | |
* Gets the rows from the list and keeps | |
* a reference to the next page ID so that | |
* on subsquent calls, it will be used. | |
* | |
* @return {jQuery.Promise} | |
* Promise is resolved with a context of the | |
* page of data that was inserted into the | |
* selector. | |
*/ | |
opt.getListRows = function(){ | |
return $.Deferred(function(dfd){ | |
// If we're already busy getting results, exit... | |
if (opt.isLoading) { | |
dfd.resolveWith($lastPage, [$lastPage]); | |
return; | |
} | |
opt.isLoading = true; | |
// Get the data from the list using the user's filter, | |
// maxResult and SelectFields. Then populate the selector | |
// with the data found. | |
$().SPServices({ | |
operation: "GetListItems", | |
listName: Inst.list, | |
async: true, | |
CAMLQuery: opt.queryXml, | |
CAMLRowLimit: Inst.maxResults, | |
CAMLViewFields: "<ViewFields>" + Inst._selectFields + | |
"</ViewFields>", | |
CAMLQueryOptions: (function(){ | |
if (opt.nextPageToken !== "") { | |
return '<QueryOptions>' + | |
"<Paging ListItemCollectionPositionNext='" + | |
$.SPWidgets.escapeXML(opt.nextPageToken) + | |
"'/></QueryOptions>" | |
} | |
})(), | |
completefunc: function(xData, status){ | |
var $resp = $(xData.responseXML), | |
$rsData = $resp.SPFilterNode("rs:data").eq(0), | |
rows = $resp | |
.SPFilterNode("z:row") | |
.SPXmlToJson({ | |
includeAllAttrs: true, | |
removeOws: true | |
}), | |
$page = $("<div/>").insertBefore(opt.$nextPage), | |
rowsHtml = ''; | |
// Store the NextPage Token | |
opt.nextPageToken = $rsData.attr("ListItemCollectionPositionNext") || ''; | |
if (opt.nextPageToken === "") { | |
opt.hasMorePages = false; | |
} | |
$.each(rows, function(i, row){ | |
// Add row to autocomplete cache | |
Inst.addToAutocompleteCache(row); | |
// Create the same attribute as those that are created for | |
// the Autocomplete widget. Ensure consistency should we | |
// do more with this in the future. | |
row.value = ""; | |
row.label = $.SPWidgets.fillTemplate(Inst.listTemplate, row ); | |
rowsHtml += '<div class="spwidget-lookup-item" data-spwidgetsindex="' + | |
i + '">' + row.label + '</div>' | |
}); | |
$page | |
.html(rowsHtml) | |
.find("div.spwidget-lookup-item") | |
.each(function(){ | |
var $e = $(this) | |
$e.hover( | |
function(){ | |
$e.addClass("ui-state-hover"); | |
}, | |
function(){ | |
$e.removeClass("ui-state-hover"); | |
} | |
); | |
}) | |
.end() | |
.on("click", "div.spwidget-lookup-item", function(ev){ | |
var thisRowIndex = $(this).data("spwidgetsindex"); | |
Inst.showSelectedItems(rows[thisRowIndex]); | |
}); | |
opt.isLoading = false; | |
dfd.resolveWith($page, [$page]); | |
return; | |
} //end: completefunc() | |
}); | |
}); | |
}; | |
// Create the "next page" button | |
opt.$nextPage = $('<div class="ui-state-highlight spwidget-lookup-selector-next">Next...</div>') | |
.appendTo(opt.$resultsCntr.empty()) | |
.click(function(ev){ | |
if (!opt.hasMorePages) { | |
return; | |
} | |
opt.$nextPage.css("display", "none"); | |
// Get teh list of rows and then: | |
// if more pages exist - display the next button | |
// if not and no items were displayed, then show message | |
opt.getListRows() | |
.then(function($page){ | |
if (opt.hasMorePages) { | |
opt.$nextPage.css("display", ""); | |
} else if (!$page.children().length) { | |
$page.append("<div class='ui-state-highlight'>No Items Found!</div>"); | |
} | |
opt.$resultsCntr.scrollTop($page.position().top); | |
}); | |
}); | |
opt.$nextPage.click(); | |
return Inst; | |
}; //end: Lookup.doSelectorDataInit() | |
/** | |
* @property | |
* @memberOf Lookup.lookupField | |
* Stores the Style sheet that is inserted into the page the first | |
* time lookupField is called. | |
* Value is set at build time. | |
* | |
*/ | |
Lookup.styleSheet = "/**\n" | |
+ " * Stylesheet for the Lookup Field widget.\n" | |
+ " * \n" | |
+ " */\n" | |
+ "\n" | |
+ ".spwidgets-lookup-cntr {\n" | |
+ " position: relative;\n" | |
+ " display: inline-block;\n" | |
+ " zoom: 1; /* IE7 hack */\n" | |
+ " *display: inline; /* IE7 hack */\n" | |
+ "}\n" | |
+ "\n" | |
+ "\n" | |
+ ".spwidgets-lookup-cntr .spwidgets-lookup-selected {\n" | |
+ " -moz-appearance: textfield;\n" | |
+ " -webkit-appearance: textfield;\n" | |
+ " background-color: white;\n" | |
+ " background-color: -moz-field;\n" | |
+ " border: 1px solid darkgray;\n" | |
+ " box-shadow: 1px 1px 1px 0 lightgray inset; \n" | |
+ " font: -moz-field;\n" | |
+ " font: -webkit-small-control;\n" | |
+ " margin-top: 5px;\n" | |
+ " padding: 2px 5px; \n" | |
+ "}\n" | |
+ "\n" | |
+ ".spwidgets-lookup-cntr .spwidgets-lookup-selected .spwidgets-item {\n" | |
+ " display: inline-block;\n" | |
+ " margin-left: .5em;\n" | |
+ "}\n" | |
+ ".spwidgets-lookup-cntr .spwidgets-item:first-child {\n" | |
+ " margin-left: 0px;\n" | |
+ "}\n" | |
+ ".spwidgets-lookup-cntr .spwidgets-item-remove {\n" | |
+ " color: red;\n" | |
+ " font-size: xx-small;\n" | |
+ " vertical-align: super;\n" | |
+ " cursor: pointer;\n" | |
+ "}\n" | |
+ "\n" | |
+ ".spwidgets-lookup-cntr .spwidgets-lookup-input {\n" | |
+ " margin: .2em 0em;\n" | |
+ " position: relative;\n" | |
+ "}\n" | |
+ ".spwidgets-lookup-cntr .spwidgets-lookup-input input {\n" | |
+ " width: 99%;\n" | |
+ "}\n" | |
+ ".spwidgets-lookup-cntr ul.ui-autocomplete {\n" | |
+ " overflow: auto;\n" | |
+ " z-index: 1;\n" | |
+ "}\n" | |
+ "\n" | |
+ "/* Ready only display */\n" | |
+ ".spwidgets-lookup-cntr div.spwidget-lookup-readyonly .spwidgets-lookup-selected {\n" | |
+ " -moz-appearance: none;\n" | |
+ " -webkit-appearance: none;\n" | |
+ " background-color: transparent;\n" | |
+ " border: none;\n" | |
+ " box-shadow: none;\n" | |
+ " font: inherit;\n" | |
+ "}\n" | |
+ ".spwidgets-lookup-cntr div.spwidget-lookup-readyonly .spwidgets-item-remove {\n" | |
+ " display: none;\n" | |
+ "}\n" | |
+ "\n" | |
+ "/** SELECTOR */\n" | |
+ ".spwidgets-lookup-cntr .spwidget-lookup-selector-showhide {\n" | |
+ " background-repeat: no-repeat;\n" | |
+ " background-image: url(\"/_layouts/images/bizdatacontentsource.gif\");\n" | |
+ " cursor: pointer;\n" | |
+ " display: block;\n" | |
+ " position: absolute;\n" | |
+ " text-indent: -99999px;\n" | |
+ " z-index: 5;\n" | |
+ " height: 16px;\n" | |
+ " width: 16px;\n" | |
+ " right: 5px;\n" | |
+ " top: .3em;\n" | |
+ "}\n" | |
+ ".spwidgets-lookup-cntr div.spwidget-lookup-selector-cntr {\n" | |
+ " display: none;\n" | |
+ " position: absolute;\n" | |
+ " left: 0px;\n" | |
+ " z-index: 10;\n" | |
+ " padding: .2em;\n" | |
+ " width: 98%;\n" | |
+ " font-size: .8em;\n" | |
+ "}\n" | |
+ ".spwidgets-lookup-cntr div.spwidget-lookup-selector-cntr > .ui-state-default {\n" | |
+ " padding: .2em;\n" | |
+ " text-align: right;\n" | |
+ "}\n" | |
+ "\n" | |
+ ".spwidgets-lookup-cntr div.spwidget-lookup-selector-item-cntr {\n" | |
+ " height: 15em;\n" | |
+ " overflow: auto;\n" | |
+ " padding: .2em;\n" | |
+ " font-size: 1em;\n" | |
+ "}\n" | |
+ ".spwidgets-lookup-cntr div.spwidget-lookup-selector-item-cntr .ui-state-highlight {\n" | |
+ " padding: .5em;\n" | |
+ " margin: 1em .2em;\n" | |
+ " text-align: center;\n" | |
+ " font-size: 1.1em;\n" | |
+ " font-weight: bold;\n" | |
+ "}\n" | |
+ ".spwidgets-lookup-cntr div.spwidget-lookup-selector-item-cntr .spwidget-lookup-selector-next {\n" | |
+ " cursor: pointer;\n" | |
+ "}\n" | |
+ ".spwidgets-lookup-cntr div.spwidget-lookup-selector-item-cntr .spwidget-lookup-item {\n" | |
+ " padding: .2em .5em;\n" | |
+ " margin: .2em;\n" | |
+ " cursor: pointer;\n" | |
+ " font-weight: normal;\n" | |
+ "}\n" | |
+ "\n"; | |
//_HAS_LOOKUP_CSS_TEMPLATE_; | |
/** | |
* @property | |
* @memberOf Lookup.lookupField | |
* Stores the HTML template for each lookup field. | |
* Value is set at build time. | |
* | |
*/ | |
Lookup.htmlTemplate = "<div>\n" | |
+ " <div class=\"spwidgets-lookup-cntr\">\n" | |
+ " <div class=\"spwidget-lookup\">\n" | |
+ " <div class=\"spwidgets-lookup-selected\" style=\"display:none;\">\n" | |
+ " </div>\n" | |
+ " <div class=\"spwidgets-lookup-input\">\n" | |
+ " <label>Add</label>\n" | |
+ " <input type=\"text\" name=\"spwidgetLookupInput\" value=\"\" />\n" | |
+ " <span class=\"spwidget-lookup-selector-showhide\" title=\"Browse\">Browse</span>\n" | |
+ " <div class=\"spwidget-lookup-selector-cntr ui-widget-content\">\n" | |
+ " <div class=\"ui-state-default\">\n" | |
+ " <button type=\"button\" name=\"close\" title=\"Close\">Close</button>\n" | |
+ " </div>\n" | |
+ " <div class=\"spwidget-lookup-selector-item-cntr\"></div>\n" | |
+ " </div>\n" | |
+ " </div>\n" | |
+ " </div>\n" | |
+ " </div>\n" | |
+ "</div>\n" | |
+ "\n"; | |
//_HAS_LOOKUP_HTML_TEMPLATE_ | |
})(jQuery); | |
/** | |
* @fileOverview jquery.SPControlPickUser.js | |
* jQuery plugin that attaches to an input field and provide a people | |
* picker widget for interaction by the user. This Plugin is dependent | |
* on jQuery UI's Autocomplete and SPServices library. | |
* | |
* | |
* @version 20131104070903NUMBER_ | |
* @author Paul Tavares, www.purtuga.com | |
* @see TODO: site url | |
* | |
* @requires jQuery.js {@link http://jquery.com} | |
* @requires jQuery-ui.js {@link http://jqueryui.com} | |
* @requires jquery.SPServices.js {@link http://spservices.codeplex.com} | |
* | |
* Build Date Paul:November 04, 2013 07:09 PM | |
* | |
*/ | |
(function(){ | |
/*jslint nomen: true, plusplus: true */ | |
/*global SPWidgets */ | |
/** | |
* Namespace for pickSPUser specific methods. | |
* @name pickSPUser | |
* @class Namespace for pickSPUser plugin | |
* @memberOf jQuery.pt | |
*/ | |
$.pt.pickSPUser = { | |
_isPickSPUserCssDone: false | |
}; | |
/** | |
* Given an input field, this method will display an interface that | |
* allows the users to select one or more users from SharePoint and | |
* stores the selected user information into the intput field in the | |
* format expected when making an update via webservices. | |
* | |
* The input field will be hidden in its current position and a UI | |
* will displayed instead. As the user picks or removes users, the | |
* input field will be updated at the same time, thus it will always | |
* be ready to be submitted as part of an update to the server. | |
* | |
* @alias $.pickSPUser() | |
* @alias jQuery.pickSPUser() | |
* @alias $().pickSPUser() | |
* @alias jQuery().pickSPUser() | |
* | |
* | |
* @param {Object} options | |
* Object with the options. See below. | |
* | |
* @param {Boolean} [options.allowMultiples=true] | |
* Determine whether multiple users can be picked. | |
* | |
* @param {String} [options.webURL=$().SPServices.SPGetCurrentSite()] | |
* The URL of the site | |
* | |
* @param {String} [options.type='User'] | |
* The type of search to conduct. Default is User. Others | |
* include: None, DistributionList, SecurityGroup, | |
* SharePointGroup, All | |
* | |
* @param {Interger} [options.maxSearchResults=50] | |
* The max number of results to be returned from the | |
* server. | |
* | |
* @param {Function} [onPickUser=null] | |
* Function that is called when user makes a selection. | |
* Function will have a context (this keyword) of the | |
* input field to which this plugin is called on, and | |
* will be given one input param; an object containing | |
* information about the selected user. | |
* | |
* @param {Function} [onCreate=null] | |
* Function that is called after the widget has been | |
* initiated on an input element. | |
* Function will have a context (this keyword) of the | |
* input field to which this plugin is called on, which | |
* will also be provided as the first argument to the | |
* function. | |
* | |
* @param {Function} [onRemoveUser=null] | |
* Function called when removing a user from the selected | |
* list. Returning false (boolean) will cancel the removal | |
* of the person from the selected list. | |
* Function will have a context (this keyword) of the | |
* input field to which this plugin is called on, and is | |
* given 3 input params: $input, $personUI, personObj | |
* | |
* @param {String} [inputPlaceholder="Type and Pick"] | |
* The text to appear in the HTML5 placeholder attribute | |
* of the input field. | |
* | |
* @return {jQuery} selection | |
* | |
* | |
* | |
* METHODS: | |
* | |
* $().pickSPUser("method", "clear") | |
* Clears the currently selected users. | |
* | |
* $().pickSPUser("method", "destroy") | |
* Destroys the widget. | |
* | |
* $().pickSPUser("method", "add", "person in id;#name format") | |
* adds a person | |
* | |
* $().pickSPUser("method", "remove", "person id or displayed name") | |
* removes a person | |
* | |
* $().pickSPUser("method", "getSelected") | |
* Returns array of people selecte. | |
* | |
* | |
* EVENTS: | |
* | |
* spwidget:peoplePickerCreate | |
* Triggered when the widget is initiated. Event will received | |
* a scope of the input element or whatever object it bubled to | |
* as well as the following input parameter: | |
* 1. jQuery Event object | |
* 2. Input element that widget was attached to (as jQuery object) | |
* | |
* spwidget:peoplePickerAdd | |
* Triggered when an item is added to the input field. Event will | |
* receive a scope of the input element or whatever object it | |
* bubbled to, as well as the following input parametes: | |
* 1. jQuery Event Object | |
* 2. Input element (as jQuery object) | |
* 3. Object with information on the user that was added. | |
* | |
* spwidget:peoplePickerRemove | |
* Triggered when an item is removed from the selected list. Event will | |
* receive a scope of the input element or whatever object it | |
* bubbled to, as well as the following input parametes: | |
* 1. jQuery Event Object | |
* 2. Input element (as jQuery object) | |
* 3. Object with information on the user that was added. | |
* | |
* | |
*/ | |
$.fn.pickSPUser = function(options) { | |
// if the global styles have not yet been inserted into the page, do it now | |
if (!$.pt.pickSPUser._isPickSPUserCssDone) { | |
$.pt.pickSPUser._isPickSPUserCssDone = true; | |
$('<style type="text/css">' + "\n\n" + | |
$.pt.pickSPUser.styleSheet + | |
"\n\n</style>") | |
.prependTo("head"); | |
} | |
// Store the arguments given to this function. Used later if the | |
// user is trying to execute a method of this plugin. | |
var arg = arguments; | |
// Define options with globals | |
// var options = $.extend({}, options2); | |
// Initiate each selection as a pickSPUser element | |
this.each(function(){ | |
var ele = $(this); | |
if (!ele.is("input") || ele.hasClass("hasPickSPUser")){ | |
// if the first argument is a string, and this is an input | |
// fild, then process methods | |
if (typeof options === "string" && ele.is("input")) { | |
return $.pt.pickSPUser.handleAction.apply(this, arg); | |
// ELse, exit | |
} else { | |
return this; | |
} | |
} | |
// Options for this element | |
var o = $.extend({}, | |
{ | |
allowMultiples: true, | |
maxSearchResults: 50, | |
webURL: $().SPServices.SPGetCurrentSite(), | |
type: 'User', | |
onPickUser: null, | |
onCreate: null, | |
onRemoveUser: null, | |
inputPlaceholder: "Type and Pick" | |
}, | |
options, | |
{ | |
eleUserInput: ele.css("display", "none").addClass("hasPickSPUser") | |
}); | |
// insure that maxsearchResults is an interger | |
o.maxSearchResults = parseInt(o.maxSearchResults) || 50; | |
// Create pick user container and insert it after the input element | |
// TODO: Clean up | |
// var cntr = $(o.htmlTemplateSelector + " .pt-pickSPUser") | |
// .clone(1).insertAfter(ele); | |
var cntr = $($.pt.pickSPUser.htmlTemplate) | |
.find(".pt-pickSPUser").clone(1).insertAfter(ele); | |
o.eleSelected = cntr.find("div.pt-pickSPUser-selected") | |
.empty() | |
.on("click", ".tt-delete-icon", function(){ | |
$.pt.pickSPUser.removeUser(this); | |
}); | |
o.elePickInput = cntr.find("div.pt-pickSPUser-input"); | |
/** | |
* Adds people to the selected list. | |
* | |
* @param {String} peopleString | |
* @param {Boolean} noEvents | |
* | |
*/ | |
o.addPeopleToList = function(peopleString, noEvents) { | |
var curUsers = new String(peopleString).split(';#'), | |
total = curUsers.length, | |
i,id,user, $ui; | |
for (i=0; i<total; i++){ | |
id = curUsers[i]; | |
i++; | |
user = curUsers[i]; | |
$ui = $.pt.pickSPUser | |
.getUserHtmlElement(o, id, user) | |
.appendTo( o.eleSelected ); | |
// Get this user's info. and store it in the input element | |
(function($thisUserUI, thisUserName){ | |
o.getSearchResults(thisUserName) | |
.done(function(rows, xData, status){ | |
var personName = String(thisUserName).toLowerCase(); | |
$.each(rows, function(i,v){ | |
var thisName = String(v.displayName).toLowerCase(); | |
if (thisName === personName) { | |
$thisUserUI.data("pickspuser_object", v); | |
return false; | |
} | |
}); | |
// TODO: should something be done if we're unable to find user? | |
}); | |
})($ui, user); | |
} | |
$.pt.addHoverEffect( | |
o.eleSelected.find("div.pt-pickSPUser-person-cntr") ); | |
// if we don't allow multiple, then hide the input area | |
if (o.allowMultiples === false) { | |
o.elePickInput.css("display", "none"); | |
} | |
$.pt.pickSPUser.storeListOfUsers(o.eleSelected, noEvents); | |
}; //end: o.addPeopleToList() | |
/** | |
* Searches SP for the value providedon input | |
* | |
* @param {String} searchString | |
* | |
* @return {jQuery.Promise} | |
* | |
*/ | |
o.getSearchResults = function(searchString) { | |
return $.Deferred(function(dfd){ | |
$().SPServices({ | |
operation: "SearchPrincipals", | |
searchText: searchString, | |
maxResults: o.maxSearchResults, | |
principalType: o.type, | |
async: true, | |
webURL: o.webURL, | |
completefunc: function(xData, status){ | |
var resp = $(xData.responseXML), | |
rows = []; | |
resp.find("PrincipalInfo").each(function(){ | |
var thisEle = $(this), | |
thisUser = { | |
displayName: thisEle.find("DisplayName").text(), | |
accountId: thisEle.find("UserInfoID").text(), | |
accountName: thisEle.find("AccountName").text(), | |
accountType: thisEle.find("PrincipalType").text(), | |
// needed attributes for autocomplete | |
value: thisEle.find("DisplayName").text(), | |
label: '' | |
}; | |
// TODO: in the future, need to find a way to show type icon on the suggestions | |
// if (thisUser.accountType === "User") { | |
// | |
// thisUser.label = "<img src='/_layouts/images/CheckNames.gif' /> "; | |
// | |
// } else { | |
// | |
// thisUser.label = "<img src='/_layouts/images/ALLUSR.GIF' /> "; | |
// | |
// } | |
thisUser.label += thisUser.displayName; | |
rows.push(thisUser); | |
}); | |
dfd.resolveWith(xData, [rows, xData, status]); | |
} | |
}); | |
}) | |
.promise(); | |
}; //end: o.getSearchResults() | |
// If multiple user are allowed to be picked, then add style to | |
// selected input area | |
if (o.allowMultiples === true) { | |
o.eleSelected.addClass("pt-pickSPUser-selected-multiple"); | |
} | |
// If the current input field has a value defined, then parse it | |
// and display the currently defined values | |
if (ele.val()) { | |
o.addPeopleToList(ele.val(), noEvents); | |
} | |
// Variable that store all search results | |
var cache = {}; | |
// Add the AutoComplete functionality to the input field | |
o.elePickInput.find("input[name='pickSPUserInputField']") | |
.attr("placeholder", o.inputPlaceholder) | |
.autocomplete({ | |
minLength: 3, | |
appendTo: o.elePickInput, | |
source: function(request, response){ | |
// If search term is in cache, return it now | |
if (request.term in cache) { | |
response(cache[request.term]); | |
return; | |
} | |
cache[request.term] = []; | |
// Search SP | |
o.getSearchResults(request.term) | |
.then(function(rows, xData, status){ | |
cache[request.term].push | |
.apply( | |
cache[request.term], | |
rows | |
); | |
response(cache[request.term]); | |
}); | |
},//end:source() | |
/** | |
* Event bound to an autocomplete suggestion. | |
* | |
* @param {jQuery} ev - jQuery event. | |
* @param {Object} u - An object containing the element generated above | |
* by the <source> method that represents the person | |
* that was selected. | |
*/ | |
select: function(ev, u){ | |
// If we store only 1 user, then clear out the current values | |
if (o.allowMultiples === false) { | |
o.eleSelected.empty(); | |
// Check if already displayed. | |
} else if ( | |
o.eleSelected.find( | |
"div[data-pickspuserid='" + | |
u.item.accountId + "']" ).length | |
) { | |
return; | |
} | |
var $newPersonUI = $.pt.pickSPUser.getUserHtmlElement( | |
o, u.item.accountId, u.item.displayName | |
) | |
.appendTo( o.eleSelected ); | |
// Store a copy of the user object on the UI | |
$newPersonUI.data("pickspuser_object", u.item); | |
$.pt.pickSPUser.storeListOfUsers(cntr); | |
$.pt.addHoverEffect( | |
cntr.find("div.pt-pickSPUser-person-cntr") ); | |
// clear out the autocomplete box | |
setTimeout(function(){ev.target.value = "";}, 50); | |
if (o.allowMultiples === false) { | |
o.elePickInput.hide(); | |
} | |
// if a callback was defined, call it now | |
if ($.isFunction(o.onPickUser)) { | |
o.onPickUser.call(o.eleUserInput, $.extend({},u.item)); | |
} | |
// Triggere event | |
ele.trigger( | |
$.Event("spwidget:peoplePickerAdd"), | |
[ o.eleUserInput, $.extend({},u.item) ] | |
); | |
} | |
});//end:autocomplete | |
// Store the options for this call on the container and include a pointer | |
// in the input field to this element | |
cntr.data("pickSPUserContainerOpt", o); | |
ele.data("pickSPUserContainer", cntr); | |
// call onCreate if defined | |
if ($.isFunction(o.onCreate)) { | |
o.onCreate.call(ele, ele); | |
} | |
// Trigger create event on this instance | |
ele.trigger( | |
$.Event("spwidget:peoplePickerCreate"), | |
[o.eleUserInput] | |
); | |
return this; | |
}); | |
return this; | |
};// $.fn.pickSPUser() | |
/** | |
* Builds the html element that surrounds a user for display on the page. | |
* | |
* @param {Object} opt - The options object given to <jQuery.fn.pickSPUser()> | |
* @param {String} id - The User's Sharepoint UID | |
* @param {String} name - The User's name. | |
* | |
* @return {jQuery} Html element | |
* | |
*/ | |
$.pt.pickSPUser.getUserHtmlElement = function(opt, id, name){ | |
var ele = $($.pt.pickSPUser.htmlTemplate) | |
.find(".pt-pickSPUser-person").clone(1); | |
ele.attr("data-pickSPUserID", id); | |
ele.find("span.pt-person-name") | |
.append(name) | |
.end() | |
.attr("data-pickSPUserNAME", name); | |
return ele; | |
};// $.pt.pickSPUser.getUserHtmlElement() | |
/** | |
* Method is bound to the X (remove) button that appears when the one | |
* hovers over the names curerntly displayed. Removes the user from | |
* the UI and updates the input field to reflect what is currently | |
* displayed. | |
* | |
* @param {Object} ele - The HTML element from where this method was | |
* called. Used to find both the div.pt-pickSPUser | |
* overall parent element as well as the specific | |
* .pt-pickSPUser-person element for the user that | |
* was clicked on. | |
* | |
* @return {undefined} | |
* | |
*/ | |
$.pt.pickSPUser.removeUser = function(ele){ | |
var cntr = $(ele).closest("div.pt-pickSPUser"), | |
o = cntr.data("pickSPUserContainerOpt"), | |
$personUI = $(ele).closest("div.pt-pickSPUser-person"), | |
personObj = $personUI.data("pickspuser_object"), | |
doRemove = true; | |
// If an onRemoveUser is defined, then call method | |
// and capture response | |
if ($.isFunction(o.onRemoveUser)) { | |
o.onRemoveUser.call( | |
o.ele, | |
o.ele, | |
$personUI, | |
personObj ); | |
} | |
if (doRemove === false) { | |
return; | |
} | |
// remove user from the view | |
$personUI.fadeOut('fast', function(){ | |
$(this).remove(); | |
$.pt.pickSPUser.storeListOfUsers(cntr); | |
}); | |
// if AllowMultiple is false, then make the picker input visible | |
if (o.allowMultiples === false) { | |
o.elePickInput.show("fast", function(){ | |
o.elePickInput.find("input").focus(); | |
}); | |
} | |
// trigger event | |
o.eleUserInput.trigger( | |
$.Event("spwidget:peoplePickerRemove"), | |
[ o.eleUserInput, personObj ] | |
); | |
return; | |
};// $.pt.pickSPUser.removeUser() | |
/** | |
* Method will look at the container that holds the currently selected | |
* users and will populate the initial input field given to | |
* <jQuery.fn.pickSPUser()> with a sting representing those users. | |
* | |
* | |
* @param {Object} ele - The HTML element from where this method was | |
* called. Used to find both the div.pt-pickSPUser | |
* overall parent element as well as the specific | |
* .pt-pickSPUser-person element for the user that | |
* was clicked on. | |
* | |
* @return {undefined} | |
* | |
*/ | |
$.pt.pickSPUser.storeListOfUsers = function(ele, noEvents){ | |
var cntr = $(ele).closest("div.pt-pickSPUser"), | |
opt = cntr.data("pickSPUserContainerOpt"), | |
newVal = "", | |
// isDone: keep track of the user already selected, | |
// so we don't add them twice to the input field. | |
isDone = {}; | |
cntr.find("div.pt-pickSPUser-selected div.pt-pickSPUser-person") | |
.each(function(){ | |
if (isDone[$(this).attr("data-pickSPUserID")]) {return;}; | |
isDone[$(this).attr("data-pickSPUserID")] = true; | |
if (newVal) { | |
newVal += ";#"; | |
} | |
newVal += $(this).attr("data-pickSPUserID"); | |
newVal += ";#"; | |
newVal += $(this).attr("data-pickSPUserNAME"); | |
}); | |
opt.eleUserInput.val(newVal); | |
if (!noEvents) { | |
opt.eleUserInput.change(); | |
} | |
return; | |
};// $.pt.pickSPUser.storeListOfUsers() | |
/** | |
* Handles method actions given to $().pickSPUser() | |
* | |
* @param {String} type1 | |
* @param {String} action | |
* @param {Object} options | |
* | |
* @return {this} | |
* | |
*/ | |
$.pt.pickSPUser.handleAction = function(type, action, options) { | |
type = String(type).toLowerCase(); | |
action = String(action).toLowerCase(); | |
var o = $(this) | |
.data("pickSPUserContainer") | |
.data("pickSPUserContainerOpt"), | |
ret = this; | |
if (type === "method") { | |
switch (action) { | |
case "clear": | |
o.eleUserInput.val(""); | |
o.eleSelected.empty(); | |
if (o.allowMultiples === false) { | |
o.eleSelected.css("display", "none"); | |
o.elePickInput.show(); | |
} | |
break; | |
case "destroy": | |
if ( $(this).hasClass('hasPickSPUser')) { | |
$(this).removeClass('hasPickSPUser') | |
.next('.pt-pickSPUser').remove() | |
.show() | |
.trigger('change'); | |
} | |
break; | |
case "add": | |
o.addPeopleToList(options); | |
break; | |
case "remove": | |
if (options) { | |
var rmEle = o.eleSelected | |
.find( | |
"div[data-pickspuserid='" + | |
options + "']" ); | |
if (!rmEle.length) { | |
rmEle = o.eleSelected | |
.find( | |
"div[data-pickspusername='" + | |
options + "']" ); | |
} | |
if (rmEle.length) { | |
$.pt.pickSPUser.removeUser(rmEle); | |
} | |
} | |
break; | |
case "getSelected": | |
ret = $.SPWidgets.parseLookupFieldValue(o.eleUserInput.val()); | |
break; | |
} | |
}//end:type===method | |
return ret; | |
};// $.pt.pickSPUser.handleAction() | |
/** | |
* @property | |
* Stores the Style sheet that is inserted into the page the first | |
* time pickSPUser is called. | |
* Value is set at build time. | |
* | |
*/ | |
$.pt.pickSPUser.styleSheet = "/**\n" | |
+ " * Styles for the Pick User Widget\n" | |
+ " */\n" | |
+ ".pt-pickSPUser .pt-pickSPUser-selected-multiple {\n" | |
+ " min-height: 3em;\n" | |
+ "}\n" | |
+ "\n" | |
+ ".pt-pickSPUser .pt-pickSPUser-selected .pt-pickSPUser-person {\n" | |
+ " float: left;\n" | |
+ " margin-left: .2em;\n" | |
+ "}\n" | |
+ ".pt-pickSPUser .pt-pickSPUser-hint {\n" | |
+ " font-size: .9em;\n" | |
+ "}\n" | |
+ "\n" | |
+ ".pt-pickSPUser div.pt-pickSPUser-input input.ui-autocomplete {\n" | |
+ " width: 99%;\n" | |
+ "}\n" | |
+ ".pt-pickSPUser div.pt-pickSPUser-input ul.ui-autocomplete {\n" | |
+ " z-index: 1;\n" | |
+ "}\n" | |
+ "\n" | |
+ ".pt-pickSPUser .pt-pickSPUser-person-cntr {\n" | |
+ " margin: .2em 0em;\n" | |
+ " padding: .2em;\n" | |
+ " position: relative;\n" | |
+ "}\n" | |
+ "\n" | |
+ ".pt-pickSPUser .pt-pickSPUser-person-cntr .pt-person-name {\n" | |
+ " padding-right: 2em;\n" | |
+ "}\n" | |
+ "\n" | |
+ "/* Item action container (delete button) */\n" | |
+ ".pt-pickSPUser .pt-pickSPUser-person-cntr .pt-pickSPUser-person-actions {\n" | |
+ " position: absolute;\n" | |
+ " right: 1px;\n" | |
+ " top: 1px;\n" | |
+ " padding: .2em;\n" | |
+ " display: none;\n" | |
+ "}\n" | |
+ ".pt-pickSPUser .pt-pickSPUser-person-cntr .pt-pickSPUser-person-actions .pt-pickSPUser-person-action-links,\n" | |
+ ".pt-pickSPUser .pt-pickSPUser-person-cntr .pt-pickSPUser-person-actions .pt-pickSPUser-person-action-links .tt-confirm-delete {\n" | |
+ " float:right;\n" | |
+ "}\n" | |
+ "\n" | |
+ "/* Make the action visible if we hover or we are trying to confirm a deletion */\n" | |
+ ".pt-pickSPUser .pt-pickSPUser-person-cntr.ui-state-hover .pt-pickSPUser-person-actions,\n" | |
+ ".pt-pickSPUser .pt-pickSPUser-person-cntr .pt-pickSPUser-person-actions.tt-confirm,\n" | |
+ ".pt-pickSPUser .pt-pickSPUser-person-cntr .pt-pickSPUser-person-actions a {\n" | |
+ " display:block;\n" | |
+ " float: right;\n" | |
+ "}\n" | |
+ "\n" | |
+ "/* autocomplete busy image */\n" | |
+ ".ui-autocomplete-loading {\n" | |
+ " background: white url('/_layouts/images/loading.gif') right center no-repeat;\n" | |
+ "}\n" | |
+ "\n" | |
+ "\n"; | |
//_HAS_PICKSPUSER_CSS_TEMPLATE_ | |
/** | |
* @property | |
* Stores the HTML template for each people picker. | |
* Value is set at build time. | |
* | |
*/ | |
$.pt.pickSPUser.htmlTemplate = "<!--\n" | |
+ " Html Templates for the PickSPUser plugin.\n" | |
+ " \n" | |
+ " |\n" | |
+ " | $Author$\n" | |
+ " | $Revision$\n" | |
+ " | $Date$\n" | |
+ " | $Id$\n" | |
+ " |\n" | |
+ "-->\n" | |
+ "<div>\n" | |
+ " <div class=\"pt-pickSPUser\">\n" | |
+ " <div class=\"pt-pickSPUser-selected\">\n" | |
+ " None Selected!\n" | |
+ " </div>\n" | |
+ " <div style=\"clear:both\"></div>\n" | |
+ " <div class=\"pt-pickSPUser-input\" \n" | |
+ " title=\"Type user name above to view search results.\">\n" | |
+ " <input name=\"pickSPUserInputField\" value=\"\" type=\"text\"/>\n" | |
+ " </div>\n" | |
+ " </div>\n" | |
+ " \n" | |
+ " <div class=\"pt-pickSPUser-person\">\n" | |
+ " <div class=\"pt-pickSPUser-person-cntr ui-state-default ui-corner-all\">\n" | |
+ " <span class=\"pt-person-name\"></span>\n" | |
+ " <div class=\"pt-pickSPUser-person-actions\">\n" | |
+ " <div class=\"tt-record-item-action-links\">\n" | |
+ " <a class=\"tt-delete-icon\" href=\"javascript:\" onclick=\"jQuery.pt.pickSPUser.removeUser(this);\">\n" | |
+ " <img style=\"border: medium none; margin-right: 2px;\" alt=\"Delete\" src=\"/_layouts/images/delitem.gif\">\n" | |
+ " </a>\n" | |
+ " <div style=\"clear:both;\"></div>\n" | |
+ " </div>\n" | |
+ " <div style=\"clear:both;\"></div>\n" | |
+ " </div>\n" | |
+ " </div>\n" | |
+ " </div>\n" | |
+ "</div>\n"; | |
//_HAS_PICKSPUSER_HTML_TEMPLATE_ | |
/** | |
* Given a list of elements, this will add a hover affect to | |
* those elements by toggling some classes from jQuery UI | |
* | |
* @memberof jQuery.pt | |
* | |
* @param {jQuery|String} ele A jQuery selector or object containing | |
* the list of elements to receive the hover | |
* effect. | |
* @return {jQuery} | |
* | |
* @example | |
* | |
* $(".tt-hover-animate").addHoverEffect(); | |
* $(".container a").addHoverEffect(); | |
* | |
*/ | |
$.pt.addHoverEffect = function(ele){ | |
return $(ele).each(function(){ | |
if ($(this).hasClass("addHoverEffectDone")) { | |
return; | |
} else { | |
$(this).addClass("addHoverEffectDone"); | |
}; | |
var e = this; | |
$(e).mouseenter(function(){$(e).toggleClass("ui-state-hover");}); | |
$(e).mouseleave(function(){$(e).toggleClass("ui-state-hover");}); | |
}); | |
};// $.pt.addHoverEffect() | |
})(jQuery); | |
/** | |
* @fileOverview jquery.SPControlUpload.js | |
* jQuery plugin that interacts with Sharepoint built in Upload.aspx through an iframe | |
* to provide to the user an upload UI without leaving actually leaving the page, thus | |
* simulating an ajax file upload interaction. | |
* Currently used to upload files to a Document Library with out having the user go | |
* through the many SP pages and without having to leave the user's current page. | |
* | |
* | |
* @version 20131106052852NUMBER_ | |
* @author Paul Tavares, www.purtuga.com | |
* | |
* @requires jQuery.js {@link http://jquery.com} | |
* @requires jQuery-ui.js {@link http://jqueryui.com} | |
* @requires jquery.SPServices.js {@link http://spservices.codeplex.com} | |
* | |
* Build Date etavapa:November 06, 2013 05:28 PM | |
* | |
*/ | |
;(function($){ | |
// Commented out strict mode. Causing errors. "use strict"; | |
/*jslint nomen: true, plusplus: true */ | |
/*global SPWidgets, unescapeProperly, escapeProperly */ | |
/** | |
* jQuery definition | |
* @see http://jquery.com/ | |
* @name jQuery | |
* @class jQuery Library | |
*/ | |
/** | |
* jQuery 'fn' definition to anchor all public plugin methods. | |
* @see http://jquery.com/ | |
* @name fn | |
* @class jQuery Library public method anchor | |
* @memberOf jQuery | |
*/ | |
/** | |
* @name Upload | |
* @class Upload widget | |
*/ | |
var Upload = {}; | |
/** | |
* @property {Boolean} Tracks if the CSS injection into the page has been done. | |
*/ | |
Upload.isSPUploadCssDone = false; | |
/** | |
* Defaults | |
*/ | |
$.SPWidgets.defaults.upload = { | |
listName: '', | |
folderPath: '', | |
uploadDonePage: '/_layouts/images/STS_ListItem_43216.gif', | |
onPageChange: null, | |
onUploadDone: null, | |
uploadUrlOpt: '', | |
overwrite: false, | |
uploadPage: '', | |
overlayClass: '', | |
overlayBgColor: 'white', | |
overlayMessage: '<div>Working on it</div>', | |
selectFileMessage: "Click here to select file...", | |
uploadDoneMessage: "Upload Successful!", | |
fileNameErrorMessage: "A file name cannot contain any of the following characters: \\ / : * ? \" < > | # { } % ~ &", | |
noFileErrorMessage: "No file selected!", | |
checkInFormHeight: '25em', | |
webURL: $().SPServices.SPGetCurrentSite(), | |
debug: false | |
}; | |
/** | |
* jQuery plugin that populates the elements selected with a UI for | |
* uploading a file to a Sharepoint location (library) without having | |
* to leave the current page that the user is currently on. | |
* | |
* @param {Object} options Object with the options below. | |
* | |
* @param {String} options.listName REQUIRED. The name or UID of the list. | |
* Example 'Shared Documents' or '{67587-89284-93884-78827-78823}' | |
* | |
* @param {String} [options.folderPath="/"] | |
* Optional. The full path to the folder inside of the List where | |
* the document should be uploaded to. Default is to place the | |
* document at the root of the Document Library | |
* Examples 'http://yourdomain.com/sites/site1/Shared Documents' or | |
* '/sites/site1/Shared Documents' | |
* | |
* @param {String} [options.uploadDonePage="/_layouts/viewlsts.aspx"] | |
* Optional. The url of the page that should be loaded after the | |
* file has been uploaded successful. Value MUTST start with http. | |
* Default is 'http://yourdomain.com/sites/site1/_layouts/viewlsts.aspx' | |
* | |
* @param {Funtion} [options.onPageChange=null] | |
* Function that is called each time the form in the | |
* iFrame is changed. The function 'this' keyword points to the | |
* element that was used when this method was called. The function | |
* is given one param; the event object created by this method. | |
* ({@link SPControlLoadEvent}) | |
* Return value of this function will control flow of plugin. | |
* Returning true (boolean), will allow processing to continue | |
* at different stages (see the event object below), while | |
* returnin false (boolean) will stop flow from continuing. The | |
* check is strict; meaning that it has to be a boolean false in | |
* order for flow to stop. | |
* | |
* @param {Function} [options.onUploadDone=null] | |
* Triggered when file is successfully uploaded - or when it reaches | |
* the uploadDonePage. This is normally triggered after a file is | |
* checkedIn (if library requires it to be checked in). | |
* Function will have a scope of the element used on input and | |
* be given 1 parameter: An object with the upload file metadata. | |
* | |
* @param {String} [options.uploadUrlOpt=""] | |
* String of data that should be appended to the upload page url, | |
* following this '?". | |
* NOTE; The option "MultipleUpload=1" is NOT SUPPORTED. | |
* This string value is assumed to have already been properly | |
* escaped for use in the url. | |
* | |
* @param {Boolean} [options.overwrite=False] | |
* True or False indicating if document being uploaded should | |
* overwrite any existing one. Default is False (don't overwrite) | |
* | |
* @param {String} [options.uploadPage=""] | |
* The relative URL from the WebSite root to the upload page. | |
* Default is "/_layouts/Upload.aspx". This value is appended to | |
* to the website full url, which is retrieved using SPServices | |
* utility. | |
* | |
* @param {String} [options.overlayClass=""] | |
* A css class to be associated with the overlay that is displayed | |
* over the iframe while loading of the page is going on. | |
* | |
* @param {String} [options.overlayBgColor="white"] | |
* A color to be used for the overlay area that is displayed over | |
* the iframe wile loading of the page is going on. Default is | |
* white. Set this to null if wanting only to use a class. | |
* | |
* @param {String|HTMLElement|jQuery} [options.overlayMessage="Loading..."] | |
* String or object/element to be displayed inside of the overlay | |
* when it is displayed. Default is "Loading..." | |
* | |
* @param {String|HTMLElement|jQuery} [options.selectFileMessage="Click here to select file..."] | |
* The message displayed for user to select file | |
* | |
* @param {String} [options.checkInFormHeight='20em'] | |
* The height of the form when a checkin is required. | |
* | |
* @param {String} [options.webURL=Current site] | |
* The URL of the web site/sub site. | |
* | |
* @param {String} [options.debug=false] | |
* Turns debug on for this widget. | |
* | |
* @return {jQuery} | |
* | |
* @example | |
* | |
* $("<di></div>").appendTo("body") | |
* .SPControlUpload({ | |
* listName: "Shared Documents" | |
* }); | |
* | |
* | |
*/ | |
$.fn.SPControlUpload = function (options) { | |
return $(this).each(function(){ | |
var opt = $.extend({}, $.SPWidgets.defaults.upload, options), | |
overlayCss; | |
// if the global styles have not yet been inserted into the page, do it now | |
if (!Upload.isSPUploadCssDone) { | |
Upload.isSPUploadCssDone = true; | |
$('<style type="text/css">' + "\n\n" + | |
Upload.StyleSheet + "\n\n</style>" | |
) | |
.prependTo("head"); | |
} | |
/** | |
* Define the log method for this instance. | |
*/ | |
opt.log = ( opt.debug ? Upload.log : function(){} ); | |
/** | |
* Shows or hides the Busy loading animation. | |
* | |
* @param {Boolean} hide | |
* if True, then loading busy animation will be hidden. | |
* | |
* @return {jQuery.Promise} | |
*/ | |
opt.showHideBusy = function(hide) { | |
return $.Deferred(function(dfd){ | |
if (hide) { | |
opt.$busyOverlay.fadeOut("fast").promise().then(function(){ | |
dfd.resolve(); | |
}); | |
} else { | |
opt.$busyOverlay.fadeIn("slow").promise().then(function(){ | |
dfd.resolve(); | |
}); | |
} | |
}) | |
.promise(); | |
}; //end: showHideBusy() | |
/** | |
* Shows or hides the full Upload form. Used when the document has | |
* been upload and perhaps there is a CheckIn page to go through. | |
* | |
* @param {Boolean} hide | |
* | |
* @return {Object} opt | |
* | |
*/ | |
opt.showHideFullForm = function(hide) { | |
// HIde full form | |
if (hide) { | |
opt.$content.removeClass("spwidget-show-full-form"); | |
opt.$iframeCntr | |
.css({ | |
overflow: "", | |
height: "" | |
}); | |
// SHOW full form | |
} else { | |
opt.$content.addClass("spwidget-show-full-form"); | |
opt.$iframeCntr | |
.css({ | |
overflow: "auto", | |
height: "auto" // (opt.$iframe.outerHeight() + 5) + "px" | |
}); | |
} | |
return opt; | |
}; //end: opt.showHideFullForm | |
/** | |
* Shows or hides the Upload Success message. | |
* | |
* @param {Boolean} [hide=false] | |
* | |
* @return {Object} opt | |
* | |
*/ | |
opt.showHideSuccess = function(hide){ | |
// HIDE | |
if (hide) { | |
opt.$successCntr | |
.stop() | |
.fadeOut() | |
.promise(function(){ | |
opt.$successCntr.css("display", "none"); | |
}); | |
// DEFAULT: SHOW | |
} else { | |
opt.$successCntr | |
.stop() | |
.show() | |
.promise(function(){ | |
opt.$successCntr.css("display", "block"); | |
}); | |
} | |
return opt; | |
}; //end: opt.showHideSuccess() | |
/** | |
* Shows an error on the widget. | |
* | |
* @param {Object} showErrorOptions | |
* | |
* @param {String} showErrorOptions.message | |
* @param {Boolean} [showErrorOptions.autoHide=true] | |
* | |
* @return {Object} opt | |
* | |
*/ | |
opt.showError = function(showErrorOptions){ | |
var thisOpt = $.extend({}, { | |
message: '', | |
autoHide: true | |
}, | |
showErrorOptions); | |
opt.$errorCntrMsg.html(thisOpt.message); | |
opt.$errorCntr | |
.stop() | |
.css("display", "block"); | |
if (thisOpt.autoHide) { | |
opt.$errorCntr.animate( | |
{ opacity: 1 }, | |
5000, | |
function(){ | |
opt.clearError(); | |
} | |
); | |
} | |
return opt; | |
}; //end: opt.showError() | |
/** | |
* Clears any error currently displayed on the widget. | |
* | |
* @return {Object} opt | |
* | |
*/ | |
opt.clearError = function() { | |
opt.$errorCntr.css("display", "none"); | |
return opt; | |
}; //end: opt.clearError | |
/** | |
* Resets the Upload widget after the upload. | |
* | |
* @return {Object} opt | |
*/ | |
opt.resetWidget = function() { | |
opt.ev = { | |
state: 1, | |
action: "uploading", | |
hideOverlay: true, | |
pageUrl: "", | |
page: null, // a jquery object | |
isUploadDone: false, | |
file: {} | |
}; | |
opt.$iframe.attr("src", opt.uploadPage); | |
return opt; | |
}; //end: opt.resetWidget() | |
/** | |
* Returns the last uploaded file for the user. | |
* | |
* @return {Object} z:row object | |
* | |
*/ | |
opt.getUploadedFileRow = function() { | |
var lastFile = {}; | |
$().SPServices({ | |
operation: "GetListItems", | |
async: false, | |
webURL: opt.webURL, | |
listName: opt.listName, | |
CAMLQuery: "<Query><Where>" + | |
"<Eq><FieldRef Name='Author' LookupId='TRUE'/>" + | |
"<Value Type='Integer'><UserID/></Value></Eq>" + | |
"</Where><OrderBy><FieldRef Name='Modified' Ascending='FALSE'/>" + | |
"</OrderBy></Query>", | |
CAMLViewFields: "<ViewFields>" + | |
"<FieldRef Name='ID'/>" + | |
"<FieldRef Name='EncodedAbsUrl'/>" + | |
"<FieldRef Name='FileLeafRef' />" + | |
"<FieldRef Name='Author' />" + | |
"<FieldRef Name='Editor' />" + | |
"<FieldRef Name='Created' />" + | |
"<FieldRef Name='Modified' />" + | |
"</ViewFields>", | |
CAMLRowLimit: 1, | |
CAMLQueryOptions: "<QueryOptions><ViewAttributes Scope='Recursive' /></QueryOptions>", | |
completefunc: function(xData, status) { | |
var rows = $(xData.responseXML) | |
.SPFilterNode("z:row") | |
.SPXmlToJson({includeAllAttrs: true}); | |
if (rows.length) { | |
lastFile = rows[0]; | |
} | |
} | |
}); | |
return lastFile; | |
}; //end: opt.getUploadedFileRow() | |
/** | |
* Given a URL, this method will check if it is one of the | |
* known upload pages of SharePoint. True = yes it is. | |
* False = no it is not. | |
* | |
* @param {String} url | |
* URL is assumed to be full url, including http. | |
* | |
* @return {Boolean} | |
*/ | |
opt.isUploadPage = function(url) { | |
// Uses parser apprach shown here: | |
// https://gist.github.com/jlong/2428561 | |
var answer = false, | |
parser = document.createElement('a'), | |
parser2 = null; | |
parser.href = String(url).toLowerCase(); | |
// If user defined their own Upload page, then | |
// parse that URL and use it in matching. | |
// Else, just see if the input url has Upload.aspx | |
// or UploadEx.aspx. | |
if (opt.userUploadPage) { | |
parser2 = document.createElement('a'); | |
parser2.href = String(opt.userUploadPage).toLowerCase(); | |
if (parser.pathname === parser2.pathname) { | |
answer = true; | |
} | |
} else { | |
// 2007 = Upload.aspx | |
// 2010, 2013 = UploadEx.aspx | |
answer = /upload(ex)?\.aspx$/.test(parser.pathname); | |
} | |
return answer; | |
}; //end: opt.isUploadPage() | |
/** ---------------------------------------------------------- **/ | |
/** -------------[ SETUP WIDGET ]----------------- **/ | |
/** ---------------------------------------------------------- **/ | |
// If list Name is not the UID, then get it now | |
if (opt.listName && opt.listName.indexOf("{") !== 0) { | |
opt.listName = $.pt.getListUID(opt.listName); | |
} | |
// If list name is not defined - error | |
if (!opt.listName) { | |
$(this).html('<div class="ui-state-error">Input parameter [listName] not valid!</div>'); | |
return this; | |
} | |
// If user did not define the Upload page on input, then set it depending | |
// on SP version. Else, if the user defined the upload page, ensure it | |
// is a full url starting at http... | |
opt.spVersion = $.SPWidgets.getSPVersion(true); | |
opt.userUploadPage = opt.uploadPage; | |
opt.uploadPage = String(opt.uploadPage); | |
if (!opt.uploadPage) { | |
switch(opt.spVersion) { | |
case "2013": | |
opt.uploadPage = opt.webURL + '/_layouts/15/UploadEx.aspx'; | |
break; | |
case "2010": | |
opt.uploadPage = opt.webURL + "/_layouts/UploadEx.aspx"; | |
break; | |
// Default: SP 2007 | |
default: | |
opt.uploadPage = opt.webURL + '/_layouts/Upload.aspx'; | |
break; | |
} | |
} else if (opt.uploadPage.toLowerCase().indexOf("http") === -1) { | |
var s = "/"; | |
if (opt.uploadPage.indexOf('/') == 0) { | |
s = ""; | |
} | |
opt.uploadPage = opt.webURL + s + opt.uploadPage; | |
} | |
// Set the uploadDonePage url | |
if (String(opt.uploadDonePage).toLowerCase().indexOf("http") === -1) { | |
var s = "/"; | |
if (opt.uploadDonePage.indexOf('/') == 0) { | |
s = ""; | |
} | |
opt.uploadDonePage = opt.webURL + s + opt.uploadDonePage; | |
} | |
// _iframeLoadId is used to determine if the onIframeChange() function | |
// should be run or not... It ensure that when a page is redirected, that | |
// only the last function to be spawn (via setTimeout) is run. | |
opt._iframeLoadId = 1; | |
// Create additional non-overridable options | |
opt._uploadUrlParams = "?List=" + | |
$.pt.getEscapedUrl(opt.listName) + "&RootFolder=" + | |
$.pt.getEscapedUrl(opt.folderPath) + "&Source=" + | |
$.pt.getEscapedUrl(opt.uploadDonePage) + "&" + opt.uploadUrlOpt; | |
opt.uploadPage = opt.uploadPage + opt._uploadUrlParams; | |
opt._lastError = ""; | |
opt._reloadCount = 0; | |
/** | |
* @name SPControlLoadEvent | |
* Event object that is given as input to the function defined in the | |
* $.fn.SPControlUpload-onPageChange parameter. | |
* | |
* @event | |
* @memberof $.fn.SPControlUpload | |
* | |
* @param {SPControlLoadEvent} ev | |
* | |
* @param {Integer} ev.state | |
* A value from 1 through 3 that represents the state of | |
* the file upload form. The flow is: | |
* | |
* [1] [2] [3] | |
* [ready for input] -> [pre-upload] -> [file uploaded] | |
* | |
* 1 = is set when the form is initially loaded and the | |
* File html element is ready for the user to attach the file. | |
* File has not yet been uploaded. | |
* 2 = is set when the form is ready to be submitted to the server | |
* along with the file set by the user. File has not yet been | |
* uploaded. | |
* 3 = is set when the user has successfully uploaded the file to | |
* the server and no errors were encountered. | |
* File has been uploaded and now sits on the server. | |
* | |
* @param {String} ev.action | |
* The event action as it pertains to this plugin. | |
* preLoad = action is taking place before the page is sent | |
* to the server. State of '2' are handled by Upload.onUpdate | |
* postLoad = action is taking place after page has completed | |
* loading, but is not yet "visible" to the user. | |
* | |
* @param {Boolean} ev.hideOverlay | |
* Used when action=postLoad. Can be set by | |
* a callback function to false, so that the busy overlay remains | |
* displayed and is not automaticaly hidden. Default value is "true". | |
* | |
* @param {String} ev.pageUrl | |
* The url of the page currently loaded in the iframe. | |
* | |
* @param {jQuery} ev.page | |
* An object representing the page loaded inside the | |
* iFrame. This can be used to further manipulate the iframe's | |
* page content. | |
* | |
* @param {Boolean} ev.isUploadDone | |
* Indicates if the upload process is done. Basically, | |
* this means that the processess has reached the page defined | |
* in the updatePageDone parameter. | |
* | |
*/ | |
opt.ev = { | |
state: 1, | |
action: "uploading", | |
hideOverlay: true, | |
pageUrl: "", | |
page: null, // a jquery object | |
isUploadDone: false, | |
file: {} // populated when file is uploaded | |
}; | |
// Create the UI on the element given used by the SPCOntrolUpload plugin | |
opt.$ele = $(this); | |
overlayCss = {}; | |
if (opt.overlayBgColor) { | |
overlayCss["background-color"] = opt.overlayBgColor; | |
} | |
// Create the UI on the page | |
opt.$cntr = $( | |
$(Upload.HtmlUI).filter("div.SPControlUploadUI").clone() | |
) | |
.appendTo(opt.$ele.addClass("hasSPControlUploadUI").empty()) | |
.data("SPControlUploadOptions", opt); | |
opt.$buttonCntr = opt.$cntr.find("div.buttonPane") | |
.click(function(ev){ | |
Upload.onUpload(this); | |
}); | |
// Store references | |
opt.$content = opt.$cntr.find("div.mainContainer"); | |
opt.$iframeCntr = opt.$cntr.find("div.iFrameWindow"); | |
opt.$iframe = opt.$iframeCntr.children('iframe'); | |
opt.$busyOverlay = opt.$cntr.find("div.loadingOverlay"); | |
opt.$busyOverlayMsg = opt.$busyOverlay.find("div.loadingOverlayMsg"); | |
opt.$successCntr = opt.$cntr.find("div.spwidget-success-cntr"); | |
opt.$errorCntr = opt.$cntr.find("div.spwidget-error-cntr"); | |
opt.$errorCntrMsg = opt.$errorCntr.find(".spwidget-msg"); | |
opt.reInvalidChr = new RegExp("[\\\/\:\*\?\"\<\>\|\#\{\}\%\~\&]"); | |
// Setup success message closure listner and include user's message | |
opt.$successCntr | |
.on("click", ".spwidget-close", function(ev){ | |
opt.showHideSuccess(true); | |
}) | |
.find(".spwidget-msg") | |
.html(opt.uploadDoneMessage); | |
// Setup error message closure | |
opt.$errorCntr.on("click", ".spwidget-close", function(ev){ | |
opt.clearError(); | |
}); | |
// Setup the overlay | |
opt.$busyOverlay | |
.addClass(opt.overlayClass) | |
.css(overlayCss); | |
opt.$busyOverlayMsg.html(opt.overlayMessage); | |
// Show the loading animation and load the UI | |
opt.showHideBusy(); | |
opt.$cntr.find("iframe") | |
.css("height", opt.checkInFormHeight) | |
.load(function(ev){ | |
Upload.onIframeChange(opt.$ele.find(".SPControlUploadUI")); | |
}) | |
.attr("src", opt.uploadPage) | |
.end(); | |
return this; | |
});//each() | |
};// $.fn.SPControlUpload | |
/** | |
* FUNCTION: Upload.onUpload() | |
* | |
* Submits the upload form that is loaded in the iframe window. | |
* Also calls any callback function defined by the user. | |
* | |
* PARAMS: | |
* | |
* @param {Object} ele - Element from within the | |
* .SPControlUploadUI class html container | |
* | |
* RETURN: | |
* | |
* @return {undefined} Nothing. | |
* | |
*/ | |
Upload.onUpload = function(ele){ | |
var e = $(ele).closest(".SPControlUploadUI"), | |
page = e.find("iframe").contents(), | |
msgs = page.find("input[type='file']").closest("tr").siblings().find("span"), | |
opt = e.data("SPControlUploadOptions"), | |
ev = opt.ev; | |
opt.log("Upload.onUpload(" + opt._iframeLoadId + "): Start...."); | |
// Insure all messages are initially hidden (these might have been | |
// visible from any prior call to upload the document where it failed.) | |
msgs.css("display", "none"); | |
// If no file was entered, then there is nothing to upload. | |
if (!page.find("input[type='file']").val()) { | |
opt.showError({message: opt.noFileErrorMessage}); | |
return; | |
} | |
// If file contains invalid charactors, then error | |
if (opt.reInvalidChr.test(page.find("div.SPControlUploadModUIFileSelected").text())) { | |
opt.showError({message: opt.fileNameErrorMessage}); | |
return; | |
} | |
// Set the event info | |
// TODO: Look into building the event with $.Event("ev name here") | |
ev.state = 2; | |
ev.action = "preLoad"; | |
// if a user function was defined, then call it now and give it the event | |
// object defined above. | |
// If fucntion returns a boolean false, then we exit here and never submit | |
// the form. | |
if (opt.onPageChange) { | |
if (opt.onPageChange.call(opt.$ele, ev) === false){ | |
return false; | |
} | |
} | |
opt.showHideFullForm(true); | |
// Hide the upload button, and Submit the form after showing the busy animation | |
opt.showHideBusy().then(function(){ | |
opt.log("Upload.onUpload(" + opt._iframeLoadId + "): Clicking the OK button on upload form."); | |
page.find("input[type='button'][id$='btnOK']").click(); | |
ev.action = "postLoad"; | |
// If error message are displayed (after we click upload button), | |
// then just return control back to the user. | |
if (msgs.is(":visible")) { | |
e.find(".loadingOverlay") | |
.css("display", "none") | |
.end(); | |
return false; | |
} | |
}); | |
};//* Upload.onUpload() | |
/** | |
* FUNTION: Upload.onIframeChange() | |
* | |
* Called when ever the iframe is "load"ed. Function is bound to | |
* the iframe html element's _load event so that it is called each | |
* time the content of the iframe (the page) is reloaded. | |
* | |
* PARAMS: | |
* | |
* @param {jQuery} ele - jQuery object representing the .SPControlUploadUI | |
* element. | |
* | |
* RETURN: | |
* | |
* @return {undefined} nothing. | |
* | |
*/ | |
Upload.onIframeChange = function(ele){ | |
var e = $(ele).closest(".SPControlUploadUI"), | |
opt = e.data("SPControlUploadOptions"), | |
id = 0; | |
// Need to capture the SP2013 page that says somethign like "wait"... | |
// window[opt.ev.state + "-" + opt.ev.action + "-html"] = $(e.find("iframe").contents()).find("html").html(); | |
// If the upload event state is 2, then {Upload.onUpload} has already | |
// taken care of the form and user call back... There is nothing to do | |
// here and form is arleady being submitted... Set the ev. to | |
// postLoad and Exit. | |
if (opt.ev.state === 2 && opt.ev.action === "preLoad") { | |
opt.log("Upload.onIframeChange(" + opt._iframeLoadId + | |
"): ev.action=[" + opt.ev.action + "] and ev.state=[" + | |
opt.ev.state+ "] - handled by onUpload(). Setting action to postLoad" | |
); | |
opt.ev.action = "postLoad"; | |
// FIXME: needed to comment this out for SP2007 | |
return; | |
} | |
opt._iframeLoadId++; | |
id = opt._iframeLoadId; | |
opt.log("Upload.onIframeChange(" + id + "): State=[" + opt.ev.state + | |
"] Action=[" + opt.ev.action + "] iframe.url: " + | |
e.find("iframe").contents()[0].location.href | |
); | |
// Because just about every browser differs on how the load() event | |
// is triggered, we do all our work in a function that is triggered | |
// 500 millisends from now. By then, the page (regardless of browser) | |
// should be in a state that is useful. | |
setTimeout( | |
function(){ | |
// if this invocation is not the last iframe refresh ID, | |
// then exit... there is another fucntion queued up... | |
if (id !== opt._iframeLoadId) { | |
opt.log("Upload.onIframeChange(" + id + "): not latest invokation! Existing."); | |
return; | |
} | |
opt.log("Upload.onIframeChange(" + id + "): START... Executing setTimeout() for iframe ID: " + id); | |
var page = $(e.find("iframe").contents()), | |
ev = opt.ev, | |
form = page.find("form").eq(0); | |
ev.pageUrl = page[0].location.href; | |
ev.page = page; | |
// Focus at the top of the form | |
opt.$iframeCntr.scrollTop(0); | |
page.scrollTop(0); | |
// If the URL of the page in the iFrame is the same as the | |
// upload page then this is either the | |
// initial load of the page or an error has occured... | |
// Hide the page and show only the upload form element. | |
if (opt.isUploadPage(ev.pageUrl)) { | |
opt.log("Upload.onIframeChange(" + id + "): URL is the upload page!"); | |
page.find("body").css({ | |
overflow: "hidden" | |
}); | |
form | |
.children(":visible") | |
.hide() | |
.end() | |
.append( | |
$(Upload.HtmlUI).filter("div#SPControlUploadModUI").clone() ) | |
.find("div.SPControlUploadModUIFileSelected") | |
.html(opt.selectFileMessage); | |
// Is the page displaying an error page without the upload interface? | |
// Capture error message and reload the page. | |
// SP2010 Seems to behave differntly and land display errors a little | |
// differently... so we try the <title> tag adn then the form action value. | |
if ( new RegExp(/error/i).test($.trim(page.find(".ms-pagetitle").text())) | |
|| new RegExp(/error/i).test($.trim(page.find("title").text())) | |
|| new RegExp(/error\.aspx/i).test($.trim(page.find("form").attr("action"))) | |
) { | |
opt.log("Upload.onIframeChange(" + id + "): page displaying an error... Storing it and reloading upload form."); | |
opt._lastError = page.find("[id$='LabelMessage']").text(); | |
// Lets avoid looping... Dont if it possible, but just in case. | |
if (opt._reloadCount > 1) { | |
alert("Error encountered during upload which is causing program to loop. Last upload error was: " + opt._lastError); | |
e.find(".loadingOverlay").fadeOut(); | |
return; | |
} | |
opt._reloadCount += 1; | |
e.find("iframe").attr("src", opt.uploadPage); | |
return; | |
}/* if: error page or upload UI? */ | |
// SP2010 Code | |
// If this is the new SP2010 "Processing..." page, then | |
// the just exit... there is nothing for us to do yet... | |
if ( page.find("#GearPage") | |
&& !page.find("input[type='file']").length | |
) { | |
opt.log("Upload.onIframeChange(" + id + | |
"): SP processing page (GearPage)... Exiting and waiting for next page..." | |
); | |
return; | |
} | |
page.find("input[type='file']").closest("table") | |
.appendTo(page.find("#SPControlUploadModUI")) | |
.removeClass("ms-authoringcontrols"); | |
// setup upload input field on the iframe page, including | |
// setting up the change, focus and click event to update | |
// the input div that shows the file name selected to the | |
// user. | |
var $fileInput = page.find("#SPControlUploadModUI") | |
.find("input[type='file']") | |
.closest('tr') | |
.siblings() | |
.css("display", "none") | |
.end() | |
.end() | |
.siblings("tr .ms-error") | |
.css("display", "") | |
.end() | |
.on("change focus click", function(ev){ | |
var $this = $(this), | |
filePath = $this.val(), | |
fileExt = '', | |
icon = '/_layouts/images/urn-content-classes-smartfolder16.gif'; | |
if (filePath) { | |
try { | |
fileExt = filePath.substr(filePath.lastIndexOf(".") + 1); | |
} catch(e) { | |
fileExt = 'GEN'; | |
} | |
icon = "/_layouts/images/IC" + | |
fileExt.toUpperCase() + ".GIF"; | |
// Get only the file name | |
filePath = (filePath.replace(/\\/g, '/').split('/').pop()) | |
|| filePath; | |
} else { | |
filePath = opt.selectFileMessage; | |
} | |
page.find("#SPControlUploadModUI > div") | |
.html(filePath) | |
.css("background-image", | |
"url('" + icon + "')"); | |
}) //end: .on() | |
.css({ | |
cursor: "pointer", | |
height: "100px", | |
position: "absolute", | |
left: "0px", | |
top: "0px", | |
filter: "alpha(opacity=1)", | |
opacity: "0.01", | |
outline: "none", | |
"-moz-opacity": "0.01", | |
"font-size": "100px", | |
'z-index': "5" | |
}); | |
// Setup the mouseover event so that the input file field | |
// follows the mouse around while user hovers over | |
// the iframe. | |
form.on("mousemove", function(ev){ | |
$fileInput | |
.css({ | |
left: (ev.pageX - ($fileInput.width() - 50)), | |
top: (ev.pageY - 30) | |
}) | |
.blur(); | |
}); | |
// If there were any errors found during a previous call, then | |
// display them now | |
if (opt._lastError) { | |
opt.showError({message: opt._lastError}); | |
opt._lastError = ""; | |
} | |
opt._reloadCount = 0; | |
// Set the override checkbox | |
if (opt.overwrite) { | |
page.find("input[type='checkbox'][name$='OverwriteSingle']") | |
.prop("checked", "checked"); | |
} else { | |
page.find("input[type='checkbox'][name$='OverwriteSingle']") | |
.prop("checked", ""); | |
} | |
// Set proper event values for user's callback | |
ev.state = 1; | |
ev.action = "postLoad"; | |
ev.hideOverlay = true; | |
// Else, we must be passed the upload page... | |
// set the state to 3 (passed upload) and bind a function to the | |
// iframe document's form element (which in turn calls the user defined | |
// onPageChange event prior to sending the form on. | |
} else { | |
opt.log( | |
"Upload.onIframeChange(" + opt._iframeLoadId + | |
"): File was uploaded to server!" | |
); | |
ev.state = 3; | |
ev.action = "postLoad"; | |
ev.hideOverlay = true; | |
ev.file = opt.getUploadedFileRow(); | |
// If the current page is the 'uploadDonePage', then set | |
// flag in the event, set flag to not hide the overlay | |
// and insert message indicating upload is done. | |
if (Upload.isSameUrlPage(ev.pageUrl, opt.uploadDonePage)) { | |
opt.log("Upload.onIframeChange(" + opt._iframeLoadId + | |
"): Upload widget process DONE!"); | |
ev.isUploadDone = true; | |
ev.hideOverlay = false; | |
// Show the busy indicator and success message. | |
opt.showHideBusy(); | |
opt.showHideSuccess(); | |
// Else, page is not the uploadDonePage... manipulate the form's | |
// onsubmit event. | |
} else { | |
opt.log("Upload.onIframeChange(" + opt._iframeLoadId + | |
"): Post Upload Form being displayed! Hooking into form.onsubmit!"); | |
var formOnSubmit = form.prop("onsubmit"); | |
// Show only the form in the page | |
form | |
.children(":visible") | |
.css("display", "none") | |
.addClass("ptWasVisible") | |
.end() | |
.find("input[title='Name']") | |
.closest("div[id^='WebPart']") | |
.appendTo(page.find("form")) | |
// 8/30/2013: ensure the UI is visible. | |
// Just in case it was at root of form | |
.css("display", "") | |
.removeClass("ptWasVisible"); | |
// SP seems to have a good hold of the Form, because | |
// we are unable o bind an event via $. Thus: | |
// The form's onsubmit has to be overriden with our | |
// own function... The original function was captured | |
// above, thus it will triggered... but we now control | |
// when we trigger it. | |
// FIXME: this does not seem to do anything (at least in FF) | |
form[0].onsubmit = function(){ | |
opt.log("Upload.onIframeChange(" + id + "): iframe form.onsubmit triggered!"); | |
// Show the overlay without animation. | |
opt.showHideBusy(); | |
var allowFormToContinue = true; | |
// if the user defined a function, then run it now and | |
// exit if the resposne is false (stop submition) | |
if ($.isFunction(opt.onPageChange)) { | |
allowFormToContinue = opt.onPageChange.call( | |
opt.$ele, | |
$.extend({}, ev, {state: 3, action: "preLoad"})); | |
} | |
if (allowFormToContinue === false) { | |
opt.showHideBusy(true); | |
return allowFormToContinue; | |
} | |
// if SP had a onSubmit defined, then execute it now and | |
// exit if the resposne is false (stop submition) | |
if ($.isFunction(formOnSubmit)) { | |
allowFormToContinue = formOnSubmit(); | |
} | |
if (allowFormToContinue === false) { | |
opt.showHideBusy(true); | |
return allowFormToContinue; | |
} | |
// hide the form before continuing | |
opt.showHideFullForm(true); | |
// Return true, allowing the form to be submitted. | |
return allowFormToContinue; | |
}; | |
} //end: if(): onUpdateDonePage? or not? | |
// Bind a function to the iframe WINDOW object for when it is | |
// unloaded.. At this point, nothing can be done to prevent | |
// the page from being submitted, but we can still execute | |
// the caller's function. | |
$(e.find("iframe")[0].contentWindow).unload(function(evv){ | |
opt.log("Upload.onIframeChange(" + opt._iframeLoadId + | |
"): iframe.unload() triggered!"); | |
// Make the busy panel visible without animation | |
// opt.$buttonCntr.css("display", ""); | |
opt.showHideBusy(); | |
opt.showHideFullForm(true); | |
if ($.isFunction(opt.onPageChange)) { | |
return opt.onPageChange.call( | |
opt.$ele, | |
$.extend({}, ev, {state: 3, action: "preLoad"}) ); | |
} | |
}); | |
}//end:if: is uploadPage? or past the file uploaded? | |
opt.log("Upload.onIframeChange(" + opt._iframeLoadId + | |
"): iframe page setup done!"); | |
// Call user event function | |
if (opt.onPageChange) { | |
opt.onPageChange.call(opt.$ele, ev); | |
} | |
// Hide our overlay area | |
if (ev.action.toLowerCase() !== "postload" || ev.hideOverlay === true) { | |
opt.showHideBusy(true); | |
if (ev.isUploadDone === false && ev.state === 3) { | |
opt.showHideFullForm(); | |
} | |
} | |
// If Upload is DONE, then reset form | |
if (ev.isUploadDone) { | |
// Reset upload form | |
opt.resetWidget(); | |
// Wait 4 seconds then hide success message | |
opt.$successCntr.animate( | |
{ opacity: 1 }, | |
3000, | |
function(){ | |
opt.showHideSuccess(true); | |
} | |
); | |
if ($.isFunction(opt.onUploadDone)) { | |
opt.onUploadDone.call(opt.$ele, ev.file); | |
} | |
} | |
return; | |
}, | |
500);//end:setTimeout() | |
};// Upload.onIframeChange | |
/** | |
* Determines whether two URLs are the same page. URLs could be the same page, but | |
* have difference url params. This function will look only at the page (eveything | |
* up to the "?") and will then compare them. | |
* | |
* @param {String} u1 First URL | |
* @param {String} u2 Second URL | |
* @return {Boolean} | |
* @memberOf jQuery.pt | |
* | |
*/ | |
Upload.isSameUrlPage = function(u1, u2) { | |
if (!u1 || !u2) { return false; } | |
var matchString = u1; | |
if (u1.indexOf("?") > -1) { | |
matchString = u1.substring(0, u1.indexOf("?")); | |
} | |
if (u2.indexOf(matchString) == 0) { | |
return true; | |
} else { | |
return false; | |
} | |
};// Upload.isSameUrlPage() | |
/** | |
* Uses sharepoint default function from {/_layouts/1033/core.js} | |
* for escaping urls. | |
* | |
* @function | |
*/ | |
$.pt.getEscapedUrl = escapeProperly; | |
/** | |
* Uses sharepoint default function from {/_layouts/1033/core.js} | |
* to un-escape urls. | |
* @function | |
*/ | |
$.pt.getUnEscapedUrl = unescapeProperly; | |
/** | |
* Given a List name or a DOcument Library name, this method will retrieve | |
* it's UID from SP. | |
* | |
* @param {String} listName The name of the list. | |
* @return {String} | |
* @memberOf jQuery.pt | |
* | |
*/ | |
$.pt.getListUID = function(listName) { | |
if (!listName) { | |
return ""; | |
} | |
var id = ""; | |
if ($.pt._cache["getListUID:" + listName]) { | |
id = $.pt._cache["getListUID:" + listName]; | |
return id; | |
} | |
$().SPServices({ | |
operation: "GetList", | |
listName: listName, | |
async: false, | |
completefunc: function (xData, Status) { | |
id = $(xData.responseXML).find("List").attr("ID"); | |
} | |
}); | |
if (id) { | |
$.pt._cache["getListUID:" + listName] = id; | |
} | |
return id; | |
};// $.pt.getListUID() | |
/** | |
* Logs information to the output console. | |
* | |
* @param {String} msg | |
*/ | |
Upload.log = (function(){ | |
var logit, $output, | |
n = 1, | |
c = 0, | |
isNative = false, | |
initDone = false, | |
bgColor = [ | |
'#FFFFFF', | |
'#F5F5F2' | |
]; | |
if ( typeof console === "undefined" | |
|| typeof console.debug === "undefined" | |
) { | |
logit = function(){ | |
var i,j, | |
data = ""; | |
for(i=0,j=arguments.length; i<j; i++){ | |
data += '<div style="padding: .1em .1em;background-color:' + | |
bgColor[c] + '"><span>[' + n + '] </span>' + | |
arguments[i] + '</div>'; | |
n++; | |
if (c === 1) { | |
c = 0; | |
} else { | |
c = 1; | |
} | |
} | |
if (data) { | |
$output.append(data); | |
if (!$output.dialog("isOpen")) { | |
$output.dialog("open"); | |
} | |
} | |
}; | |
} else { | |
isNative = true; | |
} | |
return function(){ | |
if (!initDone) { | |
initDone = true; | |
if (!isNative) { | |
$output = $("<div><h2>SPWidgets Debug Output</h2></div>") | |
.appendTo("body") | |
.dialog({ | |
title: "Debug output", | |
height: 300 | |
}); | |
} | |
} | |
if (isNative) { | |
var i,j; | |
for(i=0,j=arguments.length; i<j; i++){ | |
console.debug(arguments[i]); | |
}; | |
} else { | |
logit.apply(this, arguments); | |
} | |
}; | |
})(); // end: Upload.log(); | |
/** | |
* @property | |
* Stores the Style sheet that is inserted into the page the first | |
* time SPControlUpload is called. | |
* Value is set at build time. | |
* | |
*/ | |
Upload.StyleSheet = "/**\n" | |
+ " * FILE: jquery.SPControlUpload.css\n" | |
+ " * \n" | |
+ " * \n" | |
+ " */\n" | |
+ ".spcontrolupload .mainContainer {\n" | |
+ " position: relative;\n" | |
+ " display:block;\n" | |
+ " height: 4em;\n" | |
+ "}\n" | |
+ "\n" | |
+ ".spcontrolupload .iFrameWindow,\n" | |
+ ".spcontrolupload .buttonPane,\n" | |
+ ".spcontrolupload .spwidget-success-cntr,\n" | |
+ ".spcontrolupload .loadingOverlay {\n" | |
+ " position: absolute;\n" | |
+ " top: 0px;\n" | |
+ " height: 3em;\n" | |
+ " width: 100%;\n" | |
+ "}\n" | |
+ ".spcontrolupload .buttonPane {\n" | |
+ " left: 0px;\n" | |
+ " width: 10%;\n" | |
+ " overflow: hidden;\n" | |
+ " cursor: pointer;\n" | |
+ "}\n" | |
+ ".spcontrolupload .buttonPane .upload_button {\n" | |
+ " font-weight: bold;\n" | |
+ " font-size: 1.1em;\n" | |
+ " text-align: center;\n" | |
+ " margin-top: .8em;\n" | |
+ "}\n" | |
+ "\n" | |
+ ".spcontrolupload .iFrameWindow {\n" | |
+ " width: 90%;\n" | |
+ " left: 10%;\n" | |
+ " overflow: hidden;\n" | |
+ "}\n" | |
+ ".spcontrolupload .iFrameWindow iframe {\n" | |
+ " overflow: auto;\n" | |
+ " width: 100%;\n" | |
+ " height: 99%;\n" | |
+ "}\n" | |
+ "\n" | |
+ ".spcontrolupload .spwidget-show-full-form .iFrameWindow {\n" | |
+ " overflow: auto;\n" | |
+ " width: 100%;\n" | |
+ " margin: 0em;\n" | |
+ " left: 0px;\n" | |
+ " right: auto;\n" | |
+ "}\n" | |
+ "\n" | |
+ ".spcontrolupload .loadingOverlayMsg {\n" | |
+ " font-size: 1em;\n" | |
+ " background-position: left top;\n" | |
+ " background-repeat: no-repeat;\n" | |
+ " background-image: url('/_layouts/images/loadingcirclests16.gif');\n" | |
+ " margin: 0.5em;\n" | |
+ " padding-left: 25px;\n" | |
+ "}\n" | |
+ "\n" | |
+ ".spcontrolupload .spwidget-success-cntr,\n" | |
+ ".spcontrolupload .spwidget-error-cntr {\n" | |
+ " display: none;\n" | |
+ "}\n" | |
+ ".spcontrolupload div.spwidget-msg-cntr {\n" | |
+ " margin: 0.5em .5em .5em 3em;\n" | |
+ " font-size: 1em;\n" | |
+ " background-position: left top;\n" | |
+ " background-repeat: no-repeat; \n" | |
+ "}\n" | |
+ ".spcontrolupload .spwidget-close {\n" | |
+ " color: red;\n" | |
+ " font-size: xx-small;\n" | |
+ " font-weight: bold;\n" | |
+ " vertical-align: super;\n" | |
+ " cursor: pointer;\n" | |
+ "}\n" | |
+ "\n" | |
+ ".spcontrolupload .spwidget-success-cntr div.spwidget-msg-cntr {\n" | |
+ " background-image: url('/_layouts/images/STS_ListItem_43216.gif');\n" | |
+ " padding-left: 30px;\n" | |
+ "}\n" | |
+ "\n" | |
+ ".spcontrolupload .spwidget-error-cntr {\n" | |
+ " bottom: -1.5em;\n" | |
+ " left: 0px;\n" | |
+ " width: 100%;\n" | |
+ " position: absolute;\n" | |
+ "}\n" | |
+ "\n" | |
+ ".spcontrolupload-dev-mode .iFrameWindow {\n" | |
+ " overflow: auto !important;\n" | |
+ " height: auto !important;\n" | |
+ "}\n" | |
+ "\n" | |
+ "\n"; | |
//_HAS_SPUPLOAD_CSS_TEMPLATE_ | |
/** | |
* @property | |
* Stores the HTML templates used by this widget. | |
* Populated during the build process from the | |
* html.SPControlUpload.html file | |
*/ | |
Upload.HtmlUI = "<div class=\"SPControlUploadUI spcontrolupload\">\n" | |
+ " <div class=\"mainContainer\">\n" | |
+ " <div class=\"buttonPane ui-state-default\">\n" | |
+ " <div class=\"upload_button\">\n" | |
+ " Upload\n" | |
+ " </div>\n" | |
+ " </div>\n" | |
+ " <div class=\"iFrameWindow ui-state-default\">\n" | |
+ " <iframe name=\"SPControlUploadUI\" frameborder=\"0\" scrollbars=\"yes\" scrolling=\"yes\"></iframe>\n" | |
+ " </div>\n" | |
+ " <div class=\"loadingOverlay ui-widget-content\">\n" | |
+ " <div class=\"loadingOverlayMsg\"></div>\n" | |
+ " </div>\n" | |
+ " <div class=\"spwidget-success-cntr ui-widget-content\">\n" | |
+ " <div class=\"spwidget-msg-cntr\">\n" | |
+ " <span class=\"spwidget-msg\">Upload Successful!</span> \n" | |
+ " <span class=\"spwidget-close\">x</span> \n" | |
+ " </div>\n" | |
+ " </div>\n" | |
+ " <div class=\"spwidget-error-cntr ui-state-error\">\n" | |
+ " <div class=\"spwidget-msg-cntr\">\n" | |
+ " <span class=\"spwidget-msg\">Error</span> \n" | |
+ " <span class=\"spwidget-close\">x</span> \n" | |
+ " </div>\n" | |
+ " </div>\n" | |
+ " </div>\n" | |
+ "</div>\n" | |
+ "\n" | |
+ "<div id=\"SPControlUploadModUI\" \n" | |
+ " style=\"\n" | |
+ " position: absolute;\n" | |
+ " width: 99.9%;\n" | |
+ " height: 99.9%;\n" | |
+ " left: 0px;\n" | |
+ " top: 0px;\n" | |
+ " padding-left: .5em;\n" | |
+ " background-color: white;\">\n" | |
+ " <div class=\"SPControlUploadModUIFileSelected\"\n" | |
+ " style=\"\n" | |
+ " background-position: left center;\n" | |
+ " background-repeat: no-repeat;\n" | |
+ " background-image: url('/_layouts/images/urn-content-classes-smartfolder16.gif');\n" | |
+ " padding: 0.5em 2em;\">Select...</div>\n" | |
+ "</div>\n"; | |
//_HAS_SPUPLOAD_HTML_TEMPLATE_ | |
})(jQuery); | |
/** | |
* jquery.SPDateField.js | |
* The SPDateField widget. Introduced with v2.2, August 2013 | |
* | |
* BUILD: Paul:November 04, 2013 07:09 PM | |
* | |
*/ | |
;(function($){ | |
"use strict"; | |
/*jslint nomen: true, plusplus: true */ | |
/*global SPWidgets */ | |
/** | |
* @class SPDate | |
* @namespace | |
*/ | |
var SPDate = {}; | |
/** @property {Boolean} Is initialization done */ | |
SPDate.isInitDone = false; | |
/** @property {String} Event namespace */ | |
SPDate.evNamespace = '.spwidgets.spdatefield'; | |
/** | |
* Default options. | |
* @member Inst.opt | |
* @memberOf Inst.opt | |
*/ | |
$.SPWidgets.defaults.date = { | |
allowMultiples: false, | |
delimeter: ";", | |
remainOpen: true, | |
datepicker: { | |
dateFormat: 'mm/dd/yy', | |
buttonImage: '/_layouts/images/CALENDAR.GIF', | |
showOn: "both", | |
buttonImageOnly: true | |
}, | |
dateTemplate: '{{date}} <span class="spwidgets-item-remove">[x]</span>', | |
showTimepicker: false, | |
timeFormat: ' {{hour}}:{{minutes}} {{ampm}}', | |
timeUTC: true, | |
labelHour: 'Hour', | |
labelMinutes: 'Minutes', | |
labelAMPM: 'AM|PM', | |
labelTime: 'Time', | |
labelSet: 'Set' | |
}; | |
/** | |
* Inserts a jQuery datepicker into the UI that allows the user to | |
* pick a date and save the Sharepoint format of that date to the | |
* original input field that this widget was bound to. | |
* Display format could be defined as the local locale while the | |
* value that will actually be stored in the input value will be | |
* the format expected by SharePoint webservices mainly ISO format | |
* YYYY-MM-DD. | |
* | |
* @param {Object} [options] | |
* | |
* @param {Boolean} [options.allowMultiples=false] | |
* @param {String} [options.delimeter=";"] | |
* @param {Boolean} [options.remainOpen=true] | |
* @param {Object} [options.datepicker={...}] | |
* @param {String} [options.dateTemplate=""] | |
* @param {Boolean} [options.showTimepicker=false] | |
* @param {String} [options.timeFormat='{{our}}:{{minutes}} {{ampm}}'] | |
* @param {Boolean} [options.timeUTC=true] | |
* @param {String} [options.labelHour='Hour'] | |
* @param {String} [options.labelMinutes='Minutes'] | |
* @param {String} [options.labelAMPM='AM|PM'] | |
* @param {String} [options.labelTime='Time'] | |
* @param {String} [options.labelSet='Set'] | |
* | |
* return {jQuery} this | |
* | |
* This widget supports the following methods: | |
* | |
* $().SPDateField("reset"); | |
* $().SPDateField("getDate"); | |
* $().SPDateField("setDate", dates[], "format"); | |
* $().SPDateField("removeDate", dates[], "format"); | |
* $().SPDateField("destroy"); | |
* | |
*/ | |
$.fn.SPDateField = function(options){ | |
var arg = arguments, | |
inputEle = this; | |
// If initialization is not yet done, then do it now | |
if ( !SPDate.isInitDone ) { | |
SPDate.isInitDone = true; | |
if ( SPDate.styleSheet !== "" ) { | |
$('<style type="text/css">' + "\n\n" + | |
SPDate.styleSheet + "\n\n</style>" ) | |
.prependTo("head"); | |
} | |
$("body").on("click" + SPDate.evNamespace, SPDate.onPageClick); | |
} | |
// Process Methods | |
if (typeof options === "string") { | |
return (function(){ | |
var action = String(arg[0]).toLowerCase(), | |
resp = inputEle; | |
// Loop through all inputs and process the method | |
// on it. Note that for methods that return data | |
// if user defined more than one element in the | |
// selection, only the data for the last item on | |
// that selection will be returned. | |
$(inputEle).each(function(i, thisInput){ | |
if (!$(inputEle).hasClass("hasSPDateField")) { | |
return; | |
} | |
var $this = $(thisInput), | |
Inst = $this.data("SPDateFieldInstance"); | |
if (Inst && $this.length > 0) { | |
switch(action) { | |
//------> GET DATE METHOD: dateObj = $().SPDateField("getDate") | |
case "getdate": | |
resp = Inst.getDate(); | |
break; | |
//------> SET DATE METHOD: $().SPDateField("setDate", dates, format) | |
case "setdate": | |
if (arg[1]) { | |
Inst.setDate({ | |
date: arg[1], | |
format: (arg[2] || Inst.opt.datepicker.dateFormat) | |
}); | |
} | |
break; | |
//------> REMOVE DATE METHOD: $().SPDateField("removeDate", dates, format) | |
case "removedate": | |
if (arg[1]) { | |
Inst.removeDate({ | |
date: arg[1], | |
format: (arg[2] || Inst.opt.datepicker.dateFormat) | |
}); | |
} | |
break; | |
//------> RESET METHOD: $().SPDateField("reset") | |
case "reset": | |
Inst.reset(); | |
break; | |
//------> DESTROY METHOD: $().SPDateField("destroy") | |
case "destroy": | |
Inst.destroy(); | |
break; | |
} //end: switch() | |
} | |
}); | |
return resp; | |
})(); | |
} //end: Method? --------------------------- | |
// BUILD the widget on each input element | |
// provided by the user's selection | |
return this.each(function(){ | |
/** | |
* @class SPDate | |
*/ | |
var Inst = { | |
/** @property {jQuery} The input element used by the user */ | |
$ele: $(this).addClass("hasSPDateField") | |
}; | |
if (!Inst.$ele.is("input[type='text']")) { | |
return; | |
} | |
/** | |
* @property {String} The original value in the input | |
* @member Inst | |
* @memberOf Inst | |
*/ | |
Inst.eleOrigVal = Inst.$ele.val(); | |
Inst.$ele.val(""); | |
/** | |
* @property {Object} The input options after defaults | |
* @member Inst | |
* @memberOf Inst | |
*/ | |
Inst.opt = $.extend(true, {}, $.SPWidgets.defaults.date, options); | |
/** | |
* @property {jQuery} the UI container for the Date field. | |
* @member Inst | |
* @memberOf Inst | |
*/ | |
Inst.$ui = $(SPDate.htmlTemplate) | |
.filter("div.spwidget-date-cntr") | |
.clone() | |
.insertAfter(Inst.$ele) | |
.css("display", "none"); | |
/** | |
* @property {jQuery} the Datepicker input field. | |
* @member Inst | |
* @memberOf Inst | |
*/ | |
Inst.$input = Inst.$ui | |
.find("input[name='SPDateFieldInput']") | |
.val(Inst.$ele.val()); | |
/** | |
* @property {jQuery} The container used to display date when allowMuliples is true. | |
* @member Inst | |
* @memberOf Inst | |
*/ | |
Inst.$dtCntr = Inst.$ui.find("div.spwidget-date-selected-cntr"); | |
/** | |
* Returns the date selected by the user. An object is returned | |
* with the date formatted in differnt ways. See below. | |
* | |
* @return {Object} | |
* The returned objec will have the following format: | |
* | |
* { | |
* input: 'value of input', | |
* dates: [ | |
* 'date 1', | |
* 'date 2' | |
* ] | |
* } | |
* | |
*/ | |
Inst.getDate = function() { | |
var dtObj = { | |
input: Inst.$ele.val(), | |
dates: [] | |
}; | |
if (dtObj.input) { | |
if (Inst.opt.allowMultiples) { | |
dtObj.dates = dtObj.input.split(Inst.opt.delimeter); | |
} else { | |
dtObj.dates.push(dtObj.input); | |
} | |
} | |
return dtObj; | |
}; //end: Inst.getDate() | |
/** | |
* Resets the widget to its initial state, which could have | |
* had a prepopluated value on it. | |
* | |
* @return {Object} Inst | |
*/ | |
Inst.reset = function() { | |
Inst.$input.val("").datepicker("hide"); | |
Inst.$ele.val("").change(); | |
Inst.$dtCntr.empty(); | |
return Inst; | |
}; //end: Inst.reset() | |
/** | |
* Sets a date on the date widgets. Upon setting the date, the | |
* input's change() event is triggered. | |
* | |
* @param {Object} setDateOpt | |
* | |
* @param {Date|Array|String} setDateOpt.date | |
* The date or array of dates to set on the picker. | |
* | |
* @param {Boolean} [setDateOpt.setDatepicker=true] | |
* Then true, the datepicker jQuery UI widget input will | |
* be set to the value that was provided via this method. | |
* Used only when allowMultiples is false | |
* | |
* @param {String} [setDateOpt.format=Inst.opt.datepicker.dateFormat] | |
* The format of the dates provided on input. This param | |
* is used only if the input date (or one of them) is a | |
* string. | |
* | |
* @param {Boolean} [setDateOpt.triggerEvent=true] | |
* | |
* | |
* | |
* @return {Object} Inst | |
*/ | |
Inst.setDate = function(setDateOpt) { | |
var opt = $.extend( | |
{}, | |
{ | |
date: '', | |
time: '', | |
format: Inst.opt.datepicker.dateFormat, | |
setDatepicker: true, | |
triggerEvent: true | |
}, | |
setDateOpt | |
), | |
eleVal = Inst.$ele.val(), | |
dtShow = ''; | |
if (!opt.date) { | |
return Inst; | |
} | |
if (!$.isArray(opt.date)) { | |
opt.date = [ opt.date ]; | |
} | |
// Loop through each date and create the string that will be used | |
// to set the date on the widget. | |
$.each(opt.date, function(i, dt){ | |
var dtObj = dt, | |
dt1 = '', | |
dt2 = ''; | |
// If this date object is not an instance of Date, then | |
// parse it into a Date object. | |
// If the string has a T on it, then we assume that it is | |
// an ISO 8601 compliant string and use the $.SPWidgets.parseDateString | |
// to get a Date object. | |
// Else, it could be a date in the format defined by the datepicker | |
// date format param. | |
if (!(dtObj instanceof Date)) { | |
dtObj = String(dtObj); | |
if (dtObj.indexOf("T") > -1) { | |
dtObj = $.SPWidgets.parseDateString(dtObj); | |
} else { | |
try { | |
dtObj = $.datepicker.parseDate(opt.format, dt); | |
} catch(e){ | |
return Inst; | |
} | |
} | |
} | |
dt1 = $.datepicker.formatDate('yy-mm-dd', dtObj); | |
dt2 = $.datepicker | |
.formatDate(Inst.opt.datepicker.dateFormat, dtObj); | |
if (Inst.opt.showTimepicker) { | |
dt1 = $.SPWidgets.SPGetDateString(dtObj, Inst.opt._timeFmt); | |
dt2 += Inst.$timepicker.formatTime(dtObj); | |
} | |
// AllowMultiples = false | |
if (!Inst.opt.allowMultiples) { | |
eleVal = dt1; | |
dtShow = dt2; | |
// allowMultiples = true and date not yet stored | |
} else if (eleVal.indexOf(dt1) < 0) { | |
if (eleVal) { | |
eleVal += Inst.opt.delimeter; | |
} | |
eleVal += dt1; | |
dtShow += '<span class="spwidgets-item" data-spwidget_dt1="' + | |
dt1 + '" data-spwidget_dt2="' + dt2 + '">' + | |
$.SPWidgets.fillTemplate({ | |
tmplt: Inst.opt.dateTemplate, | |
data: { date: dt2 } | |
}) + | |
' </span>'; | |
} | |
}); | |
// If allow multiple is true, then set teh multiple dates | |
// on the display area. Then set the input value and trigger | |
// change event | |
if (Inst.opt.allowMultiples) { | |
Inst.$dtCntr.append(dtShow); | |
} else if (opt.setDatepicker) { | |
Inst.$input.val(dtShow); | |
} | |
Inst.$ele.val(eleVal); | |
if (opt.triggerEvent) { | |
Inst.$ele.change(); | |
} | |
return Inst; | |
}; //end: Inst.setDate() | |
/** | |
* Removes a date from the list of selected dates. | |
* | |
* @param {Object} removeDateOpt | |
* | |
* @param {Date|String|Array} date | |
* The date or array of dates to be removed. Can be | |
* Date objects or strings. If defined as a string | |
* the 'format' option should be set accordingly | |
* | |
* @return {Object} Inst | |
*/ | |
Inst.removeDate = function(removeDateOpt){ | |
var opt = $.extend( | |
{}, | |
{ | |
date: '', | |
format: Inst.opt.datepicker.dateFormat | |
}, | |
removeDateOpt | |
), | |
eleDtObj = Inst.getDate(); | |
if (!opt.date) { | |
return Inst; | |
} | |
if (!$.isArray(opt.date)) { | |
opt.date = [ opt.date ]; | |
} | |
$.each(opt.date, function(i, dt){ | |
var dtObj = dt, | |
dt1 = '', | |
dt1Regex = ''; | |
if (!(dtObj instanceof Date)) { | |
try { | |
dtObj = $.datepicker.parseDate(opt.format, dt); | |
} catch(e){ | |
return Inst; | |
} | |
} | |
// Get the internal representation of the date (ISO 8601) | |
// so that we can remove it from the list of selected | |
// dates. The internal representation can be just the date | |
// or the date + time. | |
// The dt1Regex is used to search and replace the | |
// date in the input to where this widget was bound, which | |
// could include multiple dates. | |
if (Inst.opt.showTimepicker) { | |
dt1 = $.SPWidgets.SPGetDateString(dtObj, Inst.opt._timeFmt); | |
} else { | |
dt1 = $.datepicker.formatDate('yy-mm-dd', dtObj); | |
} | |
dt1Regex = new RegExp( | |
"(" + Inst.opt.delimeter + ")?" + dt1, | |
"g"); | |
eleDtObj.input = eleDtObj.input.replace(dt1Regex, ""); | |
if (Inst.opt.allowMultiples) { | |
Inst.$dtCntr | |
.find("span[data-spwidget_dt1='" + dt1 + "']") | |
.remove(); | |
} | |
}); | |
// Set the value on bound input. | |
// Clean up the new string (misc. delimeteres at begining or | |
// end of string), set it to the input field and trigger event. | |
eleDtObj.input = eleDtObj.input | |
.replace((new RegExp("^" + Inst.opt.delimeter)), "") | |
.replace((new RegExp(Inst.opt.delimeter + "$")), ""); | |
Inst.$ele.val(eleDtObj.input).change(); | |
return Inst; | |
}; //end: Inst.removeDate() | |
/** | |
* Removes the widget from the page and makes the original | |
* Element visible | |
*/ | |
Inst.destroy = function() { | |
Inst.$ele.removeData("SPDateFieldInstance"); | |
Inst.$ele.removeClass("hasSPDateField").css("display", ""); | |
Inst.$ui.css("display", "none"); | |
Inst.$input.datepicker("hide"); | |
Inst.$input.datepicker("destroy"); | |
if (Inst.$timepicker) { | |
Inst.$timepicker.$timePicker.off(".spdatefield"); | |
Inst.$input.off(".spdatefield"); | |
} | |
Inst.$ui.remove(); | |
}; //end: Inst.destroy() | |
/** | |
* Creates the date picker on this field. Depending on the | |
* input options, this could be a strait jQuery UI datepicker | |
* or a customized picker that allows selection of time as well. | |
* | |
* @return {Object} Date time Picker (if showTimepicker is true) | |
*/ | |
Inst.createDatePicker = function() { | |
var wdg = {}; | |
// If showTimepicker is true, then lets build our own | |
// date and time picker, which wraps jQuery datepicker. | |
if (Inst.opt.showTimepicker) { | |
wdg.$selectorCntr = $(SPDate.htmlTemplate) | |
.filter("div.spwidget-datetime-selector") | |
.clone() | |
.appendTo(Inst.$input.parent()) | |
.css("display", "none"); | |
wdg.$datePicker = wdg.$selectorCntr.find("div.spwidget-date-selector"); | |
wdg.$timePicker = wdg.$selectorCntr.find("div.spwidget-time-selector"); | |
wdg.$setButton = wdg.$selectorCntr.find("div.spwidget-btn-set"); | |
wdg.$hourSelect = wdg.$timePicker.find("select.spwidget-hour"); | |
wdg.$minSelect = wdg.$timePicker.find("select.spwidget-min"); | |
wdg.$ampmSelect = wdg.$timePicker.find("select.spwidget-ampm"); | |
wdg.heightDone = false; | |
wdg.firstShowDone = false; | |
/** | |
* Returns an object with the time currently selected. | |
* | |
* @return {Object} | |
* An object with the following format: | |
* | |
* { | |
* hour: '', | |
* hour24: '', | |
* minutes: '' | |
* ampm: '' | |
* } | |
*/ | |
wdg.getTime = function() { | |
var time = { | |
hour: wdg.$hourSelect.val(), | |
minutes: wdg.$minSelect.val(), | |
ampm: wdg.$ampmSelect.val() | |
}; | |
time.hour24 = time.hour; | |
if (time.ampm === "PM" && time.hour !== "12") { | |
time.hour24 = String(parseInt(time.hour) + 12); | |
} else if (time.ampm === "AM" && time.hour === "12") { | |
time.hour24 = "0"; | |
} | |
return time; | |
}; //end: wdg.getTime() | |
/** | |
* Formats the time on the widget, either based on the | |
* values returned from getTime() or a Date object. | |
* | |
* @param {Date|Object} time | |
* The object that will be used to format the Time. | |
* | |
* @return {String} | |
* Date formated with the dateFormat input parameter | |
*/ | |
wdg.formatTime = function(time) { | |
var timeObj = time, | |
timeFormated = ''; | |
if (time instanceof Date) { | |
timeObj = { | |
hour: time.getHours(), | |
hour24: String(time.getHours()), | |
minutes: String(time.getMinutes()), | |
ampm: 'AM' | |
}; | |
if (timeObj.hour > 12) { | |
timeObj.hour = String(timeObj.hour - 12); | |
timeObj.ampm = 'PM'; | |
} else if (timeObj.hour === 12) { | |
timeObj.ampm = 'PM'; | |
} | |
timeObj.hour = String(timeObj.hour); | |
if (timeObj.hour === "0") { | |
timeObj.hour = "12"; | |
} | |
if (String(timeObj.minutes).length < 2) { | |
timeObj.minutes = "0" + timeObj.minutes; | |
} | |
} else if (!time) { | |
timeObj = wdg.getTime(); | |
} | |
timeFormated = $.SPWidgets.fillTemplate( | |
Inst.opt.timeFormat, | |
timeObj | |
); | |
return timeFormated; | |
}; //end: wdg.formatTime() | |
/** | |
* Updates the widget date/time with what's currently selected. | |
* If no date is selected, Today will be used. | |
* | |
* @return {undefined} | |
*/ | |
wdg.updateDateTime = function(dtObj){ | |
var time = wdg.getTime(); | |
// If dtObj is not a Date object, then create it now. | |
// First try to get it from the Datepicker... If thats | |
// Null (not yet selected by user), then just create a | |
// default Date. | |
if (!(dtObj instanceof Date)) { | |
dtObj = wdg.$datePicker.datepicker("getDate"); | |
if (dtObj === null) { | |
dtObj = new Date(); | |
} | |
} | |
// Add time elements to date object | |
dtObj.setHours(time.hour24); | |
dtObj.setMinutes(time.minutes); | |
Inst.setDate({ | |
date: dtObj, | |
format: Inst.opt.datepicker.dateFormat, | |
setDatepicker: true | |
}); | |
return; | |
}; //end: wdg.updateDateTime() | |
/** | |
* Makes the Time picker visible on the page. | |
* | |
* @return {undefined} | |
*/ | |
wdg.showPicker = function() { | |
wdg.$selectorCntr | |
.show(function(){ | |
var currentDate, tmpVal; | |
if (!wdg.heightDone) { | |
wdg.heightDone = true; | |
$.SPWidgets.makeSameHeight( | |
wdg.$datePicker | |
.find("div.ui-datepicker-inline") | |
.add(wdg.$timePicker) | |
); | |
} | |
// If this is the first time showing the picker, | |
// then pre-set the picker to the last date that | |
// was selected. | |
// If no date was selected (or was pre-set on the | |
// input), then create a new date object (now) | |
if (!wdg.firstShowDone) { | |
wdg.firstShowDone = true; | |
currentDate = Inst.getDate(); | |
if (currentDate.dates.length) { | |
currentDate = $.SPWidgets | |
.parseDateString( | |
currentDate.dates[ | |
currentDate.dates.length - 1 | |
] | |
); | |
} else { | |
currentDate = new Date(); | |
} | |
tmpVal = currentDate.getHours(); | |
if (tmpVal === 0) { | |
tmpVal = "12"; | |
} else if (tmpVal > 12) { | |
tmpVal = (tmpVal - 12); | |
} | |
wdg.$hourSelect.val(tmpVal); | |
wdg.$minSelect.val("00"); | |
if (currentDate.getHours() > 11) { | |
wdg.$ampmSelect.val("PM"); | |
} else { | |
wdg.$ampmSelect.val("AM"); | |
} | |
wdg.$datePicker.datepicker("setDate", currentDate); | |
}//end: if(): pre-set the picker values | |
}) | |
.position({ | |
my: "left top", | |
at: "left bottom", | |
of: Inst.$input | |
}); | |
return; | |
}; //end: wdg.showPicker() | |
/* ------------------------------------------------------ */ | |
/* ------------------------------------------------------ */ | |
// Remove alt field updates from datepicker. We'll handle it | |
// with the time picker | |
Inst.opt.datepicker.altFormat = ''; | |
Inst.opt.datepicker.altField = ''; | |
// If user set the icon option in the Datepicker, then need | |
// to build it manually | |
if (Inst.opt.datepicker.buttonImage) { | |
$('<img class="ui-datepicker-trigger" src="' + | |
Inst.opt.datepicker.buttonImage + | |
'" alt="..." title="...">' | |
) | |
.appendTo(Inst.$input.parent()) | |
.on("click" + SPDate.evNamespace, function(){ | |
wdg.showPicker(); | |
}); | |
} | |
// If allowMultiples is true, then make set button visible | |
if (Inst.opt.allowMultiples) { | |
wdg.$selectorCntr.addClass("spwidget-date-multiples-cntr"); | |
wdg.$setButton.find("div.spwidget-btn") | |
.button({label: Inst.opt.labelSet}) | |
.on("click" + SPDate.evNamespace, function(ev){ | |
wdg.updateDateTime(); | |
return this; | |
}); | |
} | |
// Apply the Labels for the time picker for this instance | |
wdg.$timePicker | |
.find("div.ui-widget-header") | |
.html(Inst.opt.labelTime) | |
.end() | |
.find("div.spwidget-time-hour > label") | |
.html(Inst.opt.labelHour) | |
.end() | |
.find("div.spwidget-time-min > label") | |
.html(Inst.opt.labelMinutes) | |
.end() | |
.find("div.spwidget-time-ampm > label") | |
.html(Inst.opt.labelAMPM) | |
.end(); | |
// Set up a listener on the datepicker widget so that when user picks | |
// a date, we catch it and add the time portion to it. | |
// Let's also save the existing onSelect function, if one was defined | |
// on input, so we can call it later. | |
if ($.isFunction(Inst.opt.datepicker.onSelect)) { | |
Inst.opt.datepicker._onSelect = Inst.opt.datepicker.onSelect; | |
} | |
// Ensure only 1 month | |
Inst.opt.datepicker.numberOfMonths = 1; | |
// Setup the Datepicker onSelect event, which will build a Date | |
// object of the date the user selected and call updateDateTime() | |
// to set teh widget. | |
Inst.opt.datepicker.onSelect = function(dateText, dtPicker){ | |
// If allowMultiples is true, then exit if | |
// this click is not the SET button | |
if (Inst.opt.allowMultiples) { | |
return this; | |
} | |
var newDate = new Date( | |
dtPicker.currentYear, | |
dtPicker.currentMonth, | |
dtPicker.currentDay | |
); | |
wdg.updateDateTime(newDate); | |
// Call the user defined onSelect if one was defined. | |
if ($.isFunction(Inst.opt.datepicker._onSelect)) { | |
Inst.opt.datepicker._onSelect.call(this, dateText, dtPicker ); | |
} | |
}; | |
wdg.$datePicker.datepicker(Inst.opt.datepicker); | |
// Setup listeners on the time selectors so that we can trigger | |
// an update to the widget. | |
wdg.$timePicker | |
.on("change" + SPDate.evNamespace + " keyup" + SPDate.evNamespace, | |
"select", | |
function(ev){ | |
// If allowMultiples is true, then exit if | |
// this click is not the SET button | |
if (Inst.opt.allowMultiples) { | |
return this; | |
} | |
wdg.updateDateTime(); | |
return this; | |
}); | |
// now that we have the datepicker setup, add listeners to the | |
// input field so that the date and time picker is shown. | |
Inst.$input | |
.on("focus" + SPDate.evNamespace, function(){ | |
wdg.showPicker(); | |
}); | |
///////////////////////////////////////////////////// | |
// ELSE: showTimePicker is false. Just show regular | |
// jQuery UI date widget. | |
} else { | |
// If remainOpen option is true, then turn off picker animation | |
if (Inst.opt.allowMultiples && Inst.opt.remainOpen) { | |
Inst.opt.datepicker.showAnim = ''; | |
} | |
// Store a reference to teh original onSelect method (if defined) | |
// and set our own here. Our function will take the date selected | |
// by the user in their own locale and format it to ISO 8601 | |
if ($.isFunction(Inst.opt.datepicker.onSelect)) { | |
Inst.opt.datepicker._onSelect = Inst.opt.datepicker.onSelect; | |
} | |
// Setup the Datepicker | |
Inst.opt.datepicker.onSelect = function(dateText, dtPicker){ | |
Inst.setDate({ | |
date: dateText, | |
format: dtPicker.settings.dateFormat, | |
setDatepicker: false | |
}); | |
// Call the user defined onSelect if one was defined. | |
if ($.isFunction(Inst.opt.datepicker._onSelect)) { | |
Inst.opt.datepicker._onSelect.call(this, dateText, dtPicker ); | |
} | |
// If allow multiples is true, then clear out date picker input | |
if (Inst.opt.allowMultiples) { | |
Inst.$input.val(""); | |
} | |
if (Inst.opt.allowMultiples && Inst.opt.remainOpen) { | |
setTimeout(function(){ | |
Inst.$input.datepicker("show"); | |
}, 5); | |
} | |
}; | |
Inst.$input.datepicker(Inst.opt.datepicker); | |
} | |
return wdg; | |
}; //end: createDatePicker() | |
//------------------------------------------------------ | |
//----------- INITIATE THIS INSTANCE ------------- | |
//------------------------------------------------------ | |
// Define time string format (local or utc) | |
// param that is used with SPGetDateString | |
Inst.opt._timeFmt = ( Inst.opt.timeUTC ? 'utc' : 'local' ); | |
// Setup the datepicker options | |
// TODO: should we allow the user to manipulate this? | |
Inst.opt.datepicker.altFormat = 'yy-mm-dd'; | |
Inst.opt.datepicker.altField = Inst.$ele; | |
// If allowMultiples is true, then set special processing for storing | |
// multiple dates - both on display and in the input field. | |
if (Inst.opt.allowMultiples){ | |
Inst.opt.datepicker.altFormat = ''; | |
Inst.opt.datepicker.altField = ''; | |
// Setup listener for removing selected dates. | |
Inst.$dtCntr | |
.css("display", "") | |
.on("click", ".spwidgets-item-remove", function(ev){ | |
var $dtItem = $(ev.target).closest(".spwidgets-item"), | |
date = $dtItem.data("spwidget_dt1"); | |
// If allowMultiples is true, then convert the date string | |
// to a date object | |
if (Inst.opt.allowMultiples) { | |
date = $.SPWidgets.parseDateString(date); | |
} | |
Inst.removeDate({ | |
date: date, | |
format: 'yy-mm-dd' | |
}); | |
}); | |
} //end: if(): allowMultiples | |
// Hide the input used by the caller and display our datepicker input. | |
Inst.$ele | |
.css("display", "none") | |
.data("SPDateFieldInstance", Inst); | |
Inst.$timepicker = Inst.createDatePicker(); | |
// If input field already has some date, then prepopulate the widget | |
if (Inst.eleOrigVal) { | |
Inst.setDate({ | |
date: (Inst.eleOrigVal.split(Inst.opt.delimeter)), | |
format: 'yy-mm-dd', | |
triggerEvent: false | |
}); | |
} | |
// On date change, trigger event on original | |
// element and cancel this one | |
Inst.$input.on("change", function(ev){ | |
ev.stopPropagation(); | |
Inst.$ele.change(); | |
}); | |
Inst.$ui.css("display", ""); | |
}); //end: return.each() | |
}; //end: $.fn.SPDateField() | |
/** | |
* When user clicks on the page, this method will close the | |
* Timepicker if it is open. | |
* | |
* @param {jQuery.Event} | |
* | |
* @return {Object} this | |
*/ | |
SPDate.onPageClick = function(ev) { | |
var $ele = $(ev.target), | |
$allSelectors = $("div.spwidget-datetime-selector:visible"), | |
$clickArea = null; | |
// JQuery UI Datepicker FWD/BAKC button are recreate everytime a | |
// user clicks on them... if this | |
if (!$.contains(document.documentElement, $ele[0])) { | |
return this; | |
} | |
// If Date and Time selectors are visible, then lets check if the user | |
// clicked on an element that is associated with the current time picker. | |
// This is used later to ensure we close all other pickers *except* the | |
// one associated with this element. | |
if ($allSelectors.length) { | |
$clickArea = $ele.closest("div.spwidget-datetime-selector"); | |
if (!$clickArea.length && $ele.is("input.spwidget-date-datepicker,.ui-datepicker-trigger")) { | |
$clickArea = $ele.parent().find("div.spwidget-datetime-selector"); | |
} | |
$allSelectors.not($clickArea).hide(); | |
} | |
return this; | |
}; //end: SPDate.onPageClick() | |
/** | |
* @property | |
* Stores the Style sheet for the Date widget | |
* @member SPDate | |
* @memberOf SPDate | |
*/ | |
SPDate.styleSheet = ".spwidget-date-cntr {\n" | |
+ " display: inline-block; \n" | |
+ " position: relative;\n" | |
+ "}\n" | |
+ ".spwidget-date-cntr div.spwidget-date-input-cntr {\n" | |
+ " position: relative;\n" | |
+ "}\n" | |
+ ".spwidget-date-cntr input {\n" | |
+ " width: 99%;\n" | |
+ "}\n" | |
+ ".spwidget-date-cntr img.ui-datepicker-trigger {\n" | |
+ " display: block;\n" | |
+ " position: absolute;\n" | |
+ " right: 2%;\n" | |
+ " top: .3em;\n" | |
+ "}\n" | |
+ "\n" | |
+ ".spwidget-date-cntr .spwidgets-item-remove {\n" | |
+ " color: red;\n" | |
+ " font-size: xx-small;\n" | |
+ " vertical-align: super;\n" | |
+ " cursor: pointer;\n" | |
+ "}\n" | |
+ "/** --------------------------- Date and Time picker -- */\n" | |
+ ".spwidget-date-cntr div.spwidget-datetime-selector {\n" | |
+ " padding: .5em;\n" | |
+ " position: absolute;\n" | |
+ " width: 26em;\n" | |
+ " z-index: 1;\n" | |
+ "}\n" | |
+ ".spwidget-date-cntr div.spwidget-datetime-selector div.ui-datepicker-inline {\n" | |
+ " width: 14em;\n" | |
+ "}\n" | |
+ "\n" | |
+ ".spwidget-date-cntr div.spwidget-datetime-selector div.spwidget-date-selector,\n" | |
+ ".spwidget-date-cntr div.spwidget-datetime-selector div.spwidget-time-selector {\n" | |
+ " float: left;\n" | |
+ "}\n" | |
+ ".spwidget-date-cntr div.spwidget-selectors:before,\n" | |
+ ".spwidget-date-cntr div.spwidget-selectors:after {\n" | |
+ " content: \"\";\n" | |
+ " display: table;\n" | |
+ " line-height: 0;\n" | |
+ "}\n" | |
+ ".spwidget-date-cntr div.spwidget-selectors:after {\n" | |
+ " clear: both; \n" | |
+ "}\n" | |
+ ".spwidget-date-cntr div.spwidget-datetime-selector select.spwidget-hour,\n" | |
+ ".spwidget-date-cntr div.spwidget-datetime-selector select.spwidget-min,\n" | |
+ ".spwidget-date-cntr div.spwidget-datetime-selector select.spwidget-ampm {\n" | |
+ " font-size: 1.2em;\n" | |
+ "}\n" | |
+ "/* Time selector */\n" | |
+ ".spwidget-date-cntr div.spwidget-time-selector {\n" | |
+ " margin-left: .2em;\n" | |
+ " width: 11em;\n" | |
+ "}\n" | |
+ ".spwidget-date-cntr div.spwidget-time-selector-cntr {\n" | |
+ " padding: .2em;\n" | |
+ "}\n" | |
+ ".spwidget-date-cntr div.spwidget-time-selector div.ui-widget-header {\n" | |
+ " text-align: center;\n" | |
+ " line-height: 2em;\n" | |
+ " margin-bottom: .5em;\n" | |
+ "}\n" | |
+ ".spwidget-date-cntr .spwidget-time-hour,\n" | |
+ ".spwidget-date-cntr .spwidget-time-min,\n" | |
+ ".spwidget-date-cntr .spwidget-time-ampm {\n" | |
+ " margin-top: .2em;\n" | |
+ " padding: .2em;\n" | |
+ "}\n" | |
+ ".spwidget-date-cntr .spwidget-time-selector-cntr select,\n" | |
+ ".spwidget-date-cntr .spwidget-time-selector-cntr label {\n" | |
+ " overflow: hidden;\n" | |
+ " display: inline-block;\n" | |
+ " font-weight: bold;\n" | |
+ "}\n" | |
+ ".spwidget-date-cntr .spwidget-time-selector-cntr select {\n" | |
+ " width: 4em;\n" | |
+ "}\n" | |
+ ".spwidget-date-cntr .spwidget-time-selector-cntr label {\n" | |
+ " width: 5em;\n" | |
+ " font-size: .9em;\n" | |
+ "}\n" | |
+ ".spwidget-btn-set {\n" | |
+ " display: none;\n" | |
+ " position: absolute;\n" | |
+ " right: .2em;\n" | |
+ " bottom: .2em;\n" | |
+ "}\n" | |
+ ".spwidget-date-multiples-cntr .spwidget-btn-set {\n" | |
+ " display: block;\n" | |
+ "}\n"; | |
//_HAS_DATE_CSS_TEMPLATE_ | |
/** | |
* @property | |
* Stores the HTML templates for the Date widget | |
* @member SPDate | |
* @memberOf SPDate | |
*/ | |
SPDate.htmlTemplate = "<div class=\"spwidget-date-cntr\">\n" | |
+ " <div class=\"spwidget-date-selected-cntr\" style=\"display:none;\"></div>\n" | |
+ " <div class=\"spwidget-date-input-cntr\">\n" | |
+ " <input class=\"spwidget-date-datepicker\" name=\"SPDateFieldInput\" value=\"\" />\n" | |
+ " </div>\n" | |
+ "</div>\n" | |
+ "<div class=\"spwidget-datetime-selector ui-widget-content ui-corner-all\">\n" | |
+ " <div class=\"spwidget-selectors\">\n" | |
+ " <div class=\"spwidget-date-selector\"></div>\n" | |
+ " <div class=\"spwidget-time-selector ui-widget-content ui-corner-all\">\n" | |
+ " <div class=\"spwidget-time-selector-cntr\">\n" | |
+ " <div class=\"ui-widget-header ui-helper-clearfix ui-corner-all\">\n" | |
+ " Time\n" | |
+ " </div>\n" | |
+ " <div class=\"spwidget-time-hour\">\n" | |
+ " <label>Hour</label>\n" | |
+ " <select name=\"spwidget_hour\" class=\"spwidget-hour\">\n" | |
+ " <option value=\"1\"> 1</option>\n" | |
+ " <option value=\"2\"> 2</option>\n" | |
+ " <option value=\"3\"> 3</option>\n" | |
+ " <option value=\"4\"> 4</option>\n" | |
+ " <option value=\"5\"> 5</option>\n" | |
+ " <option value=\"6\"> 6</option>\n" | |
+ " <option value=\"7\"> 7</option>\n" | |
+ " <option value=\"8\"> 8</option>\n" | |
+ " <option value=\"9\"> 9</option>\n" | |
+ " <option value=\"10\">10</option>\n" | |
+ " <option value=\"11\">11</option>\n" | |
+ " <option value=\"12\">12</option>\n" | |
+ " </select>\n" | |
+ " </div>\n" | |
+ " <div class=\"spwidget-time-min\"> \n" | |
+ " <label>Minutes</label>\n" | |
+ " <select name=\"spwidget_min\" class=\"spwidget-min\">\n" | |
+ " <option value=\"00\">00</option>\n" | |
+ " <option value=\"05\">05</option>\n" | |
+ " <option value=\"10\">10</option>\n" | |
+ " <option value=\"15\">15</option>\n" | |
+ " <option value=\"20\">20</option>\n" | |
+ " <option value=\"25\">25</option>\n" | |
+ " <option value=\"30\">30</option>\n" | |
+ " <option value=\"35\">35</option>\n" | |
+ " <option value=\"40\">40</option>\n" | |
+ " <option value=\"45\">45</option>\n" | |
+ " <option value=\"50\">50</option>\n" | |
+ " <option value=\"55\">55</option>\n" | |
+ " </select>\n" | |
+ " </div>\n" | |
+ " <div class=\"spwidget-time-ampm\">\n" | |
+ " <label>AM|PM</label>\n" | |
+ " <select name=\"spwidget_ampm\" class=\"spwidget-ampm\">\n" | |
+ " <option value=\"AM\">AM</option>\n" | |
+ " <option value=\"PM\">PM</option>\n" | |
+ " </select>\n" | |
+ " </div>\n" | |
+ " </div>\n" | |
+ " </div>\n" | |
+ " </div>\n" | |
+ " <div class=\"spwidget-btn-set\">\n" | |
+ " <div class=\"spwidget-btn\">\n" | |
+ " Set\n" | |
+ " </div>\n" | |
+ " </div>\n" | |
+ "</div>\n"; | |
//_HAS_DATE_HTML_TEMPLATE_ | |
})(jQuery); /***** End of module: jquery.SPDateField.js */ | |
/** | |
* @fileOverview - List filter panel widget | |
* | |
* BUILD: Paul:November 04, 2013 07:09 PM | |
* | |
*/ | |
(function($){ | |
"use strict"; | |
/*jslint nomen: true, plusplus: true */ | |
/*global SPWidgets */ | |
/** | |
* @class Filter | |
*/ | |
var Filter = {}; | |
/** @property {Boolean} Is initialization done */ | |
Filter.isInitDone = false; | |
/** @property {jQuery} jQuery object with templates. Loaded from Filter.htmlTemplate during initialization */ | |
Filter.templates = null; | |
/** | |
* Default options. | |
*/ | |
$.SPWidgets.defaults.filter = { | |
list: '', | |
webURL: $().SPServices.SPGetCurrentSite(), | |
columns: ['Title'], | |
textFieldTooltip: 'Use a semicolon to delimiter multiple keywords.', | |
definedClass: 'spwidget-column-dirty', | |
showFilterButton: true, | |
showFilterButtonTop: true, | |
filterButtonLabel: "Filter", | |
onFilterClick: null, | |
onReady: null, | |
onReset: null, | |
ignoreKeywords: /^(of|and|a|an|to|by|the|or|from)$/i, | |
delimeter: ';' | |
}; | |
/** | |
* Given a container, this jQuery plugin will attach a user interface | |
* that allows the user to define filter criteria for a list. | |
* | |
* @param {Object} options | |
* @param {String} options.list | |
* @param {String} [options.webURL=current site] | |
* @param {Array} [options.columns=['title']] | |
* @param {String} [options.textFieldTooltip=''] | |
* @param {String} [options.definedClass='spwidget-column-dirty'] | |
* @param {Boolean} [options.showFilterButton=true] | |
* @param {Boolean} [options.showFilterButtonTop=true] | |
* @param {String} [options.filterButtonLabel='Fitler'] | |
* @param {String} [options.onFilterClick=null] | |
* @param {String} [options.onReady=null] | |
* @param {String} [options.ignoreKeywords=RegEx] | |
* @param {String} [options.delimeter=';'] | |
* | |
* @return {jQuery} this | |
* | |
* METHODS | |
* | |
* All methods must be executed on single element. | |
* | |
* $(ele).SPFilterPanel("getFilter"); | |
* | |
* Returns an object with the filter information entered by the user. | |
* | |
* $(ele).SPFilterPanel("setFilter", {column: { matchType: "eq", values: [ '1', '2' ]} }); | |
* | |
* Returns an object with the filter information entered by the user. | |
* | |
* $(ele).SPFilterPanel("destroy"); | |
* | |
* Removes the widget from the page. | |
* | |
*/ | |
$.fn.SPFilterPanel = function(options){ | |
var arg = arguments; | |
// If initialization is not yet done, then do it now | |
if ( !Filter.isInitDone ) { | |
Filter.isInitDone = true; | |
if ( Filter.styleSheet !== "" ) { | |
$('<style type="text/css">' + "\n\n" + | |
Filter.styleSheet + "\n\n</style>" ) | |
.prependTo("head"); | |
} | |
Filter.templates = $( Filter.htmlTemplate ); | |
} | |
// If input was a string, then must be a method. | |
if (typeof options === "string") { | |
if (!this.eq(0).hasClass("hasSPFilterPanel")) { | |
return; | |
} | |
return (function(ele){ | |
// Get the instance object | |
var Inst = ele.eq(0) | |
.find("div.spwidget-filter") | |
.data("SPFilterPanelInst"), | |
method = options.toLowerCase(), | |
response = Inst.$ele; | |
switch (method) { | |
// METHOD----------> getFilter | |
// Return: {Object} | |
case "getfilter": | |
response = Filter.getFilterValues(Inst); | |
break; | |
// METHOD----------> setFilter("url param") | |
// Return: $ele | |
case "setfilter": | |
Filter.setFilterValues(Inst, arg[1]); | |
break; | |
// METHOD----------> reset | |
case "reset": | |
Filter.doResetFilter( Inst ); | |
break; | |
// METHOD----------> destroy | |
case "destroy": | |
Inst.$ele | |
.removeClass("hasSPFilterPanel") | |
.empty(); | |
break; | |
} //end: switch() | |
return response; | |
})(this); | |
} //end: if(): options === string | |
// -------------------------------- | |
// Build the plugin on each element | |
// -------------------------------- | |
return this.each(function(){ | |
var opt = $.extend({}, $.SPWidgets.defaults.filter, options), | |
/** | |
* @class Inst | |
* Widget instance | |
*/ | |
Inst = { | |
$ele: $(this), | |
$ui: null, | |
opt: opt | |
}; | |
/** | |
* Retrieves the list definition. | |
* | |
* @return {jQuery.Deferred} | |
* Deferred is resolved with a scope of the jQuery message | |
* object and given 2 parameters - xData and status | |
* | |
*/ | |
Inst.getListDefinition = function() { | |
return $.Deferred(function(dfd){ | |
// Get List Definition | |
$().SPServices({ | |
operation: "GetList", | |
listName: opt.list, | |
cacheXML: true, | |
async: true, | |
webURL: opt.webURL, | |
completefunc: function(xData, status) { | |
var $msg = $(xData.responseXML); | |
if (status === "error") { | |
dfd.rejectWith($msg, [xData, status]); | |
return; | |
} | |
if ($msg.SPMsgHasError()) { | |
dfd.rejectWith($msg, [xData, status]); | |
return; | |
} | |
dfd.resolveWith($msg, [xData, status]); | |
} //end: completefunc | |
}); | |
}).promise(); | |
}; //end: getListDefinition | |
/** | |
* Builds the widget in the container element. | |
* | |
* @return {jQuery.Deferred} | |
*/ | |
Inst.buildWidget = function() { | |
return $.Deferred(function(dfd){ | |
Inst.getListDefinition().then(function(xData, status){ | |
var $list = this, | |
columns = '', | |
colUI = Filter.templates.filter("#filter_column").html(); | |
// Insert the UI into the page and set | |
// pointer ($ui) to it. | |
Inst.$ui = $( | |
Filter.templates | |
.filter("#filter_main_ui").html() | |
) | |
.appendTo( | |
Inst.$ele | |
.empty() | |
.addClass("hasSPFilterPanel") | |
); | |
// Store list definition | |
Inst.$list = $list; | |
// Loop through list of columns to display and | |
// build the UI for them. | |
$.each(Inst.opt.columns, function(i,v){ | |
// find column in the list definition | |
var $thisCol = $list | |
.find( | |
"Field[DisplayName='" + | |
v + "']" ), | |
thisColUI = colUI, | |
inputUI = '', | |
values = null, | |
model = null; | |
if (!$thisCol.length) { | |
$thisCol = $list | |
.find("Field[Name='" + v + "']"); | |
} | |
if (!$thisCol.length){ | |
return; | |
} | |
// Now that we are sure we have a COl. definition, | |
// populate the model for this column | |
model = { | |
type: null, | |
otherFilterTypes: '', | |
sp_type: $thisCol.attr("Type"), | |
sp_format: $thisCol.attr("Format") | |
}; | |
// Set some default model values | |
model.Name = $thisCol.attr("Name"); | |
// Build the column ui based on its type | |
switch ($thisCol.attr("Type")) { | |
// CHOICE: Show checkboxes allowing user to select multiple | |
case "Choice": | |
$thisCol.find("CHOICES CHOICE").each(function(i,v){ | |
inputUI += $.SPWidgets.fillTemplate( | |
Filter.templates | |
.filter("#filter_choice_field") | |
.html(), | |
{ | |
DisplayName: $thisCol.attr("DisplayName"), | |
Name: $thisCol.attr("Name"), | |
value: $(v).text() | |
} | |
); | |
}); | |
thisColUI = thisColUI.replace(/__COLUMN__UI__/, inputUI); | |
thisColUI = $.SPWidgets.fillTemplate( | |
thisColUI, | |
{ | |
DisplayName: $thisCol.attr("DisplayName"), | |
type: 'choice', | |
Name: $thisCol.attr("Name") | |
} | |
); | |
break; | |
//============================================ | |
// === all types below use the input field === | |
//============================================ | |
// From here until DEFAUL, we only set the type. | |
case "Lookup": | |
case "LookupMulti": | |
if (model.type === null) { | |
model.type = 'lookup'; | |
model.list = $thisCol.attr("List"); | |
if ( model.list === "Self") { | |
model.list = $list.find("List").attr("Title"); | |
} | |
} | |
case "DateTime": | |
if (model.type === null) { | |
model.type = 'date'; | |
model.otherFilterTypes = | |
'<option value="Gt">After</option>' + | |
'<option value="Lt">Before</option>'; | |
} | |
case "User": | |
case "UserMulti": | |
if (model.type === null) { | |
model.type = 'people'; | |
} | |
// COUNTER: Inser additional filter types | |
case "Counter": | |
if (model.type === null) { | |
model.type = 'text'; | |
model.otherFilterTypes = | |
'<option value="Gt">Greater Than</option>' + | |
'<option value="Lt">Less Than</option>'; | |
} | |
// Date and Time: Inser additional filter types | |
case "DateTime": | |
if (model.type === null) { | |
model.type = 'text'; | |
model.otherFilterTypes = | |
'<option value="Gt">After</option>' + | |
'<option value="Lt">Before</option>'; | |
} | |
// DEFAULT: Show as a text field | |
default: | |
if (model.type === null) { | |
model.type = 'text'; | |
} | |
inputUI = Filter.templates | |
.filter("#filter_text_field") | |
.html(); | |
thisColUI = thisColUI | |
.replace(/__COLUMN__UI__/, inputUI) | |
.replace(/__OTHER_FILTER_TYPES__/, model.otherFilterTypes); | |
thisColUI = $.SPWidgets.fillTemplate( | |
thisColUI, | |
$.extend( | |
model, | |
{ | |
DisplayName: $thisCol.attr("DisplayName"), | |
Name: $thisCol.attr("Name"), | |
tooltip: Inst.opt.textFieldTooltip | |
}) | |
); | |
break; | |
} //end: switch() | |
// Add Column UI to list of columns | |
columns += thisColUI; | |
}); //end: .each() - column | |
// Insert the columns into the UI | |
Inst.$ele | |
.find("div.spwidget-filter-column-cntr") | |
.html(columns); | |
// Setup Lookup field | |
Inst.$ele.find("div.spwidget-type-lookup input") | |
.each(function(){ | |
var $field = $(this); | |
$field.SPLookupField({ | |
list: $field | |
.closest("div.spwidget-column") | |
.data("spwidget_list"), | |
template: '<div>{{Title}} <span class="spwidgets-item-remove">[x]</span></div>', | |
listTemplate: '{{Title}}', | |
allowMultiples: true, | |
readOnly: false, | |
filter: '', | |
showSelector: true | |
}); | |
$field.parent().find(".spwidget-tooltip").remove(); | |
}); | |
// Setup PEOPLE fields | |
Inst.$ele.find("div.spwidget-type-people input") | |
.each(function(){ | |
var $field = $(this), | |
colDef = $list.find( | |
"Field[Name='" + | |
$field.attr("name") + "']"), | |
peopleType = 'User'; | |
if (colDef.attr("UserSelectionMode") !== "PeopleOnly") { | |
peopleType = 'All'; | |
} | |
$field.pickSPUser({ | |
allowMultiple: true, | |
type: peopleType | |
}); | |
$field.parent().find(".spwidget-tooltip").remove(); | |
}); | |
// Setup DATE fields | |
Inst.$ele.find("div.spwidget-type-date") | |
.each(function(){ | |
var $column = $(this), | |
$field = $column.find("input"); | |
$field.SPDateField({ | |
allowMultiples: true, | |
showTimepicker: ( | |
$column.data("spwidget_sp_format") === "DateTime" | |
? true | |
: false | |
) | |
}); | |
$column.find(".spwidget-tooltip").remove(); | |
$column.find("select.spwidget-filter-type") | |
.val("Eq") | |
.find("option[value='Contains']").remove(); | |
return this; | |
}); | |
// Setup the Button on the UI (if applicable) | |
if (Inst.opt.showFilterButton || Inst.opt.showFilterButtonTop) { | |
Inst.$ui.find("div.spwidget-filter-button-cntr") | |
.each(function(){ | |
var $btnCntr = $(this), | |
$btn = $(); | |
// If Top button is true, clone adn insert at top | |
if (Inst.opt.showFilterButtonTop) { | |
$btn = $btn | |
.add( $btnCntr.clone(true) ) | |
.prependTo( Inst.$ui ); | |
} | |
// If BOttom Button is true, then added to | |
// group selection... if not, then remove it. | |
if (Inst.opt.showFilterButton) { | |
$btn = $btn.add( $btnCntr ); | |
} else { | |
$btnCntr.remove(); | |
} | |
// Setup Filter button | |
$btn.find("button[name='filter']") | |
.button({ | |
icons: { | |
primary: "ui-icon-search" | |
}, | |
label: Inst.opt.filterButtonLabel | |
}) | |
.on("click", Filter.onFilterButtonClick); | |
// Setup Filter button | |
$btn.find("button[name='reset']") | |
.button({ | |
icons: { | |
primary: "ui-icon-arrowreturnthick-1-n" | |
}, | |
text: false | |
}) | |
.on("click", function(ev){ | |
Filter.doResetFilter( Inst ); | |
return this; | |
}); | |
}); | |
// Else, remove button container | |
} else { | |
Inst.$ui | |
.find("div.spwidget-filter-button-cntr") | |
.remove(); | |
} | |
// Bind events | |
Inst.$ui | |
// Filter type change() | |
.on( | |
"change.SPWigets.SPFilterPanel", | |
"select.spwidget-filter-type", | |
Filter.onFilterTypeChange | |
); | |
// If we have a DefinedClass specified, then | |
// listen to change events | |
if (Inst.opt.definedClass !== "") { | |
Inst.$ui | |
.on( | |
"change.SPWidgets.SPFilterPanel", | |
".spwidget-filter-input", | |
Filter.onFilterInputChange | |
); | |
} | |
// Store the Widget Inst object in the UI | |
Inst.$ui | |
.data("SPFilterPanelInst", Inst); | |
// If a onReady callback was defined, then | |
// execute it now | |
if ($.isFunction(Inst.opt.onReady)) { | |
Inst.opt.onReady.call(Inst.$ele, options); | |
} | |
// Make the UI visible | |
Inst.$ui | |
.fadeIn().promise().then(function(){ | |
$(this).css("display", ""); | |
dfd.resolve(); | |
}); | |
}) //end: .then() | |
// IF getting the List definition fails, then display error | |
// in the widget container element. | |
.fail(function(xData, status){ | |
var $msg = this; | |
Inst.$ele | |
.html( | |
'<div class="ui-state-error">Unable to retrieve list information. ' + | |
$msg.SPGetMsgError() + '</div>' ); | |
dfd.reject(); | |
}); | |
}).promise(); | |
}; //end: Inst.buildWidget() | |
// A few validations | |
if ( Inst.opt.ignoreKeywords | |
&& !Inst.opt.ignoreKeywords instanceof RegExp | |
) { | |
Inst.opt.ignoreKeywords = /Inst.opt.ignoreKeywords/i; | |
} | |
// build the widget | |
Inst.buildWidget(); | |
return this; | |
}); //end: return() | |
}; //end: $.fn.SPFilterPanel() | |
/** | |
* Triggered when the change event is triggered on the | |
* input elements that collect data from the user. | |
* Sets the dirty class on the column if one is defined. | |
* | |
* @param {jQuery.Event} ev | |
* | |
* @return {HTMLElement} this | |
*/ | |
Filter.onFilterInputChange = function(ev){ | |
var $input = $(this), | |
$cntr = $input.closest("div.spwidget-filter-value-input"), | |
$col = $cntr.closest("div.spwidget-column"), | |
matchType = $col.find("div.spwidget-filter-type-cntr select.spwidget-filter-type").val(), | |
val = $input.val(), | |
Inst = $cntr | |
.closest("div.spwidget-filter") | |
.data("SPFilterPanelInst"); | |
if ($col.is(".spwidget-type-choice")) { | |
if (!$cntr.find(".spwidget-filter-input:checked").length) { | |
val = ""; | |
} | |
} | |
if (val !== "") { | |
$col.addClass(Inst.opt.definedClass); | |
} else if (matchType !== 'IsNull' && matchType !== 'IsNotNull') { | |
$col.removeClass(Inst.opt.definedClass); | |
} | |
return this; | |
}; //end: Filter.onFilterInputChange() | |
/** | |
* Bound to the $ui. Listen for changes in the filter type | |
* select element. | |
* | |
* @param {jQuery.Event} ev | |
* | |
* return {jQuery} this | |
*/ | |
Filter.onFilterTypeChange = function(ev) { | |
var $ele = $(this), | |
$col = $ele.closest("div.spwidget-column"), | |
$logicalType = $col.find("div.spwidget-filter-type-cntr select.spwidget-match-type"), | |
$colValCntr = $col.find("div.spwidget-filter-value-cntr"), | |
$colInput = $colValCntr.find(".spwidget-input"), | |
inputVal = '', | |
colType = $col.data("spwidget_column_type"), | |
eleValue = $ele.val(), | |
Inst = $ele | |
.closest("div.spwidget-filter") | |
.data("SPFilterPanelInst"); | |
if (eleValue === "IsNull" || eleValue === "IsNotNull") { | |
$colValCntr.addClass("spwidget-disabled"); | |
$colInput.attr("disabled", "disabled"); | |
$logicalType.attr("disabled", "disabled"); | |
$col.addClass(Inst.opt.definedClass); | |
} else { | |
$colValCntr.removeClass("spwidget-disabled"); | |
$colInput.removeAttr("disabled", "disabled"); | |
$logicalType.removeAttr("disabled"); | |
// Remove the higlight class from the column if | |
// no value is defined for it. For Checkboxes (choice) | |
// we need to first grab the checkboxes and then see | |
// if they are checked. | |
if (colType === "choice") { | |
$colInput.filter(":checkbox").each(function(){ | |
var $this = $(this); | |
if ($this.is(":checked")) { | |
inputVal += $this.val(); | |
return false; | |
} | |
}); | |
} else { | |
inputVal += $colInput.val(); | |
} | |
if (!inputVal ) { | |
$col.removeClass(Inst.opt.definedClass); | |
} | |
} | |
return this; | |
}; //end: Filter.onFilterTypeChange() | |
/** | |
* Calls the user defined function when user clicks the filter button. | |
* | |
* @param {jQuery.Event} ev | |
* | |
* @return {HTMLElement} this | |
*/ | |
Filter.onFilterButtonClick = function(ev) { | |
var Inst = $(this) | |
.closest("div.spwidget-filter") | |
.data("SPFilterPanelInst"), | |
filters = null; | |
if ( $.isFunction( Inst.opt.onFilterClick ) ) { | |
filters = Filter.getFilterValues(Inst); | |
Inst.opt.onFilterClick.call( Inst.$ele, filters ); | |
} | |
return this; | |
}; //end: Filter.onFilterButtonClick() | |
/** | |
* Resets the filter panel, by removing all filters | |
* defined from the form. | |
* | |
* @param {Object} Inst | |
* The widget instance object | |
* | |
* @return {Object} the instance object | |
*/ | |
Filter.doResetFilter = function(Inst) { | |
if ($.isFunction(Inst.onReset)) { | |
if (Inst.onReset.call(Inst.$ele, Filter.getFilterValues(Inst)) === true) { | |
return Inst; | |
} | |
} | |
Inst.$ui | |
// Reset regular text fields | |
.find("div[data-spwidget_column_type='text'] input") | |
.val("") | |
.end() | |
// reset checkboxes for CHOICE columns | |
.find("div[data-spwidget_column_type='choice'] input") | |
.prop("checked", false) | |
.end() | |
// reset people fields | |
.find(".hasPickSPUser") | |
.pickSPUser("method", "clear") | |
.end() | |
// reset date fields | |
.find(".hasSPDateField") | |
.SPDateField("reset") | |
.end() | |
// reset lookup fields | |
.find(".hasLookupSPField") | |
.SPLookupField("method", "clear"); | |
// Remove the Defined class | |
if (Inst.opt.definedClass !== "") { | |
Inst.$ui | |
.find("." + Inst.opt.definedClass) | |
.removeClass(Inst.opt.definedClass); | |
} | |
// Reset any IsNull and IsNotNull filters | |
Inst.$ui.find("select.spwidget-filter-type").each(function(){ | |
var $ele = $(this), | |
value = $ele.val(); | |
if (value === "IsNull" || value === "IsNotNull") { | |
$ele.val("Eq"); | |
$ele.change(); | |
} | |
}); | |
// Focus the on the first input | |
Inst.$ui.find(":input.spwidget-input:first").focus(); | |
return Inst; | |
}; // Filter.doResetFilter() | |
/** | |
* Generates the filters from the values entered by the user. | |
* | |
* @param {SPFilterPanel.Instance} Inst | |
* The Instance object generated by the $().SPFilterPanel() | |
* | |
* @return {Object} | |
* An object with the filter information. See below for the | |
* structured of the object | |
* | |
* @example | |
* | |
* Filter.getFilterValues(instObject); | |
* | |
* { | |
* CAMLQuery: 'string with query wrapped in an <And> aggregate', | |
* URLParams: 'String with query in URL params style', | |
* filters: { | |
* columnInternalName: { | |
* matchType: 'Eq', | |
* values: [ | |
* 'filter value 1', | |
* 'filter value 2', | |
* etc... | |
* ], | |
* CAMLQuery: 'string with query wrapped in an <Or> aggregate', | |
* URLParams: 'string with query in URL param style', | |
* count: 0 | |
* } | |
* }, | |
* count: 2 // number of filters created | |
* } | |
* | |
* | |
*/ | |
Filter.getFilterValues = function(Inst) { | |
var filters = { | |
CAMLQuery: '', | |
URLParams: '', | |
filters: {}, | |
count: 0 | |
}, | |
$cols = Inst.$ui.find("div.spwidget-column"), | |
colFilters = []; | |
/** | |
* Returns a CAMLQuery for the set of individual column filters. | |
* USed in fields of type Choice or Text. | |
* | |
* @param {Object} colFilterObj | |
* The object for the individual column | |
* | |
* @return {String} caml query | |
* | |
*/ | |
function getColumnCAMLQuery(colFilterObj) { | |
return $.SPWidgets.getCamlLogical({ | |
type: colFilterObj.logicalType, | |
values: colFilterObj.values, | |
onEachValue: function(filterVal){ | |
return "<" + colFilterObj.matchType + | |
"><FieldRef Name='" + colFilterObj.columnName + | |
"' /><Value Type='Text'>" + | |
$.SPWidgets.escapeXML(filterVal) + | |
"</Value></" + colFilterObj.matchType + ">"; | |
} | |
}); | |
} //end: getColumnCAMLQuery() | |
// Loop through each column and build the data | |
$cols.each(function(i,v){ | |
var $thisCol = $(v), | |
$input = $thisCol.find(".spwidget-input"), | |
colName = $input.attr("name"), | |
thisColFilter = (new Filter.ColumnFilter({ | |
columnName: colName, | |
matchType: $thisCol | |
.find("select.spwidget-filter-type") | |
.val(), | |
logicalType: $thisCol | |
.find("select.spwidget-match-type") | |
.val() | |
})), | |
colFilterWasSet = false, | |
colType = $thisCol.data("spwidget_column_type"), | |
thisColUrlParam = {}; | |
// If the match type is IsNull or IsNotNull, then | |
// build the match now... don't need to know which type | |
// of column for these. | |
if ( thisColFilter.matchType === "IsNull" | |
|| thisColFilter.matchType === "IsNotNull" | |
) { | |
thisColFilter.CAMLQuery = | |
"<" + thisColFilter.matchType + "><FieldRef Name='" + | |
colName + "' /></" + thisColFilter.matchType + ">"; | |
thisColFilter.count += 1; | |
// ELSE, process the column type | |
} else { | |
// Process column type user input | |
switch(colType) { | |
// -------------------- CHOICE COLUMNS | |
case "choice": | |
$input.each(function(){ | |
var $checkbox = $(this), | |
checkboxVal = $checkbox.val(); | |
if ($checkbox.is(":checked")) { | |
thisColFilter.values.push(checkboxVal); | |
} | |
}); | |
if (thisColFilter.values.length) { | |
thisColFilter.count = thisColFilter.values.length; | |
thisColFilter.CAMLQuery = getColumnCAMLQuery(thisColFilter); | |
} | |
break; | |
// -------------------- LOOKUP COLUMNS | |
// -------------------- PEOPLE COLUMNS | |
case "lookup": | |
case "people": | |
(function(){ | |
var lookupIDs = []; | |
$input.each(function(){ | |
var $lookup = $(this), | |
lookupVals = $.SPWidgets | |
.parseLookupFieldValue( | |
$lookup.val() | |
), | |
i,j; | |
for(i=0,j=lookupVals.length; i<j; i++){ | |
if (lookupVals[i].id) { | |
thisColFilter.values | |
.push( | |
lookupVals[i].id + ";#" + | |
lookupVals[i].title | |
); | |
lookupIDs.push(lookupVals[i].id); | |
} | |
} | |
}); | |
if (thisColFilter.values.length) { | |
thisColFilter.count = thisColFilter.values.length; | |
thisColFilter.CAMLQuery = $.SPWidgets.getCamlLogical({ | |
type: thisColFilter.logicalType, | |
values: lookupIDs, | |
onEachValue: function(filterVal){ | |
return "<" + thisColFilter.matchType + | |
"><FieldRef Name='" + thisColFilter.columnName + | |
"' LookupId='True'/><Value Type='Lookup'>" + | |
filterVal + "</Value></" + thisColFilter.matchType + ">"; | |
} | |
}); | |
} | |
})(); | |
break; | |
// -------------------- DATE FIELDS | |
case "date": | |
$input.each(function(){ | |
var dtObj = $input.SPDateField("getDate"); | |
if (dtObj.dates.length) { | |
thisColFilter.values = dtObj.dates; | |
thisColFilter.count = thisColFilter.values.length; | |
thisColFilter.CAMLQuery = $.SPWidgets.getCamlLogical({ | |
type: thisColFilter.logicalType, | |
values: thisColFilter.values, | |
onEachValue: function(filterVal){ | |
return "<" + thisColFilter.matchType + | |
"><FieldRef Name='" + | |
thisColFilter.columnName + | |
"'/><Value Type='DateTime'>" + | |
filterVal + "</Value></" + | |
thisColFilter.matchType + ">"; | |
} | |
}); | |
} | |
return false; | |
}); | |
break; | |
// -------------------- TEXT COLUMNS | |
case "text": | |
// ELSE, if user entered text, then parse it | |
if ( String( $.trim( $input.val() ) ).length ) { | |
(function(){ | |
var keywords = $input.val().split(Inst.opt.delimeter), | |
i,j, | |
thisKeyword; | |
// Loop thorugh all keywords. | |
for( i=0,j=keywords.length; i<j; i++ ){ | |
thisKeyword = $.trim(keywords[i]); | |
if ( !Inst.opt.ignoreKeywords.test(thisKeyword) | |
&& thisKeyword | |
) { | |
thisColFilter.values.push(thisKeyword); | |
} | |
} | |
thisColFilter.CAMLQuery = getColumnCAMLQuery(thisColFilter); | |
thisColFilter.count = thisColFilter.values.length; | |
})(); | |
} | |
break; | |
} //end: switch() - type of column | |
} //end if() | |
// If filters where built for this column, then add it to the | |
// list of column that the user entered values for. | |
if (thisColFilter.count > 0) { | |
colFilters.push(thisColFilter.CAMLQuery); | |
filters.count += thisColFilter.count; | |
filters.filters[colName] = thisColFilter; | |
// Create the URLParams for this column | |
thisColUrlParam[ colName ] = { | |
matchType: thisColFilter.matchType, | |
logicalType: thisColFilter.logicalType, | |
values: thisColFilter.values | |
}; | |
thisColFilter.URLParams = $.param(thisColUrlParam, false); | |
if (filters.URLParams !== "") { | |
filters.URLParams += "&"; | |
} | |
filters.URLParams += thisColFilter.URLParams; | |
} | |
}); | |
// Build the CAMLQuery | |
if (filters.count > 1) { | |
filters.CAMLQuery = $.SPWidgets.getCamlLogical({ | |
type: 'AND', | |
values: colFilters | |
}); | |
} else if (filters.count === 1 ) { | |
filters.CAMLQuery = colFilters[0]; | |
} | |
return filters; | |
}; // Filter.getFilterValues() | |
/** | |
* Clears the current panel and populates it with the | |
* filter criteria defined on the input object | |
* | |
* @param {Object} Inst | |
* The instance object for the widget on the page | |
* @param {String} filters | |
* An object with the column criteria to be set. | |
* format of object: | |
* { | |
* columnInternalName: { | |
* matchType: "", | |
* values: [ | |
* 'value 1', | |
* 'value 2' | |
* ] | |
* } | |
* } | |
* | |
* @return {Object} Inst | |
*/ | |
Filter.setFilterValues = function(Inst, filters) { | |
// If filters is not an object or is an empty object, exit | |
if (typeof filters !== "object" || $.isEmptyObject(filters)) { | |
return Inst; | |
} | |
Filter.doResetFilter(Inst); | |
$.each(filters, function(column, filter){ | |
var $input = Inst.$ui.find(".spwidget-filter-input[name='" + column + "']"), | |
$colUI = $input.closest("div.spwidget-column"), | |
type = $colUI.data("spwidget_column_type"), | |
$match = $colUI.find("select[name='" + column + "_type']"), | |
$logicalType = $colUI.find("div.spwidget-filter-type-cntr select.spwidget-match-type"), | |
thisFilter = new Filter.ColumnFilter(); | |
$.extend(thisFilter, filter); | |
// If we have a matchType or logicalType, then set it | |
if (thisFilter.matchType) { | |
$match.val(thisFilter.matchType); | |
} | |
if (thisFilter.logicalType) { | |
$logicalType.val(thisFilter.logicalType); | |
} | |
// if match type is IsNull or IsNotNull, then no need to set column value | |
if (filter.matchType !== "IsNull" && filter.matchType !== "IsNotNull") { | |
// Populate the values | |
switch (type) { | |
case "text": | |
if (thisFilter.values instanceof Array) { | |
$input.val(thisFilter.values.join(Inst.opt.delimeter)); | |
} else { | |
$input.val(thisFilter.values); | |
} | |
break; | |
case "choice": | |
$.each(thisFilter.values, function(i, colVal){ | |
$input | |
.filter("[value='" + colVal + "']") | |
.prop("checked", true); | |
}); | |
break; | |
case "lookup": | |
$input.SPLookupField("method", "add", | |
thisFilter.values.join(";#") ); | |
break; | |
case "people": | |
$input.pickSPUser("method", "add", | |
thisFilter.values.join(";#") ); | |
break; | |
case "date": | |
// If dateTime value, then let SPDateField parse values | |
if ($colUI.data("spwidget_sp_format") === "DateTime") { | |
$input.SPDateField('setDate', thisFilter.values); | |
// Regular dates - Provide format input | |
} else { | |
$input.SPDateField('setDate', thisFilter.values, 'yy-mm-dd'); | |
} | |
break; | |
} | |
// ELSE: Must have been IsNull or IsNotNull. trigger change | |
// event on dropdown so that column can be properly highlighted. | |
} else { | |
$match.change(); | |
} //end: if() | |
$input.change(); | |
}); //end: each(): thisFilter | |
return Inst; | |
}; //end: Filter.setFilterValues() | |
/** | |
* Create a new instance of a Column object. | |
* | |
* @constructor | |
* | |
* @param {Object} inst | |
* The initial object of values | |
* | |
* @return {Filter.ColumnFilter} | |
* | |
*/ | |
Filter.ColumnFilter = function(inst) { | |
var Column = function(){}, | |
newCol = new Column(); | |
if (typeof inst !== "object") { | |
inst = {}; | |
} | |
newCol.columnName = inst.columnName || ''; | |
newCol.matchType = inst.matchType || ''; | |
newCol.logicalType = inst.logicalType || ''; | |
newCol.values = inst.values || []; | |
newCol.CAMLQuery = inst.CAMLQuery || ''; | |
newCol.URLParams = inst.URLParams || ''; | |
newCol.count = inst.count || 0; | |
return newCol; | |
}; //end: ColumnFilter() | |
/** | |
* @property | |
* Stores the Style sheet that is inserted into the page the first | |
* time SPFilterPanel() is called. | |
* Value is set at build time. | |
*/ | |
Filter.styleSheet = "/** \n" | |
+ " * Stylesheet for the Board widget\n" | |
+ " * \n" | |
+ " * BUILD: September 07, 2013 - 03:52 PM\n" | |
+ " */\n" | |
+ "div.spwidget-filter {\n" | |
+ " width: 100%;\n" | |
+ " position: relative;\n" | |
+ "}\n" | |
+ "div.spwidget-filter .spwidget-date-cntr,\n" | |
+ "div.spwidget-filter .spwidgets-lookup-cntr {\n" | |
+ " display: block;\n" | |
+ "}\n" | |
+ "/* Adjust the width of the widget inputs inside the filter panel */\n" | |
+ "div.spwidget-filter .spwidget-type-text input.spwidget-filter-input,\n" | |
+ "div.spwidget-filter .spwidget-type-people input.ui-autocomplete-input,\n" | |
+ "div.spwidget-filter div.spwidget-type-choice div.spwidget-filter-value-input {\n" | |
+ " width: 95%;\n" | |
+ "}\n" | |
+ "\n" | |
+ "div.spwidget-filter .spwidgets-lookup-cntr {\n" | |
+ " width: 96%;\n" | |
+ "}\n" | |
+ "div.spwidget-filter .spwidget-date-cntr div.spwidget-date-input-cntr {\n" | |
+ " width: 97%\n" | |
+ "}\n" | |
+ "\n" | |
+ "div.spwidget-filter div.spwidget-column {\n" | |
+ " padding: .5em;\n" | |
+ " margin: .5em;\n" | |
+ " position: relative;\n" | |
+ " border-bottom: 1px solid darkgray;\n" | |
+ " box-shadow: 1px 1px 1px 0 lightgray inset;\n" | |
+ "}\n" | |
+ "div.spwidget-filter div.spwidget-filter-type-cntr {\n" | |
+ " right: 4%;\n" | |
+ " position: absolute;\n" | |
+ " font-size: .8em;\n" | |
+ " top: .6em;\n" | |
+ " opacity: .6;\n" | |
+ " filter: Alpha(opacity=60);\n" | |
+ "}\n" | |
+ "div.spwidget-filter div.spwidget-filter-type-cntr:hover {\n" | |
+ " opacity: 1;\n" | |
+ "}\n" | |
+ "div.spwidget-filter div.spwidget-filter-value-cntr {\n" | |
+ " width: 100%;\n" | |
+ "}\n" | |
+ "\n" | |
+ "div.spwidget-filter div.spwidget-filter-value-cntr > label {\n" | |
+ " display: block;\n" | |
+ " padding: .2em;\n" | |
+ " font-weight: bold;\n" | |
+ "}\n" | |
+ "div.spwidget-filter div.spwidget-column-dirty div.spwidget-filter-value-cntr > label {\n" | |
+ " color: #FF0000;\n" | |
+ "}\n" | |
+ "div.spwidget-filter .spwidget-tooltip {\n" | |
+ " display: block;\n" | |
+ " font-size: .8em;\n" | |
+ " font-style: italic;\n" | |
+ "}\n" | |
+ "\n" | |
+ "/* LOOKUP FIELDS */\n" | |
+ "div.spwidget-filter div.spwidgets-lookup-cntr div.spwidgets-lookup-selected > div.spwidgets-item {\n" | |
+ " display: block;\n" | |
+ " margin-left: 0px;\n" | |
+ "}\n" | |
+ "\n" | |
+ "/* CHOICE FIELDS */\n" | |
+ "div.spwidget-filter div.spwidget-type-choice div.spwidget-filter-value-input {\n" | |
+ " max-height: 6em;\n" | |
+ " overflow: auto;\n" | |
+ " -moz-appearance: textfield;\n" | |
+ " -webkit-appearance: textfield;\n" | |
+ " background-color: white;\n" | |
+ " background-color: -moz-field;\n" | |
+ " border: 1px solid darkgray;\n" | |
+ " box-shadow: 1px 1px 1px 0 lightgray inset;\n" | |
+ " font: -moz-field;\n" | |
+ " font: -webkit-small-control;\n" | |
+ " padding: 2px 5px;\n" | |
+ "}\n" | |
+ "div.spwidget-filter div.spwidget-type-choice div.spwidget-filter-value-input label {\n" | |
+ " display: block;\n" | |
+ " padding: .2em;\n" | |
+ "}\n" | |
+ "\n" | |
+ "/** DISABLED COLUMN VALUE CONTAINER */\n" | |
+ "div.spwidget-filter .spwidget-disabled {\n" | |
+ " -ms-filter: \"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)\";\n" | |
+ " filter: alpha(opacity=50);\n" | |
+ " opacity: 0.5;\n" | |
+ "}\n" | |
+ "\n" | |
+ "/** Button container */\n" | |
+ "div.spwidget-filter div.spwidget-filter-button-cntr {\n" | |
+ " padding: .5em 4%;\n" | |
+ " margin-top: .5em;\n" | |
+ " text-align: right;\n" | |
+ "}\n" | |
+ "\n"; | |
//_HAS_FILTER_CSS_TEMPLATE_ | |
/** | |
* @property | |
* Stores the HTML template for each Filter widget. | |
* Value is set at build time. | |
*/ | |
Filter.htmlTemplate = "<div id=\"filter_main_ui\">\n" | |
+ " <div class=\"spwidget-filter\" style=\"display: none;\">\n" | |
+ " <div class=\"spwidget-filter-column-cntr\"></div>\n" | |
+ " <div class=\"spwidget-filter-button-cntr\">\n" | |
+ " <button type=\"button\" class=\"spwidget-button\" name='reset'>Reset</button>\n" | |
+ " <button type=\"button\" class=\"spwidget-button\" name='filter'>Filter</button>\n" | |
+ " </div>\n" | |
+ " </div>\n" | |
+ "</div>\n" | |
+ "<div id=\"filter_column\">\n" | |
+ " <div class=\"spwidget-column spwidget-type-{{type}}\" \n" | |
+ " data-spwidget_column_type=\"{{type}}\" \n" | |
+ " data-spwidget_list=\"{{list}}\" \n" | |
+ " data-spwidget_sp_type=\"{{sp_type}}\" \n" | |
+ " data-spwidget_sp_format=\"{{sp_format}}\" >\n" | |
+ " <div class=\"spwidget-filter-value-cntr\">\n" | |
+ " <label>{{DisplayName}}</label>\n" | |
+ " <div class=\"spwidget-filter-value-input\">\n" | |
+ " __COLUMN__UI__\n" | |
+ " </div>\n" | |
+ " </div>\n" | |
+ " <div class=\"spwidget-filter-type-cntr\" title=\"Match Type\">\n" | |
+ " <select name=\"{{Name}}_type\" class=\"spwidget-filter-type\" tabindex=\"-1\">\n" | |
+ " <option value=\"Contains\">Contains</option>\n" | |
+ " <option value=\"Eq\" selected=\"selected\">Equal</option>\n" | |
+ " <option value=\"Neq\">Not Equal</option>\n" | |
+ " <option value=\"IsNull\">Is Blank</option> \n" | |
+ " <option value=\"IsNotNull\">Is Not Blank</option>\n" | |
+ " __OTHER_FILTER_TYPES__\n" | |
+ " </select>\n" | |
+ " <select name=\"{{Name}}_match\" class=\"spwidget-match-type\" tabindex=\"-1\">\n" | |
+ " <option value=\"Or\" selected=\"selected\">Any</option>\n" | |
+ " <option value=\"And\">All</option>\n" | |
+ " </select>\n" | |
+ " </div>\n" | |
+ " </div>\n" | |
+ "</div>\n" | |
+ "<div id=\"filter_text_field\">\n" | |
+ " <input name=\"{{Name}}\" title=\"{{DisplayName}}\" type=\"text\" value=\"\" class=\"spwidget-input spwidget-filter-input\" />\n" | |
+ " <span class=\"spwidget-tooltip\">{{tooltip}}</span>\n" | |
+ "</div>\n" | |
+ "<div id=\"filter_choice_field\">\n" | |
+ " <label>\n" | |
+ " <input name=\"{{Name}}\" title=\"{{DisplayName}}\" type=\"checkbox\" value=\"{{value}}\" class=\"spwidget-input spwidget-filter-input\" />\n" | |
+ " {{value}}\n" | |
+ " </label>\n" | |
+ "</div>\n"; | |
//_HAS_FILTER_HTML_TEMPLATE_ | |
})(jQuery); /***** End of module: jquery.SPFilterPanel.js */ | |
})(jQuery); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment