1 /**
  2  * @fileOverview xq.controls provides common UI elements such as dialog.
  3  */
  4 xq.controls = {};
  5 
  6 
  7 
  8 xq.controls.FormDialog = Class.create({
  9 	/**
 10      * @constructor
 11      *
 12      * @param {String} html HTML string which contains FORM
 13      * @param {Function} [onLoadHandler] callback function to be called when the form is loaded
 14 	 */
 15 	initialize: function(xed, html, onLoadHandler, onCloseHandler) {
 16 		this.xed = xed;
 17 		this.html = html;
 18 		this.onLoadHandler = onLoadHandler || function() {};
 19 		this.onCloseHandler = onCloseHandler || function() {};
 20 		this.form = null;
 21 	},
 22 	/**
 23 	 * Show dialog
 24 	 *
 25 	 * @param {Object} [options] collection of options
 26 	 */
 27 	show: function(options) {
 28 		options = options || {};
 29 		options.position = options.position || 'centerOfWindow';
 30 		options.mode = options.mode || 'modal';
 31 		options.cancelOnEsc = options.cancelOnEsc || true;
 32 		
 33 		var self = this;
 34 		
 35 		// create and append container
 36 		var container = $(document.createElement('DIV'));
 37 		container.style.display = 'none';
 38 		document.body.appendChild(container);
 39 		
 40 		// initialize form
 41 		container.innerHTML = this.html;
 42 		this.form = $(container.getElementsByTagName('FORM')[0]);
 43 		
 44 		this.form.onsubmit = function() {
 45 			self.onCloseHandler($(this).serialize(true));
 46 			self.close();
 47 			return false;
 48 		};
 49 		
 50 		var cancelButton = this.form.getElementsByClassName('cancel')[0];
 51 		cancelButton.onclick = function() {
 52 			self.onCloseHandler();
 53 			self.close();
 54 		};
 55 		
 56 		// append dialog
 57 		document.body.appendChild(this.form);
 58 		container.parentNode.removeChild(container);
 59 		
 60 		// place dialog to center of window
 61 		this.setPosition(options.position);
 62 		
 63 		// give focus
 64 		var elementToFocus = this.form.getElementsByClassName('initialFocus');
 65 		if(elementToFocus.length > 0) elementToFocus[0].focus();
 66 		
 67 		// handle cancelOnEsc option
 68 		if(options.cancelOnEsc) {
 69 			Event.observe(this.form, 'keydown', function(e) {
 70 				if(e.keyCode == 27) {
 71 					this.onCloseHandler();
 72 					this.close();
 73 				}
 74 				return false;
 75 			}.bind(this));
 76 		}
 77 		
 78 		this.onLoadHandler(this);
 79 	},
 80 	close: function() {
 81 		this.form.parentNode.removeChild(this.form);
 82 	},
 83 	setPosition: function(target) {
 84 		var targetElement;
 85 		
 86 		if(target == 'centerOfWindow') {
 87 			targetElement = document.documentElement;
 88 		} else if(target == 'centerOfEditor') {
 89 			targetElement = this.xed.getDoc()[xq.Browser.isTrident ? "body" : "documentElement"];
 90 		} else if(target == 'nearbyCaret') {
 91 			throw "Not implemented yet";
 92 		} else {
 93 			throw "Invalid argument: " + target;
 94 		}
 95 		
 96 		var targetWidth = targetElement.clientWidth;
 97 		var targetHeight = targetElement.clientHeight;
 98 		var dialogWidth = this.form.clientWidth;
 99 		var dialogHeight = this.form.clientHeight;
100 		
101 		var x = parseInt((targetWidth - dialogWidth) / 2);
102 		var y = parseInt((targetHeight - dialogHeight) / 2);
103 		
104 		this.form.style.left = x + "px";
105 		this.form.style.top = y + "px";
106 	}
107 })
108 
109 
110 
111 xq.controls.QuickSearchDialog = Class.create({
112 	/**
113      * @constructor
114 	 */
115 	initialize: function(xed, param) {
116 		this.xed = xed;
117 		
118 		this.rdom = xq.RichDom.createInstance();
119 		this.rdom.setRoot(document.body);
120 		
121 		this.param = param;
122 		if(!this.param.renderItem) this.param.renderItem = function(item) {
123 			return this.rdom.getInnerText(item);
124 		}.bind(this);
125 		
126 		this.container = null;
127 	},
128 	
129 	getQuery: function() {
130 		if(!this.container) return "";
131 		return this._getInputField().value;
132 	},
133 	
134 	onSubmit: function(e) {
135 		if(this.matchCount() > 0) {
136 			this.param.onSelect(this.xed, this.list[this._getSelectedIndex()]);
137 		}
138 		
139 		this.close();
140 		Event.stop(e);
141 		return false;
142 	},
143 	
144 	onCancel: function(e) {
145 		if(this.param.onCancel) this.param.onCancel(this.xed);
146 		this.close();
147 	},
148 	
149 	onBlur: function(e) {
150 		// TODO: Ugly
151 		setTimeout(function() {this.onCancel(e)}.bind(this), 400);
152 	},
153 	
154 	onKey: function(e) {
155 		var esc = new xq.Shortcut("ESC");
156 		var enter = new xq.Shortcut("ENTER");
157 		var up = new xq.Shortcut("UP");
158 		var down = new xq.Shortcut("DOWN");
159 		
160 		if(esc.matches(e)) {
161 			this.onCancel(e);
162 		} else if(enter.matches(e)) {
163 			this.onSubmit(e);
164 		} else if(up.matches(e)) {
165 			this._moveSelectionUp();
166 		} else if(down.matches(e)) {
167 			this._moveSelectionDown();
168 		} else {
169 			this.updateList();
170 		}
171 	},
172 	
173 	onClick: function(e) {
174 		var target = e.srcElement || e.target;
175 		if(target.nodeName == "LI") {
176 			
177 			var index = this._getIndexOfLI(target);
178 			this.param.onSelect(this.xed, this.list[index]);
179 		}
180 	},
181 	
182 	onList: function(list) {
183 		this.list = list;
184 		this.renderList(list);
185 	},
186 	
187 	updateList: function() {
188 		window.setTimeout(function() {
189 			this.param.listProvider(this.getQuery(), this.xed, this.onList.bind(this));
190 		}.bind(this), 0);
191 	},
192 	
193 	renderList: function(list) 
194 	{
195 		var ol = this._getListContainer();
196 		ol.innerHTML = "";
197 		
198 		for(var i = 0; i < list.length; i++) {
199 			var li = this.rdom.createElement('LI');
200 			li.innerHTML = this.param.renderItem(list[i]);
201 			ol.appendChild(li);
202 		}
203 		
204 		if(ol.hasChildNodes()) {
205 			ol.firstChild.className = "selected";
206 		}
207 	},
208 	
209 	show: function() {
210 		if(!this.container) this.container = this._create();
211 		
212 		var dialog = this.rdom.insertNodeAt(this.container, this.rdom.getRoot(), "end");
213 		this.setPosition('centerOfEditor');
214 		this.updateList();
215 		this.focus();
216 	},
217 	
218 	close: function() {
219 		this.rdom.deleteNode(this.container);
220 	},
221 	
222 	focus: function() {
223 		this._getInputField().focus();
224 	},
225 	
226 	setPosition: function(target) {
227 		var targetElement;
228 		
229 		if(target == 'centerOfWindow') {
230 			targetElement = document.documentElement;
231 		} else if(target == 'centerOfEditor') {
232 			targetElement = this.xed.getDoc().documentElement;
233 		} else if(target == 'nearbyCaret') {
234 			throw "Not implemented yet";
235 		} else {
236 			throw "Invalid argument: " + target;
237 		}
238 		
239 		var targetWidth = targetElement.clientWidth;
240 		var targetHeight = targetElement.clientHeight;
241 		var dialogWidth = this.container.clientWidth;
242 		var dialogHeight = this.container.clientHeight;
243 		
244 		var x = parseInt((targetWidth - dialogWidth) / 2);
245 		var y = parseInt((targetHeight - dialogHeight) / 2);
246 		this.container.style.left = x + "px";
247 		this.container.style.top = y + "px";
248 	},
249 	
250 	matchCount: function() {
251 		return this.list ? this.list.length : 0;
252 	},
253 	
254 	_create: function() {
255 		// make container
256 		var container = this.rdom.createElement("DIV");
257 		container.className = "xqQuickSearch";
258 		
259 		// make title
260 		if(this.param.title) {
261 			var title = this.rdom.createElement("H1");
262 			title.innerHTML = this.param.title;
263 			container.appendChild(title);
264 		}
265 		
266 		// make input field
267 		var inputWrapper = this.rdom.createElement("DIV");
268 		inputWrapper.className = "input";
269 		var form = this.rdom.createElement("FORM");
270 		var input = this.rdom.createElement("INPUT");
271 		input.type = "text";
272 		input.value = "";
273     	form.appendChild(input);
274 		inputWrapper.appendChild(form);
275 		container.appendChild(inputWrapper);
276 		
277 		// make list
278 		var list = this.rdom.createElement("OL");
279 
280 	    Event.observe(input, 'blur', this.onBlur.bindAsEventListener(this));
281     	Event.observe(input, 'keypress', this.onKey.bindAsEventListener(this));
282     	Event.observe(list, 'click', this.onClick.bindAsEventListener(this), true);
283     	Event.observe(form, 'submit', this.onSubmit.bindAsEventListener(this));
284     	Event.observe(form, 'reset', this.onCancel.bindAsEventListener(this));
285 
286 		container.appendChild(list);
287 		return container;
288 	},
289 	
290 	_getInputField: function() {
291 		return this.container.getElementsByTagName('INPUT')[0];
292 	},
293 	
294 	_getListContainer: function() {
295 		return this.container.getElementsByTagName('OL')[0];
296 	},
297 	
298 	_getSelectedIndex: function() {
299 		var ol = this._getListContainer();
300 		for(var i = 0; i < ol.childNodes.length; i++) {
301 			if(ol.childNodes[i].className == 'selected') return i;
302 		}
303 	},
304 	
305 	_getIndexOfLI: function(li) {
306 		var ol = this._getListContainer();
307 		for(var i = 0; i < ol.childNodes.length; i++) {
308 			if(ol.childNodes[i] == li) return i;
309 		}
310 	},
311 	
312 	_moveSelectionUp: function() {
313 		var count = this.matchCount();
314 		if(count == 0) return;
315 		var index = this._getSelectedIndex();
316 		var ol = this._getListContainer();
317 		ol.childNodes[index].className = "";
318 		
319 		index--;
320 		if(index < 0) index = count - 1;
321 
322 		ol.childNodes[index].className = "selected";
323 	},
324 	
325 	_moveSelectionDown: function() {
326 		var count = this.matchCount();
327 		if(count == 0) return;
328 		var index = this._getSelectedIndex();
329 		var ol = this._getListContainer();
330 		ol.childNodes[index].className = "";
331 
332 		index++;
333 		if(index >= count) index = 0;
334 		
335 		ol.childNodes[index].className = "selected";
336 	}
337 });