1 (function (angular
, $, _
) {
3 // The following directives have the same simple implementation -- load
4 // a template and export a "mailing" object into scope.
6 crmMailingBlockHeaderFooter
: '~/crmMailing/headerFooter.html',
7 crmMailingBlockMailing
: '~/crmMailing/mailing.html',
8 crmMailingBlockPublication
: '~/crmMailing/publication.html',
9 crmMailingBlockResponses
: '~/crmMailing/responses.html',
10 crmMailingBlockRecipients
: '~/crmMailing/recipients.html',
11 crmMailingBlockSchedule
: '~/crmMailing/schedule.html',
12 crmMailingBlockSummary
: '~/crmMailing/summary.html',
13 crmMailingBlockTracking
: '~/crmMailing/tracking.html',
14 crmMailingBodyHtml
: '~/crmMailing/body_html.html',
15 crmMailingBodyText
: '~/crmMailing/body_text.html'
17 _
.each(simpleBlocks
, function(templateUrl
, directiveName
){
18 angular
.module('crmMailing').directive(directiveName
, function ($parse
) {
23 templateUrl
: templateUrl
,
24 link: function (scope
, elm
, attr
) {
25 var model
= $parse(attr
.crmMailing
);
26 scope
.mailing
= model(scope
.$parent
);
27 scope
.crmMailingConst
= CRM
.crmMailing
;
28 scope
.ts
= CRM
.ts(null);
29 scope
[directiveName
] = attr
[directiveName
] ? scope
.$parent
.$eval(attr
[directiveName
]) : {};
35 // example: <div crm-mailing-block-preview crm-mailing="myMailing" on-preview="openPreview(myMailing, preview.mode)" on-send="sendEmail(myMailing,preview.recipient)">
36 // note: the directive defines a variable called "preview" with any inputs supplied by the user (e.g. the target recipient for an example mailing)
37 angular
.module('crmMailing').directive('crmMailingBlockPreview', function ($parse
) {
39 templateUrl
: '~/crmMailing/preview.html',
40 link: function (scope
, elm
, attr
) {
41 var mailingModel
= $parse(attr
.crmMailing
);
42 scope
.mailing
= mailingModel(scope
);
43 scope
.crmMailingConst
= CRM
.crmMailing
;
44 scope
.ts
= CRM
.ts(null);
45 scope
.testContact
= {email
: CRM
.crmMailing
.defaultTestEmail
};
46 scope
.testGroup
= {gid
: null};
48 scope
.doPreview = function(mode
) {
49 scope
.$eval(attr
.onPreview
, {
53 scope
.doSend
= function doSend(recipient
) {
54 scope
.$eval(attr
.onSend
, {
55 preview
: {recipient
: recipient
}
62 angular
.module('crmMailing').directive('crmMailingBlockReview', function ($parse
, crmMailingPreviewMgr
) {
67 templateUrl
: '~/crmMailing/review.html',
68 link: function (scope
, elm
, attr
) {
69 var mailingModel
= $parse(attr
.crmMailing
);
70 scope
.mailing
= mailingModel(scope
.$parent
);
71 scope
.crmMailingConst
= CRM
.crmMailing
;
72 scope
.ts
= CRM
.ts(null);
73 scope
.previewMailing
= function previewMailing(mailing
, mode
) {
74 return crmMailingPreviewMgr
.preview(mailing
, mode
);
80 // Convert between a mailing "From Address" (mailing.from_name,mailing.from_email) and a unified label ("Name" <e@ma.il>)
81 // example: <span crm-mailing-from-address="myPlaceholder" crm-mailing="myMailing"><select ng-model="myPlaceholder.label"></select></span>
82 // NOTE: This really doesn't belong in a directive. I've tried (and failed) to make this work with a getterSetter binding, eg
83 // <select ng-model="mailing.convertFromAddress" ng-model-options="{getterSetter: true}">
84 angular
.module('crmMailing').directive('crmMailingFromAddress', function ($parse
, crmFromAddresses
) {
86 link: function (scope
, element
, attrs
) {
87 var placeholder
= attrs
.crmMailingFromAddress
;
88 var model
= $parse(attrs
.crmMailing
);
89 var mailing
= model(scope
.$parent
);
90 scope
[placeholder
] = {
91 label
: crmFromAddresses
.getByAuthorEmail(mailing
.from_name
, mailing
.from_email
, true).label
93 scope
.$watch(placeholder
+ '.label', function (newValue
) {
94 var addr
= crmFromAddresses
.getByLabel(newValue
);
95 mailing
.from_name
= addr
.author
;
96 mailing
.from_email
= addr
.email
;
98 // FIXME: Shouldn't we also be watching mailing.from_name and mailing.from_email?
103 // Represent a datetime field as if it were a radio ('schedule.mode') and a datetime ('schedule.datetime').
104 // example: <div crm-mailing-radio-date="mySchedule" crm-model="mailing.scheduled_date">...</div>
105 // FIXME: use ngModel instead of adhoc crmModel
106 angular
.module('crmMailing').directive('crmMailingRadioDate', function ($parse
) {
108 link: function ($scope
, element
, attrs
) {
109 var schedModel
= $parse(attrs
.crmModel
);
111 var schedule
= $scope
[attrs
.crmMailingRadioDate
] = {
115 var updateChildren
= (function () {
116 var sched
= schedModel($scope
);
118 schedule
.mode
= 'at';
119 schedule
.datetime
= sched
;
122 schedule
.mode
= 'now';
125 var updateParent
= (function () {
126 switch (schedule
.mode
) {
128 schedModel
.assign($scope
, null);
131 schedModel
.assign($scope
, schedule
.datetime
);
134 throw 'Unrecognized schedule mode: ' + schedule
.mode
;
138 $scope
.$watch(attrs
.crmModel
, updateChildren
);
139 $scope
.$watch(attrs
.crmMailingRadioDate
+ '.mode', updateParent
);
140 $scope
.$watch(attrs
.crmMailingRadioDate
+ '.datetime', function (newValue
, oldValue
) {
141 // automatically switch mode based on datetime entry
142 if (oldValue
!= newValue
) {
143 if (!newValue
|| newValue
== " ") {
144 schedule
.mode
= 'now';
147 schedule
.mode
= 'at';
156 angular
.module('crmMailing').directive('crmMailingReviewBool', function () {
162 template
: '<span ng-class="spanClasses"><span class="icon" ng-class="iconClasses"></span>{{crmTitle}} </span>',
163 link: function (scope
, element
, attrs
) {
165 if (scope
.$parent
.$eval(attrs
.crmOn
)) {
166 scope
.spanClasses
= {'crmMailing-active': true};
167 scope
.iconClasses
= {'ui-icon-check': true};
170 scope
.spanClasses
= {'crmMailing-inactive': true};
171 scope
.iconClasses
= {'ui-icon-close': true};
173 scope
.crmTitle
= scope
.$parent
.$eval(attrs
.crmTitle
);
177 scope
.$parent
.$watch(attrs
.crmOn
, refresh
);
178 scope
.$parent
.$watch(attrs
.crmTitle
, refresh
);
183 // example: <input name="subject" /> <input crm-mailing-token crm-for="subject"/>
184 // WISHLIST: Instead of global CRM.crmMailing.mailTokens, accept token list as an input
185 angular
.module('crmMailing').directive('crmMailingToken', function () {
187 require
: '^crmUiIdScope',
191 template
: '<input type="text" class="crmMailingToken" />',
192 link: function (scope
, element
, attrs
, crmUiIdCtrl
) {
193 $(element
).addClass('crm-action-menu action-icon-token').select2({
195 dropdownAutoWidth
: true,
196 data
: CRM
.crmMailing
.mailTokens
,
197 placeholder
: ts('Tokens')
199 $(element
).on('select2-selecting', function (e
) {
200 var id
= crmUiIdCtrl
.get(attrs
.crmFor
);
201 if (CKEDITOR
.instances
[id
]) {
202 CKEDITOR
.instances
[id
].insertText(e
.val
);
203 $(element
).select2('close').select2('val', '');
204 CKEDITOR
.instances
[id
].focus();
207 var crmForEl
= $('#' + id
);
208 var origVal
= crmForEl
.val();
209 var origPos
= crmForEl
[0].selectionStart
;
210 var newVal
= origVal
.substring(0, origPos
) + e
.val
+ origVal
.substring(origPos
, origVal
.length
);
211 crmForEl
.val(newVal
);
212 var newPos
= (origPos
+ e
.val
.length
);
213 crmForEl
[0].selectionStart
= newPos
;
214 crmForEl
[0].selectionEnd
= newPos
;
216 $(element
).select2('close').select2('val', '');
217 crmForEl
.triggerHandler('change');
227 // example: <select multiple crm-mailing-recipients crm-mailing="mymailing" crm-avail-groups="myGroups" crm-avail-mailings="myMailings"></select>
228 // FIXME: participate in ngModel's validation cycle
229 angular
.module('crmMailing').directive('crmMailingRecipients', function () {
233 crmAvailGroups
: '@', // available groups
234 crmAvailMailings
: '@', // available mailings
235 crmMailing
: '@' // the mailing for which we are choosing recipients
237 templateUrl
: '~/crmMailing/directive/recipients.html',
238 link: function (scope
, element
, attrs
) {
239 scope
.mailing
= scope
.$parent
.$eval(attrs
.crmMailing
);
240 scope
.groups
= scope
.$parent
.$eval(attrs
.crmAvailGroups
);
241 scope
.mailings
= scope
.$parent
.$eval(attrs
.crmAvailMailings
);
243 scope
.ts
= CRM
.ts(null);
245 /// Convert MySQL date ("yyyy-mm-dd hh:mm:ss") to JS date object
246 scope
.parseDate = function (date
) {
247 if (!angular
.isString(date
)) {
250 var p
= date
.split(/[\- :]/);
251 return new Date(parseInt(p
[0]), parseInt(p
[1])-1, parseInt(p
[2]), parseInt(p
[3]), parseInt(p
[4]), parseInt(p
[5]));
254 /// Remove {value} from {array}
255 function arrayRemove(array
, value
) {
256 var idx
= array
.indexOf(value
);
258 array
.splice(idx
, 1);
262 // @param string id an encoded string like "4 civicrm_mailing include"
263 // @return Object keys: entity_id, entity_type, mode
264 function convertValueToObj(id
) {
265 var a
= id
.split(" ");
266 return {entity_id
: parseInt(a
[0]), entity_type
: a
[1], mode
: a
[2]};
269 // @param Object mailing
270 // @return array list of values like "4 civicrm_mailing include"
271 function convertMailingToValues(mailing
) {
273 angular
.forEach(mailing
.groups
.include
, function (v
) {
274 r
.push(v
+ " civicrm_group include");
276 angular
.forEach(mailing
.groups
.exclude
, function (v
) {
277 r
.push(v
+ " civicrm_group exclude");
279 angular
.forEach(mailing
.mailings
.include
, function (v
) {
280 r
.push(v
+ " civicrm_mailing include");
282 angular
.forEach(mailing
.mailings
.exclude
, function (v
) {
283 r
.push(v
+ " civicrm_mailing exclude");
288 // Update $(element) view based on latest data
289 function refreshUI() {
291 $(element
).select2('val', convertMailingToValues(scope
.mailing
));
295 /// @return string HTML representingn an option
296 function formatItem(item
) {
298 // return `text` for optgroup
301 var option
= convertValueToObj(item
.id
);
302 var icon
= (option
.entity_type
=== 'civicrm_mailing') ? 'EnvelopeIn.gif' : 'group.png';
303 var spanClass
= (option
.mode
== 'exclude') ? 'crmMailing-exclude' : 'crmMailing-include';
304 return "<img src='../../sites/all/modules/civicrm/i/" + icon
+ "' height=12 width=12 /> <span class='" + spanClass
+ "'>" + item
.text
+ "</span>";
308 dropdownAutoWidth
: true,
309 placeholder
: "Groups or Past Recipients",
310 formatResult
: formatItem
,
311 formatSelection
: formatItem
,
312 escapeMarkup: function (m
) {
317 $(element
).on('select2-selecting', function (e
) {
318 var option
= convertValueToObj(e
.val
);
319 var typeKey
= option
.entity_type
== 'civicrm_mailing' ? 'mailings' : 'groups';
320 if (option
.mode
== 'exclude') {
321 scope
.mailing
[typeKey
].exclude
.push(option
.entity_id
);
322 arrayRemove(scope
.mailing
[typeKey
].include
, option
.entity_id
);
325 scope
.mailing
[typeKey
].include
.push(option
.entity_id
);
326 arrayRemove(scope
.mailing
[typeKey
].exclude
, option
.entity_id
);
329 $(element
).select2('close');
333 $(element
).on("select2-removing", function (e
) {
334 var option
= convertValueToObj(e
.val
);
335 var typeKey
= option
.entity_type
== 'civicrm_mailing' ? 'mailings' : 'groups';
336 scope
.$parent
.$apply(function () {
337 arrayRemove(scope
.mailing
[typeKey
][option
.mode
], option
.entity_id
);
342 scope
.$watchCollection(attrs
.crmMailing
+ ".groups.include", refreshUI
);
343 scope
.$watchCollection(attrs
.crmMailing
+ ".groups.exclude", refreshUI
);
344 scope
.$watchCollection(attrs
.crmMailing
+ ".mailings.include", refreshUI
);
345 scope
.$watchCollection(attrs
.crmMailing
+ ".mailings.exclude", refreshUI
);
346 setTimeout(refreshUI
, 50);
351 })(angular
, CRM
.$, CRM
._
);