1 /*
  2  * RapidContext <https://www.rapidcontext.com/>
  3  * Copyright (c) 2007-2022 Per Cederberg. All rights reserved.
  4  *
  5  * This program is free software: you can redistribute it and/or
  6  * modify it under the terms of the BSD license.
  7  *
  8  * This program is distributed in the hope that it will be useful,
  9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 11  *
 12  * See the RapidContext LICENSE for more details.
 13  */
 14
 15 // Namespace initialization
 16 if (typeof(RapidContext) == "undefined") {
 17     RapidContext = {};
 18 }
 19 RapidContext.Widget = RapidContext.Widget || { Classes: {}};
 20
 21 /**
 22  * Creates a new file streamer widget.
 23  *
 24  * @constructor
 25  * @param {Object} attrs the widget and node attributes
 26  * @param {String} [attrs.url] the URL to post the data to, defaults
 27  *            to window.location.href
 28  * @param {String} [attrs.name] the file input field name,
 29  *            defaults to 'file'
 30  * @param {String} [attrs.size] the file input size, defaults to '30'
 31  * @param {Boolean} [attrs.hidden] the hidden widget flag, defaults to false
 32  *
 33  * @return {Widget} the widget DOM node
 34  *
 35  * @class The file streamer widget class. This widget is used to
 36  *     provide a file upload (file input) control that can stream the
 37  *     selected file to the server. The file data is always sent
 38  *     asynchronously (i.e. in the background) to allow the rest of
 39  *     the web page to remain active also during the potentially long
 40  *     delays caused by sending large amounts of data. The widget
 41  *     creates its own `<iframe>` HTML element inside which the actual
 42  *     `<form>` and `<input>` elements are created automatically.
 43  * @extends RapidContext.Widget
 44  *
 45  * @example {JavaScript}
 46  * var file = RapidContext.Widget.FileStreamer({ url: "rapidcontext/upload/my_id" });
 47  * form.addAll(file);
 48  * MochiKit.Signal.connect(file, "onselect", function () {
 49  *     file.hide();
 50  *     // add code to show progress bar
 51  * });
 52  */
 53 RapidContext.Widget.FileStreamer = function (attrs) {
 54     var defs = { src: "about:blank", scrolling: "no",
 55                  border: "0", frameborder: "0" };
 56     var o = MochiKit.DOM.createDOM("iframe", defs);
 57     RapidContext.Widget._widgetMixin(o, arguments.callee);
 58     o.addClass("widgetFileStreamer");
 59     o.setAttrs(MochiKit.Base.update({ url: "", name: "file", size: "30" }, attrs));
 60     // TODO: create some kind of utility function for these idioms
 61     var links = MochiKit.Selector.findDocElements("link[href*=widget.css]");
 62     if (links.length > 0) {
 63         o.cssUrl = links[0].href;
 64     }
 65     o.onload = o._handleLoad;
 66     return o;
 67 };
 68
 69 // Register widget class
 70 RapidContext.Widget.Classes.FileStreamer = RapidContext.Widget.FileStreamer;
 71
 72 /**
 73  * Returns the widget container DOM node.
 74  *
 75  * @return {Node} returns null, since child nodes are not supported
 76  */
 77 RapidContext.Widget.FileStreamer.prototype._containerNode = function () {
 78     return null;
 79 };
 80
 81 /**
 82  * Emitted when the file upload begins. This event is triggered by
 83  * the user selecting a file to upload, which automatically starts
 84  * the upload. This event signal carries no event information.
 85  *
 86  * @name RapidContext.Widget.FileStreamer#onselect
 87  * @event
 88  */
 89
 90 /**
 91  * Emitted when the file upload has completed. This event signal
 92  * carries no event information.
 93  *
 94  * @name RapidContext.Widget.FileStreamer#onupload
 95  * @event
 96  */
 97
 98 /**
 99  * Updates the widget or HTML DOM node attributes.
100  *
101  * @param {Object} attrs the widget and node attributes to set
102  * @param {String} [attrs.url] the URL to post the data to
103  * @param {String} [attrs.name] the file input field name
104  * @param {String} [attrs.size] the file input size
105  * @param {Boolean} [attrs.hidden] the hidden widget flag
106  */
107 RapidContext.Widget.FileStreamer.prototype.setAttrs = function (attrs) {
108     attrs = MochiKit.Base.update({}, attrs);
109     var locals = RapidContext.Util.mask(attrs, ["url", "name", "size"]);
110     if (typeof(locals.url) != "undefined") {
111         this.formUrl = RapidContext.Util.resolveURI(locals.url);
112     }
113     if (typeof(locals.name) != "undefined") {
114         this.inputName = locals.param;
115     }
116     if (typeof(locals.size) != "undefined") {
117         this.inputSize = locals.size;
118     }
119     // TODO: update form if already created, or recreate?
120     this.__setAttrs(attrs);
121 };
122
123 /**
124  * Handles the iframe onload event.
125  */
126 RapidContext.Widget.FileStreamer.prototype._handleLoad = function () {
127     var doc = this.contentDocument;
128     if (doc.location.href == this.formUrl) {
129         RapidContext.Widget.emitSignal(this, "onupload");
130     }
131     MochiKit.DOM.withDocument(doc, MochiKit.Base.bind("_initDocument", this));
132 };
133
134 /**
135  * Handles the file input onchange event.
136  */
137 RapidContext.Widget.FileStreamer.prototype._handleChange = function () {
138     RapidContext.Widget.emitSignal(this, "onselect");
139     var form = this.contentDocument.getElementsByTagName("form")[0];
140     form.submit();
141     form.appendChild(RapidContext.Widget.Overlay());
142 };
143
144 /**
145  * Creates the document form and file input element.
146  */
147 RapidContext.Widget.FileStreamer.prototype._initDocument = function () {
148     var doc = this.contentDocument;
149     var head = doc.getElementsByTagName("head")[0];
150     var body = doc.body;
151     if (head == null) {
152         head = doc.createElement("head");
153         body.parentElement.insertBefore(head, body);
154     }
155     var attrs = { rel: "stylesheet", href: this.cssUrl, type: "text/css" };
156     var link = MochiKit.DOM.createDOM("link", attrs);
157     head.appendChild(link);
158     var attrs = { type: "file", name: this.inputName, size: this.inputSize };
159     var input = MochiKit.DOM.INPUT(attrs);
160     var attrs = { method: "POST", action: this.formUrl, enctype: "multipart/form-data" };
161     var form = MochiKit.DOM.FORM(attrs, input);
162     input.onchange = MochiKit.Base.bind("_handleChange", this);
163     body.className = "widgetFileStreamer";
164     MochiKit.DOM.replaceChildNodes(body, form);
165 };
166
167 /**
168  * Handles widget resize calls, so that the `<iframe>` can be adjusted
169  * to the file input field.
170  *
171  * @private
172  */
173 RapidContext.Widget.FileStreamer.prototype.resizeContent = function () {
174     var doc = this.contentDocument;
175     if (doc != null && typeof(doc.getElementsByTagName) === "function") {
176         var form = doc.getElementsByTagName("form")[0];
177         if (form != null) {
178             var input = form.firstChild;
179             this.width = input.clientWidth + 2;
180             this.height = Math.max(24, input.clientHeight);
181         }
182     }
183 };
184