Revision b70f6510

b/snf-cyclades-app/synnefo/ui/static/snf/js/lib/filereader.js
1
/*!
2
    FileReader.js - v0.9
3
    A lightweight wrapper for common FileReader usage.
4
    Copyright 2012 Brian Grinstead - MIT License.
5
    See http://github.com/bgrins/filereader.js for documentation.
6
*/
7

  
8
(function(window, document) {
9

  
10
    var FileReader = window.FileReader;
11
    var FileReaderSyncSupport = false;
12
    var workerScript = "self.addEventListener('message', function(e) { var data=e.data; try { var reader = new FileReaderSync; postMessage({ result: reader[data.readAs](data.file), extra: data.extra, file: data.file})} catch(e){ postMessage({ result:'error', extra:data.extra, file:data.file}); } }, false);";
13
    var syncDetectionScript = "self.addEventListener('message', function(e) { postMessage(!!FileReaderSync); }, false);";
14
    var fileReaderEvents = ['loadstart', 'progress', 'load', 'abort', 'error', 'loadend'];
15

  
16
    var FileReaderJS = window.FileReaderJS = {
17
        enabled: false,
18
        setupInput: setupInput,
19
        setupDrop: setupDrop,
20
        setupClipboard: setupClipboard,
21
        sync: false,
22
        output: [],
23
        opts: {
24
            dragClass: "drag",
25
            accept: false,
26
            readAsDefault: 'BinaryString',
27
            readAsMap: {
28
                'image/*': 'DataURL',
29
                'text/*' : 'Text'
30
            },
31
            on: {
32
                loadstart: noop,
33
                progress: noop,
34
                load: noop,
35
                abort: noop,
36
                error: noop,
37
                loadend: noop,
38
                skip: noop,
39
                groupstart: noop,
40
                groupend: noop,
41
                beforestart: noop
42
            }
43
        }
44
    };
45

  
46
    // Setup jQuery plugin (if available)
47
    if (typeof(jQuery) !== "undefined") {
48
        jQuery.fn.fileReaderJS = function(opts) {
49
            return this.each(function() {
50
                if (jQuery(this).is("input")) {
51
                    setupInput(this, opts);
52
                }
53
                else {
54
                    setupDrop(this, opts);
55
                }
56
            });
57
        };
58

  
59
        jQuery.fn.fileClipboard = function(opts) {
60
            return this.each(function() {
61
                setupClipboard(this, opts);
62
            });
63
        };
64
    }
65

  
66
    // Not all browsers support the FileReader interface.  Return with the enabled bit = false.
67
    if (!FileReader) {
68
        return;
69
    }
70

  
71
    // WorkerHelper is a little wrapper for generating web workers from strings
72
    var WorkerHelper = (function() {
73

  
74
        var URL = window.URL || window.webkitURL;
75
        var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
76

  
77
        // May need to get just the URL in case it is needed for things beyond just creating a worker.
78
        function getURL (script) {
79
            if (window.Worker && BlobBuilder && URL) {
80
                var bb = new BlobBuilder();
81
                bb.append(script);
82
                return URL.createObjectURL(bb.getBlob());
83
            }
84

  
85
            return null;
86
        }
87

  
88
        // If there is no need to revoke a URL later, or do anything fancy then just return the worker.
89
        function getWorker (script, onmessage) {
90
            var url = getURL(script);
91
            if (url) {
92
                var worker = new Worker(url);
93
                worker.onmessage = onmessage;
94
                return worker;
95
            }
96

  
97
            return null;
98
        }
99

  
100
        return {
101
            getURL: getURL,
102
            getWorker: getWorker
103
        };
104

  
105
    })();
106

  
107
    // setupClipboard: bind to clipboard events (intended for document.body)
108
    function setupClipboard(element, opts) {
109

  
110
        if (!FileReaderJS.enabled) {
111
            return;
112
        }
113
        var instanceOptions = extend(extend({}, FileReaderJS.opts), opts);
114

  
115
        element.addEventListener("paste", onpaste, false);
116

  
117
        function onpaste(e) {
118
            var files = [];
119
            var clipboardData = e.clipboardData || {};
120
            var items = clipboardData.items || [];
121

  
122
            for (var i = 0; i < items.length; i++) {
123
                var file = items[i].getAsFile();
124

  
125
                if (file) {
126

  
127
                    // Create a fake file name for images from clipboard, since this data doesn't get sent
128
                    var matches = new RegExp("/\(.*\)").exec(file.type);
129
                    if (!file.name && matches) {
130
                        var extension = matches[1];
131
                        file.name = "clipboard" + i + "." + extension;
132
                    }
133

  
134
                    files.push(file);
135
                }
136
            }
137

  
138
            if (files.length) {
139
                processFileList(e, files, instanceOptions);
140
                e.preventDefault();
141
                e.stopPropagation();
142
            }
143
        }
144
    }
145

  
146
    // setupInput: bind the 'change' event to an input[type=file]
147
    function setupInput(input, opts) {
148

  
149
        if (!FileReaderJS.enabled) {
150
            return;
151
        }
152
        var instanceOptions = extend(extend({}, FileReaderJS.opts), opts);
153

  
154
        input.addEventListener("change", inputChange, false);
155
        input.addEventListener("drop", inputDrop, false);
156

  
157
        function inputChange(e) {
158
            processFileList(e, input.files, instanceOptions);
159
        }
160

  
161
        function inputDrop(e) {
162
            e.stopPropagation();
163
            e.preventDefault();
164
            processFileList(e, e.dataTransfer.files, instanceOptions);
165
        }
166
    }
167

  
168
    // setupDrop: bind the 'drop' event for a DOM element
169
    function setupDrop(dropbox, opts) {
170

  
171
        if (!FileReaderJS.enabled) {
172
            return;
173
        }
174
        var instanceOptions = extend(extend({}, FileReaderJS.opts), opts);
175
        var dragClass = instanceOptions.dragClass;
176
        var initializedOnBody = false;
177

  
178
        // Bind drag events to the dropbox to add the class while dragging, and accept the drop data transfer.
179
        dropbox.addEventListener("dragenter", onlyWithFiles(dragenter), false);
180
        dropbox.addEventListener("dragleave", onlyWithFiles(dragleave), false);
181
        dropbox.addEventListener("dragover", onlyWithFiles(dragover), false);
182
        dropbox.addEventListener("drop", onlyWithFiles(drop), false);
183

  
184
        // Bind to body to prevent the dropbox events from firing when it was initialized on the page.
185
        document.body.addEventListener("dragstart", bodydragstart, true);
186
        document.body.addEventListener("dragend", bodydragend, true);
187
        document.body.addEventListener("drop", bodydrop, false);
188

  
189
        function bodydragend(e) {
190
            initializedOnBody = false;
191
        }
192

  
193
        function bodydragstart(e) {
194
            initializedOnBody = true;
195
        }
196

  
197
        function bodydrop(e) {
198
            if (e.dataTransfer.files && e.dataTransfer.files.length ){
199
                e.stopPropagation();
200
                e.preventDefault();
201
            }
202
        }
203

  
204
        function onlyWithFiles(fn) {
205
            return function() {
206
                if (!initializedOnBody) {
207
                    fn.apply(this, arguments);
208
                }
209
            };
210
        }
211

  
212
        function drop(e) {
213
            e.stopPropagation();
214
            e.preventDefault();
215
            if (dragClass) {
216
                removeClass(dropbox, dragClass);
217
            }
218
            processFileList(e, e.dataTransfer.files, instanceOptions);
219
        }
220

  
221
        function dragenter(e) {
222
            e.stopPropagation();
223
            e.preventDefault();
224
            if (dragClass) {
225
                addClass(dropbox, dragClass);
226
            }
227
        }
228

  
229
        function dragleave(e) {
230
            if (dragClass) {
231
                removeClass(dropbox, dragClass);
232
            }
233
        }
234

  
235
        function dragover(e) {
236
            e.stopPropagation();
237
            e.preventDefault();
238
            if (dragClass) {
239
                addClass(dropbox, dragClass);
240
            }
241
        }
242
    }
243

  
244
    // setupCustomFileProperties: modify the file object with extra properties
245
    function setupCustomFileProperties(files, groupID) {
246
        for (var i = 0; i < files.length; i++) {
247
            var file = files[i];
248
            file.extra = {
249
                nameNoExtension: file.name.substring(0, file.name.lastIndexOf('.')),
250
                extension: file.name.substring(file.name.lastIndexOf('.') + 1),
251
                fileID: i,
252
                uniqueID: getUniqueID(),
253
                groupID: groupID,
254
                prettySize: prettySize(file.size)
255
            };
256
        }
257
    }
258

  
259
    // getReadAsMethod: return method name for 'readAs*' - http://www.w3.org/TR/FileAPI/#reading-a-file
260
    function getReadAsMethod(type, readAsMap, readAsDefault) {
261
        for (var r in readAsMap) {
262
            if (type.match(new RegExp(r))) {
263
                return 'readAs' + readAsMap[r];
264
            }
265
        }
266
        return 'readAs' + readAsDefault;
267
    }
268

  
269
    // processFileList: read the files with FileReader, send off custom events.
270
    function processFileList(e, files, opts) {
271

  
272
        var filesLeft = files.length;
273
        var group = {
274
            groupID: getGroupID(),
275
            files: files,
276
            started: new Date()
277
        };
278

  
279
        function groupEnd() {
280
            group.ended = new Date();
281
            opts.on.groupend(group);
282
        }
283

  
284
        function groupFileDone() {
285
            if (--filesLeft === 0) {
286
                groupEnd();
287
            }
288
        }
289

  
290
        FileReaderJS.output.push(group);
291
        setupCustomFileProperties(files, group.groupID);
292

  
293
        opts.on.groupstart(group);
294

  
295
        // No files in group - end immediately
296
        if (!files.length) {
297
            groupEnd();
298
            return;
299
        }
300

  
301
        var sync = FileReaderJS.sync && FileReaderSyncSupport;
302
        var syncWorker;
303

  
304
        // Only initialize the synchronous worker if the option is enabled - to prevent the overhead
305
        if (sync) {
306
            syncWorker = WorkerHelper.getWorker(workerScript, function(e) {
307
                var file = e.data.file;
308
                var result = e.data.result;
309

  
310
                // Workers seem to lose the custom property on the file object.
311
                if (!file.extra) {
312
                    file.extra = e.data.extra;
313
                }
314

  
315
                file.extra.ended = new Date();
316

  
317
                // Call error or load event depending on success of the read from the worker.
318
                opts.on[result === "error" ? "error" : "load"]({ target: { result: result } }, file);
319
                groupFileDone();
320

  
321
            });
322
        }
323

  
324
        Array.prototype.forEach.call(files, function(file) {
325

  
326
            file.extra.started = new Date();
327

  
328
            if (opts.accept && !file.type.match(new RegExp(opts.accept))) {
329
                opts.on.skip(file);
330
                groupFileDone();
331
                return;
332
            }
333

  
334
            if (opts.on.beforestart(file) === false) {
335
                opts.on.skip(file);
336
                groupFileDone();
337
                return;
338
            }
339

  
340
            var readAs = getReadAsMethod(file.type, opts.readAsMap, opts.readAsDefault);
341

  
342
            if (sync && syncWorker) {
343
                syncWorker.postMessage({
344
                    file: file,
345
                    extra: file.extra,
346
                    readAs: readAs
347
                });
348
            }
349
            else {
350

  
351
                var reader = new FileReader();
352
                reader.originalEvent = e;
353

  
354
                fileReaderEvents.forEach(function(eventName) {
355
                    reader['on' + eventName] = function(e) {
356
                        if (eventName == 'load' || eventName == 'error') {
357
                            file.extra.ended = new Date();
358
                        }
359
                        opts.on[eventName](e, file);
360
                        if (eventName == 'loadend') {
361
                            groupFileDone();
362
                        }
363
                    };
364
                });
365

  
366
                reader[readAs](file);
367
            }
368
        });
369
    }
370

  
371
    // checkFileReaderSyncSupport: Create a temporary worker and see if FileReaderSync exists
372
    function checkFileReaderSyncSupport() {
373
        var worker = WorkerHelper.getWorker(syncDetectionScript, function(e) {
374
            FileReaderSyncSupport = e.data;
375
        });
376

  
377
        if (worker) {
378
            worker.postMessage({});
379
        }
380
    }
381

  
382
    // noop: do nothing
383
    function noop() {
384

  
385
    }
386

  
387
    // extend: used to make deep copies of options object
388
    function extend(destination, source) {
389
        for (var property in source) {
390
            if (source[property] && source[property].constructor &&
391
                source[property].constructor === Object) {
392
                destination[property] = destination[property] || {};
393
                arguments.callee(destination[property], source[property]);
394
            }
395
            else {
396
                destination[property] = source[property];
397
            }
398
        }
399
        return destination;
400
    }
401

  
402
    // hasClass: does an element have the css class?
403
    function hasClass(el, name) {
404
        return new RegExp("(?:^|\\s+)" + name + "(?:\\s+|$)").test(el.className);
405
    }
406

  
407
    // addClass: add the css class for the element.
408
    function addClass(el, name) {
409
        if (!hasClass(el, name)) {
410
          el.className = el.className ? [el.className, name].join(' ') : name;
411
        }
412
    }
413

  
414
    // removeClass: remove the css class from the element.
415
    function removeClass(el, name) {
416
        if (hasClass(el, name)) {
417
          var c = el.className;
418
          el.className = c.replace(new RegExp("(?:^|\\s+)" + name + "(?:\\s+|$)", "g"), " ").replace(/^\s\s*/, '').replace(/\s\s*$/, '');
419
        }
420
    }
421

  
422
    // prettySize: convert bytes to a more readable string.
423
    function prettySize(bytes) {
424
        var s = ['bytes', 'kb', 'MB', 'GB', 'TB', 'PB'];
425
        var e = Math.floor(Math.log(bytes)/Math.log(1024));
426
        return (bytes/Math.pow(1024, Math.floor(e))).toFixed(2)+" "+s[e];
427
    }
428

  
429
    // getGroupID: generate a unique int ID for groups.
430
    var getGroupID = (function(id) {
431
        return function() {
432
            return id++;
433
        };
434
    })(0);
435

  
436
    // getUniqueID: generate a unique int ID for files
437
    var getUniqueID = (function(id) {
438
        return function() {
439
            return id++;
440
        };
441
    })(0);
442

  
443
    // The interface is supported, bind the FileReaderJS callbacks
444
    FileReaderJS.enabled = true;
445

  
446
})(this, document);
b/snf-cyclades-app/synnefo/ui/static/snf/js/lib/filesaver.js
1
/* FileSaver.js
2
 * A saveAs() FileSaver implementation.
3
 * 2013-10-21
4
 *
5
 * By Eli Grey, http://eligrey.com
6
 * License: X11/MIT
7
 *   See LICENSE.md
8
 */
9

  
10
/*global self */
11
/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
12
  plusplus: true */
13

  
14
/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
15

  
16
var saveAs = saveAs
17
  || (typeof navigator !== 'undefined' && navigator.msSaveOrOpenBlob && navigator.msSaveOrOpenBlob.bind(navigator))
18
  || (function(view) {
19
	"use strict";
20
	var
21
		  doc = view.document
22
		  // only get URL when necessary in case BlobBuilder.js hasn't overridden it yet
23
		, get_URL = function() {
24
			return view.URL || view.webkitURL || view;
25
		}
26
		, URL = view.URL || view.webkitURL || view
27
		, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
28
		, can_use_save_link =  !view.externalHost && "download" in save_link
29
		, click = function(node) {
30
			var event = doc.createEvent("MouseEvents");
31
			event.initMouseEvent(
32
				"click", true, false, view, 0, 0, 0, 0, 0
33
				, false, false, false, false, 0, null
34
			);
35
			node.dispatchEvent(event);
36
		}
37
		, webkit_req_fs = view.webkitRequestFileSystem
38
		, req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
39
		, throw_outside = function (ex) {
40
			(view.setImmediate || view.setTimeout)(function() {
41
				throw ex;
42
			}, 0);
43
		}
44
		, force_saveable_type = "application/octet-stream"
45
		, fs_min_size = 0
46
		, deletion_queue = []
47
		, process_deletion_queue = function() {
48
			var i = deletion_queue.length;
49
			while (i--) {
50
				var file = deletion_queue[i];
51
				if (typeof file === "string") { // file is an object URL
52
					URL.revokeObjectURL(file);
53
				} else { // file is a File
54
					file.remove();
55
				}
56
			}
57
			deletion_queue.length = 0; // clear queue
58
		}
59
		, dispatch = function(filesaver, event_types, event) {
60
			event_types = [].concat(event_types);
61
			var i = event_types.length;
62
			while (i--) {
63
				var listener = filesaver["on" + event_types[i]];
64
				if (typeof listener === "function") {
65
					try {
66
						listener.call(filesaver, event || filesaver);
67
					} catch (ex) {
68
						throw_outside(ex);
69
					}
70
				}
71
			}
72
		}
73
		, FileSaver = function(blob, name) {
74
			// First try a.download, then web filesystem, then object URLs
75
			var
76
				  filesaver = this
77
				, type = blob.type
78
				, blob_changed = false
79
				, object_url
80
				, target_view
81
				, get_object_url = function() {
82
					var object_url = get_URL().createObjectURL(blob);
83
					deletion_queue.push(object_url);
84
					return object_url;
85
				}
86
				, dispatch_all = function() {
87
					dispatch(filesaver, "writestart progress write writeend".split(" "));
88
				}
89
				// on any filesys errors revert to saving with object URLs
90
				, fs_error = function() {
91
					// don't create more object URLs than needed
92
					if (blob_changed || !object_url) {
93
						object_url = get_object_url(blob);
94
					}
95
					if (target_view) {
96
						target_view.location.href = object_url;
97
					} else {
98
                        window.open(object_url, "_blank");
99
                    }
100
					filesaver.readyState = filesaver.DONE;
101
					dispatch_all();
102
				}
103
				, abortable = function(func) {
104
					return function() {
105
						if (filesaver.readyState !== filesaver.DONE) {
106
							return func.apply(this, arguments);
107
						}
108
					};
109
				}
110
				, create_if_not_found = {create: true, exclusive: false}
111
				, slice
112
			;
113
			filesaver.readyState = filesaver.INIT;
114
			if (!name) {
115
				name = "download";
116
			}
117
			if (can_use_save_link) {
118
				object_url = get_object_url(blob);
119
				// FF for Android has a nasty garbage collection mechanism
120
				// that turns all objects that are not pure javascript into 'deadObject'
121
				// this means `doc` and `save_link` are unusable and need to be recreated
122
				// `view` is usable though:
123
				doc = view.document;
124
				save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a");
125
				save_link.href = object_url;
126
				save_link.download = name;
127
				var event = doc.createEvent("MouseEvents");
128
				event.initMouseEvent(
129
					"click", true, false, view, 0, 0, 0, 0, 0
130
					, false, false, false, false, 0, null
131
				);
132
				save_link.dispatchEvent(event);
133
				filesaver.readyState = filesaver.DONE;
134
				dispatch_all();
135
				return;
136
			}
137
			// Object and web filesystem URLs have a problem saving in Google Chrome when
138
			// viewed in a tab, so I force save with application/octet-stream
139
			// http://code.google.com/p/chromium/issues/detail?id=91158
140
			if (view.chrome && type && type !== force_saveable_type) {
141
				slice = blob.slice || blob.webkitSlice;
142
				blob = slice.call(blob, 0, blob.size, force_saveable_type);
143
				blob_changed = true;
144
			}
145
			// Since I can't be sure that the guessed media type will trigger a download
146
			// in WebKit, I append .download to the filename.
147
			// https://bugs.webkit.org/show_bug.cgi?id=65440
148
			if (webkit_req_fs && name !== "download") {
149
				name += ".download";
150
			}
151
			if (type === force_saveable_type || webkit_req_fs) {
152
				target_view = view;
153
			}
154
			if (!req_fs) {
155
				fs_error();
156
				return;
157
			}
158
			fs_min_size += blob.size;
159
			req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
160
				fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
161
					var save = function() {
162
						dir.getFile(name, create_if_not_found, abortable(function(file) {
163
							file.createWriter(abortable(function(writer) {
164
								writer.onwriteend = function(event) {
165
									target_view.location.href = file.toURL();
166
									deletion_queue.push(file);
167
									filesaver.readyState = filesaver.DONE;
168
									dispatch(filesaver, "writeend", event);
169
								};
170
								writer.onerror = function() {
171
									var error = writer.error;
172
									if (error.code !== error.ABORT_ERR) {
173
										fs_error();
174
									}
175
								};
176
								"writestart progress write abort".split(" ").forEach(function(event) {
177
									writer["on" + event] = filesaver["on" + event];
178
								});
179
								writer.write(blob);
180
								filesaver.abort = function() {
181
									writer.abort();
182
									filesaver.readyState = filesaver.DONE;
183
								};
184
								filesaver.readyState = filesaver.WRITING;
185
							}), fs_error);
186
						}), fs_error);
187
					};
188
					dir.getFile(name, {create: false}, abortable(function(file) {
189
						// delete file if it already exists
190
						file.remove();
191
						save();
192
					}), abortable(function(ex) {
193
						if (ex.code === ex.NOT_FOUND_ERR) {
194
							save();
195
						} else {
196
							fs_error();
197
						}
198
					}));
199
				}), fs_error);
200
			}), fs_error);
201
		}
202
		, FS_proto = FileSaver.prototype
203
		, saveAs = function(blob, name) {
204
			return new FileSaver(blob, name);
205
		}
206
	;
207
	FS_proto.abort = function() {
208
		var filesaver = this;
209
		filesaver.readyState = filesaver.DONE;
210
		dispatch(filesaver, "abort");
211
	};
212
	FS_proto.readyState = FS_proto.INIT = 0;
213
	FS_proto.WRITING = 1;
214
	FS_proto.DONE = 2;
215

  
216
	FS_proto.error =
217
	FS_proto.onwritestart =
218
	FS_proto.onprogress =
219
	FS_proto.onwrite =
220
	FS_proto.onabort =
221
	FS_proto.onerror =
222
	FS_proto.onwriteend =
223
		null;
224

  
225
	view.addEventListener("unload", process_deletion_queue, false);
226
	return saveAs;
227
}(this.self || this.window || this.content));
228
// `self` is undefined in Firefox for Android content script context
229
// while `this` is nsIContentFrameMessageManager
230
// with an attribute `content` that corresponds to the window
231

  
232
if (typeof module !== 'undefined') module.exports = saveAs;

Also available in: Unified diff