EmailSender = function( options ) { options = $.extend( true, { allowSendCopy : true, allowImportant : true, allowReadReceipt : true, allowAttachments : true }, options ); let instance = this; instance.to = options.to; instance.from = options.from ? options.from : Fse.Portal.getConfiguration( "STP.userEmailAddress" ); instance.cc = options.cc; instance.subject = options.subject; instance.message = options.message ? options.message : "Enter your message here"; instance.messageDefaultsURL = options.messageDefaultsURL; instance.messageFormatter = options.messageFormatter; instance.trackingCode = options.trackingCode; instance.attachmentLinks = options.attachmentLinks; // [ { text : , href : }] instance.title = options.title ? options.title : "Send Email"; instance.allowSendCopy = options.allowSendCopy; instance.allowImportant = options.allowImportant; instance.allowReadReceipt = options.allowReadReceipt; instance.allowAttachments = options.allowAttachments; instance.prepareAjax = options.prepareAjax; instance.customOptions = options.customOptions; // list of options on the right instance.toItems = options.toItems; // changes the to from a free form field to a drop down with the provided items if(( instance.customOptions || instance.toItems ) && ! instance.prepareAjax ) { // customOptions and toItems require the use of a prepareAjax for the customization of the send action instance.customOptions = null; instance.toItems = null; } // these are only need if we want something added to the activity log instance.subjectId = options.subjectId; instance.subjectType = options.subjectType; instance.readyPromise = $.Deferred(); } EmailSender.prototype.constructor = EmailSender; EmailSender.prototype.element = function() { let instance = this; if( instance.rootElement ) return instance.rootElement; instance.rootElement = $("
"); let initialFormData = { to : instance.to, from : instance.from, subject : instance.subject, cc : instance.cc, message : instance.message, trackingCode : instance.trackingCode, sendCopy : false, attachmentLinks : instance.attachmentLinks, messageFormatter : instance.messageFormatter }; let emailValidationCallback = function( e ) { let emailAddresses = e.value.replace( /\s/g, "" ).split(/[,;\s]/); let badEmailAddresses = []; emailAddresses.forEach( function( emailAddressToCheck ) { if(! emailAddressToCheck.match( /^[\d\w._-]+@[\d\w._-]+\.[\w]+$/i )) { badEmailAddresses.push( emailAddressToCheck ); } }) return badEmailAddresses.length == 0; } let toField = { dataField : "to", isRequired : true, validationRules : [ { type : "required" }, { type : "custom", ignoreEmptyValue : true, message : "Invalid Email Address(s)", validationCallback : emailValidationCallback } ], editorType : "dxTextBox", editorOptions : { readOnly : instance.to ? true : false, buttons : [ { name : "picker", location : "after", options : { icon : "more", stylingMode : "text", onClick : function( ee ) { instance.pickRecipient( "to" ); } }} ] } }; if( instance.toItems ) { toField = { itemType : "group", items : [ { dataField : "toOption", label : { text : "To" }, isRequired : true, editorType : "dxSelectBox", editorOptions : { dataSource : { store : { type : "array", data : instance.toItems, key : "value" } }, displayExpr : "text", valueExpr : "value" } }, { dataField : "to", label : { text : "Recipients" }, isRequired : true, validationRules : [ { type : "required" }, { type : "custom", ignoreEmptyValue : true, message : "Invalid Email Address(s)", validationCallback : emailValidationCallback }, { type : "async", ignoreEmptyValue : true, validationCallback : function( callbackOptions ) { let d = $.Deferred(); let badEmails = []; let emailsToValidate = callbackOptions.value; if( emailsToValidate ) { emailsToValidate = emailsToValidate.split(","); } else { emailsToValidate = []; } let waitCount = emailsToValidate.length; d.progress( function( notify ) { // console.log( "NOTIFY", notify ); waitCount--; if( ! notify.isValid ) { badEmails.push( notify.email ); } if( waitCount == 0 ) { d.resolve( { isValid : badEmails.length ? false : true, message : `Invalid Email Addresses: ${badEmails.join(",")}` } ); } }) if( emailsToValidate.length == 0 ) { d.resolve( { isValid : true } ) } else { let emailStore= instance.form.getEditor( "toOption").option( "selectedItem" ).dataSource.store(); emailsToValidate.forEach( function( email ) { email = email.replace( /^\s+|\s+$/g, '' ); emailStore.byKey( email ) .done( function( data ) { d.notify( { isValid : data ? true : false, email : email }) }) .fail( function() { d.notify( { isValid : false, email : email } ); }) }) } return d.promise(); }} ], editorType : "dxTextBox", editorOptions : { readOnly : instance.to ? true : false, disabled : true, buttons : [ { name : "picker", location : "after", options : { icon : "more", stylingMode : "text", onClick : function( ee ) { instance.pickRecipient( "to" ); } }} ] } } ] } initialFormData.toOption = instance.toItems[0].value; } let emailFields = [ toField, { dataField : "cc", validationRules : [ { type : "custom", ignoreEmptyValue : true, message : "Invalid Email Address(s)", validationCallback : emailValidationCallback } ], editorType : "dxTextBox", editorOptions : { buttons : [ { name : "picker", location : "after", options : { icon : "more", stylingMode : "text", onClick : function( ee ) { instance.pickRecipient( "cc" ); } }} ] }}, { dataField : "from", isRequired : true, validationRules : [ { type : "email" } ], editorType : "dxTextBox", editorOptions : { readOnly : instance.from ? true : false } }, { dataField : "subject", isRequired : true, editorType : "dxTextBox", editorOptions : { // readOnly : instance.subject ? true : false }}, { dataField : "message", isRequired : true, editorType : "dxTextArea", editorOptions : { height : 400 }}, { dataField : "attachmentLinks", label : { text : "Sending with", visible : initialFormData.attachmentLinks ? true : false }, template : function( options ) { // console.log( "attachmentLinks template", options ); let container = $("
").css( { "height" : "26px;" }); let attachmentLinks = options.editorOptions.value; if( attachmentLinks ) { if( ! Array.isArray( attachmentLinks )) attachmentLinks = [ attachmentLinks ]; attachmentLinks.forEach( function( link ) { let tag = $("").attr( { href : link.href, target : "_blank" } ).text( link.text ).css( { "padding-right" : "10px" }); container.append( tag ); }) } instance.attachmentLinksSocket = $("
") return container; } } ]; // begin delivery options let deliveryOptions = []; if( instance.allowSendCopy ) { deliveryOptions.push( { dataField : "sendCopy", label : { visible : false, text : "Send Copy" }, editorType : "dxCheckBox", editorOptions : { text : "Send a copy to myself as a record" } } ); } if( instance.allowImportant ) { deliveryOptions.push( { dataField : "highPriority", label : { visible : false, text : "Important" }, editorType : "dxCheckBox", editorOptions : { text : "Mark as high important" } } ) } if( instance.allowReadReceipt ) { deliveryOptions.push( { dataField : "readReceipt", label : { visible : false, text : "Read Receipt" }, editorType : "dxCheckBox", editorOptions : { text : "Request read receipt" } } ) } if( instance.customOptions ) { instance.customOptions.forEach( function( option ){ deliveryOptions.push( { dataField : `customOption_${option.dataField}`, label : { visible : false, text : option.text }, editorType : "dxCheckBox", editorOptions : { text : option.text } } ) if( option.value ) { initialFormData[`customOption_${option.dataField}`] = option.value; } }) } let formFields = emailFields; let colCount = 1; if( deliveryOptions.length || instance.allowAttachments ) { let optionFields = deliveryOptions; if( optionFields.length ) { optionFields[0].label = { text : "Options", location : "top", visible : true }; } if( instance.allowAttachments ) { optionFields.push( { itemType : "empty" }, { dataField : "attachment", label : { text : "Attachments", location : "top", visible : true }, editorType : "dxFileUploader", editorOptions : { uploadMode : "useForm", showFileList : false, multiple : true, onValueChanged : function( e ) { let attachmentList = instance.form.option( "attachmentList" ); if( attachmentList ) { let currentAttachments = attachmentList.option( "items" ); if( ! currentAttachments ) currentAttachments = []; let revisedAttachments = []; currentAttachments.forEach( function( file ) { revisedAttachments.push( file ); }) let newAttachments = []; if( e.value ) { let values = e.value; if( ! Array.isArray( values ) ) values = [ values ]; values.forEach( function( file ) { newAttachments.push( file ); }) } newAttachments.forEach( function( newFile ) { let alreadyExists = false; revisedAttachments.forEach( function( file ) { if( alreadyExists ) return; if( file.name == newFile.name && file.lastModified == newFile.lastModified && file.size == newFile.size ) { alreadyExists = true; } }) if( ! alreadyExists ) { revisedAttachments.push( newFile ); } }) if( revisedAttachments.length != currentAttachments.length ) { if( revisedAttachments.length == 0 ) revisedAttachments = null; attachmentList.option( "items", revisedAttachments ); } } } } }, { template : function( options ) { let list = $("
").dxList( { displayExpr : "name", allowItemDeleting : true, form : options.component, noDataText : "", onItemDeleted : function( e ) { let attachments = e.component.option( "items" ); let form = e.component.option( "form" ); form.updateData( { "attachments" : attachments } ) } }).dxList( "instance" ); options.component.option( "attachmentList", list ); return list.element(); } } ) } colCount = 4; formFields = [ { itemType : "group", name : "main", colSpan : 3, items : emailFields, }, { itemType : "group", name : "options", colSpan : 1, items : optionFields } ] } instance.form = $("
").dxForm( { disabled : true, formData : initialFormData, items : formFields, colCount : colCount, onFieldDataChanged : function( e ) { // console.log( e ); if( e.dataField == "toOption" ) { let toField = e.component.getEditor( "to" ); let toOptionField = e.component.getEditor( "toOption" ); let toOptionItem = toOptionField.option( "selectedItem" ); if( toOptionItem && toOptionItem.dataSource ) { toField.option( { disabled : false } ) } else { toField.option( { disabled : true } ); } } } }).dxForm( "instance" ); instance.readyPromise.done( function() { instance.form.option( "disabled", false ); if( instance.loadPanel ) { instance.loadPanel.hide(); instance.loadPanel = null; } }) if( instance.messageDefaultsURL ) { instance.loadMessageDefaults(); } else { Fse.Data.newDataSource( { object : "USR.emailSignature" } ).load().done( function( emailSignature ) { // console.log( emailSignature ); let messageDefaults = { message : `${instance.message}\r\n\r\n${emailSignature[0].emailSignature}` } instance.form.updateData( messageDefaults ) instance.readyPromise.resolve() }) } instance.rootElement.append( instance.form.element() ); return instance.rootElement; } EmailSender.prototype.loadMessageDefaults = function() { let instance = this; instance.loadPanel = $("
").dxLoadPanel( { message : "Loading...", onHidden : function( e ) { e.component.element().remove(); } }).appendTo( $("body") ).dxLoadPanel( "instance" ); instance.loadPanel.show(); $.ajax( { method : "GET", url : instance.messageDefaultsURL, headers : { fseAjax : true } }).done( function( messageDefaults ) { if( typeof messageDefaults == "string" ) { messageDefaults = { message : messageDefaults }; } instance.form.updateData( messageDefaults ); // console.log( "Updated" ); if( messageDefaults.attachmentLinks ) { instance.form.itemOption( "main.attachmentLinks", "label", { text : "Sending with", visible : true } ); } instance.readyPromise.resolve() }) } EmailSender.prototype.pickRecipient = function( dataField ) { let instance = this; let pickerOptions = { subjectId : instance.subjectId, subjectType : instance.subjectType }; if( dataField == "to" ) { let toOptionEditor = instance.form.getEditor( "toOption" ); if( toOptionEditor ) { let selectedItem = toOptionEditor.option( "selectedItem" ); if( selectedItem && selectedItem.dataSource ) { pickerOptions.otherDataSource = selectedItem.dataSource; pickerOptions.otherLabel = selectedItem.dataSourceLabel; } } } let cp = new EmailRecipientPicker( pickerOptions ); cp.show().done( function( emailList ) { // console.log( "Email List", emailList ); let emailAddressArray = []; let emailAddress = instance.form.option( "formData" )[dataField]; if( emailAddress ) { emailAddressArray = emailAddress.replace( /\s/g, "" ).split( /[,;\s]/ ); } let emailAddressLookup = {}; emailAddressArray.forEach( function( email ) { emailAddressLookup[email.toUpperCase()] = email; }) emailList.forEach( function( email ){ emailAddressLookup[email.toUpperCase()] = email; }) let updatedEmailAddresses = []; for( let emailKey in emailAddressLookup ) { updatedEmailAddresses.push(emailAddressLookup[emailKey]); } let newValue = updatedEmailAddresses.join(", "); let formData = {}; formData[dataField] = newValue; instance.form.updateData( formData ); }); } EmailSender.prototype.send = function() { let sendPromise = $.Deferred(); let instance = this; let vr = instance.form.validate(); if( ! vr.isValid ) { sendPromise.reject(); return sendPromise; } else { let formData = instance.form.option( "formData" ); formData.to = formData.to.replace( /\s/g, "" ); if( formData.cc ) formData.cc = formData.cc.replace( /\s/g, "" ); formData.from = formData.from.replace( /\s/g, "" ); formData.readReceipt = formData.readReceipt ? formData.readReceipt : false; formData.highPriority = formData.highPriority ? formData.highPriority : false; let messageReady = $.Deferred(); messageReady.done( function() { let files = []; // if( formData.attachment ) { // formData.attachment.forEach( function( fe ) { // files.push( { fieldName : `attachment_${files.length + 1}`, data : fe } ) ; // }) // } let attachmentList = instance.form.option( "attachmentList" ); if( attachmentList ) { attachmentList.option( "items" ).forEach( function( fe ) { files.push( { fieldName : `attachment_${files.length + 1}`, data : fe } ) ; }) delete formData.attachment; } if( instance.prepareAjax ) { $.ajax( instance.prepareAjax( formData ) ).done( function( result ) { Fse.UI.toast( "Email Sent" ); sendPromise.resolve(); }).fail( function() { sendPromise.reject(); }) } else { let sendingPanel = $("
").dxLoadPanel( { message : "Sending...", onHidden : function( e ) { e.component.element().remove(); } }).appendTo( $("body") ).dxLoadPanel( "instance" ); sendingPanel.show(); Fse.Ajax.performAction( { object : "WRK.sendEmail", data : formData, files : files }).done( function( sendEmailResult ) { Fse.UI.toast( "Email Sent" ); sendPromise.resolve(); }).fail( function() { sendPromise.reject(); }).always( function() { sendingPanel.hide(); }) } }) messageReady.resolve(); // if( instance.messageFormatter ) { // // format the message // Fse.Ajax.performAction( { // object : instance.messageFormatter, // data : { // text : formData.message // } // }).done( function( formatMessageResult ) { // formData.message = formatMessageResult.html; // formData.messageContentType = "html"; // messageReady.resolve(); // }) // } else { // messageReady.resolve(); // } } return sendPromise; } EmailSender.prototype.show = function() { let instance = this; instance.popup = $("
").dxPopup( { title : instance.title, height : "auto", width : "60vw", hideOnOutsideClick : true, onHidden : function( e ) { e.component.element().remove; e.component.dispose(); }, contentTemplate : function() { return instance.element(); }, toolbarItems : [ { toolbar : "bottom", location : "after", widget : "dxButton", options : { text : "Cancel", onClick : function(e) { instance.popup.hide(); } } }, { toolbar : "bottom", location : "after", template : function() { instance.sendButton = $("
").dxButton( { text : "Send", type : "default", onClick : function() { instance.send().done( function() { instance.popup.hide(); }); } }).dxButton( "instance" ); return instance.sendButton.element(); } } ] }).appendTo( $("body") ).dxPopup( "instance" ); instance.popup.show(); } EmailRecipientPicker = function( options ) { let instance = this; instance.submitButton = null; instance.popup = null; instance.pickPromise = null; instance.staffDataGrid = null; instance.selectedEmails = {}; instance.otherDataGrid = null; instance.interactionInfo = null; instance.otherDataSource = options.otherDataSource; instance.otherLabel = options.otherLabel; instance.readyToShow = $.Deferred(); if( options.subjectType == "CLL" || options.subjectId) { let interactionInfoDataSource = Fse.Data.newDataSource( { object : "BCM.interactionInfo", objectParams : { interactionId : options.subjectId } }); interactionInfoDataSource.load().done( function( ii ) { instance.interactionInfo = ii[0]; // console.log( "interactionInfo", instance.interactionInfo ); instance.readyToShow.resolve(); }) } else { instance.readyToShow.resolve(); } // this is where we may need to load some data to determine who to scope the picker and load the lists } EmailRecipientPicker.prototype.constructor = EmailRecipientPicker; EmailRecipientPicker.prototype._onSelectionChanged = function( e ) { let instance = this; e.currentSelectedRowKeys.forEach( function( email ) { instance.selectedEmails[email] = email; }) e.currentDeselectedRowKeys.forEach( function( email ) { delete instance.selectedEmails[email]; }) let submitDisabled = true; for( let email in instance.selectedEmails ) { submitDisabled = false; break; } instance.submitButton.option( "disabled", submitDisabled ); } EmailRecipientPicker.prototype._createDataGrid = function( dataSource, type ) { const instance = this; let dataGrid = $("
").dxDataGrid( { height : 300, columns : [ { dataField : "fullName", caption : "Name" }, { dataField : "title", caption : "Title" }, { dataField : type == "partner" ? "partnerName" : "companyName", caption : "Company" }, { dataField : "primaryContactId", caption : "Primary Contact", visible : type == "partner" ? true : false, width : 125 }, { dataField : "contactDecisionMaker", caption : "Decision Maker", visible : type == "partner" ? true : false, width : 100 }, { dataField : "email", width : 300, cellTemplate : function( container, options ) { container.append( options.text ); if( options.data.knownBadEmail == "Y" ) { container.css( { color : "#f53611" }).attr( { title : "Past messages sent to this address have failed!" }) } } } //, { dataField : "knownBadEmail", caption : "Failed Email", width : 80 } ], filterRow : { visible : true }, dataSource : dataSource, remoteOperations : { paging : true, filtering : true, sorting : true }, scrolling : { mode : "virtual" }, showBorders : true, selection : { allowSelectAll : false, mode : "multiple", showCheckBoxesMode : "always" }, onSelectionChanged : function( e ) { instance._onSelectionChanged( e ); }, onRowClick : function( e ) { let email = e.data.email; if( e.isSelected ) { e.component.deselectRows( [ email ]); } else { e.component.selectRows( [ email ], true ); } } }).dxDataGrid("instance"); return dataGrid; } EmailRecipientPicker.prototype._createContent = function() { let instance = this; let contentRoot = $("
"); let multiViewItems = []; // operator contacts if( instance.interactionInfo && instance.interactionInfo.partnerType == "OPR" ) { let operatorId = instance.interactionInfo.partnerId; let cdr_recordId = instance.interactionInfo.cdrDistributorId; multiViewItems.push( { label : "Call Contacts", template : function() { let operatorContactsDataSource = Fse.Data.newDataSource( { object : "CRM.partnerContacts", keyField : "email", objectParams : { partnerType : 'OPR', partnerId : operatorId } }); operatorContactsDataSource.filter( [ "email", "isnotblank" ] ); instance.contactsDataGrid = instance._createDataGrid( null, "partner" ); if( ! cdr_recordId ) { instance.contactsDataGrid.option( "dataSource", operatorContactsDataSource ); } else { Fse.Ajax.showWait( 0, "Loading..." ); let combinedContacts = []; operatorContactsDataSource.load().done( function( operatorContacts ) { operatorContacts.forEach( function( contact ) { combinedContacts.push( contact ); }) let distributorContactsDataSource = Fse.Data.newDataSource( { object : "CRM.partnerContacts", keyField : "email", objectParams : { partnerType : 'CDR', partnerId : cdr_recordId } }); distributorContactsDataSource.filter( [ "email", "isnotblank" ] ); distributorContactsDataSource.load().done( function( distributorContacts ) { distributorContacts.forEach( function( contact ) { combinedContacts.push( contact ); }) let sortedCominedContacts = DevExpress.data.query(combinedContacts) .sortBy("fullName") .toArray(); let combinedDataSource = new DevExpress.data.DataSource( { store : { type : "array", data : sortedCominedContacts, key : "email" } }) Fse.Ajax.hideWait(); instance.contactsDataGrid.option( "dataSource", combinedDataSource ); }) }) } return instance.contactsDataGrid.element(); } } ) } // distributor contacts if( instance.interactionInfo && instance.interactionInfo.partnerType == "CDR" ) { let cdr_recordId = instance.interactionInfo.partnerId; multiViewItems.push( { label : "Call Contacts", template : function() { let partnerContactsDataSource = Fse.Data.newDataSource( { object : "CRM.partnerContacts", keyField : "email", objectParams : { partnerType : 'CDR', partnerId : cdr_recordId } }); partnerContactsDataSource.filter( [ "email", "isnotblank" ] ); instance.distributorContactsDataGrid = instance._createDataGrid( partnerContactsDataSource, "partner" ); return instance.distributorContactsDataGrid.element(); } } ) } if( instance.otherDataSource ) { // add the other tab multiViewItems.push( { label : instance.otherLabel ? instance.otherLabel : "Other Contacts", template : function() { instance.otherDataSource.filter( [ "email", "isnotblank" ] ); instance.otherDataSource.sort( [ "fullName" ] ); instance.otherDataGrid = instance._createDataGrid( instance.otherDataSource, "other" ); return instance.otherDataGrid.element(); } } ) } else { // add the staff tab multiViewItems.push( { label : "Internal Contacts", template : function() { let staffDataSource = Fse.Data.newDataSource( { object : "CRM.salesRepList", keyField : "email", objectParams : { staffOnly : true, loginEnabledOnly : true, useTerritoryVisibility : false, useSalesRepVisibility : false } }); staffDataSource.filter( [ "email", "isnotblank" ] ); staffDataSource.sort( [ "fullName" ] ); instance.staffDataGrid = instance._createDataGrid( staffDataSource, "staff" ); return instance.staffDataGrid.element(); } } ) // add the broker staff tab if( Fse.Portal.appConfiguration.STP.ownerType === "MFR" ) { multiViewItems.push( { label : "Broker Contacts", template : function() { let staffDataSource = Fse.Data.newDataSource( { object : "CRM.salesRepList", keyField : "email", objectParams : { brokerStaffOnly : true, loginEnabledOnly : true } }); staffDataSource.filter( [ "email", "isnotblank" ] ); instance.staffDataGrid = instance._createDataGrid( staffDataSource, "staff" ); return instance.staffDataGrid.element(); } } ) } } let radioGroupItems = []; for( let x = 0; x < multiViewItems.length; x++ ) { mvi = multiViewItems[x]; let rgi = { text : mvi.label, multiViewIdx : x } radioGroupItems.push( rgi ); } let toolbar = $("
").dxToolbar( { items : [ { location : "before", widget : "dxRadioGroup", options : { layout : "horizontal", items : radioGroupItems, displayExpr : "text", value : radioGroupItems[0], onValueChanged : function( e ) { instance.multiView.option( "selectedIndex", e.value.multiViewIdx ); } } } ] }).appendTo( contentRoot ).css( { "margin-bottom" : "5px" }); instance.multiView = $("
").dxMultiView( { items : multiViewItems, selectedIndex : 0, swipeEnabled : false }).dxMultiView( "instance" ); contentRoot.append( instance.multiView.element() ); return contentRoot; } EmailRecipientPicker.prototype._cancel = function() { let instance = this; instance.popup.hide(); instance.pickPromise.reject(); } EmailRecipientPicker.prototype._submit = function() { let instance = this; let emailList = []; for( let email in instance.selectedEmails ) { emailList.push( email ); } instance.pickPromise.resolve( emailList ); instance.popup.hide(); } EmailRecipientPicker.prototype.show = function() { let instance = this; instance.pickPromise = $.Deferred(); instance.popup = $("
").dxPopup( { title : "Select Email Recipients", width : "70vw", height : "auto", hideOnOutsideClick : true, contentTemplate : function() { return instance._createContent(); }, onHidden : function( e ) { e.component.element().remove(); e.component.dispose(); }, toolbarItems : [ { toolbar : "bottom", location : "after", widget : "dxButton", options : { text : "Cancel", onClick : function(e) { instance._cancel(); instance.popup.hide(); } } }, { toolbar : "bottom", location : "after", template : function() { instance.submitButton = $("
").dxButton( { text : "Submit", type : "default", disabled : true, onClick : function( e ) { instance._submit(); } }).dxButton( "instance" ); return instance.submitButton.element(); } } ] }).appendTo( $("body") ).dxPopup("instance"); instance.readyToShow.done( function() { instance.popup.show(); }) return instance.pickPromise; }