1 | define([ |
---|
2 | "dojo",//FIXME |
---|
3 | "dijit",//FIXME |
---|
4 | "dijit/registry", |
---|
5 | "dijit/_base/popup", |
---|
6 | "dijit/_editor/_Plugin", |
---|
7 | "dijit/_editor/plugins/LinkDialog", |
---|
8 | "dijit/TooltipDialog", |
---|
9 | "dijit/form/_TextBoxMixin", |
---|
10 | "dijit/form/Button", |
---|
11 | "dijit/form/ValidationTextBox", |
---|
12 | "dijit/form/DropDownButton", |
---|
13 | "dojo/_base/connect", |
---|
14 | "dojo/_base/declare", |
---|
15 | "dojo/_base/sniff", |
---|
16 | "dojox/form/FileUploader", //FIXME: deprecated. Use Uploader instead |
---|
17 | "dojo/i18n!dojox/editor/plugins/nls/LocalImage" |
---|
18 | ], function(dojo, dijit, registry, popup, _Plugin, LinkDialog, TooltipDialog, |
---|
19 | _TextBoxMixin, Button, ValidationTextBox, DropDownButton, |
---|
20 | connect, declare, has, FileUploader, messages) { |
---|
21 | |
---|
22 | var LocalImage = dojo.declare("dojox.editor.plugins.LocalImage", LinkDialog.ImgLinkDialog, { |
---|
23 | // summary: |
---|
24 | // This plugin provides an enhanced image link dialog that |
---|
25 | // not only insert the online images, but upload the local image files onto |
---|
26 | // to server then insert them as well. |
---|
27 | // |
---|
28 | // Dependencies: |
---|
29 | // This plugin depends on dojox.form.FileUploader to upload the images on the local driver. |
---|
30 | // Do the regression test whenever FileUploader is upgraded. |
---|
31 | |
---|
32 | // uploadable: [public] Boolean |
---|
33 | // Indicate whether the user can upload a local image file onto the server. |
---|
34 | // If it is set to true, the Browse button will be available. |
---|
35 | uploadable: false, |
---|
36 | |
---|
37 | // uploadUrl: [public] String |
---|
38 | // The url targeted for uploading. Both absolute and relative URLs are OK. |
---|
39 | uploadUrl: "", |
---|
40 | |
---|
41 | // baseImageUrl: [public] String |
---|
42 | // The prefix of the image url on the server. |
---|
43 | // For example, an image is uploaded and stored at |
---|
44 | // `http://www.myhost.com/images/uploads/test.jpg`. |
---|
45 | // When the image is uploaded, the server returns "uploads/test.jpg" as the |
---|
46 | // relative path. So the baseImageUrl should be set to "http://www.myhost.com/images/" |
---|
47 | // so that the client can retrieve the image from the server. |
---|
48 | // If the image file is located on the same domain as that of the current web page, |
---|
49 | // baseImageUrl can be a relative path. For example: |
---|
50 | // | baseImageUrl = images/ |
---|
51 | // and the server returns uploads/test.jpg |
---|
52 | // The complete URL of the image file is images/upload/test.jpg |
---|
53 | baseImageUrl: "", |
---|
54 | |
---|
55 | // fileMask: [public] String |
---|
56 | // Specify the types of images that are allowed to be uploaded. |
---|
57 | // Note that the type checking on server is also very important! |
---|
58 | fileMask: "*.jpg;*.jpeg;*.gif;*.png;*.bmp", |
---|
59 | |
---|
60 | // urlRegExp: [protected] String |
---|
61 | // Used to validate if the input is a valid image URL. |
---|
62 | urlRegExp: "", |
---|
63 | |
---|
64 | // htmlFieldName: [private] htmlFieldName |
---|
65 | htmlFieldName:"uploadedfile", |
---|
66 | |
---|
67 | // _isLocalFile: [private] Boolean |
---|
68 | // Indicate if a local file is to be uploaded to the server |
---|
69 | // If false, the text of _urlInput field is regarded as the |
---|
70 | // URL of the online image |
---|
71 | _isLocalFile: false, |
---|
72 | |
---|
73 | // _messages: [private] Array<String> |
---|
74 | // Contains i18n strings. |
---|
75 | _messages: "", |
---|
76 | |
---|
77 | // _cssPrefix: [private] String |
---|
78 | // The prefix of the CSS style |
---|
79 | _cssPrefix: "dijitEditorEilDialog", |
---|
80 | |
---|
81 | // _closable: [private] Boolean |
---|
82 | // Indicate if the tooltip dialog can be closed. Used to workaround Safari 5 bug |
---|
83 | // where the file dialog doesn't pop up in modal until after the first click. |
---|
84 | _closable: true, |
---|
85 | |
---|
86 | // linkDialogTemplate: [protected] String |
---|
87 | // Over-ride for template since this is an enhanced image dialog. |
---|
88 | linkDialogTemplate: [ |
---|
89 | "<div style='border-bottom: 1px solid black; padding-bottom: 2pt; margin-bottom: 4pt;'></div>", // <hr/> breaks the dialog in IE6 |
---|
90 | "<div class='dijitEditorEilDialogDescription'>${prePopuTextUrl}${prePopuTextBrowse}</div>", |
---|
91 | "<table role='presentation'><tr><td colspan='2'>", |
---|
92 | "<label for='${id}_urlInput' title='${prePopuTextUrl}${prePopuTextBrowse}'>${url}</label>", |
---|
93 | "</td></tr><tr><td class='dijitEditorEilDialogField'>", |
---|
94 | "<input dojoType='dijit.form.ValidationTextBox' class='dijitEditorEilDialogField'" + |
---|
95 | "regExp='${urlRegExp}' title='${prePopuTextUrl}${prePopuTextBrowse}' selectOnClick='true' required='true' " + |
---|
96 | "id='${id}_urlInput' name='urlInput' intermediateChanges='true' invalidMessage='${invalidMessage}' " + |
---|
97 | "prePopuText='<${prePopuTextUrl}${prePopuTextBrowse}>'>", |
---|
98 | "</td><td>", |
---|
99 | "<div id='${id}_browse' style='display:${uploadable}'>${browse}</div>", |
---|
100 | "</td></tr><tr><td colspan='2'>", |
---|
101 | "<label for='${id}_textInput'>${text}</label>", |
---|
102 | "</td></tr><tr><td>", |
---|
103 | "<input dojoType='dijit.form.TextBox' required='false' id='${id}_textInput' " + |
---|
104 | "name='textInput' intermediateChanges='true' selectOnClick='true' class='dijitEditorEilDialogField'>", |
---|
105 | "</td><td></td></tr><tr><td>", |
---|
106 | "</td><td>", |
---|
107 | "</td></tr><tr><td colspan='2'>", |
---|
108 | "<button dojoType='dijit.form.Button' id='${id}_setButton'>${set}</button>", |
---|
109 | "</td></tr></table>" |
---|
110 | ].join(""), |
---|
111 | |
---|
112 | _initButton: function(){ |
---|
113 | // summary: |
---|
114 | // Override _Plugin._initButton() to initialize DropDownButton and TooltipDialog. |
---|
115 | // tags: |
---|
116 | // protected |
---|
117 | var _this = this; |
---|
118 | this._messages = messages; |
---|
119 | this.tag = "img"; |
---|
120 | var dropDown = (this.dropDown = new TooltipDialog({ |
---|
121 | title: messages[this.command + "Title"], |
---|
122 | onOpen: function(){ |
---|
123 | _this._initialFileUploader(); |
---|
124 | _this._onOpenDialog(); |
---|
125 | TooltipDialog.prototype.onOpen.apply(this, arguments); |
---|
126 | setTimeout(function(){ |
---|
127 | // Auto-select the text if it is not empty |
---|
128 | _TextBoxMixin.selectInputText(_this._urlInput.textbox); |
---|
129 | _this._urlInput.isLoadComplete = true; |
---|
130 | }, 0); |
---|
131 | }, |
---|
132 | onClose: function(){ |
---|
133 | dojo.disconnect(_this.blurHandler); |
---|
134 | _this.blurHandler = null; |
---|
135 | this.onHide(); |
---|
136 | }, |
---|
137 | onCancel: function(){ |
---|
138 | setTimeout(dojo.hitch(_this, "_onCloseDialog"),0); |
---|
139 | } |
---|
140 | })); |
---|
141 | |
---|
142 | var label = this.getLabel(this.command), |
---|
143 | className = this.iconClassPrefix + " " + this.iconClassPrefix + this.command.charAt(0).toUpperCase() + this.command.substr(1), |
---|
144 | props = dojo.mixin({ |
---|
145 | label: label, |
---|
146 | showLabel: false, |
---|
147 | iconClass: className, |
---|
148 | dropDown: this.dropDown, |
---|
149 | tabIndex: "-1" |
---|
150 | }, this.params || {}); |
---|
151 | |
---|
152 | if(!has('ie')){ |
---|
153 | // Workaround for Non-IE problem: |
---|
154 | // Safari 5: After the select-file dialog opens, the first time the user clicks anywhere (even on that dialog) |
---|
155 | // it's treated like a plain click on the page, and the tooltip dialog closes |
---|
156 | // FF & Chrome: the select-file dialog does not block the execution of JS |
---|
157 | props.closeDropDown = function(/*Boolean*/ focus){ |
---|
158 | if(_this._closable){ |
---|
159 | if(this._opened){ |
---|
160 | popup.close(this.dropDown); |
---|
161 | if(focus){ this.focus(); } |
---|
162 | this._opened = false; |
---|
163 | this.state = ""; |
---|
164 | } |
---|
165 | } |
---|
166 | setTimeout(function(){ _this._closable = true; }, 10); |
---|
167 | }; |
---|
168 | } |
---|
169 | |
---|
170 | this.button = new DropDownButton(props); |
---|
171 | |
---|
172 | // Generate the RegExp of the ValidationTextBox from fileMask |
---|
173 | // *.jpg;*.png => /.*\.jpg|.*\.JPG|.*\.png|.*\.PNG/ |
---|
174 | var masks = this.fileMask.split(";"), |
---|
175 | temp = ""; |
---|
176 | dojo.forEach(masks, function(m){ |
---|
177 | m = m.replace(/\./, "\\.").replace(/\*/g, ".*"); |
---|
178 | temp += "|" + m + "|" + m.toUpperCase(); |
---|
179 | }); |
---|
180 | messages.urlRegExp = this.urlRegExp = temp.substring(1); |
---|
181 | |
---|
182 | if(!this.uploadable){ |
---|
183 | messages.prePopuTextBrowse = "."; |
---|
184 | } |
---|
185 | |
---|
186 | messages.id = registry.getUniqueId(this.editor.id); |
---|
187 | messages.uploadable = this.uploadable ? "inline" : "none"; |
---|
188 | this._uniqueId = messages.id; |
---|
189 | this._setContent("<div class='" + this._cssPrefix + "Title'>" + dropDown.title + "</div>" + |
---|
190 | dojo.string.substitute(this.linkDialogTemplate, messages)); |
---|
191 | dropDown.startup(); |
---|
192 | |
---|
193 | var urlInput = (this._urlInput = registry.byId(this._uniqueId + "_urlInput")); |
---|
194 | this._textInput = registry.byId(this._uniqueId + "_textInput"); |
---|
195 | this._setButton = registry.byId(this._uniqueId + "_setButton"); |
---|
196 | |
---|
197 | if(urlInput){ |
---|
198 | var pt = ValidationTextBox.prototype; |
---|
199 | urlInput = dojo.mixin(urlInput, { |
---|
200 | // Indicate if the widget is ready to validate the input text |
---|
201 | isLoadComplete: false, |
---|
202 | isValid: function(isFocused){ |
---|
203 | if(this.isLoadComplete){ |
---|
204 | return pt.isValid.apply(this, arguments); |
---|
205 | }else{ |
---|
206 | return this.get("value").length > 0; |
---|
207 | } |
---|
208 | }, |
---|
209 | reset: function(){ |
---|
210 | this.isLoadComplete = false; |
---|
211 | pt.reset.apply(this, arguments); |
---|
212 | } |
---|
213 | }); |
---|
214 | |
---|
215 | this.connect(urlInput, "onKeyDown", "_cancelFileUpload"); |
---|
216 | this.connect(urlInput, "onChange", "_checkAndFixInput"); |
---|
217 | } |
---|
218 | if(this._setButton){ |
---|
219 | this.connect(this._setButton, "onClick", "_checkAndSetValue"); |
---|
220 | } |
---|
221 | this._connectTagEvents(); |
---|
222 | }, |
---|
223 | |
---|
224 | _initialFileUploader: function(){ |
---|
225 | // summary: |
---|
226 | // Initialize the FileUploader and connect up its events |
---|
227 | // tags: |
---|
228 | // private |
---|
229 | var fup = null, |
---|
230 | _this = this, |
---|
231 | widgetId = _this._uniqueId, |
---|
232 | fUpId = widgetId + "_browse", |
---|
233 | urlInput = _this._urlInput; |
---|
234 | |
---|
235 | if(_this.uploadable && !_this._fileUploader){ |
---|
236 | fup = _this._fileUploader = new FileUploader({ |
---|
237 | force: "html", // Noticed that SWF may cause browsers to crash sometimes |
---|
238 | uploadUrl: _this.uploadUrl, |
---|
239 | htmlFieldName: _this.htmlFieldName, |
---|
240 | uploadOnChange: false, |
---|
241 | selectMultipleFiles: false, |
---|
242 | showProgress: true |
---|
243 | }, fUpId); |
---|
244 | |
---|
245 | // TooltipDialog will call reset on all the widgets contained within it. |
---|
246 | // Have FileUploader be responsive to this call. |
---|
247 | fup.reset = function(){ |
---|
248 | _this._isLocalFile = false; |
---|
249 | fup._resetHTML(); |
---|
250 | }; |
---|
251 | |
---|
252 | _this.connect(fup, "onClick", function(){ |
---|
253 | urlInput.validate(false); |
---|
254 | if(!has('ie')){ |
---|
255 | // Firefox, Chrome and Safari have a strange behavior: |
---|
256 | // When the File Upload dialog is open, the browse div (FileUploader) will lose its focus |
---|
257 | // and triggers onBlur event. This event will cause the whole tooltip dialog |
---|
258 | // to be closed when the File Upload dialog is open. The popup dialog should hang up |
---|
259 | // the js execution rather than triggering an event. IE does not have such a problem. |
---|
260 | _this._closable = false; |
---|
261 | } |
---|
262 | }); |
---|
263 | |
---|
264 | |
---|
265 | _this.connect(fup, "onChange", function(data){ |
---|
266 | _this._isLocalFile = true; |
---|
267 | urlInput.set("value", data[0].name); //Single selection |
---|
268 | urlInput.focus(); |
---|
269 | }); |
---|
270 | |
---|
271 | _this.connect(fup, "onComplete", function(data){ |
---|
272 | var urlPrefix = _this.baseImageUrl; |
---|
273 | urlPrefix = urlPrefix && urlPrefix.charAt(urlPrefix.length - 1) == "/" ? urlPrefix : urlPrefix + "/"; |
---|
274 | urlInput.set("value", urlPrefix + data[0].file); //Single selection |
---|
275 | _this._isLocalFile = false; |
---|
276 | _this._setDialogStatus(true); |
---|
277 | _this.setValue(_this.dropDown.get("value")); |
---|
278 | }); |
---|
279 | |
---|
280 | _this.connect(fup, "onError", function(evtObject){ |
---|
281 | // summary: |
---|
282 | // Fires on errors |
---|
283 | console.log("Error occurred when uploading image file!"); |
---|
284 | _this._setDialogStatus(true); |
---|
285 | }); |
---|
286 | } |
---|
287 | }, |
---|
288 | |
---|
289 | _checkAndFixInput: function(){ |
---|
290 | // summary: |
---|
291 | // Over-ride the original method |
---|
292 | this._setButton.set("disabled", !this._isValid()); |
---|
293 | }, |
---|
294 | |
---|
295 | _isValid: function(){ |
---|
296 | // summary: |
---|
297 | // Invalid cases: URL is not ended with the suffix listed |
---|
298 | return this._urlInput.isValid(); |
---|
299 | }, |
---|
300 | |
---|
301 | _cancelFileUpload: function(){ |
---|
302 | this._fileUploader.reset(); |
---|
303 | this._isLocalFile = false; |
---|
304 | }, |
---|
305 | |
---|
306 | _checkAndSetValue: function(){ |
---|
307 | // summary: |
---|
308 | // Determine if a local file is to be uploaded. |
---|
309 | // If a local file is to be uploaded, do not close the dialog |
---|
310 | // until the file uploading is finished. Else, insert the image directly into the editor. |
---|
311 | // tags: |
---|
312 | // private |
---|
313 | if(this._fileUploader && this._isLocalFile){ |
---|
314 | this._setDialogStatus(false); |
---|
315 | this._fileUploader.upload(); |
---|
316 | }else{ |
---|
317 | this.setValue(this.dropDown.get("value")); |
---|
318 | } |
---|
319 | }, |
---|
320 | |
---|
321 | _setDialogStatus: function(/*Boolean*/ value){ |
---|
322 | this._urlInput.set("disabled", !value); |
---|
323 | this._textInput.set("disabled", !value); |
---|
324 | this._setButton.set("disabled", !value); |
---|
325 | }, |
---|
326 | |
---|
327 | destroy: function(){ |
---|
328 | // summary: |
---|
329 | // Cleanup of the plugin. |
---|
330 | this.inherited(arguments); |
---|
331 | if(this._fileUploader){ |
---|
332 | this._fileUploader.destroy(); |
---|
333 | delete this._fileUploader; |
---|
334 | } |
---|
335 | } |
---|
336 | }); |
---|
337 | |
---|
338 | var plugin = function(args){ |
---|
339 | return new LocalImage({ |
---|
340 | command: "insertImage", |
---|
341 | uploadable: ("uploadable" in args) ? args.uploadable : false, |
---|
342 | uploadUrl: ("uploadable" in args && "uploadUrl" in args) ? args.uploadUrl : "", |
---|
343 | htmlFieldName: ("uploadable" in args && "htmlFieldName" in args) ? args.htmlFieldName : "uploadedfile", |
---|
344 | baseImageUrl: ("uploadable" in args && "baseImageUrl" in args) ? args.baseImageUrl : "", |
---|
345 | fileMask: ("fileMask" in args) ? args.fileMask : "*.jpg;*.jpeg;*.gif;*.png;*.bmp" |
---|
346 | }); |
---|
347 | }; |
---|
348 | |
---|
349 | // Register the plugin and some name varients. |
---|
350 | _Plugin.registry["LocalImage"] = plugin; |
---|
351 | _Plugin.registry["localImage"] = plugin; |
---|
352 | _Plugin.registry["localimage"] = plugin; |
---|
353 | |
---|
354 | return LocalImage; |
---|
355 | |
---|
356 | }); |
---|