Commit | Line | Data |
---|---|---|
f44610a1 TO |
1 | /// crmFile: Manage file attachments |
2 | (function (angular, $, _) { | |
f44610a1 | 3 | |
0b199194 | 4 | angular.module('crmAttachment', CRM.angRequires('crmAttachment')); |
f44610a1 TO |
5 | |
6 | // crmAttachment manages the list of files which are attached to a given entity | |
7 | angular.module('crmAttachment').factory('CrmAttachments', function (crmApi, crmStatus, FileUploader, $q) { | |
f44610a1 TO |
8 | // @param target an Object(entity_table:'',entity_id:'') or function which generates an object |
9 | function CrmAttachments(target) { | |
f44610a1 | 10 | var crmAttachments = this; |
d4343b47 | 11 | this._target = target; |
f44610a1 | 12 | this.files = []; |
95fd5cb2 | 13 | this.trash = []; |
f44610a1 TO |
14 | this.uploader = new FileUploader({ |
15 | url: CRM.url('civicrm/ajax/attachment'), | |
16 | onAfterAddingFile: function onAfterAddingFile(item) { | |
17 | item.crmData = { | |
18 | description: '' | |
19 | }; | |
20 | }, | |
21 | onSuccessItem: function onSuccessItem(item, response, status, headers) { | |
22 | crmAttachments.files.push(response.file.values[response.file.id]); | |
23 | crmAttachments.uploader.removeFromQueue(item); | |
24 | }, | |
25 | onErrorItem: function onErrorItem(item, response, status, headers) { | |
26 | var msg = (response && response.file && response.file.error_message) ? response.file.error_message : ts('Unknown error'); | |
27 | CRM.alert(item.file.name + ' - ' + msg, ts('Attachment failed')); | |
28 | crmAttachments.uploader.removeFromQueue(item); | |
29 | } | |
30 | }); | |
31 | } | |
32 | ||
33 | angular.extend(CrmAttachments.prototype, { | |
34 | // @return Object(entity_table:'',entity_id:'') | |
35 | getTarget: function () { | |
d4343b47 | 36 | return (angular.isFunction(this._target) ? this._target() : this._target); |
f44610a1 TO |
37 | }, |
38 | // @return Promise<Attachment> | |
39 | load: function load() { | |
40 | var target = this.getTarget(); | |
41 | var Attachment = this; | |
42 | ||
43 | if (target.entity_id) { | |
44 | var params = { | |
45 | entity_table: target.entity_table, | |
46 | entity_id: target.entity_id | |
47 | }; | |
48 | return crmApi('Attachment', 'get', params).then(function (apiResult) { | |
49 | Attachment.files = _.values(apiResult.values); | |
50 | return Attachment; | |
51 | }); | |
52 | } | |
53 | else { | |
54 | var dfr = $q.defer(); | |
55 | Attachment.files = []; | |
56 | dfr.resolve(Attachment); | |
57 | return dfr.promise; | |
58 | } | |
59 | }, | |
60 | // @return Promise | |
61 | save: function save() { | |
62 | var crmAttachments = this; | |
63 | var target = this.getTarget(); | |
64 | if (!target.entity_table || !target.entity_id) { | |
65 | throw "Cannot save attachments: unknown entity_table or entity_id"; | |
66 | } | |
67 | ||
68 | var params = _.extend({}, target); | |
69 | params.values = crmAttachments.files; | |
70 | return crmApi('Attachment', 'replace', params) | |
71 | .then(function () { | |
72 | var dfr = $q.defer(); | |
73 | ||
74 | var newItems = crmAttachments.uploader.getNotUploadedItems(); | |
75 | if (newItems.length > 0) { | |
76 | _.each(newItems, function (item) { | |
2b7de044 | 77 | item.formData = [_.extend({crm_attachment_token: CRM.crmAttachment.token}, target, item.crmData)]; |
f44610a1 TO |
78 | }); |
79 | crmAttachments.uploader.onCompleteAll = function onCompleteAll() { | |
80 | delete crmAttachments.uploader.onCompleteAll; | |
81 | dfr.resolve(crmAttachments); | |
82 | }; | |
83 | crmAttachments.uploader.uploadAll(); | |
84 | } | |
85 | else { | |
86 | dfr.resolve(crmAttachments); | |
87 | } | |
88 | ||
89 | return dfr.promise; | |
90 | }); | |
91 | }, | |
95fd5cb2 TO |
92 | // Compute a digest over the list of files. The signature should change if the attachment list has changed |
93 | // (become dirty). | |
94 | getAutosaveSignature: function getAutosaveSignature() { | |
95 | var sig = []; | |
96 | // Attachments have a special lifecycle, and attachments.queue is not properly serializable, so | |
97 | // it takes some special effort to figure out a suitable signature. Issues which can cause gratuitous saving: | |
98 | // - Files move from this.uploader.queue to this.files after upload. | |
99 | // - File names are munged after upload. | |
100 | // - Deletes are performed immediately (outside the save process). | |
101 | angular.forEach(this.files, function(item) { | |
102 | sig.push({f: item.name.replace(/[^a-zA0-Z0-9\.]/, '_'), d: item.description}); | |
103 | }); | |
104 | angular.forEach(this.uploader.queue, function(item) { | |
105 | sig.push({f: item.file.name.replace(/[^a-zA0-Z0-9\.]/, '_'), d: item.crmData.description}); | |
106 | }); | |
107 | angular.forEach(this.trash, function(item) { | |
108 | sig.push({f: item.name.replace(/[^a-zA0-Z0-9\.]/, '_'), d: item.description}); | |
109 | }); | |
110 | return _.sortBy(sig, 'name'); | |
111 | }, | |
f44610a1 TO |
112 | // @param Object file APIv3 attachment record (e.g. id, entity_table, entity_id, description) |
113 | deleteFile: function deleteFile(file) { | |
114 | var crmAttachments = this; | |
115 | ||
116 | var idx = _.indexOf(this.files, file); | |
117 | if (idx != -1) { | |
118 | this.files.splice(idx, 1); | |
119 | } | |
120 | ||
95fd5cb2 TO |
121 | this.trash.push(file); |
122 | ||
f44610a1 TO |
123 | if (file.id) { |
124 | var p = crmApi('Attachment', 'delete', {id: file.id}).then( | |
125 | function () { // success | |
126 | }, | |
127 | function (response) { // error; restore the file | |
128 | var msg = angular.isObject(response) ? response.error_message : ''; | |
129 | CRM.alert(msg, ts('Deletion failed')); | |
130 | crmAttachments.files.push(file); | |
95fd5cb2 TO |
131 | |
132 | var trashIdx = _.indexOf(crmAttachments.trash, file); | |
133 | if (trashIdx != -1) { | |
134 | crmAttachments.trash.splice(trashIdx, 1); | |
135 | } | |
f44610a1 TO |
136 | } |
137 | ); | |
138 | return crmStatus({start: ts('Deleting...'), success: ts('Deleted')}, p); | |
139 | } | |
140 | } | |
141 | }); | |
142 | ||
143 | return CrmAttachments; | |
144 | }); | |
145 | ||
146 | // example: | |
ab0a4aec TO |
147 | // $scope.myAttachments = new CrmAttachments({entity_table: 'civicrm_mailing', entity_id: 123}); |
148 | // <div crm-attachments="myAttachments"/> | |
149 | angular.module('crmAttachment').directive('crmAttachments', function ($parse, $timeout) { | |
150 | return { | |
151 | scope: { | |
152 | crmAttachments: '@' | |
153 | }, | |
154 | template: '<div ng-if="ready" ng-include="inclUrl"></div>', | |
155 | link: function (scope, elm, attr) { | |
156 | var model = $parse(attr.crmAttachments); | |
157 | scope.att = model(scope.$parent); | |
158 | scope.ts = CRM.ts(null); | |
080eefc3 JP |
159 | CRM.api4('Setting', 'get', { |
160 | select: ["maxFileSize"] | |
161 | }).then(function(settings) { | |
162 | scope.max_size = settings[0].value; | |
163 | }); | |
ef5d18a1 | 164 | scope.inclUrl = '~/crmAttachment/attachments.html'; |
ab0a4aec TO |
165 | |
166 | // delay rendering of child tree until after model has been populated | |
167 | scope.ready = true; | |
168 | } | |
f44610a1 TO |
169 | }; |
170 | }); | |
171 | ||
172 | })(angular, CRM.$, CRM._); |