X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=js%2Fangular-crmMailing2.js;h=0edf9ecd455eaa32762b413136854e462cfd83ca;hb=0a993c89f9c6d70de6b100a766fa2a04093dba1f;hp=04b9ae1e16d20f207f24ac40e4043d81f68319f9;hpb=7df1588ef8fd4e910628d1a82e290915aedd9e5a;p=civicrm-core.git diff --git a/js/angular-crmMailing2.js b/js/angular-crmMailing2.js index 04b9ae1e16..0edf9ecd45 100644 --- a/js/angular-crmMailing2.js +++ b/js/angular-crmMailing2.js @@ -1,54 +1,18 @@ (function (angular, $, _) { - var partialUrl = function (relPath) { + var partialUrl = function partialUrl(relPath) { return CRM.resourceUrls['civicrm'] + '/partials/crmMailing2/' + relPath; }; - var crmMailing2 = angular.module('crmMailing2', ['ngRoute', 'ui.utils', 'crmUi', 'dialogService']); // TODO ngSanitize, unsavedChanges - - /** - * Initialize a new mailing - * TODO Move to separate file or service - */ - var createMailing = function () { - var pickDefaultMailComponent = function(type) { - var mcs = _.where(CRM.crmMailing.headerfooterList, { - component_type:type, - is_default: "1" - }); - return (mcs.length >= 1) ? mcs[0].id : null; - }; - - return { - name: "", - campaign_id: null, - from: _.where(CRM.crmMailing.fromAddress, {is_default: "1"})[0].label, - replyto_email: "", - subject: "", - groups: {include: [], exclude: [4]}, // fixme - mailings: {include: [], exclude: []}, - body_html: "", - body_text: "", - footer_id: null, // pickDefaultMailComponent('Footer'), - header_id: null, // pickDefaultMailComponent('Header'), - visibility: "Public Pages", - url_tracking: "1", - dedupe_email: "1", - forward_replies: "0", - auto_responder: "0", - open_tracking: "1", - override_verp: "1", - optout_id: pickDefaultMailComponent('OptOut'), - reply_id: pickDefaultMailComponent('Reply'), - resubscribe_id: pickDefaultMailComponent('Resubscribe'), - unsubscribe_id: pickDefaultMailComponent('Unsubscribe') - }; - }; + var crmMailing2 = angular.module('crmMailing2', [ + 'crmUtil', 'crmAttachment', 'ngRoute', 'ui.utils', 'crmUi', 'dialogService' + ]); // TODO ngSanitize, unsavedChanges - var getMailing = function ($route, crmApi) { - return ($route.current.params.id == 'new') ? createMailing() : crmApi('Mailing', 'getsingle', {id: $route.current.params.id}); - }; + // Time to wait before triggering AJAX update to recipients list + var RECIPIENTS_DEBOUNCE_MS = 100; + var RECIPIENTS_PREVIEW_LIMIT = 10000; - crmMailing2.config(['$routeProvider', + crmMailing2.config([ + '$routeProvider', function ($routeProvider) { $routeProvider.when('/mailing2', { template: '
', @@ -58,34 +22,42 @@ templateUrl: partialUrl('edit.html'), controller: 'EditMailingCtrl', resolve: { - selectedMail: getMailing + selectedMail: function selectedMail($route, crmMailingMgr) { + return crmMailingMgr.getOrCreate($route.current.params.id); + } } }); $routeProvider.when('/mailing2/:id/unified', { templateUrl: partialUrl('edit-unified.html'), controller: 'EditMailingCtrl', resolve: { - selectedMail: getMailing + selectedMail: function selectedMail($route, crmMailingMgr) { + return crmMailingMgr.getOrCreate($route.current.params.id); + } } }); $routeProvider.when('/mailing2/:id/unified2', { templateUrl: partialUrl('edit-unified2.html'), controller: 'EditMailingCtrl', resolve: { - selectedMail: getMailing + selectedMail: function selectedMail($route, crmMailingMgr) { + return crmMailingMgr.getOrCreate($route.current.params.id); + } } }); $routeProvider.when('/mailing2/:id/wizard', { templateUrl: partialUrl('edit-wizard.html'), controller: 'EditMailingCtrl', resolve: { - selectedMail: getMailing + selectedMail: function selectedMail($route, crmMailingMgr) { + return crmMailingMgr.getOrCreate($route.current.params.id); + } } }); } ]); - crmMailing2.controller('ListMailingsCtrl', function ($scope) { + crmMailing2.controller('ListMailingsCtrl', function ListMailingsCtrl() { // We haven't implemented this in Angular, but some users may get clever // about typing URLs, so we'll provide a redirect. window.location = CRM.url('civicrm/mailing/browse/unscheduled', { @@ -94,28 +66,434 @@ }); }); - crmMailing2.controller('EditMailingCtrl', function ($scope, selectedMail) { + crmMailing2.controller('EditMailingCtrl', function EditMailingCtrl($scope, selectedMail, $location, crmMailingMgr, crmStatus, CrmAttachments) { $scope.mailing = selectedMail; + $scope.attachments = new CrmAttachments(function () { + return {entity_table: 'civicrm_mailing', entity_id: $scope.mailing.id}; + }); + $scope.attachments.load(); $scope.crmMailingConst = CRM.crmMailing; $scope.partialUrl = partialUrl; - $scope.ts = CRM.ts('CiviMail'); + var ts = $scope.ts = CRM.ts('CiviMail'); - $scope.send = function() { - CRM.alert('Send!'); + // @return Promise + $scope.submit = function submit() { + var promise = crmMailingMgr.save($scope.mailing) + .then(function () { + // pre-condition: the mailing exists *before* saving attachments to it + return $scope.attachments.save(); + }) + .then(function () { + return crmMailingMgr.submit($scope.mailing); + }); + return crmStatus({start: ts('Submitting...'), success: ts('Submitted')}, promise); }; - $scope.save = function() { - CRM.alert('Save!'); + // @return Promise + $scope.save = function save() { + return crmStatus(null, + crmMailingMgr + .save($scope.mailing) + .then(function () { + // pre-condition: the mailing exists *before* saving attachments to it + return $scope.attachments.save(); + }) + ); }; - $scope.cancel = function() { - CRM.alert('Cancel!'); + // @return Promise + $scope.delete = function cancel() { + return crmStatus({start: ts('Deleting...'), success: ts('Deleted')}, + crmMailingMgr.delete($scope.mailing) + ); }; - $scope.leave = function() { + $scope.leave = function leave() { window.location = CRM.url('civicrm/mailing/browse/unscheduled', { reset: 1, scheduled: 'false' }); }; + + // Transition URL "/mailing2/new" => "/mailing2/123" as soon as ID is known + $scope.$watch('mailing.id', function (newValue, oldValue) { + if (newValue && newValue != oldValue) { + var parts = $location.path().split('/'); // e.g. "/mailing2/new" or "/mailing2/123/wizard" + parts[2] = newValue; + $location.path(parts.join('/')); + $location.replace(); + // FIXME: Angular unnecessarily refreshes UI + } + }); + }); + + // Controller for the edit-recipients fields ( + // WISHLIST: Move most of this to a (cache-enabled) service + // Scope members: + // - [input] mailing: object + // - [output] recipients: array of recipient records + crmMailing2.controller('EditRecipCtrl', function EditRecipCtrl($scope, dialogService, crmApi, crmMailingMgr) { + var ts = $scope.ts = CRM.ts('CiviMail'); + $scope.recipients = null; + $scope.getRecipientsEstimate = function () { + var ts = $scope.ts; + if ($scope.recipients == null) { + return ts('(Estimating)'); + } + if ($scope.recipients.length == 0) { + return ts('No recipients'); + } + if ($scope.recipients.length == 1) { + return ts('~1 recipient'); + } + if (RECIPIENTS_PREVIEW_LIMIT > 0 && $scope.recipients.length >= RECIPIENTS_PREVIEW_LIMIT) { + return ts('>%1 recipients', {1: RECIPIENTS_PREVIEW_LIMIT}); + } + return ts('~%1 recipients', {1: $scope.recipients.length}); + }; + $scope.getIncludesAsString = function () { + var first = true; + var names = ''; + _.each($scope.mailing.groups.include, function (id) { + if (!first) { + names = names + ', '; + } + var group = _.where(CRM.crmMailing.groupNames, {id: '' + id}); + names = names + group[0].title; + first = false; + }); + _.each($scope.mailing.mailings.include, function (id) { + if (!first) { + names = names + ', '; + } + var oldMailing = _.where(CRM.crmMailing.civiMails, {id: '' + id}); + names = names + oldMailing[0].name; + first = false; + }); + return names; + }; + $scope.getExcludesAsString = function () { + var first = true; + var names = ''; + _.each($scope.mailing.groups.exclude, function (id) { + if (!first) { + names = names + ', '; + } + var group = _.where(CRM.crmMailing.groupNames, {id: '' + id}); + names = names + group[0].title; + first = false; + }); + _.each($scope.mailing.mailings.exclude, function (id) { + if (!first) { + names = names + ', '; + } + var oldMailing = _.where(CRM.crmMailing.civiMails, {id: '' + id}); + names = names + oldMailing[0].name; + first = false; + }); + return names; + }; + + // We monitor four fields -- use debounce so that changes across the + // four fields can settle-down before AJAX. + var refreshRecipients = _.debounce(function () { + $scope.$apply(function () { + $scope.recipients = null; + crmMailingMgr.previewRecipients($scope.mailing, RECIPIENTS_PREVIEW_LIMIT).then(function (recipients) { + $scope.recipients = recipients; + }); + }); + }, RECIPIENTS_DEBOUNCE_MS); + $scope.$watchCollection("mailing.groups.include", refreshRecipients); + $scope.$watchCollection("mailing.groups.exclude", refreshRecipients); + $scope.$watchCollection("mailing.mailings.include", refreshRecipients); + $scope.$watchCollection("mailing.mailings.exclude", refreshRecipients); + + $scope.previewRecipients = function previewRecipients() { + var model = { + recipients: $scope.recipients + }; + var options = { + autoOpen: false, + modal: true, + title: ts('Preview (%1)', { + 1: $scope.getRecipientsEstimate() + }) + }; + dialogService.open('recipDialog', partialUrl('dialog/recipients.html'), model, options); + }; + }); + + // Controller for the "Preview Recipients" dialog + // Note: Expects $scope.model to be an object with properties: + // - recipients: array of contacts + crmMailing2.controller('PreviewRecipCtrl', function ($scope) { + $scope.ts = CRM.ts('CiviMail'); + }); + + // Controller for the "Preview Mailing" segment + // Note: Expects $scope.model to be an object with properties: + // - mailing: object + // - attachments: object + crmMailing2.controller('PreviewMailingCtrl', function ($scope, dialogService, crmMailingMgr, crmStatus) { + var ts = $scope.ts = CRM.ts('CiviMail'); + + $scope.testContact = {email: CRM.crmMailing.defaultTestEmail}; + $scope.testGroup = {gid: null}; + + $scope.previewHtml = function previewHtml() { + $scope.previewDialog(partialUrl('dialog/previewHtml.html')); + }; + $scope.previewText = function previewText() { + $scope.previewDialog(partialUrl('dialog/previewText.html')); + }; + $scope.previewFull = function previewFull() { + $scope.previewDialog(partialUrl('dialog/previewFull.html')); + }; + // Open a dialog with a preview of the current mailing + // @param template string URL of the template to use in the preview dialog + $scope.previewDialog = function previewDialog(template) { + var p = crmMailingMgr + .preview($scope.mailing) + .then(function (content) { + var options = { + autoOpen: false, + modal: true, + title: ts('Subject: %1', { + 1: content.subject + }) + }; + dialogService.open('previewDialog', template, content, options); + }); + CRM.status({start: ts('Previewing'), success: ''}, CRM.toJqPromise(p)); + }; + $scope.sendTestToContact = function sendTestToContact() { + $scope.sendTest($scope.mailing, $scope.attachments, $scope.testContact.email, null); + }; + $scope.sendTestToGroup = function sendTestToGroup() { + $scope.sendTest($scope.mailing, $scope.attachments, null, $scope.testGroup.gid); + }; + $scope.sendTest = function sendTest(mailing, attachments, testEmail, testGroup) { + var promise = crmMailingMgr.save(mailing) + .then(function () { + return attachments.save(); + }) + .then(function () { + return crmMailingMgr.sendTest(mailing, testEmail, testGroup); + }) + .then(function (deliveryInfos) { + var count = Object.keys(deliveryInfos).length; + if (count === 0) { + CRM.alert(ts('Could not identify any recipients. Perhaps the group is empty?')); + } + }) + ; + return crmStatus({start: ts('Sending...'), success: ts('Sent')}, promise); + }; + }); + + // Controller for the "Preview Mailing" dialog + // Note: Expects $scope.model to be an object with properties: + // - "subject" + // - "body_html" + // - "body_text" + crmMailing2.controller('PreviewMailingDialogCtrl', function PreviewMailingDialogCtrl($scope, crmMailingMgr) { + $scope.ts = CRM.ts('CiviMail'); + }); + + // Controller for the "Preview Mailing Component" segment + // which displays header/footer/auto-responder + crmMailing2.controller('PreviewComponentCtrl', function PreviewMailingDialogCtrl($scope, dialogService) { + var ts = $scope.ts = CRM.ts('CiviMail'); + + $scope.previewComponent = function previewComponent(title, componentId) { + var component = _.where(CRM.crmMailing.headerfooterList, {id: "" + componentId}); + if (!component || !component[0]) { + CRM.alert(ts('Invalid component ID (%1)', { + 1: componentId + })); + return; + } + var options = { + autoOpen: false, + modal: true, + title: title // component[0].name + }; + dialogService.open('previewComponentDialog', partialUrl('dialog/previewComponent.html'), component[0], options); + }; + }); + + // Controller for the "Preview Mailing" dialog + // Note: Expects $scope.model to be an object with properties: + // - "name" + // - "subject" + // - "body_html" + // - "body_text" + crmMailing2.controller('PreviewComponentDialogCtrl', function PreviewMailingDialogCtrl($scope) { + $scope.ts = CRM.ts('CiviMail'); + }); + + // Controller for the in-place msg-template management + crmMailing2.controller('MsgTemplateCtrl', function MsgTemplateCtrl($scope, crmMsgTemplates, dialogService, $parse) { + var ts = $scope.ts = CRM.ts('CiviMail'); + $scope.crmMsgTemplates = crmMsgTemplates; + + // @return Promise MessageTemplate (per APIv3) + $scope.saveTemplate = function saveTemplate(mailing) { + var model = { + selected_id: mailing.msg_template_id, + tpl: { + msg_title: '', + msg_subject: mailing.subject, + msg_text: mailing.body_text, + msg_html: mailing.body_html + } + }; + var options = { + autoOpen: false, + modal: true, + title: ts('Save Template') + }; + return dialogService.open('saveTemplateDialog', partialUrl('dialog/saveTemplate.html'), model, options) + .then(function (item) { + mailing.msg_template_id = item.id; + return item; + }); + }; + + // @param int id + // @return Promise + $scope.loadTemplate = function loadTemplate(mailing, id) { + return crmMsgTemplates.get(id).then(function (tpl) { + mailing.subject = tpl.msg_subject; + mailing.body_text = tpl.msg_text; + mailing.body_html = tpl.msg_html; + }); + }; + }); + + // Controller for the "Save Message Template" dialog + // Scope members: + // - [input] "model": Object + // - "selected_id": int + // - "tpl": Object + // - "msg_subject": string + // - "msg_text": string + // - "msg_html": string + crmMailing2.controller('SaveMsgTemplateDialogCtrl', function SaveMsgTemplateDialogCtrl($scope, crmMsgTemplates, dialogService) { + var ts = $scope.ts = CRM.ts('CiviMail'); + $scope.saveOpt = {mode: '', newTitle: ''}; + $scope.selected = null; + + $scope.save = function save() { + var tpl = _.extend({}, $scope.model.tpl); + switch ($scope.saveOpt.mode) { + case 'add': + tpl.msg_title = $scope.saveOpt.newTitle; + break; + case 'update': + tpl.id = $scope.selected.id; + tpl.msg_title = $scope.selected.msg_title; + break; + default: + throw 'SaveMsgTemplateDialogCtrl: Unrecognized mode: ' + $scope.saveOpt.mode; + } + return crmMsgTemplates.save(tpl) + .then(function (item) { + CRM.status(ts('Saved')); + return item; + }); + }; + + function scopeApply(f) { + return function () { + var args = arguments; + $scope.$apply(function () { + f.apply(args); + }); + }; + } + + function init() { + crmMsgTemplates.get($scope.model.selected_id).then( + function (tpl) { + $scope.saveOpt.mode = 'update'; + $scope.selected = tpl; + }, + function () { + $scope.saveOpt.mode = 'add'; + $scope.selected = null; + } + ); + // When using dialogService with a button bar, the major button actions + // need to be registered with the dialog widget (and not embedded in + // the body of the dialog). + var buttons = {}; + buttons[ts('Save')] = function () { + $scope.save().then(function (item) { + dialogService.close('saveTemplateDialog', item); + }); + }; + buttons[ts('Cancel')] = function () { + dialogService.cancel('saveTemplateDialog'); + }; + dialogService.setButtons('saveTemplateDialog', buttons); + } + + setTimeout(scopeApply(init), 0); + }); + + crmMailing2.controller('EmailAddrCtrl', function EmailAddrCtrl($scope, crmFromAddresses){ + $scope.crmFromAddresses = crmFromAddresses; + }); + + // Controller for schedule-editing widget. + // Scope members: + // - [input] mailing: object + // - scheduled_date: null|string(YYYY-MM-DD hh:mm) + crmMailing2.controller('EditScheduleCtrl', function EditScheduleCtrl($scope, $parse) { + var schedModelExpr = 'mailing.scheduled_date'; + var schedModel = $parse(schedModelExpr); + + $scope.schedule = { + mode: 'now', + datetime: '' + }; + var updateChildren = (function () { + var sched = schedModel($scope); + if (sched) { + $scope.schedule.mode = 'at'; + $scope.schedule.datetime = sched; + } + else { + $scope.schedule.mode = 'now'; + } + }); + var updateParent = (function () { + switch ($scope.schedule.mode) { + case 'now': + schedModel.assign($scope, null); + break; + case 'at': + schedModel.assign($scope, $scope.schedule.datetime); + break; + default: + throw 'Unrecognized schedule mode: ' + $scope.schedule.mode; + } + }); + + $scope.$watch(schedModelExpr, updateChildren); + $scope.$watch('schedule.mode', updateParent); + $scope.$watch('schedule.datetime', function (newValue, oldValue) { + // automatically switch mode based on datetime entry + if (oldValue != newValue) { + if (!newValue || newValue == " ") { + $scope.schedule.mode = 'now'; + } + else { + $scope.schedule.mode = 'at'; + } + } + updateParent(); + }); }); })(angular, CRM.$, CRM._);