├── .gitignore
├── .travis.yml
├── .versions
├── LICENSE
├── README.md
├── cfs-autoform-hooks.js
├── cfs-autoform-util.js
├── cfs-autoform.css
├── cfs-autoform.html
├── cfs-autoform.js
└── package.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .build*
3 | .npm*
4 | .meteor*
5 | smart.lock
6 | /packages/
7 | .idea*
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.10"
4 | before_install:
5 | - "curl -L http://git.io/ejPSng | /bin/sh"
--------------------------------------------------------------------------------
/.versions:
--------------------------------------------------------------------------------
1 | aldeed:autoform@4.0.0
2 | aldeed:simple-schema@1.1.0
3 | base64@1.0.3
4 | binary-heap@1.0.3
5 | blaze@2.1.2
6 | blaze-tools@1.0.3
7 | boilerplate-generator@1.0.3
8 | callback-hook@1.0.3
9 | cfs:access-point@0.1.46
10 | cfs:autoform@2.2.1
11 | cfs:base-package@0.0.29
12 | cfs:collection@0.5.5
13 | cfs:collection-filters@0.2.4
14 | cfs:data-man@0.0.6
15 | cfs:file@0.1.17
16 | cfs:http-methods@0.0.27
17 | cfs:http-publish@0.0.13
18 | cfs:power-queue@0.9.11
19 | cfs:reactive-list@0.0.9
20 | cfs:reactive-property@0.0.4
21 | cfs:standard-packages@0.5.7
22 | cfs:storage-adapter@0.2.1
23 | cfs:tempstore@0.1.4
24 | cfs:upload-http@0.0.20
25 | cfs:worker@0.1.4
26 | check@1.0.5
27 | ddp@1.1.0
28 | deps@1.0.7
29 | ejson@1.0.6
30 | geojson-utils@1.0.3
31 | html-tools@1.0.4
32 | htmljs@1.0.4
33 | http@1.1.0
34 | id-map@1.0.3
35 | jquery@1.11.3_2
36 | json@1.0.3
37 | livedata@1.0.13
38 | logging@1.0.7
39 | meteor@1.1.6
40 | minifiers@1.1.5
41 | minimongo@1.0.8
42 | mongo@1.1.0
43 | mongo-livedata@1.0.8
44 | mrt:moment@2.6.0
45 | observe-sequence@1.0.6
46 | ordered-dict@1.0.3
47 | raix:eventemitter@0.1.1
48 | raix:ui-dropped-event@0.0.7
49 | random@1.0.3
50 | reactive-var@1.0.5
51 | retry@1.0.3
52 | routepolicy@1.0.5
53 | spacebars@1.0.6
54 | spacebars-compiler@1.0.6
55 | templating@1.1.1
56 | tracker@1.0.7
57 | ui@1.0.6
58 | underscore@1.0.3
59 | url@1.0.4
60 | webapp@1.2.0
61 | webapp-hashing@1.0.3
62 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-2015 Eric Dobbertin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | cfs:autoform
2 | =========================
3 |
4 | A smart package for Meteor that provides a file UI component for use within an autoform. The UI component supports clicking to select files or dropping them. Once the full form is valid, the files are uploaded using CollectionFS. The form document is inserted only if the uploads are all successful. If the form document fails to insert on the server, the uploaded files are removed.
5 |
6 | ## Installation
7 |
8 | In a Meteor app directory, enter:
9 |
10 | ```
11 | $ meteor add cfs:autoform
12 | ```
13 |
14 | ## Prerequisites
15 |
16 | Requires Meteor 0.9.3+
17 |
18 | Add `aldeed:collection2`, `aldeed:autoform`, and `cfs:standard-packages` packages to your app. Also add any other CFS packages you need, particularly a storage adapter package such as `cfs:gridfs`.
19 |
20 | ## Example
21 |
22 | *Assumption: autopublish, insecure, and cfs:gridfs packages are in use*
23 |
24 | *common.js*
25 |
26 | ```js
27 | Docs = new Mongo.Collection("docs");
28 | Docs.attachSchema(new SimpleSchema({
29 | name: {
30 | type: String
31 | },
32 | fileId: {
33 | type: String
34 | }
35 | }));
36 |
37 | Files = new FS.Collection("files", {
38 | stores: [new FS.Store.GridFS("filesStore")]
39 | });
40 |
41 | Files.allow({
42 | download: function () {
43 | return true;
44 | },
45 | fetch: null
46 | });
47 | ```
48 |
49 | *html:*
50 |
51 | ```html
52 |
53 | {{#autoForm id="insertForm" type="insert" collection="Docs"}}
54 | {{> afQuickField name="name"}}
55 | {{> afQuickField name="fileId" type="cfs-file" collection="files"}}
56 |
57 | {{/autoForm}}
58 |
59 | ```
60 |
61 | ## Schema Example
62 |
63 | If you are not able to change the `afQuickField` attributes directly or you want to use a `quickForm`, you can put the attributes in the schema instead:
64 |
65 | ```js
66 | Docs.attachSchema(new SimpleSchema({
67 | name: {
68 | type: String
69 | },
70 | fileId: {
71 | type: String,
72 | autoform: {
73 | afFieldInput: {
74 | type: "cfs-file",
75 | collection: "files"
76 | }
77 | }
78 | }
79 | }));
80 | ```
81 |
82 | ## Server Method Example
83 |
84 | In addition to the above, on a server method we need to reference the schema later.
85 |
86 | ```js
87 | mySchema = new SimpleSchema({
88 | name: {
89 | type: String
90 | },
91 | fileId: {
92 | type: String,
93 | autoform: {
94 | afFieldInput: {
95 | type: "cfs-file",
96 | collection: "files"
97 | }
98 | }
99 | }
100 | }));
101 | Docs.attachSchema(mySchema);
102 | ```
103 |
104 | Change the html to reflect the server method type:
105 | ```html
106 |
107 | {{#autoForm id="insertForm" type="method" collection="Docs" meteormethod="myServerMethod"}}
108 | {{> afQuickField name="name"}}
109 | {{> afQuickField name="fileId" type="cfs-file" collection="files"}}
110 |
111 | {{/autoForm}}
112 |
113 | ```
114 |
115 | Then manually add the required AutoForm hooks to the form:
116 | ```js
117 | AutoForm.addHooks(
118 | ["insertForm"],
119 | {
120 | before : {
121 | method: CfsAutoForm.Hooks.beforeInsert
122 | },
123 | after : {
124 | method: CfsAutoForm.Hooks.afterInsert
125 | }
126 | }
127 | );
128 | ```
129 |
130 | And on the server-sde:
131 | ```js
132 | Meteor.methods({
133 | myServerMethod: function(doc) {
134 | try {
135 | check(doc, mySchema);
136 | mySchema.clean(doc);
137 | }catch(e){
138 | throw new Meteor.Error(e);
139 | }
140 |
141 | //do some stuff here and throw a new Meteor.Error if there is a problem
142 | }});
143 | ```
144 | Please note that myServerMethod, insertForm, and mySchema can (and should) be changed to whatever you like.
145 |
146 | ## Notes
147 |
148 | * Only insert and server method forms (`type="insert"` or `type="method"`) are supported
149 | * Use `type="cfs-file"` to allow one file to be selected or dropped. Use `type="cfs-files"` to allow multiple files to be selected or dropped.
150 | * The `collection` attribute must be the same as the first argument you passed to the FS.Collection constructor.
151 | * Files are uploaded only after you click submit and the form is valid.
152 | * If file upload fails, the form document is not inserted.
153 | * If one file fails to upload, any other files from that form that did upload are deleted.
154 | * If the form document insert fails on the server, the associated files are automatically deleted as part of the latency compensation rollback.
155 |
156 | ## TODO
157 |
158 | * Insert FS.File itself when using cfs-ejson-file package.
159 | * Display customizable progress bar template in place of each field while uploading.
160 | * Update forms
161 |
162 | [](https://gratipay.com/aldeed/)
163 |
--------------------------------------------------------------------------------
/cfs-autoform-hooks.js:
--------------------------------------------------------------------------------
1 | Hooks = {
2 | beforeInsert: function (doc) {
3 | var self = this, template = this.template;
4 | if (!AutoForm.validateForm(this.formId)) {
5 | return false;
6 | }
7 |
8 | // Loop through all hidden file inputs in the form.
9 | var totalFiles = 0;
10 | var arrayFields = {};
11 | template.$('.cfsaf-hidden').each(function () {
12 | var elem = $(this);
13 |
14 | // Get schema key that this input is for
15 | var key = elem.attr("data-schema-key");
16 |
17 | // no matter what, we want to delete the dummyId value
18 | //delete doc[key];
19 | CfsAutoForm.Util.deepDelete(doc,key);
20 |
21 | // Get list of files that were attached for this key
22 | var fileList = elem.data("cfsaf_files");
23 |
24 | // If we have some attached files
25 | if (fileList) {
26 | // add all files to total count
27 | totalFiles += fileList.length;
28 | }
29 |
30 | // Otherwise it might be a multiple files field
31 | else {
32 | var fileListList = elem.data("cfsaf_files_multi");
33 | if (fileListList) {
34 | // make a note that it's an array field
35 | arrayFields[key] = true;
36 | // add all files to total count
37 | _.each(fileListList, function (fileList) {
38 | totalFiles += fileList.length;
39 | });
40 | // prep the array
41 | doc[key] = [];
42 | }
43 | }
44 | });
45 |
46 | // If no files were attached anywhere on the form, we're done.
47 | // We pass back the doc synchronously
48 | if (totalFiles === 0) {
49 | return doc;
50 | }
51 |
52 | // Create the callback that will be called either
53 | // upon file insert error or upon each file being uploaded.
54 | var doneFiles = 0;
55 | var failedFiles = 0;
56 | function cb(error, fileObj, key) {
57 | // Increment the done files count
58 | doneFiles++;
59 |
60 | // Increment the failed files count if it failed
61 | if (error) {
62 | failedFiles++;
63 | }
64 |
65 | // If it didn't fail, set the new ID as the property value in the doc,
66 | // or push it into the array of IDs if it's a multiple files field.
67 | else {
68 | if (arrayFields[key]) {
69 | CfsAutoForm.Util.deepFind(doc,key)[key].push(fileObj._id);
70 | } else {
71 | //doc[key] = fileObj._id;
72 | CfsAutoForm.Util.deepSet(doc,key,fileObj._id);
73 | }
74 | }
75 |
76 | // If this is the last file to be processed, pass execution back to autoform
77 | if (doneFiles === totalFiles) {
78 | // If any files failed
79 | if (failedFiles > 0) {
80 | // delete all that succeeded
81 | CfsAutoForm.deleteUploadedFiles(template);
82 | // pass back to autoform code, telling it we failed
83 | self.result(false);
84 | }
85 | // Otherwise if all files succeeded
86 | else {
87 | // pass updated doc back to autoform code, telling it we succeeded
88 | self.result(doc);
89 | }
90 | }
91 | }
92 |
93 | // Loop through all hidden file fields, inserting
94 | // and uploading all files that have been attached to them.
95 | template.$('.cfsaf-hidden').each(function () {
96 | var elem = $(this);
97 |
98 | // Get schema key that this input is for
99 | var key = elem.attr("data-schema-key");
100 |
101 | // Get the FS.Collection instance
102 | var fsCollectionName = elem.attr("data-cfs-collection");
103 | var fsCollection = FS._collections[fsCollectionName];
104 |
105 | // Loop through all files that were attached to this field
106 | function loopFileList(fileList) {
107 | _.each(fileList, function (file) {
108 | // Create the FS.File instance
109 | var fileObj = new FS.File(file);
110 |
111 | // Listen for the "uploaded" event on this file, so that we
112 | // can call our callback. We want to wait until uploaded rather
113 | // than just inserted. XXX Maybe should wait for stored?
114 | fileObj.once("uploaded", function () {
115 | // track successful uploads so we can delete them if any
116 | // of the other files fail to upload
117 | var uploadedFiles = elem.data("cfsaf_uploaded-files") || [];
118 | uploadedFiles.push(fileObj);
119 | elem.data("cfsaf_uploaded-files", uploadedFiles);
120 | // call callback
121 | cb(null, fileObj, key);
122 | });
123 |
124 | // Insert the FS.File instance into the FS.Collection
125 | fsCollection.insert(fileObj, function (error, fileObj) {
126 | // call callback if insert/upload failed
127 | if (error) {
128 | cb(error, fileObj, key);
129 | }
130 | // TODO progress bar during uploads
131 | });
132 | });
133 | }
134 |
135 | // single fields first
136 | loopFileList(elem.data("cfsaf_files"));
137 | // then multiple fields
138 | _.each(elem.data("cfsaf_files_multi"), function (fileList) {
139 | loopFileList(fileList);
140 | });
141 | });
142 | },
143 | afterInsert: function (error) {
144 | var template = this.template, elems = template.$('.cfsaf-hidden');
145 | if (error) {
146 | CfsAutoForm.deleteUploadedFiles(template);
147 | if (FS.debug || AutoForm._debug)
148 | console.log("There was an error inserting so all uploaded files were removed.", error);
149 | } else {
150 | // cleanup files data
151 | elems.removeData("cfsaf_files");
152 | elems.removeData("cfsaf_files_multi");
153 | }
154 | // cleanup uploaded files data
155 | elems.removeData("cfsaf_uploaded-files");
156 | },
157 | beforeUpdate: function(doc){
158 | var self = this, template = this.template;
159 | if (!AutoForm.validateForm(this.formId)) {
160 | return false;
161 | }
162 | // Loop through all hidden file inputs in the form.
163 | var totalFiles = 0;
164 | var arrayFields = {};
165 | template.$('.cfsaf-hidden').each(function () {
166 | var elem = $(this);
167 |
168 | // Get schema key that this input is for
169 | var key = elem.attr("data-schema-key");
170 |
171 | //Maintain current key
172 | doc.$set[key] = self.currentDoc[key];
173 |
174 | // Get list of files that were attached for this key
175 | var fileList = elem.data("cfsaf_files");
176 |
177 | // If we have some attached files
178 | if (fileList) {
179 | // add all files to total count
180 | totalFiles += fileList.length;
181 | //we delete the id only if we uploaded another file
182 | //delete doc[key];
183 | CfsAutoForm.Util.deepDelete(doc,key);
184 | }
185 |
186 | // Otherwise it might be a multiple files field
187 | else {
188 | var fileListList = elem.data("cfsaf_files_multi");
189 | if (fileListList) {
190 | //we delete the id only if we uploaded another file
191 | //delete doc[key];
192 | CfsAutoForm.Util.deepDelete(doc,key);
193 | // make a note that it's an array field
194 | arrayFields[key] = true;
195 | // add all files to total count
196 | _.each(fileListList, function (fileList) {
197 | totalFiles += fileList.length;
198 | });
199 | // prep the array
200 | doc[key] = [];
201 | }
202 | }
203 | });
204 |
205 | // If no files were attached anywhere on the form, we're done.
206 | // We pass back the doc synchronously
207 | if (totalFiles === 0) {
208 | return doc;
209 | }
210 |
211 | // Create the callback that will be called either
212 | // upon file insert error or upon each file being uploaded.
213 | var doneFiles = 0;
214 | var failedFiles = 0;
215 | function cb(error, fileObj, key) {
216 | // Increment the done files count
217 | doneFiles++;
218 |
219 | // Increment the failed files count if it failed
220 | if (error) {
221 | failedFiles++;
222 | }
223 |
224 | // If it didn't fail, set the new ID as the property value in the doc,
225 | // or push it into the array of IDs if it's a multiple files field.
226 | else {
227 | if (arrayFields[key]) {
228 | CfsAutoForm.Util.deepFind(doc.$set,key).push(fileObj._id);
229 | } else {
230 | //doc[key] = fileObj._id;
231 | CfsAutoForm.Util.deepSet(doc.$set,key,fileObj._id);
232 | }
233 | }
234 |
235 | // If this is the last file to be processed, pass execution back to autoform
236 | if (doneFiles === totalFiles) {
237 | // If any files failed
238 | if (failedFiles > 0) {
239 | // delete all that succeeded
240 | CfsAutoForm.deleteUploadedFiles(template);
241 | // pass back to autoform code, telling it we failed
242 | self.result(false);
243 | }
244 | // Otherwise if all files succeeded
245 | else {
246 | // pass updated doc back to autoform code, telling it we succeeded
247 | console.log(doc);
248 | self.result(doc);
249 | }
250 | }
251 | }
252 |
253 | // Loop through all hidden file fields, inserting
254 | // and uploading all files that have been attached to them.
255 | template.$('.cfsaf-hidden').each(function () {
256 | var elem = $(this);
257 |
258 | // Get schema key that this input is for
259 | var key = elem.attr("data-schema-key");
260 |
261 | // Get the FS.Collection instance
262 | var fsCollectionName = elem.attr("data-cfs-collection");
263 | var fsCollection = FS._collections[fsCollectionName];
264 |
265 | // Loop through all files that were attached to this field
266 | function loopFileList(fileList) {
267 | _.each(fileList, function (file) {
268 | // Create the FS.File instance
269 | var fileObj = new FS.File(file);
270 |
271 | // Listen for the "uploaded" event on this file, so that we
272 | // can call our callback. We want to wait until uploaded rather
273 | // than just inserted. XXX Maybe should wait for stored?
274 | fileObj.once("uploaded", function () {
275 | // track successful uploads so we can delete them if any
276 | // of the other files fail to upload
277 | var uploadedFiles = elem.data("cfsaf_uploaded-files") || [];
278 | uploadedFiles.push(fileObj);
279 | elem.data("cfsaf_uploaded-files", uploadedFiles);
280 | // call callback
281 | cb(null, fileObj, key);
282 | });
283 |
284 | // Insert the FS.File instance into the FS.Collection
285 | fsCollection.insert(fileObj, function (error, fileObj) {
286 | // call callback if insert/upload failed
287 | if (error) {
288 | cb(error, fileObj, key);
289 | }
290 | // TODO progress bar during uploads
291 | });
292 | });
293 | }
294 |
295 | // single fields first
296 | loopFileList(elem.data("cfsaf_files"));
297 | // then multiple fields
298 | _.each(elem.data("cfsaf_files_multi"), function (fileList) {
299 | loopFileList(fileList);
300 | });
301 | });
302 | },
303 | afterUpdate: function(error, result){
304 | var template = this.template;
305 | var elems = template.$('.cfsaf-hidden');
306 | if (error) {
307 | CfsAutoForm.deleteUploadedFiles(template);
308 | if (FS.debug || AutoForm._debug)
309 | console.log("There was an error inserting so all uploaded files were removed.", error);
310 | } else {
311 | // cleanup files data
312 | elems.removeData("cfsaf_files");
313 | elems.removeData("cfsaf_files_multi");
314 | }
315 | // cleanup uploaded files data
316 | elems.removeData("cfsaf_uploaded-files");
317 | }
318 | };
319 |
--------------------------------------------------------------------------------
/cfs-autoform-util.js:
--------------------------------------------------------------------------------
1 | Util = {
2 | //delete prop from obj
3 | //prop can be something like "obj.3.badprop
4 | deepDelete: function(obj, prop){
5 | return CfsAutoForm.Util.deepDo(obj, prop, function(obj, prop){
6 | delete obj[prop];
7 | });
8 | },
9 | deepSet: function(obj, prop, value){
10 | return CfsAutoForm.Util.deepDo(obj, prop, function(obj, prop){
11 | obj[prop] = value;
12 | });
13 | },
14 | //returns the object that CONTAINS the last property
15 | deepFind: function(obj, path){
16 | path = path.split('.');
17 | for (i = 0; i < path.length - 1; i++)
18 | obj = obj[path[i]];
19 |
20 | return obj;
21 | },
22 | //executes closure(obj, prop) where prop might be a string of properties and array indices
23 | deepDo: function(obj, path, closure){
24 | path = path.split('.');
25 | for (i = 0; i < path.length - 1; i++)
26 | obj = obj[path[i]];
27 |
28 | closure.apply(this, [obj, path[i]]);
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/cfs-autoform.css:
--------------------------------------------------------------------------------
1 | /* CSS declarations go here */
2 | .cfsaf-hidden {
3 | display: none !important;
4 | }
5 |
6 | .cfsaf-field {
7 | cursor: pointer !important;
8 | }
--------------------------------------------------------------------------------
/cfs-autoform.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/cfs-autoform.js:
--------------------------------------------------------------------------------
1 | CfsAutoForm = CfsAutoForm || {};
2 | CfsAutoForm.Util = Util;
3 | CfsAutoForm.Hooks = Hooks;
4 |
5 | CfsAutoForm.deleteUploadedFiles = function(template) {
6 | template.$('.cfsaf-hidden').each(function () {
7 | var uploadedFiles = $(this).data("cfsaf_uploaded-files") || [];
8 | _.each(uploadedFiles, function ( f ) {
9 | f.remove();
10 | });
11 | });
12 | };
13 |
14 |
15 | if (Meteor.isClient) {
16 | // Adds a custom "cfs-file" input type that AutoForm will recognize
17 | AutoForm.addInputType("cfs-file", {
18 | template:"cfsFileField",
19 | valueOut: function () {
20 | return "dummyId";
21 | },
22 | contextAdjust: function (context) {
23 | context.atts.placeholder = context.atts.placeholder || "Click to upload a file or drop it here";
24 | context.atts["class"] = (context.atts["class"] || "") + " cfsaf-field";
25 | return context;
26 | }
27 | });
28 |
29 | // Adds a custom "cfs-files" input type that AutoForm will recognize
30 | AutoForm.addInputType("cfs-files", {
31 | template:"cfsFilesField",
32 | valueOut: function () {
33 | return ["dummyId"];
34 | },
35 | contextAdjust: function (context) {
36 | context.atts.placeholder = context.atts.placeholder || "Click to upload files or drop them here";
37 | context.atts["class"] = (context.atts["class"] || "") + " cfsaf-field";
38 | return context;
39 | }
40 | });
41 |
42 | function textInputAtts() {
43 | var atts = _.clone(this.atts);
44 | delete atts.collection;
45 | // we want the schema key tied to the hidden file field only
46 | delete atts["data-schema-key"];
47 | atts["class"] = (atts["class"] || "") + " form-control";
48 | return atts;
49 | }
50 |
51 | function fileInputAtts() {
52 | return {'data-schema-key': this.atts["data-schema-key"]};
53 | }
54 |
55 | Template.cfsFileField_bootstrap3.helpers({
56 | textInputAtts: textInputAtts,
57 | fileInputAtts: fileInputAtts
58 | });
59 |
60 | Template.cfsFilesField_bootstrap3.helpers({
61 | textInputAtts: textInputAtts,
62 | fileInputAtts: fileInputAtts
63 | });
64 |
65 | var hookTracking = {};
66 | Template.cfsFileField_bootstrap3.rendered =
67 | Template.cfsFilesField_bootstrap3.rendered = function () {
68 | var formId;
69 |
70 | // backwards compatibility with pre 5.0 autoform
71 | if (typeof AutoForm.find === 'function') {
72 | formId = AutoForm.find().formId;
73 | } else {
74 | formId = AutoForm.getFormId();
75 | }
76 |
77 | // By adding hooks dynamically on render, hopefully any user hooks will have
78 | // been added before so we won't disrupt expected behavior.
79 | if (!hookTracking[formId]) {
80 | hookTracking[formId] = true;
81 | addAFHook(formId);
82 | }
83 | };
84 |
85 | var singleHandler = function (event, template) {
86 | var fileList = [];
87 | FS.Utility.eachFile(event, function (f) {
88 | fileList.push(f.name);
89 | });
90 | template.$('.cfsaf-field').val(fileList.join(", "));
91 | var fileList = event.originalEvent.dataTransfer ? event.originalEvent.dataTransfer.files : event.currentTarget.files;
92 | // Store the FileList on the file input for later
93 | template.$('.cfsaf-hidden').data("cfsaf_files", fileList);
94 | };
95 |
96 | Template.cfsFileField_bootstrap3.events({
97 | 'click .cfsaf-field': function (event, template) {
98 | template.$('.cfsaf-hidden').click();
99 | },
100 | 'change .cfsaf-hidden': singleHandler,
101 | 'dropped .cfsaf-field': singleHandler
102 | });
103 |
104 | var multipleHandler = function (event, template) {
105 | // Get the FileList object from the event object
106 | var fileList = event.originalEvent.dataTransfer ? event.originalEvent.dataTransfer.files : event.currentTarget.files;
107 |
108 | // Store the FileList on the file input for later. We store an array of
109 | // separate FileList objects. Browsers don't let you add/remove items from
110 | // an existing FileList.
111 | var fileListList = template.$('.cfsaf-hidden').data("cfsaf_files_multi") || [];
112 | fileListList.push(fileList);
113 | template.$('.cfsaf-hidden').data("cfsaf_files_multi", fileListList);
114 |
115 | // Get full list of files to display in the visible, read-only field
116 | var fullNameList = [];
117 | _.each(fileListList, function (fileList) {
118 | _.each(fileList, function (f) {
119 | fullNameList.push(f.name);
120 | });
121 | });
122 | template.$('.cfsaf-field').val(fullNameList.join(", "));
123 | };
124 |
125 | Template.cfsFilesField_bootstrap3.events({
126 | 'click .cfsaf-field': function (event, template) {
127 | template.$('.cfsaf-hidden').click();
128 | },
129 | 'change .cfsaf-hidden': multipleHandler,
130 | 'dropped .cfsaf-field': multipleHandler
131 | });
132 |
133 | function addAFHook(formId) {
134 | AutoForm.addHooks(formId, {
135 | before: {
136 | // We add a before.insert hook to upload all the files in the form.
137 | // This hook doesn't allow the form to continue submitting until
138 | // all the files are successfully uploaded.
139 | insert: CfsAutoForm.Hooks.beforeInsert,
140 | update: CfsAutoForm.Hooks.beforeUpdate
141 | },
142 | after: {
143 | // We add an after.insert hook to delete uploaded files if the doc insert fails.
144 | insert: CfsAutoForm.Hooks.afterInsert,
145 | update: CfsAutoForm.Hooks.afterUpdate
146 | }
147 | });
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/package.js:
--------------------------------------------------------------------------------
1 | Package.describe({
2 | name: "cfs:autoform",
3 | version: "2.2.1",
4 | summary: "Upload files as part of autoform submission",
5 | git: "https://github.com/aldeed/meteor-cfs-autoform.git"
6 | });
7 |
8 | Package.onUse(function(api) {
9 | api.use('underscore@1.0.1', 'client');
10 | api.use('templating@1.0.9', 'client');
11 |
12 | api.use('aldeed:autoform@4.0.0 || 5.0.0');
13 | api.use('cfs:standard-packages@0.0.2', ['client', 'server'], {weak: true});
14 | api.use('raix:ui-dropped-event@0.0.7', 'client');
15 |
16 | // ensure standard packages are available globally incase the user didn't `meteor add cfs:standard-packages`
17 | api.imply('cfs:standard-packages');
18 |
19 | api.export('CfsAutoForm', 'client');
20 |
21 | api.add_files([
22 | 'cfs-autoform.html',
23 | 'cfs-autoform-hooks.js',
24 | 'cfs-autoform-util.js',
25 | 'cfs-autoform.js',
26 | 'cfs-autoform.css'
27 | ], 'client');
28 | });
29 |
--------------------------------------------------------------------------------