Flip some state flags in an attempt to get mouse events working.
[armadillo.git] / web_frontend / path_control.js
1 //
2 // Armadillo File Manager
3 // Copyright (c) 2010, Robert Sesek <http://www.bluestatic.org>
4 //
5 // This program is free software: you can redistribute it and/or modify it under
6 // the terms of the GNU General Public License as published by the Free Software
7 // Foundation, either version 3 of the License, or any later version.
8 //
9
10 goog.provide('armadillo.PathControl');
11 goog.provide('armadillo.PathControl.NameControlRenderer_');
12
13 goog.require('goog.array');
14 goog.require('goog.ui.Control');
15 goog.require('goog.ui.FilteredMenu');
16 goog.require('goog.ui.LabelInput');
17 goog.require('goog.ui.MenuButton');
18 goog.require('goog.ui.MenuItem');
19
20 /**
21 * Creates a new path editing control for a given path.
22 * @param {string} path The path to create an editor for
23 * @param {bool} editLastComponent Whether the last component should be shown as an edit box
24 * @param {DomHelper} opt_domHelper Optional DOM helper
25 * @constructor
26 */
27 armadillo.PathControl = function(path, editLastComponent, opt_domHelper) {
28 goog.ui.Control.call(this, opt_domHelper);
29
30 this.setSupportedState(goog.ui.Component.State.FOCUSED, false);
31
32 /**
33 * Full path of the control.
34 * @type {string}
35 */
36 this.path_ = null;
37
38 /**
39 * The name of the file at the |path_|.
40 * @type {string}
41 */
42 this.name_ = null;
43 this.setPath(path);
44
45 /**
46 * Whether or not the last component is editable.
47 * @type {bool}
48 */
49 this.editableLastComponent_ = editLastComponent;
50
51 /**
52 * Control UI for the name component of the path.
53 * @type {goog.ui.Control}
54 */
55 this.nameControl_ = null;
56
57 /**
58 * Event Handler
59 * @type {goog.events.EventHandler}
60 */
61 this.eh_ = new goog.events.EventHandler();
62 };
63 goog.inherits(armadillo.PathControl, goog.ui.Control);
64
65 /**
66 * Disposer
67 * @protected
68 */
69 armadillo.PathControl.prototype.disposeInternal = function() {
70 armadillo.PathControl.superClass_.disposeInternal.call(this);
71 this.nameControl_ = null;
72
73 this.eh_.dispose();
74 this.eh_ = null;
75 };
76
77 /**
78 * Sets the path.
79 * @param {string} path
80 */
81 armadillo.PathControl.prototype.setPath = function(path) {
82 this.path_ = app.stripLastPathComponent(path);
83 this.name_ = path.substr(this.path_.length);
84 };
85
86 /**
87 * Gets the new path.
88 * @returns {string}
89 */
90 armadillo.PathControl.prototype.getPath = function() {
91 return app.joinPath(this.path_, this.name_);
92 };
93
94 /**
95 * Gets the name control.
96 * @returns {goog.ui.Control}
97 */
98 armadillo.PathControl.prototype.getNameControl = function() {
99 return this.nameControl_;
100 };
101
102 /**
103 * Creates a new path control object.
104 */
105 armadillo.PathControl.prototype.createDom = function() {
106 this.decorateInternal(this.dom_.createElement('div'));
107 };
108
109 /**
110 * @inheritDoc
111 */
112 armadillo.PathControl.prototype.canDecorate = function() {
113 return true;
114 };
115
116 /**
117 * Decorates the given element into a path control.
118 * @param {Element} element
119 */
120 armadillo.PathControl.prototype.decorateInternal = function(element) {
121 this.element_ = element;
122 var components = this.path_.split('/');
123
124 // If this is an item that lives at the root, generate a special node for
125 // moving between items at the top level.
126 components[0] = '/';
127
128 // If the last component is emtpy, do not use it because it means a directory
129 // is being moved.
130 if (components[components.length - 1] == '') {
131 goog.array.removeAt(components, components.length - 1);
132 }
133
134 var path = '';
135 goog.array.forEach(components, function (part, i) {
136 this.addChild(this.createComponentNode_(path, part), true);
137 path = app.joinPath(path, part);
138 }, this);
139
140 if (this.editableLastComponent_) {
141 var attrs = {
142 'type' : 'text',
143 'name' : 'pathName',
144 'value' : this.name_
145 };
146 this.nameControl_ = new goog.ui.Control(this.dom_.createDom('input', attrs),
147 new armadillo.PathControl.NameControlRenderer_());
148 this.nameControl_.setAllowTextSelection(true);
149 this.nameControl_.setHandleMouseEvents(false);
150 this.addChild(this.nameControl_, true);
151
152 this.eh_.listen(this.nameControl_.getElement(), goog.events.EventType.CHANGE,
153 this.nameChanged_, false, this);
154 this.eh_.listen(this.nameControl_.getElement(), goog.events.EventType.KEYDOWN,
155 this.nameChanged_, false, this);
156 } else {
157 this.nameControl_ = new goog.ui.Control(this.name_);
158 this.addChild(this.nameControl_, true);
159 }
160 goog.dom.classes.add(this.nameControl_.getElement(), 'goog-inline-block');
161 };
162
163 /**
164 * @inheritDoc
165 */
166 armadillo.PathControl.prototype.enterDocument = function() {
167 armadillo.PathControl.superClass_.enterDocument.call(this);
168 this.nameControl_.getElement().focus();
169 };
170
171 /**
172 * Creates a node for a single path component.
173 * @param {string} path The path up to this point.
174 * @param {string} name The current component after |path|.
175 */
176 armadillo.PathControl.prototype.createComponentNode_ = function(path, name) {
177 var menu = new goog.ui.FilteredMenu();
178 menu.setFilterLabel(name);
179 menu.setAllowMultiple(false);
180 menu.setOpenFollowsHighlight(true);
181 goog.events.listen(menu, goog.ui.Component.EventType.ACTION,
182 this.componentChanged_, false, this);
183 this.fetchMenuContents_(path, name, menu);
184
185 var button = new goog.ui.MenuButton(name, menu, null, this.dom_);
186 button.setFocusablePopupMenu(true);
187 button.setScrollOnOverflow(true);
188 button.setVisible(true);
189 return button;
190 };
191
192 /**
193 * Queries the back-end for all the items at a given path and attaches them to
194 * the given menu.
195 * @param {string} path The path to get a list of items in
196 * @param {string} name The name to select
197 * @param {goog.ui.Menu} The menu to attach items to
198 */
199 armadillo.PathControl.prototype.fetchMenuContents_ = function(path, name, menu) {
200 var callback = function(e) {
201 var data = e.target.getResponseJson();
202 if (data['error']) {
203 app.showError(data['message']);
204 return;
205 }
206 if (path == '') {
207 // If this is the root path element, make sure the root is accessible for
208 // moving items.
209 goog.array.insertAt(data, '/', 0);
210 }
211 goog.array.forEach(data, function (caption) {
212 // It only makes sense to be able to move into directories.
213 if (!app.isDirectory(caption)) {
214 return;
215 }
216 var item = new goog.ui.MenuItem(caption);
217 item.setValue(app.joinPath(path, name, caption));
218 menu.addItem(item);
219 if (caption == name) {
220 menu.setHighlighted(item);
221 }
222 });
223 };
224 app.sendRequest('list', {'path' : app.joinPath(path, name)}, callback);
225 };
226
227 /**
228 * Handler for changing a component of the control.
229 * @param {Event} e
230 */
231 armadillo.PathControl.prototype.componentChanged_ = function(e) {
232 this.path_ = e.target.getValue();
233 this.removeChildren(true);
234 this.decorateInternal(this.element_);
235 };
236
237 /**
238 * Handler for changing the editable name component.
239 * @param {Event} e
240 */
241 armadillo.PathControl.prototype.nameChanged_ = function(e) {
242 // TODO: assert(this.editableLastComponent_)
243 this.name_ = e.target.value;
244 e.stopPropagation();
245 return true;
246 };
247
248 /**
249 * Renderer for the Name Control of the Path Control
250 * @constructor_
251 */
252 armadillo.PathControl.NameControlRenderer_ = function() {
253 goog.ui.ControlRenderer.call(this);
254 };
255 goog.inherits(armadillo.PathControl.NameControlRenderer_, goog.ui.ControlRenderer);
256
257 armadillo.PathControl.NameControlRenderer_.prototype.createDom = function(control) {
258 var content = control.getContent();
259 if (content instanceof HTMLElement) {
260 return content;
261 }
262 return armadillo.PathControl.NameControlRenderer_.superClass_.createDom.call(this, control);
263 };