Merge pull request #1 from shackbarth/keith1
[xtuple] / lib / enyo-x / source / widgets / file_input.js
1 /*jshint node:true, indent:2, curly:true, eqeqeq:true, immed:true, latedef:true, newcap:true, noarg:true,
2 regexp:true, undef:true, trailing:true, white:true */
3 /*global XT:true, XV:true, enyo:true, _:true, Globalize:true, FileReader:true */
4
5 (function () {
6
7   /**
8     @name XV.FileInput
9     @class An input control for managing the upload of files.<br />
10     Creates a file-type HTML input element,
11       with some HTML5 functionality.
12     @extends XV.Input
13    */
14   enyo.kind(
15     /** @lends XV.FileInput# */{
16     name: "XV.FileInput",
17     kind: "XV.InputWidget",
18     showLabel: false,
19     type: "file",
20     events: {
21       onValueChange: "",
22       onNotify: ""
23     },
24     handlers: {
25       onValueChange: "valueChange"
26     },
27     components: [
28       {name: "label", fit: true, classes: "xv-label"},
29       {name: "input", kind: "onyx.Input", onchange: "inputChanged"},
30       {name: "scrim", kind: "onyx.Scrim", showing: false, floating: true}
31     ],
32
33     /**
34       Generally we don't want to set the value of the widget, because
35       setting the value of a file input with the binary data will just throw a security
36       exception. But this function is also used as an essential part of selecting a file.
37       In that circumstance the value is the filename and the options has no silent attribute,
38       which is what's used to differentiate the appropriate times to suppress the setting of
39       the value.
40      */
41     setValue: function (value, options) {
42       if (options && options.silent) {
43         // don't try to update widget. Just throws a security exception if you do.
44         this.value = value;
45       } else {
46         this.inherited(arguments);
47       }
48     },
49     /**
50       Turns the payload of the bubbled event into the file instead of the filename
51       using HTML5.
52      */
53     valueChange: function (inSender, inEvent) {
54       if (inEvent.transformedByFileInput) {
55         // we've already been here. We want to propagate up, but don't run this function again.
56         return false;
57       }
58
59       // I feel bad going to the DOM like this but not that bad.
60       // Some inspiration from https://github.com/JMTK/decorated-file-input
61       // which we can use to replace this widget if we want
62       var that = this,
63         file = inEvent.originator.$.input.hasNode().files[0],
64         filename = inEvent.value,
65         reader;
66
67       if (!file) {
68         return;
69       }
70
71       if (filename.indexOf("C:\\fakepath\\") === 0) {
72         // some browsers obnoxiously give you a fake path, but the only thing
73         // we want is the filename really.
74         filename = filename.replace("C:\\fakepath\\", "");
75       }
76
77       // XXX Browser support for this HTML5 construct is not universal
78       if (FileReader) {
79         // XXX unsure about the overhead of this constructor. maybe save it globally?
80         reader = new FileReader();
81       } else {
82         this.doNotify({
83           originator: this,
84           message: "Sorry! File upload is only supported on modern browsers"
85         });
86         inEvent.value = null;
87         return;
88       }
89
90       // prepare callback
91       reader.onload = function () {
92         that.$.scrim.setShowing(false);
93         inEvent.value = reader.result;
94         inEvent.filename = filename;
95         inEvent.transformedByFileInput = true; // used to avoid infinite loop
96         that.doValueChange(inEvent);
97       };
98
99       // XXX not sure why this scrim isn't working
100       this.$.scrim.setShowing(true);
101
102       // XXX binary string is only one of several options here
103       // http://www.html5rocks.com/en/tutorials/file/dndfiles/
104       reader.readAsBinaryString(file); // async
105
106       // this event will be bubbled by the callback
107       return true;
108     }
109   });
110
111 }());