ComboBox.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. /**
  2. * ComboBox.js
  3. *
  4. * Copyright, Moxiecode Systems AB
  5. * Released under LGPL License.
  6. *
  7. * License: http://www.tinymce.com/license
  8. * Contributing: http://www.tinymce.com/contributing
  9. */
  10. /**
  11. * This class creates a combobox control. Select box that you select a value from or
  12. * type a value into.
  13. *
  14. * @-x-less ComboBox.less
  15. * @class tinymce.ui.ComboBox
  16. * @extends tinymce.ui.Widget
  17. */
  18. define("tinymce/ui/ComboBox", [
  19. "tinymce/ui/Widget",
  20. "tinymce/ui/Factory",
  21. "tinymce/ui/DomUtils"
  22. ], function(Widget, Factory, DomUtils) {
  23. "use strict";
  24. return Widget.extend({
  25. /**
  26. * Constructs a new control instance with the specified settings.
  27. *
  28. * @constructor
  29. * @param {Object} settings Name/value object with settings.
  30. * @setting {String} placeholder Placeholder text to display.
  31. */
  32. init: function(settings) {
  33. var self = this;
  34. self._super(settings);
  35. self.addClass('combobox');
  36. self.subinput = true;
  37. self.ariaTarget = 'inp'; // TODO: Figure out a better way
  38. settings = self.settings;
  39. settings.menu = settings.menu || settings.values;
  40. if (settings.menu) {
  41. settings.icon = 'caret';
  42. }
  43. self.on('click', function(e) {
  44. var elm = e.target, root = self.getEl();
  45. while (elm && elm != root) {
  46. if (elm.id && elm.id.indexOf('-open') != -1) {
  47. self.fire('action');
  48. if (settings.menu) {
  49. self.showMenu();
  50. if (e.aria) {
  51. self.menu.items()[0].focus();
  52. }
  53. }
  54. }
  55. elm = elm.parentNode;
  56. }
  57. });
  58. // TODO: Rework this
  59. self.on('keydown', function(e) {
  60. if (e.target.nodeName == "INPUT" && e.keyCode == 13) {
  61. self.parents().reverse().each(function(ctrl) {
  62. e.preventDefault();
  63. self.fire('change');
  64. if (ctrl.hasEventListeners('submit') && ctrl.toJSON) {
  65. ctrl.fire('submit', {data: ctrl.toJSON()});
  66. return false;
  67. }
  68. });
  69. }
  70. });
  71. if (settings.placeholder) {
  72. self.addClass('placeholder');
  73. self.on('focusin', function() {
  74. if (!self._hasOnChange) {
  75. DomUtils.on(self.getEl('inp'), 'change', function() {
  76. self.fire('change');
  77. });
  78. self._hasOnChange = true;
  79. }
  80. if (self.hasClass('placeholder')) {
  81. self.getEl('inp').value = '';
  82. self.removeClass('placeholder');
  83. }
  84. });
  85. self.on('focusout', function() {
  86. if (self.value().length === 0) {
  87. self.getEl('inp').value = settings.placeholder;
  88. self.addClass('placeholder');
  89. }
  90. });
  91. }
  92. },
  93. showMenu: function() {
  94. var self = this, settings = self.settings, menu;
  95. if (!self.menu) {
  96. menu = settings.menu || [];
  97. // Is menu array then auto constuct menu control
  98. if (menu.length) {
  99. menu = {
  100. type: 'menu',
  101. items: menu
  102. };
  103. } else {
  104. menu.type = menu.type || 'menu';
  105. }
  106. self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm());
  107. self.fire('createmenu');
  108. self.menu.reflow();
  109. self.menu.on('cancel', function(e) {
  110. if (e.control === self.menu) {
  111. self.focus();
  112. }
  113. });
  114. self.menu.on('show hide', function(e) {
  115. e.control.items().each(function(ctrl) {
  116. ctrl.active(ctrl.value() == self.value());
  117. });
  118. }).fire('show');
  119. self.menu.on('select', function(e) {
  120. self.value(e.control.value());
  121. });
  122. self.on('focusin', function(e) {
  123. if (e.target.tagName.toUpperCase() == 'INPUT') {
  124. self.menu.hide();
  125. }
  126. });
  127. self.aria('expanded', true);
  128. }
  129. self.menu.show();
  130. self.menu.layoutRect({w: self.layoutRect().w});
  131. self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']);
  132. },
  133. /**
  134. * Getter/setter function for the control value.
  135. *
  136. * @method value
  137. * @param {String} [value] Value to be set.
  138. * @return {String|tinymce.ui.ComboBox} Value or self if it's a set operation.
  139. */
  140. value: function(value) {
  141. var self = this;
  142. if (typeof(value) != "undefined") {
  143. self._value = value;
  144. self.removeClass('placeholder');
  145. if (self._rendered) {
  146. self.getEl('inp').value = value;
  147. }
  148. return self;
  149. }
  150. if (self._rendered) {
  151. value = self.getEl('inp').value;
  152. if (value != self.settings.placeholder) {
  153. return value;
  154. }
  155. return '';
  156. }
  157. return self._value;
  158. },
  159. /**
  160. * Getter/setter function for the disabled state.
  161. *
  162. * @method value
  163. * @param {Boolean} [state] State to be set.
  164. * @return {Boolean|tinymce.ui.ComboBox} True/false or self if it's a set operation.
  165. */
  166. disabled: function(state) {
  167. var self = this;
  168. if (self._rendered && typeof(state) != 'undefined') {
  169. self.getEl('inp').disabled = state;
  170. }
  171. return self._super(state);
  172. },
  173. /**
  174. * Focuses the input area of the control.
  175. *
  176. * @method focus
  177. */
  178. focus: function() {
  179. this.getEl('inp').focus();
  180. },
  181. /**
  182. * Repaints the control after a layout operation.
  183. *
  184. * @method repaint
  185. */
  186. repaint: function() {
  187. var self = this, elm = self.getEl(), openElm = self.getEl('open'), rect = self.layoutRect();
  188. var width, lineHeight;
  189. if (openElm) {
  190. width = rect.w - DomUtils.getSize(openElm).width - 10;
  191. } else {
  192. width = rect.w - 10;
  193. }
  194. // Detect old IE 7+8 add lineHeight to align caret vertically in the middle
  195. var doc = document;
  196. if (doc.all && (!doc.documentMode || doc.documentMode <= 8)) {
  197. lineHeight = (self.layoutRect().h - 2) + 'px';
  198. }
  199. DomUtils.css(elm.firstChild, {
  200. width: width,
  201. lineHeight: lineHeight
  202. });
  203. self._super();
  204. return self;
  205. },
  206. /**
  207. * Post render method. Called after the control has been rendered to the target.
  208. *
  209. * @method postRender
  210. * @return {tinymce.ui.ComboBox} Current combobox instance.
  211. */
  212. postRender: function() {
  213. var self = this;
  214. DomUtils.on(this.getEl('inp'), 'change', function() {
  215. self.fire('change');
  216. });
  217. return self._super();
  218. },
  219. remove: function() {
  220. DomUtils.off(this.getEl('inp'));
  221. this._super();
  222. },
  223. /**
  224. * Renders the control as a HTML string.
  225. *
  226. * @method renderHtml
  227. * @return {String} HTML representing the control.
  228. */
  229. renderHtml: function() {
  230. var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix;
  231. var value = settings.value || settings.placeholder || '';
  232. var icon, text, openBtnHtml = '', extraAttrs = '';
  233. if ("spellcheck" in settings) {
  234. extraAttrs += ' spellcheck="' + settings.spellcheck + '"';
  235. }
  236. if (settings.maxLength) {
  237. extraAttrs += ' maxlength="' + settings.maxLength + '"';
  238. }
  239. if (settings.size) {
  240. extraAttrs += ' size="' + settings.size + '"';
  241. }
  242. if (settings.subtype) {
  243. extraAttrs += ' type="' + settings.subtype + '"';
  244. }
  245. if (self.disabled()) {
  246. extraAttrs += ' disabled="disabled"';
  247. }
  248. icon = settings.icon;
  249. if (icon && icon != 'caret') {
  250. icon = prefix + 'ico ' + prefix + 'i-' + settings.icon;
  251. }
  252. text = self._text;
  253. if (icon || text) {
  254. openBtnHtml = (
  255. '<div id="' + id + '-open" class="' + prefix + 'btn ' + prefix + 'open" tabIndex="-1" role="button">' +
  256. '<button id="' + id + '-action" type="button" hidefocus="1" tabindex="-1">' +
  257. (icon != 'caret' ? '<i class="' + icon + '"></i>' : '<i class="' + prefix + 'caret"></i>') +
  258. (text ? (icon ? ' ' : '') + text : '') +
  259. '</button>' +
  260. '</div>'
  261. );
  262. self.addClass('has-open');
  263. }
  264. return (
  265. '<div id="' + id + '" class="' + self.classes() + '">' +
  266. '<input id="' + id + '-inp" class="' + prefix + 'textbox ' + prefix + 'placeholder" value="' +
  267. value + '" hidefocus="1"' + extraAttrs + ' />' +
  268. openBtnHtml +
  269. '</div>'
  270. );
  271. }
  272. });
  273. });