module Shockout { export class KoComponents { public static registerKoComponents ( ) { var uniqueId = ( function ( ) { var i = 0 ; return function ( ) { return i ++ ; } ; } ) ( ) ; ko . components . register ( 'so-text-field' , { viewModel : soFieldModel , template : Templates . soTextField } ) ; ko . components . register ( 'so-html-field' , { viewModel : soFieldModel , template : Templates . soHtmlFieldTemplate } ) ; ko . components . register ( 'so-person-field' , { viewModel : soFieldModel , template : Templates . soTextField . replace ( /data-bind="(value|text): modelValue/g , 'data-bind="spPerson: modelValue' ) } ) ; ko . components . register ( 'so-date-field' , { viewModel : soFieldModel , template : Templates . soTextField . replace ( /data-bind="(value|text): modelValue/g , 'data-bind="spDate: modelValue' ) } ) ; ko . components . register ( 'so-datetime-field' , { viewModel : soFieldModel , template : Templates . soTextField . replace ( /data-bind="(value|text): modelValue/g , 'data-bind="spDateTime: modelValue' ) } ) ; ko . components . register ( 'so-money-field' , { viewModel : soFieldModel , template : Templates . soTextField . replace ( /data-bind="(value|text): modelValue/g , 'data-bind="spMoney: modelValue' ) } ) ; ko . components . register ( 'so-number-field' , { viewModel : soFieldModel , template : Templates . soTextField . replace ( /data-bind="(value|text): modelValue/g , 'data-bind="spNumber: modelValue' ) } ) ; ko . components . register ( 'so-decimal-field' , { viewModel : soFieldModel , template : Templates . soTextField . replace ( /data-bind="(value|text): modelValue/g , 'data-bind="spDecimal: modelValue' ) } ) ; ko . components . register ( 'so-checkbox-field' , { viewModel : soFieldModel , template : Templates . soCheckboxField } ) ; ko . components . register ( 'so-select-field' , { viewModel : soFieldModel , template : Templates . soSelectField } ) ; ko . components . register ( 'so-checkbox-group' , { viewModel : soFieldModel , template : Templates . soCheckboxGroup } ) ; ko . components . register ( 'so-radio-group' , { viewModel : soFieldModel , template : Templates . soRadioGroup } ) ; ko . components . register ( 'so-usermulti-group' , { viewModel : soUsermultiModel , template : Templates . soUsermultiField } ) ; ko . components . register ( 'so-static-field' , { viewModel : soStaticModel , template : Templates . soStaticField } ) ; ko . components . register ( 'so-static-person' , { viewModel : soStaticModel , template : Templates . soStaticField . replace ( /data-bind="text: modelValue/g , 'data-bind="spPerson: modelValue' ) } ) ; ko . components . register ( 'so-static-date' , { viewModel : soStaticModel , template : Templates . soStaticField . replace ( /data-bind="text: modelValue/g , 'data-bind="spDate: modelValue' ) } ) ; ko . components . register ( 'so-static-datetime' , { viewModel : soStaticModel , template : Templates . soStaticField . replace ( /data-bind="text: modelValue/g , 'data-bind="spDateTime: modelValue' ) } ) ; ko . components . register ( 'so-static-money' , { viewModel : soStaticModel , template : Templates . soStaticField . replace ( /data-bind="text: modelValue/g , 'data-bind="spMoney: modelValue' ) } ) ; ko . components . register ( 'so-static-number' , { viewModel : soStaticModel , template : Templates . soStaticField . replace ( /data-bind="text: modelValue/g , 'data-bind="spNumber: modelValue' ) } ) ; ko . components . register ( 'so-static-decimal' , { viewModel : soStaticModel , template : Templates . soStaticField . replace ( /data-bind="text: modelValue/g , 'data-bind="spDecimal: modelValue' ) } ) ; ko . components . register ( 'so-static-html' , { viewModel : soStaticModel , template : Templates . soStaticField . replace ( /data-bind="text: modelValue/g , 'data-bind="html: modelValue' ) } ) ; ko . components . register ( 'so-attachments' , { viewModel : soAttachmentsModel , template : Templates . soAttachments } ) ; ko . components . register ( 'so-created-modified-info' , { viewModel : soCreatedModifiedInfoModel , template : Templates . soCreatedModifiedInfo } ) ; ko . components . register ( 'so-nav-menu' , { viewModel : soNavMenuModel , template : Templates . soNavMenu } ) ; ko . components . register ( 'so-workflow-history' , { viewModel : function ( params ) { this . historyItems = < Array < IHistoryItem > > ( params . val || params . historyItems ) ; } , template : Templates . soWorkflowHistory } ) ; function soCreatedModifiedInfoModel ( params ) { this . CreatedBy = < KnockoutObservable < ISpPerson > > params . createdBy ; this . ModifiedBy = < KnockoutObservable < ISpPerson > > params . modifiedBy ; this . profiles = ko . observableArray ( [ { header : 'Created By' , profile : this . CreatedBy } , { header : 'Modified By' , profile : this . ModifiedBy } ] ) ; this . Created = params . created ; this . Modified = params . modified ; this . showUserProfiles = params . showUserProfiles ; } ; function soStaticModel ( params ) { if ( ! params ) { throw 'params is undefined in so-static-field' ; } var koObj = params . val || params . modelValue ; if ( ! koObj ) { throw "Parameter `val` or `modelValue` for so-static-field is required!" ; } this . modelValue = koObj ; this . id = params . id || koObj . _koName ; this . label = params . label || koObj . _displayName ; this . description = params . description || koObj . _description ; var labelX = parseInt ( params . labelColWidth || 3 ) ; var fieldX = parseInt ( params . fieldColWidth || ( 12 - ( labelX - 0 ) ) ) ; this . labelColWidth = 'col-sm-' + labelX ; this . fieldColWidth = 'col-sm-' + fieldX ; } ; function soFieldModel ( params ) { if ( ! params ) { throw 'params is undefined in soFieldModel' ; } var koObj = params . val || params . modelValue ; if ( ! koObj ) { throw "Parameter `val` or `modelValue` for so-text-field is required!" ; } this . modelValue = koObj ; this . id = params . id || koObj . _koName ; this . name = params . name || koObj . _koName || params . id ; this . label = params . label = null || params . label == '' ? undefined : params . label || koObj . _displayName ; this . title = params . title ; this . caption = params . caption ; this . maxlength = params . maxlength || 255 ; this . placeholder = params . placeholder || params . label || koObj . _displayName ; this . description = ( typeof params . description != 'undefined' ) ? ( params . description == null ? undefined : params . description ) : koObj . _description ; this . valueUpdate = params . valueUpdate ; this . editable = ! ! koObj . _koName ; this . koName = koObj . _koName ; this . options = params . options || koObj . _options ; this . required = ( typeof params . required == 'function' ) ? params . required : ko . observable ( ! ! params . required || false ) ; this . inline = params . inline || false ; this . multiline = params . multiline || false ; var labelX = parseInt ( params . labelColWidth || 3 ) ; var fieldX = parseInt ( params . fieldColWidth || ( 12 - ( labelX - 0 ) ) ) ; this . labelColWidth = 'col-sm-' + labelX ; this . fieldColWidth = 'col-sm-' + fieldX ; this . readOnly = ( typeof params . readOnly == 'function' ) ? params . readOnly : ko . observable ( ! ! params . readOnly || false ) ; } ; function soAttachmentsModel ( params ) { var self = this ; var w = window ; this . errorMsg = ko . observable ( null ) ; if ( ! ! ! params ) { this . errorMsg ( '`params` is undefined in component so-attachments' ) ; throw this . errorMsg ( ) ; } if ( ! ! ! params . val ) { this . errorMsg ( 'Parameter `val` for component so-attachments is required!' ) ; throw this . errorMsg ( ) ; } var spForm = params . val . getSpForm ( ) ; var vm = spForm . getViewModel ( ) ; var allowedExtensions = params . allowedExtensions || spForm . allowedExtensions ; var reader ; var cafe ; var asyncFns ; this . attachments = < IViewModelAttachments > params . val || ko . observableArray ( [ ] ) ; this . label = params . label || 'Attach Files' ; this . drop = params . drop || true ; this . dropLabel = params . dropLabel || '...or Drag and Drop Files Here' ; this . className = params . className || 'btn btn-primary' ; this . title = params . title || 'Attachments' ; this . description = params . description ; this . readOnly = ( typeof params . readOnly == 'function' ) ? params . readOnly : ko . observable ( params . readOnly || false ) ; this . length = ko . pureComputed ( function ( ) { let att = self . attachments ( ) ; return ! ! att ? att . length : 0 ; } ) ; this . fileUploads = < KnockoutObservableArray < any > > ko . observableArray ( ) ; this . hasFileReader = ko . observable ( w . File && w . FileReader && w . FileList && w . Blob ) ; if ( ! this . hasFileReader ( ) ) { this . errorMsg ( 'This browser does not support the FileReader class required for uplaoding files. You may be using IE 9 or another unsupported browser.' ) ; } this . id = params . id || 'so_fileUploader_' + uniqueId ( ) ; this . deleteAttachment = function ( att , event ) { if ( ! confirm ( 'Are you sure you want to delete ' + att . FileName + '? This can\'t be undone.' ) ) { return ; } Shockout . SpApi15 . DeleteAttachment ( spForm . 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 ) ; } ) ; } ; this . fileHandler = function ( e ) { var files = document . getElementById ( self . id ) [ 'files' ] ; readFiles ( files ) ; } ; this . onSelect = function ( e ) { cancel ( e ) ; document . getElementById ( self . id ) . click ( ) ; } ; this . onDrop = function ( localViewModel , e ) { cancel ( e ) ; if ( spForm . debug ) { console . info ( 'dropped files over dropzone, arguments are...' ) ; console . info ( arguments ) ; } var dt = ( e . originalEvent || e ) . dataTransfer ; var files = dt . files ; if ( ! ! ! files ) { console . warn ( 'Error in so-attachments - event.dataTransfer.files is ' + typeof files ) ; return false ; } else { readFiles ( files ) ; } } ; function readFiles ( files ) { cafe = Cafe . create ( ) . finally ( function ( msg , success , args ) { let vm = spForm . getViewModel ( ) ; spForm . getAttachments ( spForm ) . done ( ( attachments ) => { vm . attachments ( attachments ) ; vm . attachments . valueHasMutated ( ) ; } ) ; } ) ; asyncFns = [ ] ; let fileArray = Array . prototype . slice . call ( files , 0 ) ; fileArray . map ( function ( file , i ) { asyncFns . push ( function ( cafe , args ) { readFile ( file , cafe , args ) ; } ) ; } ) ; cafe . asyncFns = asyncFns ; if ( ! ! ! vm . Id ( ) ) { spForm . saveListItem ( vm , false , undefined , false ) . done ( ( listItem ) => { spForm . listItem = listItem ; } ) . then ( ( ) => { cafe . start ( ) ; } ) ; } else { cafe . start ( ) ; } } ; function readFile ( file , cafe , args ) { if ( spForm . debug ) { console . info ( 'uploading file...' ) ; console . info ( file ) ; } if ( ! ! ! self . attachments ( ) ) { self . attachments ( [ ] ) ; } var fileName = file . name . replace ( /[^a-zA-Z0-9_\-\.]/g , '' ) ; var ext = /\.\w{2,4}$/ . exec ( fileName ) [ 0 ] ; var rootName = fileName . replace ( new RegExp ( ext + '$' ) , '' ) ; var allowedExtension = new RegExp ( "^(\\.|)(" + allowedExtensions . join ( '|' ) + ")$" , "i" ) . test ( ext ) ; if ( ! allowedExtension ) { self . errorMsg ( 'Only files with the extensions: ' + allowedExtensions . join ( ', ' ) + ' are allowed.' ) ; return ; } for ( var i = 0 ; i < self . attachments ( ) . length ; i ++ ) { if ( new RegExp ( fileName , 'i' ) . test ( self . attachments ( ) [ i ] . FileName ) ) { fileName = rootName + '-' + new Date ( ) . getTime ( ) + ext ; break ; } } var fileUpload = new FileUpload ( fileName , file . size ) ; self . fileUploads ( ) . push ( fileUpload ) ; self . fileUploads . valueHasMutated ( ) ; reader = new FileReader ( ) ; reader . onerror = function errorHandler ( e ) { var evt = e ; var className = fileUpload . className ( ) ; fileUpload . className ( className . replace ( '-success' , '-danger' ) ) ; switch ( evt . target . error . code ) { case evt . target . error . NOT_FOUND_ERR : self . errorMsg = 'File Not Found!' ; break ; case evt . target . error . NOT_READABLE_ERR : self . errorMsg = 'File is not readable.' ; break ; case evt . target . error . ABORT_ERR : break ; default : self . errorMsg = 'An error occurred reading this file.' ; } ; } ; reader . onprogress = function ( e ) { updateProgress ( e , fileUpload ) ; } ; reader . onabort = function ( e ) { self . errorMsg ( 'File read cancelled' ) ; } ; reader . onloadstart = function ( e ) { fileUpload . progress ( 0 ) ; } ; reader . onload = function ( e ) { var event = e ; fileUpload . progress ( 100 ) ; Shockout . SpSoap . addAttachment ( event . target . result , fileName , spForm . listName , spForm . viewModel . Id ( ) , spForm . siteUrl , function ( ) { self . fileUploads . remove ( fileUpload ) ; cafe . next ( true ) ; } ) ; } ; reader . onloadend = function ( loadend ) { } ; reader . readAsDataURL ( file ) ; } ; this . onDragenter = cancel ; this . onDragover = cancel ; function updateProgress ( e , fileUpload ) { if ( e . lengthComputable ) { var percentLoaded = Math . round ( ( e . loaded / e . total ) * 100 ) ; if ( percentLoaded < 100 ) { fileUpload . progress ( percentLoaded ) ; } } } ; function cancel ( e ) { if ( e . preventDefault ) { e . preventDefault ( ) ; } if ( e . stopPropagation ) { e . stopPropagation ( ) ; } } ; if ( ! spForm . enableAttachments ) { this . errorMsg ( 'Attachments are disabled for this form or SharePoint list.' ) ; this . readOnly ( true ) ; } } function soUsermultiModel ( params ) { if ( ! params ) { throw 'params is undefined in soFieldModel' ; } var self = this ; var koObj = params . val || params . modelValue ; if ( ! koObj ) { throw "Parameter `val` or `modelValue` for so-text-field is required!" ; } this . modelValue = koObj ; this . placeholder = "" ; this . id = params . id || koObj . _koName ; this . name = params . name || koObj . _koName || params . id ; this . label = params . label || koObj . _displayName ; this . title = params . title ; this . required = params . required ; this . description = params . description || koObj . _description ; this . editable = ! ! koObj . _koName ; this . koName = koObj . _koName ; this . person = ko . observable ( null ) ; this . readOnly = ( typeof params . readOnly == 'function' ) ? params . readOnly : ko . observable ( ! ! params . readOnly || false ) ; this . addPerson = function ( model , ctrl ) { if ( self . modelValue ( ) == null ) { self . modelValue ( [ ] ) ; } var isAlreadyInArray = false ; ko . utils . arrayForEach ( self . modelValue ( ) , function ( item ) { if ( item == self . person ( ) ) { isAlreadyInArray = true ; } return ; } ) ; if ( ! isAlreadyInArray ) { self . modelValue ( ) . push ( self . person ( ) ) ; self . modelValue . valueHasMutated ( ) ; self . person ( null ) ; } else { this . shake ( ctrl ) ; } return false ; } ; this . removePerson = function ( person , event ) { try { self . modelValue . remove ( person ) ; } catch ( err ) { var index = self . modelValue ( ) . indexOf ( person ) ; if ( index > - 1 ) { self . modelValue ( ) . splice ( self . modelValue ( ) . indexOf ( person ) , 1 ) ; self . modelValue . valueHasMutated ( ) ; } } return false ; } ; this . showRequiredText = ko . pureComputed ( function ( ) { if ( self . required ) { if ( ! ! self . modelValue ( ) ) { return self . modelValue ( ) . length < 1 ; } return true ; } return false ; } ) ; this . shake = function ( element ) { var $el = $ ( 'button[id=' + element . currentTarget . id + ']' ) ; var shakes = 3 ; var distance = 5 ; var duration = 200 ; $el . css ( "position" , "relative" ) ; for ( var x = 1 ; x <= shakes ; x ++ ) { $el . removeClass ( "btn-success" ) . addClass ( "btn-danger" ) . animate ( { left : ( distance * - 1 ) } , ( ( ( duration / shakes ) / 4 ) ) ) . animate ( { left : distance } , ( ( duration / shakes ) / 2 ) ) . animate ( { left : 0 } , ( ( ( duration / shakes ) / 4 ) ) ) ; setTimeout ( function ( ) { $el . removeClass ( "btn-danger btn-warning" ) . addClass ( "btn-success" ) ; } , 1000 ) ; } } ; } ; function soNavMenuModel ( params ) { this . navMenuItems = < KnockoutObservableArray < any > > params . val ; this . title = params . title || 'Navigation' ; } ; } ; } }