1 (function (angular
, $, _
) {
2 var partialUrl = function (relPath
) {
3 return '~/crmMailing/' + relPath
;
6 // The following directives have the same simple implementation -- load
7 // a template and export a "mailing" object into scope.
9 crmMailingBlockHeaderFooter
: partialUrl('headerFooter.html'),
10 crmMailingBlockMailing
: partialUrl('mailing.html'),
11 crmMailingBlockPublication
: partialUrl('publication.html'),
12 crmMailingBlockResponses
: partialUrl('responses.html'),
13 crmMailingBlockRecipients
: partialUrl('recipients.html'),
14 crmMailingBlockSchedule
: partialUrl('schedule.html'),
15 crmMailingBlockSummary
: partialUrl('summary.html'),
16 crmMailingBlockTracking
: partialUrl('tracking.html'),
17 crmMailingBodyHtml
: partialUrl('body_html.html'),
18 crmMailingBodyText
: partialUrl('body_text.html')
20 _
.each(simpleBlocks
, function(templateUrl
, directiveName
){
21 angular
.module('crmMailing').directive(directiveName
, function ($parse
) {
26 templateUrl
: templateUrl
,
27 link: function (scope
, elm
, attr
) {
28 var model
= $parse(attr
.crmMailing
);
29 scope
.mailing
= model(scope
.$parent
);
30 scope
.crmMailingConst
= CRM
.crmMailing
;
31 scope
.ts
= CRM
.ts(null);
32 scope
[directiveName
] = attr
[directiveName
] ? scope
.$parent
.$eval(attr
[directiveName
]) : {};
38 // example: <div crm-mailing-block-preview crm-mailing="myMailing" on-preview="openPreview(myMailing, preview.mode)" on-send="sendEmail(myMailing,preview.recipient)">
39 // note: the directive defines a variable called "preview" with any inputs supplied by the user (e.g. the target recipient for an example mailing)
40 angular
.module('crmMailing').directive('crmMailingBlockPreview', function ($parse
) {
42 templateUrl
: partialUrl('preview.html'),
43 link: function (scope
, elm
, attr
) {
44 var mailingModel
= $parse(attr
.crmMailing
);
45 scope
.mailing
= mailingModel(scope
);
46 scope
.crmMailingConst
= CRM
.crmMailing
;
47 scope
.ts
= CRM
.ts(null);
48 scope
.testContact
= {email
: CRM
.crmMailing
.defaultTestEmail
};
49 scope
.testGroup
= {gid
: null};
51 scope
.doPreview = function(mode
) {
52 scope
.$eval(attr
.onPreview
, {
56 scope
.doSend
= function doSend(recipient
) {
57 scope
.$eval(attr
.onSend
, {
58 preview
: {recipient
: recipient
}
65 angular
.module('crmMailing').directive('crmMailingBlockReview', function ($parse
, crmMailingPreviewMgr
) {
70 templateUrl
: partialUrl('review.html'),
71 link: function (scope
, elm
, attr
) {
72 var mailingModel
= $parse(attr
.crmMailing
);
73 scope
.mailing
= mailingModel(scope
.$parent
);
74 scope
.crmMailingConst
= CRM
.crmMailing
;
75 scope
.ts
= CRM
.ts(null);
76 scope
.previewMailing
= function previewMailing(mailing
, mode
) {
77 return crmMailingPreviewMgr
.preview(mailing
, mode
);
83 // Convert between a mailing "From Address" (mailing.from_name,mailing.from_email) and a unified label ("Name" <e@ma.il>)
84 // example: <span crm-mailing-from-address="myPlaceholder" crm-mailing="myMailing"><select ng-model="myPlaceholder.label"></select></span>
85 // NOTE: This really doesn't belong in a directive. I've tried (and failed) to make this work with a getterSetter binding, eg
86 // <select ng-model="mailing.convertFromAddress" ng-model-options="{getterSetter: true}">
87 angular
.module('crmMailing').directive('crmMailingFromAddress', function ($parse
, crmFromAddresses
) {
89 link: function (scope
, element
, attrs
) {
90 var placeholder
= attrs
.crmMailingFromAddress
;
91 var model
= $parse(attrs
.crmMailing
);
92 var mailing
= model(scope
.$parent
);
93 scope
[placeholder
] = {
94 label
: crmFromAddresses
.getByAuthorEmail(mailing
.from_name
, mailing
.from_email
, true).label
96 scope
.$watch(placeholder
+ '.label', function (newValue
) {
97 var addr
= crmFromAddresses
.getByLabel(newValue
);
98 mailing
.from_name
= addr
.author
;
99 mailing
.from_email
= addr
.email
;
101 // FIXME: Shouldn't we also be watching mailing.from_name and mailing.from_email?
106 // Represent a datetime field as if it were a radio ('schedule.mode') and a datetime ('schedule.datetime').
107 // example: <div crm-mailing-radio-date="mySchedule" crm-model="mailing.scheduled_date">...</div>
108 // FIXME: use ngModel instead of adhoc crmModel
109 angular
.module('crmMailing').directive('crmMailingRadioDate', function ($parse
) {
111 link: function ($scope
, element
, attrs
) {
112 var schedModel
= $parse(attrs
.crmModel
);
114 var schedule
= $scope
[attrs
.crmMailingRadioDate
] = {
118 var updateChildren
= (function () {
119 var sched
= schedModel($scope
);
121 schedule
.mode
= 'at';
122 schedule
.datetime
= sched
;
125 schedule
.mode
= 'now';
128 var updateParent
= (function () {
129 switch (schedule
.mode
) {
131 schedModel
.assign($scope
, null);
134 schedModel
.assign($scope
, schedule
.datetime
);
137 throw 'Unrecognized schedule mode: ' + schedule
.mode
;
141 $scope
.$watch(attrs
.crmModel
, updateChildren
);
142 $scope
.$watch(attrs
.crmMailingRadioDate
+ '.mode', updateParent
);
143 $scope
.$watch(attrs
.crmMailingRadioDate
+ '.datetime', function (newValue
, oldValue
) {
144 // automatically switch mode based on datetime entry
145 if (oldValue
!= newValue
) {
146 if (!newValue
|| newValue
== " ") {
147 schedule
.mode
= 'now';
150 schedule
.mode
= 'at';
159 angular
.module('crmMailing').directive('crmMailingReviewBool', function () {
165 template
: '<span ng-class="spanClasses"><span class="icon" ng-class="iconClasses"></span>{{crmTitle}} </span>',
166 link: function (scope
, element
, attrs
) {
168 if (scope
.$parent
.$eval(attrs
.crmOn
)) {
169 scope
.spanClasses
= {'crmMailing-active': true};
170 scope
.iconClasses
= {'ui-icon-check': true};
173 scope
.spanClasses
= {'crmMailing-inactive': true};
174 scope
.iconClasses
= {'ui-icon-close': true};
176 scope
.crmTitle
= scope
.$parent
.$eval(attrs
.crmTitle
);
180 scope
.$parent
.$watch(attrs
.crmOn
, refresh
);
181 scope
.$parent
.$watch(attrs
.crmTitle
, refresh
);
186 // example: <input name="subject" /> <input crm-mailing-token crm-for="subject"/>
187 // WISHLIST: Instead of global CRM.crmMailing.mailTokens, accept token list as an input
188 angular
.module('crmMailing').directive('crmMailingToken', function () {
190 require
: '^crmUiIdScope',
194 template
: '<input type="text" class="crmMailingToken" />',
195 link: function (scope
, element
, attrs
, crmUiIdCtrl
) {
198 dropdownAutoWidth
: true,
199 data
: CRM
.crmMailing
.mailTokens
,
200 placeholder
: ts('Insert')
202 $(element
).on('select2-selecting', function (e
) {
203 var id
= crmUiIdCtrl
.get(attrs
.crmFor
);
204 if (CKEDITOR
.instances
[id
]) {
205 CKEDITOR
.instances
[id
].insertText(e
.val
);
206 $(element
).select2('close').select2('val', '');
207 CKEDITOR
.instances
[id
].focus();
210 var crmForEl
= $('#' + id
);
211 var origVal
= crmForEl
.val();
212 var origPos
= crmForEl
[0].selectionStart
;
213 var newVal
= origVal
.substring(0, origPos
) + e
.val
+ origVal
.substring(origPos
, origVal
.length
);
214 crmForEl
.val(newVal
);
215 var newPos
= (origPos
+ e
.val
.length
);
216 crmForEl
[0].selectionStart
= newPos
;
217 crmForEl
[0].selectionEnd
= newPos
;
219 $(element
).select2('close').select2('val', '');
220 crmForEl
.triggerHandler('change');
230 // example: <select multiple crm-mailing-recipients crm-mailing="mymailing" crm-avail-groups="myGroups" crm-avail-mailings="myMailings"></select>
231 // FIXME: participate in ngModel's validation cycle
232 angular
.module('crmMailing').directive('crmMailingRecipients', function () {
236 crmAvailGroups
: '@', // available groups
237 crmAvailMailings
: '@', // available mailings
238 crmMailing
: '@' // the mailing for which we are choosing recipients
240 templateUrl
: partialUrl('directive/recipients.html'),
241 link: function (scope
, element
, attrs
) {
242 scope
.mailing
= scope
.$parent
.$eval(attrs
.crmMailing
);
243 scope
.groups
= scope
.$parent
.$eval(attrs
.crmAvailGroups
);
244 scope
.mailings
= scope
.$parent
.$eval(attrs
.crmAvailMailings
);
246 scope
.ts
= CRM
.ts(null);
248 /// Convert MySQL date ("yyyy-mm-dd hh:mm:ss") to JS date object
249 scope
.parseDate = function (date
) {
250 if (!angular
.isString(date
)) {
253 var p
= date
.split(/[\- :]/);
254 return new Date(parseInt(p
[0]), parseInt(p
[1])-1, parseInt(p
[2]), parseInt(p
[3]), parseInt(p
[4]), parseInt(p
[5]));
257 /// Remove {value} from {array}
258 function arrayRemove(array
, value
) {
259 var idx
= array
.indexOf(value
);
261 array
.splice(idx
, 1);
265 // @param string id an encoded string like "4 civicrm_mailing include"
266 // @return Object keys: entity_id, entity_type, mode
267 function convertValueToObj(id
) {
268 var a
= id
.split(" ");
269 return {entity_id
: parseInt(a
[0]), entity_type
: a
[1], mode
: a
[2]};
272 // @param Object mailing
273 // @return array list of values like "4 civicrm_mailing include"
274 function convertMailingToValues(mailing
) {
276 angular
.forEach(mailing
.groups
.include
, function (v
) {
277 r
.push(v
+ " civicrm_group include");
279 angular
.forEach(mailing
.groups
.exclude
, function (v
) {
280 r
.push(v
+ " civicrm_group exclude");
282 angular
.forEach(mailing
.mailings
.include
, function (v
) {
283 r
.push(v
+ " civicrm_mailing include");
285 angular
.forEach(mailing
.mailings
.exclude
, function (v
) {
286 r
.push(v
+ " civicrm_mailing exclude");
291 // Update $(element) view based on latest data
292 function refreshUI() {
294 $(element
).select2('val', convertMailingToValues(scope
.mailing
));
298 /// @return string HTML representingn an option
299 function formatItem(item
) {
301 // return `text` for optgroup
304 var option
= convertValueToObj(item
.id
);
305 var icon
= (option
.entity_type
=== 'civicrm_mailing') ? 'EnvelopeIn.gif' : 'group.png';
306 var spanClass
= (option
.mode
== 'exclude') ? 'crmMailing-exclude' : 'crmMailing-include';
307 return "<img src='../../sites/all/modules/civicrm/i/" + icon
+ "' height=12 width=12 /> <span class='" + spanClass
+ "'>" + item
.text
+ "</span>";
311 dropdownAutoWidth
: true,
312 placeholder
: "Groups or Past Recipients",
313 formatResult
: formatItem
,
314 formatSelection
: formatItem
,
315 escapeMarkup: function (m
) {
320 $(element
).on('select2-selecting', function (e
) {
321 var option
= convertValueToObj(e
.val
);
322 var typeKey
= option
.entity_type
== 'civicrm_mailing' ? 'mailings' : 'groups';
323 if (option
.mode
== 'exclude') {
324 scope
.mailing
[typeKey
].exclude
.push(option
.entity_id
);
325 arrayRemove(scope
.mailing
[typeKey
].include
, option
.entity_id
);
328 scope
.mailing
[typeKey
].include
.push(option
.entity_id
);
329 arrayRemove(scope
.mailing
[typeKey
].exclude
, option
.entity_id
);
332 $(element
).select2('close');
336 $(element
).on("select2-removing", function (e
) {
337 var option
= convertValueToObj(e
.val
);
338 var typeKey
= option
.entity_type
== 'civicrm_mailing' ? 'mailings' : 'groups';
339 scope
.$parent
.$apply(function () {
340 arrayRemove(scope
.mailing
[typeKey
][option
.mode
], option
.entity_id
);
345 scope
.$watchCollection(attrs
.crmMailing
+ ".groups.include", refreshUI
);
346 scope
.$watchCollection(attrs
.crmMailing
+ ".groups.exclude", refreshUI
);
347 scope
.$watchCollection(attrs
.crmMailing
+ ".mailings.include", refreshUI
);
348 scope
.$watchCollection(attrs
.crmMailing
+ ".mailings.exclude", refreshUI
);
349 setTimeout(refreshUI
, 50);
354 })(angular
, CRM
.$, CRM
._
);