Subversion Repositories wpShopGermany4

Rev

Rev 5261 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1067 daniel 1
/**
2
 * AJAX Upload ( http://valums.com/ajax-upload/ )
3
 * Copyright (c) Andris Valums
4
 * Licensed under the MIT license ( http://valums.com/mit-license/ )
5
 * Thanks to Gary Haran, David Mark, Corey Burns and others for contributions
6
 */
7
(function () {
8
    /* global window */
9
    /* jslint browser: true, devel: true, undef: true, nomen: true, bitwise: true, regexp: true, newcap: true, immed: true */
10
 
11
    /**
12
     * Wrapper for FireBug's console.log
13
     */
14
    function log(){
15
        if (typeof(console) != 'undefined' && typeof(console.log) == 'function'){
16
            Array.prototype.unshift.call(arguments, '[Ajax Upload]');
17
            console.log( Array.prototype.join.call(arguments, ' '));
18
        }
19
    }
20
 
21
    /**
22
     * Attaches event to a dom element.
23
     * @param {Element} el
24
     * @param type event name
25
     * @param fn callback This refers to the passed element
26
     */
27
    function addEvent(el, type, fn){
28
        if (el.addEventListener) {
29
            el.addEventListener(type, fn, false);
30
        } else if (el.attachEvent) {
31
            el.attachEvent('on' + type, function(){
32
                fn.call(el);
33
	        });
34
	    } else {
35
            throw new Error('not supported or DOM not loaded');
36
        }
37
    }
38
 
39
    /**
40
     * Attaches resize event to a window, limiting
41
     * number of event fired. Fires only when encounteres
42
     * delay of 100 after series of events.
43
     *
44
     * Some browsers fire event multiple times when resizing
45
     * http://www.quirksmode.org/dom/events/resize.html
46
     *
47
     * @param fn callback This refers to the passed element
48
     */
49
    function addResizeEvent(fn){
50
        var timeout;
51
 
52
	    addEvent(window, 'resize', function(){
53
            if (timeout){
54
                clearTimeout(timeout);
55
            }
56
            timeout = setTimeout(fn, 100);
57
        });
58
    }
59
 
60
    // Needs more testing, will be rewriten for next version
61
    // getOffset function copied from jQuery lib (http://jquery.com/)
62
    if (document.documentElement.getBoundingClientRect){
63
        // Get Offset using getBoundingClientRect
64
        // http://ejohn.org/blog/getboundingclientrect-is-awesome/
65
        var getOffset = function(el){
66
            var box = el.getBoundingClientRect();
67
            var doc = el.ownerDocument;
68
            var body = doc.body;
69
            var docElem = doc.documentElement; // for ie
70
            var clientTop = docElem.clientTop || body.clientTop || 0;
71
            var clientLeft = docElem.clientLeft || body.clientLeft || 0;
72
 
73
            // In Internet Explorer 7 getBoundingClientRect property is treated as physical,
74
            // while others are logical. Make all logical, like in IE8.
75
            var zoom = 1;
76
            if (body.getBoundingClientRect) {
77
                var bound = body.getBoundingClientRect();
78
                zoom = (bound.right - bound.left) / body.clientWidth;
79
            }
80
 
81
            if (zoom > 1) {
82
                clientTop = 0;
83
                clientLeft = 0;
84
            }
85
 
86
            var top = box.top / zoom + (window.pageYOffset || docElem && docElem.scrollTop / zoom || body.scrollTop / zoom) - clientTop, left = box.left / zoom + (window.pageXOffset || docElem && docElem.scrollLeft / zoom || body.scrollLeft / zoom) - clientLeft;
87
 
88
            return {
89
                top: top,
90
                left: left
91
            };
92
        };
93
    } else {
94
        // Get offset adding all offsets
95
        var getOffset = function(el){
96
            var top = 0, left = 0;
97
            do {
98
                top += el.offsetTop || 0;
99
                left += el.offsetLeft || 0;
100
                el = el.offsetParent;
101
            } while (el);
102
 
103
            return {
104
                left: left,
105
                top: top
106
            };
107
        };
108
    }
109
 
110
    /**
111
     * Returns left, top, right and bottom properties describing the border-box,
112
     * in pixels, with the top-left relative to the body
113
     * @param {Element} el
114
     * @return {Object} Contains left, top, right,bottom
115
     */
116
    function getBox(el){
117
        var left, right, top, bottom;
118
        var offset = getOffset(el);
119
        left = offset.left;
120
        top = offset.top;
121
 
122
        right = left + el.offsetWidth;
123
        bottom = top + el.offsetHeight;
124
 
125
        return {
126
            left: left,
127
            right: right,
128
            top: top,
129
            bottom: bottom
130
        };
131
    }
132
 
133
    /**
134
     * Helper that takes object literal
135
     * and add all properties to element.style
136
     * @param {Element} el
137
     * @param {Object} styles
138
     */
139
    function addStyles(el, styles){
140
        for (var name in styles) {
141
            if (styles.hasOwnProperty(name)) {
142
                el.style[name] = styles[name];
143
            }
144
        }
145
    }
146
 
147
    /**
148
     * Function places an absolutely positioned
149
     * element on top of the specified element
150
     * copying position and dimentions.
151
     * @param {Element} from
152
     * @param {Element} to
153
     */
154
    function copyLayout(from, to){
155
	    var box = getBox(from);
156
 
157
        addStyles(to, {
158
	        position: 'absolute',
159
	        left : box.left + 'px',
160
	        top : box.top + 'px',
161
	        width : from.offsetWidth + 'px',
162
	        height : from.offsetHeight + 'px'
163
	    });
164
    }
165
 
166
    /**
167
    * Creates and returns element from html chunk
168
    * Uses innerHTML to create an element
169
    */
170
    var toElement = (function(){
171
        var div = document.createElement('div');
172
        return function(html){
173
            div.innerHTML = html;
174
            var el = div.firstChild;
175
            return div.removeChild(el);
176
        };
177
    })();
178
 
179
    /**
180
     * Function generates unique id
181
     * @return unique id
182
     */
183
    var getUID = (function(){
184
        var id = 0;
185
        return function(){
186
            return 'ValumsAjaxUpload' + id++;
187
        };
188
    })();
189
 
190
    /**
191
     * Get file name from path
192
     * @param {String} file path to file
193
     * @return filename
194
     */
195
    function fileFromPath(file){
196
        return file.replace(/.*(\/|\\)/, "");
197
    }
198
 
199
    /**
200
     * Get file extension lowercase
201
     * @param {String} file name
202
     * @return file extenstion
203
     */
204
    function getExt(file){
205
        return (-1 !== file.indexOf('.')) ? file.replace(/.*[.]/, '') : '';
206
    }
207
 
208
    function hasClass(el, name){
209
        var re = new RegExp('\\b' + name + '\\b');
210
        return re.test(el.className);
211
    }
212
    function addClass(el, name){
213
        if ( ! hasClass(el, name)){
214
            el.className += ' ' + name;
215
        }
216
    }
217
    function removeClass(el, name){
218
        var re = new RegExp('\\b' + name + '\\b');
219
        el.className = el.className.replace(re, '');
220
    }
221
 
222
    function removeNode(el){
223
        el.parentNode.removeChild(el);
224
    }
225
 
226
    /**
227
     * Easy styling and uploading
228
     * @constructor
229
     * @param button An element you want convert to
230
     * upload button. Tested dimentions up to 500x500px
231
     * @param {Object} options See defaults below.
232
     */
233
    window.AjaxUpload = function(button, options){
234
        this._settings = {
235
            // Location of the server-side upload script
236
            action: 'upload.php',
237
            // File upload name
238
            name: 'userfile',
239
            // Additional data to send
240
            data: {},
241
            // Submit file as soon as it's selected
242
            autoSubmit: true,
243
            // The type of data that you're expecting back from the server.
244
            // html and xml are detected automatically.
245
            // Only useful when you are using json data as a response.
246
            // Set to "json" in that case.
247
            responseType: false,
248
            // Class applied to button when mouse is hovered
249
            hoverClass: 'hover',
250
            // Class applied to button when AU is disabled
251
            disabledClass: 'disabled',
252
            // When user selects a file, useful with autoSubmit disabled
253
            // You can return false to cancel upload
254
            onChange: function(file, extension){
255
            },
256
            // Callback to fire before file is uploaded
257
            // You can return false to cancel upload
258
            onSubmit: function(file, extension){
259
            },
260
            // Fired when file upload is completed
261
            // WARNING! DO NOT USE "FALSE" STRING AS A RESPONSE!
262
            onComplete: function(file, response){
263
            }
264
        };
265
 
266
        // Merge the users options with our defaults
267
        for (var i in options) {
268
            if (options.hasOwnProperty(i)){
269
                this._settings[i] = options[i];
270
            }
271
        }
272
 
273
        // button isn't necessary a dom element
274
        if (button.jquery){
275
            // jQuery object was passed
276
            button = button[0];
277
        } else if (typeof button == "string") {
278
            if (/^#.*/.test(button)){
279
                // If jQuery user passes #elementId don't break it
280
                button = button.slice(1);
281
            }
282
 
283
            button = document.getElementById(button);
284
        }
285
 
286
        if ( ! button || button.nodeType !== 1){
287
            throw new Error("Please make sure that you're passing a valid element");
288
        }
289
 
290
        if ( button.nodeName.toUpperCase() == 'A'){
291
            // disable link
292
            addEvent(button, 'click', function(e){
293
                if (e && e.preventDefault){
294
                    e.preventDefault();
295
                } else if (window.event){
296
                    window.event.returnValue = false;
297
                }
298
            });
299
        }
300
 
301
        // DOM element
302
        this._button = button;
303
        // DOM element
304
        this._input = null;
305
        // If disabled clicking on button won't do anything
306
        this._disabled = false;
307
 
308
        // if the button was disabled before refresh if will remain
309
        // disabled in FireFox, let's fix it
310
        this.enable();
311
 
312
        this._rerouteClicks();
313
    };
314
 
315
    // assigning methods to our class
316
    AjaxUpload.prototype = {
317
        setData: function(data){
318
            this._settings.data = data;
319
        },
320
        disable: function(){
321
            addClass(this._button, this._settings.disabledClass);
322
            this._disabled = true;
323
 
324
            var nodeName = this._button.nodeName.toUpperCase();
325
            if (nodeName == 'INPUT' || nodeName == 'BUTTON'){
326
                this._button.setAttribute('disabled', 'disabled');
327
            }
328
 
329
            // hide input
330
            if (this._input){
331
                // We use visibility instead of display to fix problem with Safari 4
332
                // The problem is that the value of input doesn't change if it
333
                // has display none when user selects a file
334
                this._input.parentNode.style.visibility = 'hidden';
335
            }
336
        },
337
        enable: function(){
338
            removeClass(this._button, this._settings.disabledClass);
339
            this._button.removeAttribute('disabled');
340
            this._disabled = false;
341
 
342
        },
343
        /**
344
         * Creates invisible file input
345
         * that will hover above the button
346
         * <div><input type='file' /></div>
347
         */
348
        _createInput: function(){
349
            var self = this;
350
 
351
            var input = document.createElement("input");
352
            input.setAttribute('type', 'file');
5447 daniel 353
            input.setAttribute('multiple', 'multiple');
354
            input.setAttribute('name', this._settings.name + '[]');
1067 daniel 355
 
356
            addStyles(input, {
357
                'position' : 'absolute',
358
                // in Opera only 'browse' button
359
                // is clickable and it is located at
360
                // the right side of the input
361
                'right' : 0,
362
                'margin' : 0,
363
                'padding' : 0,
364
                'fontSize' : '480px',
365
                'cursor' : 'pointer'
366
            });
367
 
368
            var div = document.createElement("div");
369
            addStyles(div, {
370
                'display' : 'block',
371
                'position' : 'absolute',
372
                'overflow' : 'hidden',
373
                'margin' : 0,
374
                'padding' : 0,
375
                'opacity' : 0,
376
                // Make sure browse button is in the right side
377
                // in Internet Explorer
378
                'direction' : 'ltr',
379
                //Max zIndex supported by Opera 9.0-9.2
380
                'zIndex': 2147483583
381
            });
382
 
383
            // Make sure that element opacity exists.
384
            // Otherwise use IE filter
385
            if ( div.style.opacity !== "0") {
386
                if (typeof(div.filters) == 'undefined'){
387
                    throw new Error('Opacity not supported by the browser');
388
                }
389
                div.style.filter = "alpha(opacity=0)";
390
            }
391
 
392
            addEvent(input, 'change', function(){
393
 
394
                if ( ! input || input.value === ''){
395
                    return;
396
                }
397
 
398
                // Get filename from input, required
399
                // as some browsers have path instead of it
400
                var file = fileFromPath(input.value);
401
 
402
                if (false === self._settings.onChange.call(self, file, getExt(file))){
403
                    self._clearInput();
404
                    return;
405
                }
406
 
407
                // Submit form when value is changed
408
                if (self._settings.autoSubmit) {
409
                    self.submit();
410
                }
411
            });
412
 
413
            addEvent(input, 'mouseover', function(){
414
                addClass(self._button, self._settings.hoverClass);
415
            });
416
 
417
            addEvent(input, 'mouseout', function(){
418
                removeClass(self._button, self._settings.hoverClass);
419
 
420
                // We use visibility instead of display to fix problem with Safari 4
421
                // The problem is that the value of input doesn't change if it
422
                // has display none when user selects a file
423
                input.parentNode.style.visibility = 'hidden';
424
 
425
            });
426
 
427
	        div.appendChild(input);
428
            document.body.appendChild(div);
429
 
430
            this._input = input;
431
        },
432
        _clearInput : function(){
433
            if (!this._input){
434
                return;
435
            }
436
 
437
            removeNode(this._input.parentNode);
438
            this._input = null;
439
            this._createInput();
440
 
441
            removeClass(this._button, this._settings.hoverClass);
442
        },
443
        /**
444
         * Function makes sure that when user clicks upload button,
445
         * the this._input is clicked instead
446
         */
447
        _rerouteClicks: function(){
448
            var self = this;
449
 
450
            // IE will later display 'access denied' error
451
            // if you use using self._input.click()
452
            // other browsers just ignore click()
453
 
454
            addEvent(self._button, 'mouseover', function(){
455
                if (self._disabled){
456
                    return;
457
                }
458
 
459
                if ( ! self._input){
460
	                self._createInput();
461
                }
462
 
463
                var div = self._input.parentNode;
464
                copyLayout(self._button, div);
465
                div.style.visibility = 'visible';
466
 
467
            });
468
 
469
 
470
            // commented because we now hide input on mouseleave
471
            /**
472
             * When the window is resized the elements
473
             * can be misaligned if button position depends
474
             * on window size
475
             */
476
            //addResizeEvent(function(){
477
            //    if (self._input){
478
            //        copyLayout(self._button, self._input.parentNode);
479
            //    }
480
            //});
481
 
482
        },
483
        /**
484
         * Creates iframe with unique name
485
         * @return {Element} iframe
486
         */
487
        _createIframe: function(){
488
            // We can't use getTime, because it sometimes return
489
            // same value in safari :(
490
            var id = getUID();
491
 
492
            // We can't use following code as the name attribute
493
            // won't be properly registered in IE6, and new window
494
            // on form submit will open
495
            // var iframe = document.createElement('iframe');
496
            // iframe.setAttribute('name', id);
497
 
498
            var iframe = toElement('<iframe src="javascript:false;" name="' + id + '" />');
499
            // src="javascript:false; was added
500
            // because it possibly removes ie6 prompt
501
            // "This page contains both secure and nonsecure items"
502
            // Anyway, it doesn't do any harm.
503
            iframe.setAttribute('id', id);
504
 
505
            iframe.style.display = 'none';
506
            document.body.appendChild(iframe);
507
 
508
            return iframe;
509
        },
510
        /**
511
         * Creates form, that will be submitted to iframe
512
         * @param {Element} iframe Where to submit
513
         * @return {Element} form
514
         */
515
        _createForm: function(iframe){
516
            var settings = this._settings;
517
 
518
            // We can't use the following code in IE6
519
            // var form = document.createElement('form');
520
            // form.setAttribute('method', 'post');
521
            // form.setAttribute('enctype', 'multipart/form-data');
522
            // Because in this case file won't be attached to request
523
            var form = toElement('<form method="post" enctype="multipart/form-data"></form>');
524
 
525
            form.setAttribute('action', settings.action);
526
            form.setAttribute('target', iframe.name);
527
            form.style.display = 'none';
528
            document.body.appendChild(form);
529
 
530
            // Create hidden input element for each data key
531
            for (var prop in settings.data) {
532
                if (settings.data.hasOwnProperty(prop)){
533
                    var el = document.createElement("input");
534
                    el.setAttribute('type', 'hidden');
535
                    el.setAttribute('name', prop);
536
                    el.setAttribute('value', settings.data[prop]);
537
                    form.appendChild(el);
538
                }
539
            }
540
            return form;
541
        },
542
        /**
543
         * Gets response from iframe and fires onComplete event when ready
544
         * @param iframe
545
         * @param file Filename to use in onComplete callback
546
         */
547
        _getResponse : function(iframe, file){
548
            // getting response
549
            var toDeleteFlag = false, self = this, settings = this._settings;
550
 
551
            addEvent(iframe, 'load', function(){
552
 
553
                if (// For Safari
554
                    iframe.src == "javascript:'%3Chtml%3E%3C/html%3E';" ||
555
                    // For FF, IE
556
                    iframe.src == "javascript:'<html></html>';"){
557
                        // First time around, do not delete.
558
                        // We reload to blank page, so that reloading main page
559
                        // does not re-submit the post.
560
 
561
                        if (toDeleteFlag) {
562
                            // Fix busy state in FF3
563
                            setTimeout(function(){
564
                                removeNode(iframe);
565
                            }, 0);
566
                        }
567
 
568
                        return;
569
                }
570
 
571
                var doc = iframe.contentDocument ? iframe.contentDocument : window.frames[iframe.id].document;
572
 
573
                // fixing Opera 9.26,10.00
574
                if (doc.readyState && doc.readyState != 'complete') {
575
                   // Opera fires load event multiple times
576
                   // Even when the DOM is not ready yet
577
                   // this fix should not affect other browsers
578
                   return;
579
                }
580
 
581
                // fixing Opera 9.64
582
                if (doc.body && doc.body.innerHTML == "false") {
583
                    // In Opera 9.64 event was fired second time
584
                    // when body.innerHTML changed from false
585
                    // to server response approx. after 1 sec
586
                    return;
587
                }
588
 
589
                var response;
590
 
591
                if (doc.XMLDocument) {
592
                    // response is a xml document Internet Explorer property
593
                    response = doc.XMLDocument;
594
                } else if (doc.body){
595
                    // response is html document or plain text
596
                    response = doc.body.innerHTML;
597
 
598
                    if (settings.responseType && settings.responseType.toLowerCase() == 'json') {
599
                        // If the document was sent as 'application/javascript' or
600
                        // 'text/javascript', then the browser wraps the text in a <pre>
601
                        // tag and performs html encoding on the contents.  In this case,
602
                        // we need to pull the original text content from the text node's
603
                        // nodeValue property to retrieve the unmangled content.
604
                        // Note that IE6 only understands text/html
605
                        if (doc.body.firstChild && doc.body.firstChild.nodeName.toUpperCase() == 'PRE') {
606
                            response = doc.body.firstChild.firstChild.nodeValue;
607
                        }
608
 
609
                        if (response) {
610
                            response = eval("(" + response + ")");
611
                        } else {
612
                            response = {};
613
                        }
614
                    }
615
                } else {
616
                    // response is a xml document
617
                    response = doc;
618
                }
619
 
620
                settings.onComplete.call(self, file, response);
621
 
622
                // Reload blank page, so that reloading main page
623
                // does not re-submit the post. Also, remember to
624
                // delete the frame
625
                toDeleteFlag = true;
626
 
627
                // Fix IE mixed content issue
628
                iframe.src = "javascript:'<html></html>';";
629
            });
630
        },
631
        /**
632
         * Upload file contained in this._input
633
         */
634
        submit: function(){
635
            var self = this, settings = this._settings;
636
 
637
            if ( ! this._input || this._input.value === ''){
638
                return;
639
            }
640
 
641
            var file = fileFromPath(this._input.value);
642
 
643
            // user returned false to cancel upload
644
            if (false === settings.onSubmit.call(this, file, getExt(file))){
645
                this._clearInput();
646
                return;
647
            }
648
 
649
            // sending request
650
            var iframe = this._createIframe();
651
            var form = this._createForm(iframe);
652
 
653
            // assuming following structure
654
            // div -> input type='file'
655
            removeNode(this._input.parentNode);
656
            removeClass(self._button, self._settings.hoverClass);
657
 
658
            form.appendChild(this._input);
659
 
660
            form.submit();
661
 
662
            // request set, clean up
663
            removeNode(form); form = null;
664
            removeNode(this._input); this._input = null;
665
 
666
            // Get response from iframe and fire onComplete event when ready
667
            this._getResponse(iframe, file);
668
 
669
            // get ready for next request
670
            this._createInput();
671
        }
672
    };
673
})();