1 (function (angular
, $, _
) {
3 // The following directives have the same simple implementation -- load
4 // a template and export a "mailing" object into scope.
6 crmMailingBlockApprove
: '~/crmMailing/approve.html',
7 crmMailingBlockHeaderFooter
: '~/crmMailing/headerFooter.html',
8 crmMailingBlockMailing
: '~/crmMailing/mailing.html',
9 crmMailingBlockPublication
: '~/crmMailing/publication.html',
10 crmMailingBlockResponses
: '~/crmMailing/responses.html',
11 crmMailingBlockRecipients
: '~/crmMailing/recipients.html',
12 crmMailingBlockSchedule
: '~/crmMailing/schedule.html',
13 crmMailingBlockSummary
: '~/crmMailing/summary.html',
14 crmMailingBlockTracking
: '~/crmMailing/tracking.html',
15 crmMailingBodyHtml
: '~/crmMailing/body_html.html',
16 crmMailingBodyText
: '~/crmMailing/body_text.html'
18 _
.each(simpleBlocks
, function(templateUrl
, directiveName
){
19 angular
.module('crmMailing').directive(directiveName
, function ($q
, crmMetadata
) {
24 templateUrl
: templateUrl
,
25 link: function (scope
, elm
, attr
) {
26 scope
.$parent
.$watch(attr
.crmMailing
, function(newValue
){
27 scope
.mailing
= newValue
;
29 scope
.crmMailingConst
= CRM
.crmMailing
;
30 scope
.ts
= CRM
.ts(null);
31 scope
[directiveName
] = attr
[directiveName
] ? scope
.$parent
.$eval(attr
[directiveName
]) : {};
32 $q
.when(crmMetadata
.getFields('Mailing'), function(fields
) {
33 scope
.mailingFields
= fields
;
40 // example: <div crm-mailing-block-preview crm-mailing="myMailing" on-preview="openPreview(myMailing, preview.mode)" on-send="sendEmail(myMailing,preview.recipient)">
41 // note: the directive defines a variable called "preview" with any inputs supplied by the user (e.g. the target recipient for an example mailing)
42 angular
.module('crmMailing').directive('crmMailingBlockPreview', function () {
44 templateUrl
: '~/crmMailing/preview.html',
45 link: function (scope
, elm
, attr
) {
46 scope
.$watch(attr
.crmMailing
, function(newValue
){
47 scope
.mailing
= newValue
;
49 scope
.crmMailingConst
= CRM
.crmMailing
;
50 scope
.ts
= CRM
.ts(null);
51 scope
.testContact
= {email
: CRM
.crmMailing
.defaultTestEmail
};
52 scope
.testGroup
= {gid
: null};
54 scope
.doPreview = function(mode
) {
55 scope
.$eval(attr
.onPreview
, {
59 scope
.doSend
= function doSend(recipient
) {
60 scope
.$eval(attr
.onSend
, {
61 preview
: {recipient
: recipient
}
68 angular
.module('crmMailing').directive('crmMailingBlockReview', function (crmMailingPreviewMgr
) {
73 templateUrl
: '~/crmMailing/review.html',
74 link: function (scope
, elm
, attr
) {
75 scope
.$parent
.$watch(attr
.crmMailing
, function(newValue
){
76 scope
.mailing
= newValue
;
78 scope
.crmMailingConst
= CRM
.crmMailing
;
79 scope
.ts
= CRM
.ts(null);
80 scope
.previewMailing
= function previewMailing(mailing
, mode
) {
81 return crmMailingPreviewMgr
.preview(mailing
, mode
);
87 // Convert between a mailing "From Address" (mailing.from_name,mailing.from_email) and a unified label ("Name" <e@ma.il>)
88 // example: <span crm-mailing-from-address="myPlaceholder" crm-mailing="myMailing"><select ng-model="myPlaceholder.label"></select></span>
89 // NOTE: This really doesn't belong in a directive. I've tried (and failed) to make this work with a getterSetter binding, eg
90 // <select ng-model="mailing.convertFromAddress" ng-model-options="{getterSetter: true}">
91 angular
.module('crmMailing').directive('crmMailingFromAddress', function (crmFromAddresses
) {
93 link: function (scope
, element
, attrs
) {
94 var placeholder
= attrs
.crmMailingFromAddress
;
96 scope
.$watch(attrs
.crmMailing
, function(newValue
){
98 scope
[placeholder
] = {
99 label
: crmFromAddresses
.getByAuthorEmail(mailing
.from_name
, mailing
.from_email
, true).label
102 scope
.$watch(placeholder
+ '.label', function (newValue
) {
103 var addr
= crmFromAddresses
.getByLabel(newValue
);
104 mailing
.from_name
= addr
.author
;
105 mailing
.from_email
= addr
.email
;
107 // FIXME: Shouldn't we also be watching mailing.from_name and mailing.from_email?
112 // Represent a datetime field as if it were a radio ('schedule.mode') and a datetime ('schedule.datetime').
113 // example: <div crm-mailing-radio-date="mySchedule" ng-model="mailing.scheduled_date">...</div>
114 angular
.module('crmMailing').directive('crmMailingRadioDate', function () {
117 link: function ($scope
, element
, attrs
, ngModel
) {
119 var schedule
= $scope
[attrs
.crmMailingRadioDate
] = {
124 ngModel
.$render
= function $render() {
125 var sched
= ngModel
.$viewValue
;
126 if (!_
.isEmpty(sched
)) {
127 schedule
.mode
= 'at';
128 schedule
.datetime
= sched
;
131 schedule
.mode
= 'now';
136 var updateParent
= (function () {
137 switch (schedule
.mode
) {
139 ngModel
.$setViewValue(null);
140 schedule
.datetime
= ' ';
143 ngModel
.$setViewValue(schedule
.datetime
);
146 throw 'Unrecognized schedule mode: ' + schedule
.mode
;
151 function validate() {
152 switch (schedule
.mode
) {
154 ngModel
.$setValidity('empty', true);
157 ngModel
.$setValidity('empty', !_
.isEmpty(schedule
.datetime
) && schedule
.datetime
!== ' ');
160 throw 'Unrecognized schedule mode: ' + schedule
.mode
;
164 $scope
.$watch(attrs
.crmMailingRadioDate
+ '.mode', updateParent
);
165 $scope
.$watch(attrs
.crmMailingRadioDate
+ '.datetime', function (newValue
, oldValue
) {
166 // automatically switch mode based on datetime entry
167 if (oldValue
!= newValue
) {
168 if (_
.isEmpty(newValue
) || newValue
== " ") {
169 schedule
.mode
= 'now';
172 schedule
.mode
= 'at';
181 angular
.module('crmMailing').directive('crmMailingReviewBool', function () {
187 template
: '<span ng-class="spanClasses"><span class="icon" ng-class="iconClasses"></span>{{evalTitle}} </span>',
188 link: function (scope
, element
, attrs
) {
190 if (scope
.$parent
.$eval(attrs
.crmOn
)) {
191 scope
.spanClasses
= {'crmMailing-active': true};
192 scope
.iconClasses
= {'ui-icon-check': true};
195 scope
.spanClasses
= {'crmMailing-inactive': true};
196 scope
.iconClasses
= {'ui-icon-close': true};
198 scope
.evalTitle
= scope
.$parent
.$eval(attrs
.crmTitle
);
202 scope
.$parent
.$watch(attrs
.crmOn
, refresh
);
203 scope
.$parent
.$watch(attrs
.crmTitle
, refresh
);
208 // example: <input name="subject" /> <input crm-mailing-token on-select="doSomething(token.name)" />
209 // WISHLIST: Instead of global CRM.crmMailing.mailTokens, accept token list as an input
210 angular
.module('crmMailing').directive('crmMailingToken', function () {
212 require
: '^crmUiIdScope',
216 template
: '<input type="text" class="crmMailingToken" />',
217 link: function (scope
, element
, attrs
, crmUiIdCtrl
) {
218 $(element
).addClass('crm-action-menu action-icon-token').select2({
220 dropdownAutoWidth
: true,
221 data
: CRM
.crmMailing
.mailTokens
,
222 placeholder
: ts('Tokens')
224 $(element
).on('select2-selecting', function (e
) {
226 $(element
).select2('close').select2('val', '');
227 scope
.$parent
.$eval(attrs
.onSelect
, {
235 // example: <select multiple crm-mailing-recipients crm-mailing="mymailing" crm-avail-groups="myGroups" crm-avail-mailings="myMailings"></select>
236 // FIXME: participate in ngModel's validation cycle
237 angular
.module('crmMailing').directive('crmMailingRecipients', function (crmUiAlert
) {
242 crmAvailGroups
: '@', // available groups
243 crmAvailMailings
: '@', // available mailings
244 crmMandatoryGroups
: '@', // hard-coded/mandatory groups
247 templateUrl
: '~/crmMailing/directive/recipients.html',
248 link: function (scope
, element
, attrs
, ngModel
) {
249 scope
.recips
= ngModel
.$viewValue
;
250 scope
.groups
= scope
.$parent
.$eval(attrs
.crmAvailGroups
);
251 scope
.mailings
= scope
.$parent
.$eval(attrs
.crmAvailMailings
);
254 var ts
= scope
.ts
= CRM
.ts(null);
256 /// Convert MySQL date ("yyyy-mm-dd hh:mm:ss") to JS date object
257 scope
.parseDate = function (date
) {
258 if (!angular
.isString(date
)) {
261 var p
= date
.split(/[\- :]/);
262 return new Date(parseInt(p
[0]), parseInt(p
[1])-1, parseInt(p
[2]), parseInt(p
[3]), parseInt(p
[4]), parseInt(p
[5]));
265 /// Remove {value} from {array}
266 function arrayRemove(array
, value
) {
267 var idx
= array
.indexOf(value
);
269 array
.splice(idx
, 1);
273 // @param string id an encoded string like "4 civicrm_mailing include"
274 // @return Object keys: entity_id, entity_type, mode
275 function convertValueToObj(id
) {
276 var a
= id
.split(" ");
277 return {entity_id
: parseInt(a
[0]), entity_type
: a
[1], mode
: a
[2]};
280 // @param Object mailing
281 // @return array list of values like "4 civicrm_mailing include"
282 function convertMailingToValues(recipients
) {
284 angular
.forEach(recipients
.groups
.include
, function (v
) {
285 r
.push(v
+ " civicrm_group include");
287 angular
.forEach(recipients
.groups
.exclude
, function (v
) {
288 r
.push(v
+ " civicrm_group exclude");
290 angular
.forEach(recipients
.mailings
.include
, function (v
) {
291 r
.push(v
+ " civicrm_mailing include");
293 angular
.forEach(recipients
.mailings
.exclude
, function (v
) {
294 r
.push(v
+ " civicrm_mailing exclude");
299 function refreshMandatory() {
300 if (ngModel
.$viewValue
&& ngModel
.$viewValue
.groups
) {
301 scope
.mandatoryGroups
= _
.filter(scope
.$parent
.$eval(attrs
.crmMandatoryGroups
), function(grp
) {
302 return _
.contains(ngModel
.$viewValue
.groups
.include
, parseInt(grp
.id
));
304 scope
.mandatoryIds
= _
.map(_
.pluck(scope
.$parent
.$eval(attrs
.crmMandatoryGroups
), 'id'), function(n
) {
309 scope
.mandatoryGroups
= [];
310 scope
.mandatoryIds
= [];
314 function isMandatory(grpId
) {
315 return _
.contains(scope
.mandatoryIds
, parseInt(grpId
));
318 var refreshUI
= ngModel
.$render
= function refresuhUI() {
319 scope
.recips
= ngModel
.$viewValue
;
320 if (ngModel
.$viewValue
) {
321 $(element
).select2('val', convertMailingToValues(ngModel
.$viewValue
));
327 /// @return string HTML representingn an option
328 function formatItem(item
) {
330 // return `text` for optgroup
333 var option
= convertValueToObj(item
.id
);
334 var icon
= (option
.entity_type
=== 'civicrm_mailing') ? 'EnvelopeIn.gif' : 'group.png';
335 var spanClass
= (option
.mode
== 'exclude') ? 'crmMailing-exclude' : 'crmMailing-include';
336 if (option
.entity_type
!= 'civicrm_mailing' && isMandatory(option
.entity_id
)) {
337 spanClass
= 'crmMailing-mandatory';
339 return "<img src='../../sites/all/modules/civicrm/i/" + icon
+ "' height=12 width=12 /> <span class='" + spanClass
+ "'>" + item
.text
+ "</span>";
342 function validate() {
343 if (scope
.$parent
.$eval(attrs
.ngRequired
)) {
344 var empty
= (_
.isEmpty(ngModel
.$viewValue
.groups
.include
) && _
.isEmpty(ngModel
.$viewValue
.mailings
.include
));
345 ngModel
.$setValidity('empty', !empty
);
347 ngModel
.$setValidity('empty', true);
352 dropdownAutoWidth
: true,
353 placeholder
: "Groups or Past Recipients",
354 formatResult
: formatItem
,
355 formatSelection
: formatItem
,
356 escapeMarkup: function (m
) {
361 $(element
).on('select2-selecting', function (e
) {
362 var option
= convertValueToObj(e
.val
);
363 var typeKey
= option
.entity_type
== 'civicrm_mailing' ? 'mailings' : 'groups';
364 if (option
.mode
== 'exclude') {
365 ngModel
.$viewValue
[typeKey
].exclude
.push(option
.entity_id
);
366 arrayRemove(ngModel
.$viewValue
[typeKey
].include
, option
.entity_id
);
369 ngModel
.$viewValue
[typeKey
].include
.push(option
.entity_id
);
370 arrayRemove(ngModel
.$viewValue
[typeKey
].exclude
, option
.entity_id
);
373 $(element
).select2('close');
378 $(element
).on("select2-removing", function (e
) {
379 var option
= convertValueToObj(e
.val
);
380 var typeKey
= option
.entity_type
== 'civicrm_mailing' ? 'mailings' : 'groups';
381 if (typeKey
== 'groups' && isMandatory(option
.entity_id
)) {
383 text
: ts('This mailing was generated based on search results. The search results cannot be removed.'),
384 title
: ts('Required')
389 scope
.$parent
.$apply(function () {
390 arrayRemove(ngModel
.$viewValue
[typeKey
][option
.mode
], option
.entity_id
);
396 scope
.$watchCollection("recips.groups.include", refreshUI
);
397 scope
.$watchCollection("recips.groups.exclude", refreshUI
);
398 scope
.$watchCollection("recips.mailings.include", refreshUI
);
399 scope
.$watchCollection("recips.mailings.exclude", refreshUI
);
400 setTimeout(refreshUI
, 50);
402 scope
.$watchCollection(attrs
.crmAvailGroups
, function() {
403 scope
.groups
= scope
.$parent
.$eval(attrs
.crmAvailGroups
);
405 scope
.$watchCollection(attrs
.crmAvailMailings
, function() {
406 scope
.mailings
= scope
.$parent
.$eval(attrs
.crmAvailMailings
);
408 scope
.$watchCollection(attrs
.crmMandatoryGroups
, function() {
415 })(angular
, CRM
.$, CRM
._
);