Merge pull request #5473 from aydun/CRM-16160
[civicrm-core.git] / ang / crmAttachment.js
1 /// crmFile: Manage file attachments
2 (function (angular, $, _) {
3
4 angular.module('crmAttachment', ['angularFileUpload']);
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) {
8 // @param target an Object(entity_table:'',entity_id:'') or function which generates an object
9 function CrmAttachments(target) {
10 var crmAttachments = this;
11 this._target = target;
12 this.files = [];
13 this.trash = [];
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 () {
36 return (angular.isFunction(this._target) ? this._target() : this._target);
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) {
77 item.formData = [_.extend({crm_attachment_token: CRM.crmAttachment.token}, target, item.crmData)];
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 },
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 },
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
121 this.trash.push(file);
122
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);
131
132 var trashIdx = _.indexOf(crmAttachments.trash, file);
133 if (trashIdx != -1) {
134 crmAttachments.trash.splice(trashIdx, 1);
135 }
136 }
137 );
138 return crmStatus({start: ts('Deleting...'), success: ts('Deleted')}, p);
139 }
140 }
141 });
142
143 return CrmAttachments;
144 });
145
146 // example:
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);
159 scope.inclUrl = '~/crmAttachment/attachments.html';
160
161 // delay rendering of child tree until after model has been populated
162 scope.ready = true;
163 }
164 };
165 });
166
167 })(angular, CRM.$, CRM._);