CRM-15578 - crmMailing - Fix for sending tests
authorTim Otten <totten@civicrm.org>
Thu, 18 Dec 2014 08:11:32 +0000 (00:11 -0800)
committerTim Otten <totten@civicrm.org>
Thu, 18 Dec 2014 10:51:19 +0000 (02:51 -0800)
The basic problem is that one must save before sending a test -- and the
"save" mechanics are necessarily different for crmMailing and crmMailingAB.

This commit resolves the problem by refactoring to eliminate PreviewMailingCtrl.
Now:

 * Ultimate authority for deciding how to perform a preview or test mailing lies with
   the main page (EditMailingCtrl, crmMailing/edit.html, CrmMailingABEditCtrl, crmMailingAB/edit.html)
 * We don't want it to be difficult for the main page to do that, so we define two reusable
   components -- directive crmMailingBlockPreview and service crmMailingPreviewMgr.

js/angular-crmMailing.js
js/angular-crmMailing/directives.js
js/angular-crmMailing/services.js
js/angular-crmMailingAB.js
partials/crmMailing/edit-unified.html
partials/crmMailing/edit-unified2.html
partials/crmMailing/edit-wizard.html
partials/crmMailing/edit.html
partials/crmMailing/preview.html
partials/crmMailing/review.html
partials/crmMailingAB/edit.html

index 6fd49879648114fe73469a2ce93cfa35cd4c6b52..0f5b3bff035316b01407bc24e4826e2a4b7488f1 100644 (file)
@@ -66,7 +66,7 @@
     });
   });
 
-  angular.module('crmMailing').controller('EditMailingCtrl', function EditMailingCtrl($scope, selectedMail, $location, crmMailingMgr, crmStatus, CrmAttachments) {
+  angular.module('crmMailing').controller('EditMailingCtrl', function EditMailingCtrl($scope, selectedMail, $location, crmMailingMgr, crmStatus, CrmAttachments, crmMailingPreviewMgr) {
     $scope.mailing = selectedMail;
     $scope.attachments = new CrmAttachments(function () {
       return {entity_table: 'civicrm_mailing', entity_id: $scope.mailing.id};
     $scope.partialUrl = partialUrl;
     var ts = $scope.ts = CRM.ts('CiviMail');
 
+    // @return Promise
+    $scope.previewMailing = function previewMailing(mailing, mode) {
+      return crmMailingPreviewMgr.preview(mailing, mode);
+    };
+
+    // @return Promise
+    $scope.sendTest = function sendTest(mailing, attachments, recipient) {
+      var savePromise = crmMailingMgr.save(mailing)
+        .then(function () {
+          return attachments.save();
+        });
+      return crmStatus({start: ts('Saving...'), success: ''}, savePromise)
+        .then(function () {
+          crmMailingPreviewMgr.sendTest(mailing, recipient);
+        });
+    };
+
     // @return Promise
     $scope.submit = function submit() {
       var promise = crmMailingMgr.save($scope.mailing)
         });
       return crmStatus({start: ts('Submitting...'), success: ts('Submitted')}, promise);
     };
+
     // @return Promise
     $scope.save = function save() {
       return crmStatus(null,
           })
       );
     };
+
     // @return Promise
     $scope.delete = function cancel() {
       return crmStatus({start: ts('Deleting...'), success: ts('Deleted')},
         crmMailingMgr.delete($scope.mailing)
       );
     };
+
     $scope.leave = function leave() {
       window.location = CRM.url('civicrm/mailing/browse/unscheduled', {
         reset: 1,
     $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
-  angular.module('crmMailing').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"
     setTimeout(scopeApply(init), 0);
   });
 
-  angular.module('crmMailing').controller('EmailAddrCtrl', function EmailAddrCtrl($scope, crmFromAddresses){
+  angular.module('crmMailing').controller('EmailAddrCtrl', function EmailAddrCtrl($scope, crmFromAddresses) {
     $scope.crmFromAddresses = crmFromAddresses;
   });
 })(angular, CRM.$, CRM._);
index bf1845fe209b8874bef9acdde402cd28b3214f43..53b24c41ca126480acb047300095a78bdc9d109f 100644 (file)
@@ -8,11 +8,9 @@
   var simpleBlocks = {
     crmMailingBlockHeaderFooter: partialUrl('headerFooter.html'),
     crmMailingBlockMailing: partialUrl('mailing.html'),
-    crmMailingBlockPreview: partialUrl('preview.html'),
     crmMailingBlockPublication: partialUrl('publication.html'),
     crmMailingBlockResponses: partialUrl('responses.html'),
     crmMailingBlockRecipients: partialUrl('recipients.html'),
-    crmMailingBlockReview: partialUrl('review.html'),
     crmMailingBlockSchedule: partialUrl('schedule.html'),
     crmMailingBlockSummary: partialUrl('summary.html'),
     crmMailingBlockTracking: partialUrl('tracking.html'),
     });
   });
 
+  // example: <div crm-mailing-block-preview crm-mailing="myMailing" on-preview="openPreview(myMailing, preview.mode)" on-send="sendEmail(myMailing,preview.recipient)">
+  // note: the directive defines a variable called "preview" with any inputs supplied by the user (e.g. the target recipient for an example mailing)
+  angular.module('crmMailing').directive('crmMailingBlockPreview', function ($parse) {
+    return {
+      templateUrl: partialUrl('preview.html'),
+      link: function (scope, elm, attr) {
+        var mailingModel = $parse(attr.crmMailing);
+        scope.mailing = mailingModel(scope);
+        scope.crmMailingConst = CRM.crmMailing;
+        scope.ts = CRM.ts('CiviMail');
+        scope.testContact = {email: CRM.crmMailing.defaultTestEmail};
+        scope.testGroup = {gid: null};
+
+        scope.doPreview = function(mode) {
+          scope.$eval(attr.onPreview, {
+            preview: {mode: mode}
+          });
+        };
+        scope.doSend = function doSend(recipient) {
+          scope.$eval(attr.onSend, {
+            preview: {recipient: recipient}
+          });
+        };
+      }
+    };
+  });
+
+  angular.module('crmMailing').directive('crmMailingBlockReview', function ($parse, crmMailingPreviewMgr) {
+    return {
+      scope: {
+        crmMailing: '@'
+      },
+      templateUrl: partialUrl('review.html'),
+      link: function (scope, elm, attr) {
+        var mailingModel = $parse(attr.crmMailing);
+        scope.mailing = mailingModel(scope.$parent);
+        scope.crmMailingConst = CRM.crmMailing;
+        scope.ts = CRM.ts('CiviMail');
+        scope.previewMailing = function previewMailing(mailing, mode) {
+          return crmMailingPreviewMgr.preview(mailing, mode);
+        };
+      }
+    };
+  });
+
   // Convert between a mailing "From Address" (mailing.from_name,mailing.from_email) and a unified label ("Name" <e@ma.il>)
   // example: <span crm-mailing-from-address="myPlaceholder" crm-mailing="myMailing"><select ng-model="myPlaceholder.label"></select></span>
   // NOTE: This really doesn't belong in a directive. I've tried (and failed) to make this work with a getterSetter binding, eg
 
         // Update $(element) view based on latest data
         function refreshUI() {
-          $(element).select2('val', convertMailingToValues(scope.mailing));
+          if (scope.mailing) {
+            $(element).select2('val', convertMailingToValues(scope.mailing));
+          }
         }
 
         /// @return string HTML representingn an option
index 7ecca99e559024d7a855bf57e97a14d357a3f3ad..3202ee054dbf70967682118f110f48e6794cafb0 100644 (file)
           'api.mailing_job.create': 1, // note: exact match to API default
           'api.MailingRecipients.get': {
             mailing_id: '$value.id',
-            options: { limit: previewLimit },
+            options: {limit: previewLimit},
             'api.contact.getvalue': {'return': 'display_name'},
             'api.email.getvalue': {'return': 'email'}
           }
 
       // Immediately send a test message
       // @param mailing Object (per APIv3)
-      // @param testEmail string
-      // @param testGroup int (id#)
+      // @param to Object with either key "email" (string) or "gid" (int)
       // @return Promise for a list of delivery reports
-      sendTest: function (mailing, testEmail, testGroup) {
+      sendTest: function (mailing, recipient) {
         var params = _.extend({}, mailing, {
           // options:  {force_rollback: 1}, // Test mailings include tracking features, so the mailing must be persistent
           'api.Mailing.send_test': {
             mailing_id: '$value.id',
-            test_email: testEmail,
-            test_group: testGroup
+            test_email: recipient.email,
+            test_group: recipient.gid
           }
         });
 
       }
     };
   });
+
+  // The preview manager performs preview actions while putting up a visible UI (e.g. dialogs & status alerts)
+  angular.module('crmMailing').factory('crmMailingPreviewMgr', function (dialogService, crmMailingMgr, crmStatus) {
+    return {
+      // @param mode string one of 'html', 'text', or 'full'
+      // @return Promise
+      preview: function preview(mailing, mode) {
+        var templates = {
+          html: partialUrl('dialog/previewHtml.html'),
+          text: partialUrl('dialog/previewText.html'),
+          full: partialUrl('dialog/previewFull.html')
+        };
+        var result = null;
+        var p = crmMailingMgr
+          .preview(mailing)
+          .then(function (content) {
+            var options = {
+              autoOpen: false,
+              modal: true,
+              title: ts('Subject: %1', {
+                1: content.subject
+              })
+            };
+            result = dialogService.open('previewDialog', templates[mode], content, options);
+          });
+        crmStatus({start: ts('Previewing'), success: ''}, p);
+        return result;
+      },
+
+      // @param to Object with either key "email" (string) or "gid" (int)
+      // @return Promise
+      sendTest: function sendTest(mailing, recipient) {
+        var promise = crmMailingMgr.sendTest(mailing, recipient)
+            .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);
+      }
+    };
+  });
+
 })(angular, CRM.$, CRM._);
index d4841feae304410d3b252b20bc3ba4c45b9d7dc1..47273b8d2b946f3d572de7af74bafaf4f809a58f 100644 (file)
     $scope.testing_criteria = crmMailingABCriteria.getAll();
   });
 
-  angular.module('crmMailingAB').controller('CrmMailingABEditCtrl', function ($scope, abtest, crmMailingABCriteria, crmMailingMgr) {
+  angular.module('crmMailingAB').controller('CrmMailingABEditCtrl', function ($scope, abtest, crmMailingABCriteria, crmMailingMgr, crmMailingPreviewMgr, crmStatus) {
     window.ab = abtest;
     $scope.abtest = abtest;
-    $scope.ts = CRM.ts('CiviMail');
+    var ts = $scope.ts = CRM.ts('CiviMail');
     $scope.crmMailingABCriteria = crmMailingABCriteria;
     $scope.crmMailingConst = CRM.crmMailing;
     $scope.partialUrl = partialUrl;
     };
     $scope.save = function save() {
       $scope.sync();
-      return abtest.save();
+      return crmStatus({start: ts('Saving...'), success: ts('Saved')}, abtest.save());
+    };
+    // @return Promise
+    $scope.previewMailing = function previewMailing(mailingName, mode) {
+      return crmMailingPreviewMgr.preview(abtest.mailings[mailingName], mode);
+    };
+
+    // @return Promise
+    $scope.sendTest = function sendTest(mailingName, recipient) {
+      return crmStatus({start: ts('Saving...'), success: ''}, abtest.save())
+        .then(function () {
+          crmMailingPreviewMgr.sendTest(abtest.mailings[mailingName], recipient);
+        });
     };
     $scope.delete = function () {
       throw "Not implemented: EditCtrl.delete"
index 548e6c46cfb19d42030a50ada9e08bee90bbc7cd..005c57178d1a78c7ec15ae1639c16b568871d2db 100644 (file)
@@ -37,7 +37,7 @@
     </div>
 
     <div crm-ui-accordion crm-title="ts('Preview')">
-      <div crm-mailing-block-preview crm-mailing="mailing"/>
+      <div crm-mailing-block-preview crm-mailing="mailing" on-preview="previewMailing(mailing, preview.mode)" on-send="sendTest(mailing, attachments, preview.recipient)" />
     </div>
 
     <button ng-click="submit().then(leave)">{{ts('Submit Mailing')}}</button>
index 4f2905254cd97b0f62a2b1f7449509223194890b..9140e522cdf1b3a6d57e42132ac76214a7e20221 100644 (file)
@@ -30,7 +30,7 @@
       <div crm-mailing-block-tracking crm-mailing="mailing"/>
     </div>
     <div crm-ui-accordion crm-title="ts('Preview')">
-      <div crm-mailing-block-preview crm-mailing="mailing"/>
+      <div crm-mailing-block-preview crm-mailing="mailing" on-preview="previewMailing(mailing, preview.mode)" on-send="sendTest(mailing, attachments, preview.recipient)" />
     </div>
     <div crm-ui-accordion id="tab-schedule" crm-title="ts('Schedule')">
       <div crm-mailing-block-schedule crm-mailing="mailing"/>
index 472577ce7239030c40256667337b2f02dc205483..1548e5b3c0124f6ee5a70f74b8ff6f3665dfbece 100644 (file)
@@ -26,7 +26,7 @@
           <div crm-attachments="attachments"/>
         </div>
         <div crm-ui-accordion crm-title="ts('Preview')">
-          <div crm-mailing-block-preview crm-mailing="mailing"/>
+          <div crm-mailing-block-preview crm-mailing="mailing" on-preview="previewMailing(mailing, preview.mode)" on-send="sendTest(mailing, attachments, preview.recipient)" />
         </div>
       </div>
 
index 39db68f60604381288d5503c8b5d00a5cd2303ea..f6ed9c29f11d5d3dff8915b190dd9f17881e0e01 100644 (file)
@@ -36,7 +36,7 @@
           </div>
         </div>
         <div crm-ui-accordion crm-title="ts('Preview')">
-          <div crm-mailing-block-preview crm-mailing="mailing"/>
+          <div crm-mailing-block-preview crm-mailing="mailing" on-preview="previewMailing(mailing, preview.mode)" on-send="sendTest(mailing, attachments, preview.recipient)" />
         </div>
       </div>
       <div crm-ui-wizard-step crm-title="ts('Review and Schedule')">
index 65111542f7c755e5988ccda57e867dbe3383d5ba..4c6cab28360bc0d1a66a9152426f879e177bc2df 100644 (file)
@@ -1,4 +1,7 @@
-<div ng-controller="PreviewMailingCtrl" class="crmMailing-preview">
+<!--
+Vars: mailing:obj, testContact:obj, testGroup:obj, crmMailing:FormController
+-->
+<div class="crmMailing-preview">
   <!-- Note:
   In Firefox (at least), clicking the preview buttons causes the browser to display validation warnings
   for unrelated fields *and* display preview. To avoid this weird UX, we disable preview buttons when the form is incomplete/invalid.
       <em>({{ts('No content to preview')}})</em>
     </div>
     <div ng-hide="!mailing.body_html">
-      <button ng-disabled="crmMailing.$invalid" ng-click="previewHtml()">{{ts('Preview as HTML')}}</button>
+      <button ng-disabled="crmMailing.$invalid" ng-click="doPreview('html')">{{ts('Preview as HTML')}}</button>
     </div>
     <div ng-hide="!mailing.body_text">
-      <button ng-disabled="crmMailing.$invalid" ng-click="previewText()">{{ts('Preview as Plain Text')}}</button>
+      <button ng-disabled="crmMailing.$invalid" ng-click="doPreview('text')">{{ts('Preview as Plain Text')}}</button>
     </div>
     <!--
     <div ng-hide="!mailing.body_html && !mailing.body_text">
-      <button ng-disabled="crmMailing.$invalid" ng-click="previewFull()">{{ts('Preview')}}</button>
+      <button ng-disabled="crmMailing.$invalid" ng-click="doPreview('full')">{{ts('Preview')}}</button>
     </div>
     -->
   </div>
@@ -32,7 +35,7 @@
         placeholder="example@example.org"
         />
     </div>
-    <button ng-disabled="crmMailing.$invalid || !testContact.email" ng-click="sendTestToContact()">{{ts('Send test')}}</button>
+    <button ng-disabled="crmMailing.$invalid || !testContact.email" ng-click="doSend({email: testContact.email})">{{ts('Send test')}}</button>
   </div>
   <div class="preview-group" ng-form>
     <div>
@@ -49,7 +52,7 @@
         <option value=""></option>
       </select>
     </div>
-    <button ng-disabled="crmMailing.$invalid || !testGroup.gid" ng-click="sendTestToGroup()">{{ts('Send test')}}</button>
+    <button ng-disabled="crmMailing.$invalid || !testGroup.gid" ng-click="doSend({gid: testGroup.gid})">{{ts('Send test')}}</button>
   </div>
   <div class="clear"></div>
 </div>
index 01a26f1f5511b0c7f3249889a1e44d7d206a96b3..f8b9bec20fb4d5ebf923dd24a750a9f31539f186 100644 (file)
@@ -20,11 +20,9 @@ Required vars: mailing
         </div>
       </div>
       <div crm-ui-field crm-title="ts('Content')">
-        <div ng-controller="PreviewMailingCtrl">
-          <span ng-show="mailing.body_html"><a class="crm-hover-button action-item" ng-click="previewHtml()">{{ts('HTML')}} <span class="icon ui-icon-newwin"></span></a></span>
-          <span ng-show="mailing.body_text"><a class="crm-hover-button action-item" ng-click="previewText()">{{ts('Plain Text')}} <span class="icon ui-icon-newwin"></span></a></span>
-          <!-- TODO: attachments -->
-        </div>
+        <span ng-show="mailing.body_html"><a class="crm-hover-button action-item" ng-click="previewMailing(mailing, 'html')">{{ts('HTML')}} <span class="icon ui-icon-newwin"></span></a></span>
+        <span ng-show="mailing.body_text"><a class="crm-hover-button action-item" ng-click="previewMailing(mailing, 'text')">{{ts('Plain Text')}} <span class="icon ui-icon-newwin"></span></a></span>
+        <!-- TODO: attachments -->
       </div>
       <div crm-ui-field crm-title="ts('Attachments')" ng-show="attachments.files.length > 0 || attachments.uploader.queue.length > 0">
         <div ng-repeat="file in attachments.files">
index a0fff6206ebd43aa73c113fa1e3b1214d1babf68..ae935fde70eb54d50115bf9597b062423e6c405e 100644 (file)
           </div>
         </div>
         <div crm-ui-accordion crm-title="ts('Preview (A)')">
-          <div crm-mailing-block-preview crm-mailing="abtest.mailings.a"/>
+          <div crm-mailing-block-preview crm-mailing="abtest.mailings.a" on-preview="previewMailing('a', preview.mode)" on-send="sendTest('a', preview.recipient)" />
         </div>
         <div crm-ui-accordion crm-title="ts('Preview (B)')">
-          <div crm-mailing-block-preview crm-mailing="abtest.mailings.b"/>
+          <div crm-mailing-block-preview crm-mailing="abtest.mailings.b" on-preview="previewMailing('b', preview.mode)" on-send="sendTest('b', preview.recipient)" />
         </div>
       </div>
       <div crm-ui-wizard-step="21" crm-title="ts('Compose (A)')" ng-if="criteriaName == 'Two different emails'">
           </div>
         </div>
         <div crm-ui-accordion crm-title="ts('Preview')">
-          <div crm-mailing-block-preview crm-mailing="abtest.mailings.a"/>
+          <div crm-mailing-block-preview crm-mailing="abtest.mailings.a" on-preview="previewMailing('a', preview.mode)" on-send="sendTest('a', preview.recipient)" />
         </div>
       </div>
       <div crm-ui-wizard-step="22" crm-title="ts('Compose (B)')" ng-if="criteriaName == 'Two different emails'">
           </div>
         </div>
         <div crm-ui-accordion crm-title="ts('Preview')">
-          <div crm-mailing-block-preview crm-mailing="abtest.mailings.b"/>
+          <div crm-mailing-block-preview crm-mailing="abtest.mailings.b" on-preview="previewMailing('b', preview.mode)" on-send="sendTest('b', preview.recipient)" />
         </div>
       </div>
       <div crm-ui-wizard-step="30" crm-title="ts('Schedule')">