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