'use strict' ; /**
* -----------------
* Shockout SP Form
* -----------------
* By John Bonfardeci <john.bonfardeci@gmail.com>
*
* GitHub: https://github.com/jbonfardeci/ShockoutForms
*
* A Replacement for InfoPath and XSLT Forms
* Leverage the power Knockout JS databinding with SharePoint services for modern and dynamic web form development.
*
* Minimum Usage:
* `var spForm = new Shockout.SPForm('My SharePoint List Name', 'my-form-ID', {});`
*
* Dependencies: jQuery 1.72+, jQuery UI<any>, KnockoutJS 3.2+
* Compatible with Bootstrap 3.5.x CSS - http://getbootstrap.com
*
* The MIT License (MIT) - https://tldrlegal.com/license/mit-license
* Copyright (c) 2015 John T. Bonfardeci
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*/ module Shockout { export class SPForm { public static DEBUG : boolean = false ; public onCloseAction : Function ; public formId : string ; public listName : string ; public listNameRest : string ; public static errorLogListName : string ; public static errorLogSiteUrl : string ; public static enableErrorLog : boolean ; public static searchPrincipals : boolean ; public $dialog ; public $form ; public $formAction ; public $formStatus ; public allowDelete : boolean = false ; public allowPrint : boolean = true ; public allowSave : boolean = false ; public allowedExtensions : Array < string > = [ 'txt' , 'rtf' , 'zip' , 'pdf' , 'doc' , 'docx' , 'jpg' , 'gif' , 'png' , 'ppt' , 'tif' , 'pptx' , 'csv' , 'pub' , 'msg' , 'xls' , 'xlsx' ] ; public asyncFns : Array < any > ; public attachmentMessage : string = 'An attachment is required.' ; public confirmationUrl : string = '/SitePages/Confirmation.aspx' ; public debug : boolean = false ; public dialogOpts : any = { width : 400 , height : 250 , autoOpen : false , show : { effect : "blind" , duration : 1000 } , hide : { effect : "explode" , duration : 1000 } } ; public editableFields : Array < string > = [ ] ; public enableAttachments : boolean = false ; public enableErrorLog : boolean = true ; public errorLogListName : string = 'Error Log' ; public errorLogSiteUrl : string = '/' ; public fieldNames : Array < string > = [ ] ; public includeUserProfiles : boolean = true ; public includeWorkflowHistory : boolean = true ; public includeNavigationMenu : boolean = true ; public peopleFilter : Function ; public preRender : Function ; public postRender : Function ; public preSave : Function ; public requireAttachments : boolean = false ; public searchPrincipals : boolean = true ; public siteUrl : string = '' ; public utils : Utils = Utils ; public viewModel : IViewModel ; public viewModelIsBound : boolean = false ; public workflowHistoryListName : string = 'Workflow History' ; return public getCurrentUser ( ) { return this . currentUser ; } private currentUser : ICurrentUser = { id : null , title : null , login : null , email : null , account : null , jobtitle : null , department : null , isAdmin : false , groups : [ ] } ; return public getDefaultViewUrl ( ) { return this . defaultViewUrl ; } private defaultViewUrl : string ; return public getDefailtMobileViewUrl ( ) { return this . defaultMobileViewUrl ; } private defaultMobileViewUrl : string ; return public getForm ( ) { return this . form ; } private form : HTMLElement ; return public getItemId ( ) { return this . itemId ; } public setItemId ( id ) { this . itemId = id ; } private itemId : number = null ; return public getListId ( ) { return this . listId ; } private listId : string = null ; return public getListItem ( ) { return this . listItem ; } public listItem : ISpItem = null ; return private requireCheckout : boolean = false ; public requiresCheckout ( ) { return this . requireCheckout ; } return private rootUrl : string = window . location . protocol + '//' + window . location . hostname + ( ! ! window . location . port ? ':' + window . location . port : '' ) ; public getRootUrl ( ) { return this . rootUrl ; } return private sourceUrl : string = null ; public getSourceUrl ( ) { return this . sourceUrl ; } return public getViewModel ( ) { return this . viewModel ; } return public getVersion ( ) { return this . version ; } private version : string = '1.0.1' ; public queryStringId : string = 'formid' ; private cafe : ICafe ; public listItemType : string ; private listItemMetadata : ISpMetadata ; public getListItemMetadata ( ) { return this . listItemMetadata ; } constructor ( listName , formId , options = undefined ) { var self = this ; var error ; if ( ! ( this instanceof SPForm ) ) { error = 'You must declare an instance of this class with `new`.' ; alert ( error ) ; throw error ; } if ( ! ! ! formId || ! ! ! listName ) { var errors = [ 'Missing required parameters:' ] ; if ( ! ! ! this . formId ) { errors . push ( ' `formId`' ) ; } if ( ! ! ! this . listName ) { errors . push ( ' `listName`' ) ; } errors = errors . join ( '' ) ; alert ( errors ) ; throw errors ; } this . formId = formId ; this . listName = listName ; this . listNameRest = Utils . toCamelCase ( listName ) ; this . form = < HTMLElement > ( typeof formId == 'string' ? document . getElementById ( formId ) : formId ) ; if ( ! ! ! this . form ) { alert ( 'An element with the ID "' + this . formId + '" was not found. Ensure the `formId` parameter in the constructor matches the ID attribute of the form element.' ) ; return ; } this . $form = $ ( this . form ) . addClass ( 'sp-form' ) ; $ ( 'form' ) . attr ( { 'novalidate' : 'novalidate' } ) ; this . sourceUrl = Utils . getQueryParam ( 'source' ) ; if ( ! ! this . sourceUrl ) { this . sourceUrl = decodeURIComponent ( this . sourceUrl ) ; } if ( options && options . constructor === Object ) { for ( var p in options ) { this [ p ] = options [ p ] ; } } SPForm . DEBUG = this . debug ; SPForm . searchPrincipals = this . searchPrincipals ; this . itemId = Utils . getIdFromHash ( ) ; var idFromQs = Utils . getQueryParam ( this . queryStringId ) || Utils . getQueryParam ( 'ID' ) ; if ( ! ! ! this . itemId && /\d/ . test ( idFromQs ) ) { this . itemId = parseInt ( idFromQs ) ; Utils . setIdHash ( this . itemId ) ; } SPForm . errorLogListName = this . errorLogListName ; SPForm . errorLogSiteUrl = this . errorLogSiteUrl ; SPForm . enableErrorLog = this . enableErrorLog ; KoHandlers . bindKoHandlers ( ) ; this . viewModel = new ViewModel ( this ) ; this . viewModel . showUserProfiles ( this . includeUserProfiles ) ; self . $formStatus = $ ( '<div>' , { 'class' : 'form-status' } ) . appendTo ( self . $form ) ; self . $dialog = $ ( '<div>' , { 'id' : 'formdialog' } ) . appendTo ( self . $form ) . dialog ( self . dialogOpts ) ; this . asyncFns = [ self . getCurrentUserAsync , function ( self ) { if ( self . preRender ) { self . preRender ( self , self . viewModel ) ; } self . nextAsync ( true ) ; } , self . getListAsync , self . initForm , function ( self , args = undefined ) { KoComponents . registerKoComponents ( ) ; ko . applyBindings ( self . viewModel , self . form ) ; self . viewModelIsBound = true ; self . nextAsync ( true ) ; } , self . getListItemAsync , self . getHistoryAsync , function ( self ) { if ( self . listItem ) { if ( self . listItem . AttachmentFiles ) { if ( self . listItem . AttachmentFiles . results ) { self . viewModel . attachments ( self . listItem . AttachmentFiles . results ) ; self . viewModel . attachments . valueHasMutated ( ) ; } } } if ( self . postRender ) { self . postRender ( self , self . viewModel ) ; } self . nextAsync ( true ) ; } , self . implementPermissions , self . finalize ] ; this . nextAsync ( true , 'Begin initialization...' ) ; } param success param msg param args return nextAsync ( success = true , msg = undefined , args = undefined ) { var self = this ; if ( msg ) { this . updateStatus ( msg , success , self ) ; } if ( ! success ) { return ; } if ( this . asyncFns . length == 0 ) { setTimeout ( function ( ) { self . $formStatus . hide ( ) ; } , 2000 ) ; return ; } this . asyncFns . shift ( ) ( self , args ) ; } param self param args return getCurrentUserAsync ( self , args = undefined ) { self . updateStatus ( 'Retrieving your account...' , true , self ) ; var success = 'Retrieved your account.' ; SpApi15 . getCurrentUser ( function ( user , error ) { self . currentUser = user ; self . viewModel . currentUser ( user ) ; if ( self . debug ) { console . info ( 'This is the SP 2013 API.' ) ; console . info ( 'Current user is...' ) ; console . info ( self . viewModel . currentUser ( ) ) ; } self . nextAsync ( true , success ) ; } , true , self . siteUrl ) ; } param self param args return getListAsync ( self , args = undefined ) { var rxIsChoice = /choice/i ; var rxExcludeNames = /\b(FolderChildCount|ItemChildCount|MetaInfo|ContentType|Edit|Type|LinkTitleNoMenu|LinkTitle|LinkTitle2|Version|Attachments)\b/ ; SpSoap . getList ( self . siteUrl , self . listName , function ( xmlDoc , error ) { if ( ! ! error ) { var msg = 'Failed to retrieve list data. ' + error ; self . nextAsync ( false , 'Failed to retrieve list data. ' + error ) ; self . logError ( msg ) ; return ; } setupList ( xmlDoc ) ; } ) ; function setupList ( xmlDoc ) { try { var $list = $ ( xmlDoc ) . find ( 'List' ) . first ( ) ; var listId = $list . attr ( 'ID' ) ; self . listId = listId ; var requireCheckout = $list . attr ( 'RequireCheckout' ) ; self . requireCheckout = ! ! requireCheckout ? requireCheckout . toLowerCase ( ) == 'true' : false ; var enableAttachments = $list . attr ( 'EnableAttachments' ) ; self . enableAttachments = ! ! enableAttachments ? enableAttachments . toLowerCase ( ) == 'true' : false ; self . defaultViewUrl = $list . attr ( 'DefaultViewUrl' ) ; self . defaultMobileViewUrl = $list . attr ( 'MobileDefaultViewUrl' ) ; var rootFolder = $list . attr ( 'RootFolder' ) ; self . listItemType = Utils . tail ( rootFolder . split ( '/' ) ) . toString ( ) ; $ ( xmlDoc ) . find ( 'Field' ) . filter ( function ( i , el ) { return ! ! ( $ ( el ) . attr ( 'StaticName' ) ) && $ ( el ) . attr ( 'Hidden' ) != 'TRUE' && ! rxExcludeNames . test ( $ ( el ) . attr ( 'Name' ) ) ; } ) . each ( setupKoVar ) ; self . fieldNames . sort ( ) ; if ( self . debug ) { console . info ( self . listName + ' list ID = ' + self . listId ) ; console . info ( 'Field names are...' ) ; console . info ( self . fieldNames ) ; } self . nextAsync ( true , 'Initialized list settings.' ) ; } catch ( e ) { if ( self . debug ) { throw e ; } var error = 'Failed to initialize list settings.' ; self . logError ( error + ' SPForm.getListAsync.setupList(): ' , e ) ; self . nextAsync ( false , error ) ; } } function setupKoVar ( i , el ) { if ( ! ! ! el ) { return ; } try { var $el = $ ( el ) ; var displayName = $el . attr ( 'DisplayName' ) ; var spType = $el . attr ( 'Type' ) ; var spName = $el . attr ( 'StaticName' ) ; var spFormat = $el . attr ( 'Format' ) ; var spRequired = ! ! ( $el . attr ( 'Required' ) ) ? $el . attr ( 'Required' ) . toLowerCase ( ) == 'true' : false ; var spReadOnly = ! ! ( $el . attr ( 'ReadOnly' ) ) ? $el . attr ( 'ReadOnly' ) . toLowerCase ( ) == 'true' : false ; var spDesc = $el . attr ( 'Description' ) ; var vm = self . viewModel ; var koName = Utils . toCamelCase ( spName ) ; if ( koName in self . viewModel ) { return ; } self . fieldNames . push ( koName ) ; var defaultValue ; $el . find ( '> Default' ) . each ( function ( j , def ) : void { var val = $ . trim ( $ ( def ) . text ( ) ) ; if ( ! ! spType ) { switch ( spType . toLowerCase ( ) ) { case 'boolean' : val = val == '0' ? false : true ; break ; case 'number' : case 'currency' : val = val - 0 ; break ; case 'datetime' : if ( val == '[today]' ) { val = new Date ( ) ; } break ; default : break ; } } defaultValue = val ; } ) ; var koObj = ! ! spType && /^multi/i . test ( spType ) ? ko . observableArray ( [ ] ) : ko . observable ( ! ! defaultValue ? defaultValue : spType == 'Boolean' ? false : null ) ; koObj . _metadata = { 'koName' : koName , 'displayName' : displayName || null , 'name' : spName || null , 'format' : spFormat || null , 'required' : spRequired || false , 'readOnly' : spReadOnly || false , 'description' : spDesc || null , 'type' : spType } ; for ( var p in koObj . _metadata ) { koObj [ '_' + p ] = koObj . _metadata [ p ] ; } if ( rxIsChoice . test ( spType ) ) { var isFillIn = $el . attr ( 'FillInChoice' ) ; koObj . _isFillInChoice = ! ! isFillIn && isFillIn == 'True' ; var choices = [ ] ; var options = [ ] ; $el . find ( 'CHOICE' ) . each ( function ( j , choice ) { var txt = $ ( choice ) . text ( ) ; choices . push ( { 'value' : $ ( choice ) . text ( ) , 'selected' : false } ) ; options . push ( txt ) ; } ) ; koObj . _choices = choices ; koObj . _options = options ; koObj . _multiChoice = ! ! spType && spType == 'MultiChoice' ; koObj . _metadata . choices = choices ; koObj . _metadata . options = options ; koObj . _metadata . multichoice = koObj . _multiChoice ; } koObj . _metadata . $parent = koObj ; vm [ koName ] = koObj ; if ( self . debug ) { console . info ( 'Created KO object: ' + koName + ', type: ' + spType + ', default val: ' + defaultValue ) ; } } catch ( e ) { self . logError ( 'Failed to setup KO object at SPForm.getListAsync.setupKoVar(): ' , e ) ; if ( self . debug ) { throw e ; } } } ; } param self param args return initForm ( self , args = undefined ) { try { self . updateStatus ( "Initializing dynamic form features..." , true , self ) ; var vm = self . viewModel ; var rx = /submitted/i ; if ( Utils . indexOf ( self . fieldNames , 'IsSubmitted' ) > - 1 ) { self . allowSave = true ; ViewModel . isSubmittedKey = 'IsSubmitted' ; if ( self . debug ) { console . info ( 'initFormAsync: IsSubmitted key: ' + ViewModel . isSubmittedKey ) ; } } self . viewModel . allowSave ( self . allowSave ) ; self . viewModel . allowPrint ( self . allowPrint ) ; self . viewModel . allowDelete ( self . allowDelete ) ; self . $formAction = $ ( Templates . getFormAction ( ) ) . appendTo ( self . $form ) ; if ( self . enableAttachments ) { self . setupAttachments ( self ) ; } if ( self . enableErrorLog ) { SpApi . getListItems ( self . errorLogListName , function ( data , error ) { if ( ! ! error ) { self . enableErrorLog = SPForm . enableErrorLog = false ; } } , self . errorLogSiteUrl , null , 'Title,Error' , 'Modified' , 1 , false ) ; } if ( self . includeNavigationMenu ) { var $navMenu = self . $form . find ( ".so-nav-menu, [data-so-nav-menu], [so-nav-menu]" ) ; if ( $navMenu . length > 0 ) { $navMenu . replaceWith ( Templates . soNavMenuControl ) ; } else { self . $form . prepend ( Templates . soNavMenuControl ) ; } } self . $form . find ( ".created-info, [data-sp-created-info], [data-so-created-info], [sp-created-info], [so-created-info]" ) . replaceWith ( Templates . soCreatedModifiedInfoControl ) ; if ( self . includeWorkflowHistory ) { var $wfControls = self . $form . find ( ".workflow-history, [data-so-workflow-history], [so-workflow-history]" ) ; if ( $wfControls . length > 0 ) { $wfControls . replaceWith ( Templates . soWorkflowHistoryControl ) ; } else { self . $form . append ( Templates . soWorkflowHistoryControl ) ; } } self . $form . find ( '[data-new-only]' ) . before ( '<!-- ko ifnot: !!$root.Id() -->' ) . after ( '<!-- /ko -->' ) ; self . $form . find ( '[data-edit-only]' ) . before ( '<!-- ko if: !!$root.Id() -->' ) . after ( '<!-- /ko -->' ) ; self . $form . find ( '[data-author-only]' ) . before ( '<!-- ko if: !!$root.isAuthor() -->' ) . after ( '<!-- /ko -->' ) ; self . $form . find ( '[data-non-authors]' ) . before ( '<!-- ko ifnot: !!$root.isAuthor() -->' ) . after ( '<!-- /ko -->' ) ; self . nextAsync ( true , "Form initialized." ) ; return ; } catch ( e ) { if ( self . debug ) { throw e ; } self . logError ( "Error in SPForm.initFormAsync(): " , e ) ; self . nextAsync ( false , "Failed to initialize form." ) ; return ; } } param self param args return getListItemAsync ( self , args = undefined ) { if ( ! ! ! self . itemId ) { self . nextAsync ( true , "This is a New form." ) ; return ; } self . updateStatus ( "Retrieving form values..." , true , self ) ; SpApi15 . GetListItem ( self . listName , self . itemId , self . siteUrl , false , 'AttachmentFiles' ) . done ( ( data , error = undefined ) => { if ( ! ! error ) { if ( /not found/i . test ( error + '' ) ) { self . showDialog ( "The form with ID " + self . itemId + " doesn't exist or it was deleted." ) ; } self . nextAsync ( false , error ) ; return ; } self . listItem = data ; self . listItemMetadata = data . __metadata ; self . bindListItemValues ( self ) ; } ) . then ( ( ) => { if ( self . includeWorkflowHistory ) { self . getHistoryAsync ( self ) ; } self . nextAsync ( true , "Retrieved form data." ) ; } ) . fail ( ( ) => { self . nextAsync ( false , "Failed to retrieve form data." ) ; } ) ; } param self param args return implementPermissions ( self , args = undefined ) { try { self . updateStatus ( "Retrieving your permissions..." , true , self ) ; self . $form . find ( "[data-sp-groups], [user-groups]" ) . each ( function ( i , el ) : void { var groups = $ ( el ) . attr ( "data-sp-groups" ) ; if ( ! ! ! groups ) { groups = $ ( el ) . attr ( "user-groups" ) ; } $ ( el ) . before ( '<!-- ko if: !!$root.isMember(' + groups + ') -->' ) . after ( '<!-- /ko -->' ) ; } ) ; self . nextAsync ( true , "Retrieved your permissions." ) ; } catch ( e ) { if ( self . debug ) { throw e ; } self . logError ( "Error in SPForm.implementPermissionsAsync() " , e ) ; self . nextAsync ( true , "Failed to retrieve your permissions." ) ; } } param self param args return getHistoryAsync ( self , args = undefined ) { if ( ! ! ! self . itemId || ! self . includeWorkflowHistory ) { self . nextAsync ( true ) ; return ; } self . updateStatus ( 'Retrieving workflow history...' , true , self ) ; var filter = "ListID eq '" + self . listId + "' and PrimaryItemID eq " + self . itemId ; var select = "Description,DateOccurred" ; var orderby = "DateOccurred" ; SpApi . getListItems ( self . workflowHistoryListName , callback , self . siteUrl , filter , select , orderby , 25 , false ) ; function callback ( items , error ) { if ( self . debug ) { console . info ( 'Retrieved workflow history...' ) ; console . info ( items . length ) ; } if ( ! ! error || ! ! ! items ) { var msg = 'The ' + self . workflowHistoryListName + ' list may be full at <a href="{url}">{url}</a>. Failed to retrieve workflow history in method, getHistoryAsync().' . replace ( /\{url\}/g , self . rootUrl + self . siteUrl + '/Lists/' + self . workflowHistoryListName . replace ( /\s/g , '%20' ) ) ; self . logError ( msg ) ; self . nextAsync ( false , 'Failed to retrieve workflow history. ' + error ) ; return ; } self . viewModel . historyItems ( [ ] ) ; for ( var i = 0 ; i < items . length ; i ++ ) { self . viewModel . historyItems ( ) . push ( new HistoryItem ( items [ i ] . Description , Utils . parseDate ( items [ i ] . DateOccurred ) ) ) ; } self . viewModel . historyItems . valueHasMutated ( ) ; self . nextAsync ( true , "Retrieved workflow history." ) ; } } param self param args return bindListItemValues ( self = undefined ) { self = self || this ; try { if ( ! ! ! self . itemId ) { return ; } var item = self . listItem ; var vm = self . viewModel ; if ( self . debug ) { console . info ( 'binding values from list item: ' , item ) ; } var rxExclude = /\b(__metadata|ContentTypeID|ContentType|Owshiddenversion|Version|Attachments|AttachmentFiles|Path)\b/ ; self . itemId = item . Id ; vm . Id ( item . Id ) ; for ( var key in self . viewModel ) { if ( ! item [ key ] || ! vm [ key ] . _type || rxExclude . test ( key ) ) { continue ; } if ( self . debug ) { console . info ( 'binding ko value: ' , key , item [ key ] ) ; } if ( ( item [ key ] != null && vm [ key ] . _type == 'DateTime' ) ) { vm [ key ] ( Utils . parseDate ( item [ key ] ) ) ; } else if ( vm [ key ] . _type == 'MultiChoice' ) { vm [ key ] ( item [ key ] . results ) ; } else { vm [ key ] ( item [ key ] || null ) ; } } if ( ! ! item . AuthorId || ! ! item . Author ) { if ( ! isNaN ( item . Author ) ) { item . AuthorId = item . Author ; } Shockout . SpApi15 . GetUserById ( self . siteUrl , item . AuthorId ) . done ( ( user ) => { if ( ! ! ! user . Picture ) { user . Picture = Shockout . SpApi15 . GetGenericPersonPng ( ) ; } item . CreatedBy = user ; vm . CreatedBy ( item . CreatedBy ) ; } ) ; } if ( ! ! item . EditorId || ! ! item . Editor ) { if ( ! isNaN ( item . Editor ) ) { item . EditorId = item . Editor ; } Shockout . SpApi15 . GetUserById ( self . siteUrl , item . EditorId ) . done ( ( user ) => { if ( ! ! ! user . Picture ) { user . Picture = Shockout . SpApi15 . GetGenericPersonPng ( ) ; } item . ModifiedBy = user ; vm . ModifiedBy ( item . ModifiedBy ) ; } ) ; } vm . Created ( Utils . parseDate ( item . Created ) ) ; vm . Modified ( Utils . parseDate ( item . Modified ) ) ; } catch ( e ) { if ( self . debug ) { throw e ; } self . logError ( 'Failed to bind form values in SPForm.bindListItemValues(): ' , e ) ; } } param model param callback return deleteListItem ( model , callback = undefined , timeout = 3000 ) { if ( ! confirm ( 'Are you sure you want to delete this form?' ) ) { return ; } var self = model . parent ; var item = self . listItem ; SpApi . deleteListItem ( item , function ( data , error ) { if ( ! ! error ) { if ( callback ) { callback ( data , error ) ; } return ; } self . showDialog ( "The form was deleted. You'll be redirected in " + timeout / 1000 + " seconds." ) ; if ( callback ) { callback ( data ) ; } setTimeout ( function ( ) { window . location . replace ( self . sourceUrl != null ? self . sourceUrl : self . rootUrl ) ; } , timeout ) ; } ) ; } param vm param isSubmit param customMsg return saveListItem ( vm , isSubmit = false , customMsg = undefined , showDialog = true ) { let d = $ . Deferred ( ) ; let self = vm . parent ; let isNew = ! ! ( self . itemId == null ) , timeout = 3000 , saveMsg = customMsg || '<p>Your form has been saved.</p>' , fields = [ ] , payload = { } ; try { var editable = Utils . getEditableKoNames ( self . $form ) ; $ ( editable ) . each ( function ( i , key ) { self . pushEditableFieldName ( key ) ; } ) ; self . editableFields . sort ( ) ; if ( self . debug ) { console . info ( 'Editable fields...' ) ; console . info ( self . editableFields ) ; } isSubmit = typeof ( isSubmit ) == "undefined" ? true : isSubmit ; if ( self . preSave ) { var retVal = self . preSave ( self , self . viewModel , isSubmit ) ; if ( typeof ( retVal ) != 'undefined' && ! ! ! retVal ) { return ; } } if ( isSubmit && ! self . formIsValid ( vm ) ) { return ; } var isSubmitted = vm [ ViewModel . isSubmittedKey ] ; if ( typeof ( isSubmitted ) != "undefined" && ( isSubmitted ( ) == null || isSubmitted ( ) == false ) ) { fields . push ( [ ViewModel . isSubmittedKey , ( isSubmit ? 1 : 0 ) ] ) ; payload [ ViewModel . isSubmittedKey ] = isSubmit ; } const $fields = $ ( self . editableFields ) ; $fields . each ( function ( i , key ) : void { if ( ! ( '_metadata' in vm [ key ] ) ) { return ; } let val = vm [ key ] ( ) ; let keyName = vm [ key ] . _name ; let spType = vm [ key ] . _type || vm [ key ] . _metadata . type ; spType = ! ! spType ? spType . toLowerCase ( ) : null ; if ( self . debug ) { console . info ( 'saving: ' , keyName , spType ) ; } if ( typeof ( val ) == "undefined" || key == ViewModel . isSubmittedKey ) { return ; } if ( spType == 'datetime' ) { const d = Utils . parseDate ( val ) ; if ( ! ! d ) { val = d ; } } else if ( val != null && spType == 'note' ) { val = $ ( '<div>' ) . html ( val ) . html ( ) ; } else if ( spType == 'user' ) { payload [ keyName + 'Id' ] = ! ! val ? parseInt ( val . split ( ';' ) [ 0 ] ) : null ; return ; } payload [ keyName ] = val ; } ) ; if ( isNew ) { SpApi15 . AddListItem ( self . siteUrl , self . listName , self . listItemType , payload ) . done ( ( data ) => { saveListItemCallback ( vm , self , data . Id ) . done ( ( listItem ) => { d . resolve ( listItem ) ; } ) ; } ) ; } else { SpApi15 . UpdateListItem ( self . siteUrl , self . listItem . __metadata , payload ) . then ( ( data ) => { saveListItemCallback ( vm , self , self . itemId ) . done ( ( listItem ) => { d . resolve ( listItem ) ; } ) ; } ) ; } } catch ( e ) { if ( self . debug ) { throw e ; } self . logError ( 'Error in SpForm.saveListItem(): ' , e ) ; d . reject ( e ) ; } return d . promise ( ) ; function saveListItemCallback ( vm , self , itemId ) { let d = $ . Deferred ( ) ; if ( self . debug ) { console . info ( 'saveListItemCallback(): ' ) ; console . info ( self . itemId ) ; } self . itemId = itemId ; vm . Id ( itemId ) ; if ( Utils . getIdFromHash ( ) == null && self . itemId != null ) { Utils . setIdHash ( self . itemId ) ; } if ( isSubmit ) { if ( showDialog ) { self . showDialog ( '<p>Your form has been submitted. You will be redirected in ' + timeout / 1000 + ' seconds.</p>' , 'Form Submission Successful' ) ; } if ( self . debug ) { console . warn ( 'DEBUG MODE: Would normally redirect user to confirmation page: ' + self . confirmationUrl ) ; self . _getListItem ( self ) . done ( ( listItem ) => { d . resolve ( listItem ) ; } ) ; } else { d . resolve ( self . listItem ) ; setTimeout ( function ( ) { window . location . href = self . sourceUrl != null ? self . sourceUrl : self . confirmationUrl ; } , timeout ) ; } return d . promise ( ) ; } if ( showDialog ) { self . showDialog ( saveMsg , 'The form has been saved.' , timeout ) ; } self . _getListItem ( self ) . done ( ( listItem ) => { d . resolve ( listItem ) ; } ) ; return d . promise ( ) ; } } _getListItem ( self ) { self = self || this ; let d = $ . Deferred ( ) ; SpApi15 . GetListItem ( self . listName , self . itemId , self . siteUrl , false , 'AttachmentFiles' ) . done ( ( data , error = undefined ) => { if ( ! ! error ) { if ( /not found/i . test ( error + '' ) ) { self . showDialog ( "The form with ID " + self . itemId + " doesn't exist or it was deleted." ) ; } self . nextAsync ( false , error ) ; return ; } self . listItem = data ; self . listItemMetadata = data . __metadata ; self . bindListItemValues ( self ) ; } ) . then ( ( ) => { if ( self . includeWorkflowHistory ) { self . getHistoryAsync ( self ) ; } d . resolve ( self . listItem ) ; } ) . fail ( ( ) => { d . reject ( false ) ; } ) ; return d . promise ( ) ; } param salef return finalize ( self ) { try { self . setupNavigation ( self ) ; self . setupDatePickers ( self ) ; self . nextAsync ( true , 'Finalized form controls.' ) ; } catch ( e ) { if ( self . debug ) { throw e ; } self . logError ( 'Error in SpForm.finalize(): ' , e ) ; self . nextAsync ( false , 'Failed to finalize form controls.' ) ; } } /**
        * Delete an attachment.
        */ deleteAttachment ( att , event ) { var self = ViewModel . parent ; var vm = self . viewModel ; if ( self . debug ) { console . info ( 'deleting file: ' , att ) ; } if ( ! confirm ( 'Are you sure you want to delete ' + att . FileName + '? This can\'t be undone.' ) ) { return ; } SpApi15 . DeleteAttachment ( self . siteUrl , att ) . done ( function ( data , status , jqXhr ) { if ( self . debug ) { console . info ( 'deleted file: ' , att ) ; } const attachments = vm . attachments ; attachments . remove ( att ) ; attachments . valueHasMutated ( ) ; } ) . fail ( function ( jqXhr , status , error ) { alert ( "Failed to delete attachment: " + status + ': ' + error ) ; } ) ; } param self param callback return getAttachments ( self = undefined ) { self = self || this ; let d = $ . Deferred ( ) ; let path = `${ self . listItem . __metadata . uri }/AttachmentFiles` ; SpApi15 . Get ( path , false ) . done ( ( data , status , jqXhr ) => { d . resolve ( data . results ) ; } ) . fail ( ( ) => { self . showDialog ( "Failed to retrieve attachments in SpForm.getAttachments()" ) ; d . resolve ( [ ] ) ; } ) ; return d . promise ( ) ; } param msg return log ( msg ) { if ( this . debug ) { console . log ( msg ) ; } } param msg param success return updateStatus ( msg , success = true , spForm ) { var self = spForm ; self . $formStatus . html ( msg ) . css ( 'color' , ( success ? "#ff0" : "$f00" ) ) . show ( ) ; setTimeout ( function ( ) { self . $formStatus . hide ( ) ; } , 2000 ) ; } param msg param title param timeout return showDialog ( msg , title = undefined , timeout = undefined ) { var self = this ; title = title || "Form Dialog" ; msg = ( msg ) . toString ( ) . match ( /<\w>\w*/ ) == null ? '<p>' + msg + '</p>' : msg ; self . $dialog . html ( msg ) . dialog ( 'open' ) ; if ( timeout ) { setTimeout ( function ( ) { self . $dialog . dialog ( 'close' ) ; } , timeout ) ; } } param model param showDialog return formIsValid ( model , showDialog = false ) { var self = model . parent , labels = [ ] , errorCount = 0 , invalidCount = 0 , invalidLabels = [ ] ; try { self . $form . find ( '.required, [required]' ) . each ( function checkRequired ( i , n ) : void { var koName = Utils . observableNameFromControl ( n , self . viewModel ) ; if ( ! ! koName && model [ koName ] ) { var val = model [ koName ] ( ) ; if ( val == null || $ . trim ( val + '' ) . length == 0 ) { var labelTxt ; var $label = $ ( "label[for='" + koName + "']" ) ; if ( ! ! $label ) { labelTxt = $label . html ( ) ; } if ( ! ! ! labelTxt ) { labelTxt = $ ( n ) . closest ( '.form-group' ) . find ( 'label:first' ) . html ( ) ; } if ( ! ! ! labelTxt ) { labelTxt = model [ koName ] [ '_displayName' ] ; } if ( ! ! ! labelTxt ) { $ ( n ) . parent ( ) . first ( ) . html ( ) ; } if ( Utils . indexOf ( labels , labelTxt ) < 0 ) { labels . push ( labelTxt ) ; errorCount ++ ; } } } } ) ; self . $form . find ( ".invalid" ) . each ( function ( i , el ) : void { var $parent = $ ( el ) . parent ( ) ; invalidLabels . push ( $ ( parent ) . first ( ) . html ( ) ) ; invalidCount ++ ; } ) ; if ( invalidCount > 0 ) { labels . push ( '<p class="warning">There are validation errors with the following fields. Please correct before saving.</p><p style="color:#f00;">' + invalidLabels . join ( '<br />' ) + '</p>' ) ; } if ( self . enableAttachments && self . requireAttachments && model . attachments ( ) . length == 0 ) { errorCount ++ ; labels . push ( self . attachmentMessage ) ; } if ( errorCount > 0 ) { self . showDialog ( '<p>The following fields are required or invalid:</p><div class="error">' + labels . join ( '<br/>' ) + '</div>' ) ; return false ; } return true ; } catch ( e ) { if ( self . debug ) { throw e ; } self . logError ( "Form validation error at SPForm.formIsValid(): " , e ) ; return false ; } } param id param callback return getPersonById ( id , koField ) { var self = this ; if ( ! ! ! id ) { return ; } SpApi . getPersonById ( id , function ( person , error ) { if ( ! ! error ) { var msg = 'Error in SPForm.getPersonById: ' + error ; Utils . logError ( msg , SPForm . errorLogListName ) ; if ( self . debug ) { console . warn ( msg ) ; } return ; } var name = person . Id + ';#' + person . Name . replace ( /^i\:0\#\.w\|/ , '' ) ; koField ( name ) ; if ( self . debug ) { console . warn ( 'Retrieved person by ID... ' + name ) ; } } ) ; } param key return pushEditableFieldName ( key ) { if ( ! ! ! key || Utils . indexOf ( this . editableFields , key ) > - 1 || key . match ( /^(_|\$)/ ) != null || Utils . indexOf ( this . fieldNames , key ) < 0 || this . viewModel [ key ] . _readOnly ) { return - 1 ; } return this . editableFields . push ( key ) ; } param msg param self return logError ( msg , e = undefined , self = undefined ) { self = self || this ; var err = [ msg ] ; if ( ! ! e ) { err . push ( e + '' ) ; } err = err . length > 0 ? err . join ( '; ' ) : err . join ( '' ) ; if ( self . enableErrorLog ) { Utils . logError ( err , self . errorLogListName , self . errorLogSiteUrl , self . debug ) ; self . showDialog ( '<p>An error has occurred and the web administrator has been notified.</p><pre>' + err + '</pre>' ) ; } } param self return setupAttachments ( self = undefined ) { self = self || this ; var vm = self . viewModel ; var count = 0 ; if ( ! self . enableAttachments ) { return count ; } try { self . $form . find ( ".attachments, [data-sp-attachments]" ) . each ( function ( i , att ) { $ ( att ) . replaceWith ( '<so-attachments params="val: attachments"></so-attachments>' ) ; } ) ; if ( self . debug ) { console . info ( 'Attachments are enabled.' ) ; } } catch ( e ) { if ( self . debug ) { throw e ; } self . logError ( 'Error in SPForm.setupAttachments(): ' , e ) ; } return count ; } return setupNavigation ( self = undefined ) { var self = self || this ; var count = 0 ; if ( ! self . includeNavigationMenu ) { return count ; } try { var $navSections = self . $form . find ( '.nav-section' ) ; if ( $navSections . length == 0 ) { return count ; } self . $form . find ( ".nav-section:visible" ) . each ( function ( i , el ) { var $el = $ ( el ) ; var $header = $el . find ( "> h4" ) ; if ( $header . length == 0 ) { return ; } var title = $header . html ( ) ; var anchorName = Utils . toCamelCase ( title ) + 'Nav' ; $el . before ( '<div style="height:1px;" id="' + anchorName + '">&nbsp;</div>' ) ; self . viewModel . navMenuItems ( ) . push ( { 'title' : title , 'anchorName' : anchorName } ) ; count ++ ; } ) ; self . viewModel . navMenuItems . valueHasMutated ( ) ; self . $form . append ( '<a href="#TOP" class="back-to-top"><span class="glyphicon glyphicon-chevron-up"></span></a>' ) ; $ ( 'body' ) . delegate ( 'a[href*=#]:not([href=#])' , 'click' , function ( ) { if ( window . location . pathname . replace ( /^\// , '' ) == this . pathname . replace ( /^\// , '' ) && location . hostname == this . hostname ) { var target = $ ( this . hash ) ; target = target . length ? target : $ ( '[name=' + this . hash . slice ( 1 ) + ']' ) ; if ( target . length ) { $ ( 'html,body' ) . animate ( { scrollTop : target . offset ( ) . top - 50 } , 1000 ) ; return false ; } } } ) ; } catch ( e ) { if ( self . debug ) { throw e ; } self . logError ( 'Error in SpForm.setupNavigation(): ' , e ) ; } return count ; } param { SPForm = undefined returns setupDatePickers ( self = undefined ) { self = self || this ; var $datepickers = self . $form . find ( 'input.datepicker' ) . datepicker ( ) ; if ( self . debug ) { console . info ( 'Bound ' + $datepickers . length + ' jQueryUI datepickers.' ) ; } return $datepickers . length ; } param targetGroups return currentUserIsMemberOfGroups ( targetGroups ) { var groupNames = [ ] ; if ( Utils . isString ( targetGroups ) ) { groupNames = targetGroups . match ( /\,/ ) != null ? targetGroups . split ( ',' ) : [ targetGroups ] ; } else if ( targetGroups . constructor === Array ) { groupNames = targetGroups ; } else { return false ; } for ( var i = 0 ; i < groupNames . length ; i ++ ) { var group = groupNames [ i ] ; group = group . match ( /\;#/ ) != null ? group . split ( ';' ) [ 0 ] : group ; group = Utils . trim ( group ) ; for ( var j = 0 ; j < this . currentUser . groups . length ; j ++ ) { var g = this . currentUser . groups [ j ] ; if ( group == g . name || parseInt ( group ) == g . id ) { return true ; } } } return false ; } } } window [ 'so' ] = window [ 'so' ] || Shockout ;