CCS.Beeswax.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. // Licensed to Cloudera, Inc. under one
  2. // or more contributor license agreements. See the NOTICE file
  3. // distributed with this work for additional information
  4. // regarding copyright ownership. Cloudera, Inc. licenses this file
  5. // to you under the Apache License, Version 2.0 (the
  6. // "License"); you may not use this file except in compliance
  7. // with the License. You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. /*
  17. ---
  18. description: Beeswax (the Hive UI)
  19. provides: [CCS.Beeswax]
  20. requires: [ccs-shared/CCS.JBrowser, clientcide/TabSwapper, More/Form.Validator.Inline, More/Fx.Accordion, ccs-shared/DynamicTextarea, ccs-shared/CCS.PostEditor.Simple, ccs-shared/CCS.JFrame.Chooser]
  21. script: CCS.Beeswax.js
  22. ...
  23. */
  24. ART.Sheet.define('window.beeswax.browser', {
  25. 'min-width': 740
  26. });
  27. ART.Sheet.define('splitview.bw-editor', {
  28. 'right-background-color': '#333'
  29. });
  30. (function(){
  31. CCS.Beeswax = new Class({
  32. Extends: CCS.JBrowser,
  33. options: {
  34. displayHistory: false,
  35. className: 'art beeswax browser logo_header',
  36. height: 350,
  37. jframeOptions: {
  38. clickRelays: 'a, .relays'
  39. }
  40. },
  41. initialize: function(path, options){
  42. this.parent(path || '/beeswax/', options);
  43. this.jframe.addShortcuts({
  44. 'Open Selected': {
  45. keys: 'enter',
  46. shortcut: 'enter',
  47. handler: function(e){
  48. var table = $(this).getElement('[data-filters*=HtmlTable]');
  49. if (!table) return;
  50. hTable = table.retrieve('HtmlTable');
  51. var selected = hTable.selectedRows[0];
  52. if (!selected) return;
  53. CCS.JFrame.doubleClickHandler(this.jframe, selected);
  54. }.bind(this),
  55. description: 'Open the selected item.'
  56. }
  57. });
  58. //linkers for beewswax
  59. this.jframe.addLinkers({
  60. //toggles the display of a group of elements when another is clicked
  61. '[data-group-toggle]': function(event, el) {
  62. /*
  63. get the json data for the toggle
  64. looks like:
  65. {
  66. 'group': '.bw-config-data li', //the elements in the group
  67. 'show':'.bw-serde-options' //the element that you show when you click; all others in the group get hidden
  68. }
  69. See Also: the Collapsible JFrame pattern, which I need to update to work similar to this.
  70. The main diff being that this is about toggling a group; one shows while the others hide.
  71. */
  72. var toggles = JSON.decode(el.get('data-group-toggle'));
  73. var sections = $(this).getElements(toggles.group);
  74. var show = $(this).getElements(toggles.show);
  75. sections.each(function(section) {
  76. section.setStyle('display', show.contains(section) ? 'block' : 'none');
  77. });
  78. },
  79. '.ccs-form_submit': function(event, el) {
  80. /*
  81. submit the form that the element is in.
  82. el.getParent('form').formUpdate();
  83. */
  84. el.getParent('form').retrieve('form.request').setOptions({extraData: el.getJSONData('extraData')}).send();
  85. }
  86. });
  87. this.jframe.addFilters({
  88. // breadcrumb pattern; has to set z-index programatically for layout
  89. breadcrumb: function(container){
  90. if (!container.get('html').contains('ccs-breadcrumb')) return;
  91. container.getElements('.ccs-breadcrumb').each(function(bc) {
  92. var items = bc.getElements('li');
  93. items.reverse().each(function(item, i){
  94. item.setStyle('z-index', i+1);
  95. });
  96. });
  97. },
  98. //breadcrumb form; displays a breadcrumb that grows as you fill out the form
  99. breadcrumb_form: function(container){
  100. if (!container.get('html').contains('ccs-bc-form')) return;
  101. /*
  102. .ccs-bc-form is a UL with LI items for each breadcrumb; looks like this:
  103. <ul class="ccs-bc-form ccs-breadcrumb clearfix" data-bc-sections=".ccs-bc-section" data-bc-form="form">
  104. <li><a href="#step1">One</a></li>
  105. <li><a href="#step2">Two</a></li>
  106. <li><a href="#step3">Ect</a></li>
  107. </ul>
  108. it has a data-bc-sections definition which is a selector to select the sections that correlate to the bc links
  109. it has a data-bc-form selector that correlates to the form for this bc form.
  110. */
  111. container.getElements('.ccs-bc-form').each(function(bc){
  112. var items = bc.getElements('li');
  113. var form = container.getElement(bc.get('data-bc-form')) || container.getElement('form');
  114. var sections = container.getElements(bc.get('data-bc-sections'));
  115. //extend tab swapper; we need to manage things like OverText and FormValidator when we change sections
  116. var TS = new Class({
  117. Extends: TabSwapper,
  118. showSection: function(index) {
  119. //we validate the form when the user pages forward and not on the first display when this.now is still undefined
  120. //(where this.now is the current index of the TabSwapper; it's undefined on init)
  121. var validate = index > this.now && this.now != undefined;
  122. if (!validate) {
  123. //reset the error state
  124. form.get('validator').reset();
  125. if (this.now != undefined) items[this.now].removeClass('ccs-bc-error');
  126. }
  127. if (!validate || getValidator(form).validate()) {
  128. //show the section if we're valid
  129. this.parent(index);
  130. //show the overtext that might have been hidden
  131. OverText.update();
  132. }
  133. return this;
  134. }
  135. });
  136. //get the form validator and add an event for when elements are validated
  137. //if the element fails, get the nav item and breadcrumb section and add an error class for styling
  138. getValidator(form).addEvents({
  139. elementValidate: function(valid, field){
  140. var section = field.getParent(bc.get('data-bc-sections'));
  141. var nav = items[sections.indexOf(section)];
  142. if (valid) nav.removeClass('ccs-bc-error');
  143. else nav.addClass('ccs-bc-error');
  144. }
  145. });
  146. //create our modified tabswapper
  147. var tabs = new TS({
  148. tabs: items,
  149. sections: sections,
  150. smooth: true
  151. });
  152. //find any sections with an element with the class .ccs-multipart-next and make that
  153. //next button change tabs; note that this is the only way the user can progress forward (for now)
  154. //todo: add some keyboard love; enter, tab, arrows, etc.
  155. sections.each(function(section, i) {
  156. section.getElements('.ccs-multipart-next').addEvent('click', function(e){
  157. e.stop();
  158. if (getValidator(form).validate()) tabs.show(i+1);
  159. });
  160. });
  161. });
  162. },
  163. //displays an input field when the user choose "other" in a select input.
  164. //
  165. // Note that this overwrites the default JFrameFilter because it has extra
  166. // validation logic.
  167. select_with_other: function(container) {
  168. if (!container.get('html').contains('ccs-select-with-other')) return;
  169. container.getElements('.ccs-select-with-other').each(function(el) {
  170. //get the 'other' input
  171. var other = el.getElement('input').set('alt', 'Enter a custom value').addClass('required').addDataFilter('OverText');
  172. //create hint text
  173. var ot = new OverText(other);
  174. //get the select input
  175. var sel = el.getElement('select');
  176. //when the select changes, if the user chooses "other"
  177. //reveal the input, enable the overtext, and enforce validation
  178. sel.addEvent('change', function() {
  179. if (sel.getSelected()[0].get('value') == '__other__') {
  180. other.removeClass('ignoreValidation').reveal().get('reveal').chain(function(){
  181. ot.enable();
  182. });
  183. //else hide and disable the input
  184. } else {
  185. ot.disable();
  186. other.dissolve().addClass('ignoreValidation');
  187. getValidator(el.getParent('form')).validate();
  188. }
  189. });
  190. });
  191. },
  192. dynamic_textarea: function(container) {
  193. if (!container.get('html').contains('ccs-dynamic-textarea')) return;
  194. container.getElements('.ccs-dynamic-textarea').each(function(el){
  195. new DynamicTextarea(el, {
  196. minRows: 5
  197. });
  198. });
  199. },
  200. posteditor: function(container){
  201. if (!container.get('html').contains('ccs-simple-posteditor')) return;
  202. container.getElements('textarea.ccs-simple-posteditor, .ccs-simple-posteditor textarea').each(function(input) {
  203. new CCS.PostEditor.Simple(input);
  204. });
  205. },
  206. accordion: function(container){
  207. if (!container.get('html').contains('accordion')) return;
  208. container.getElements('.accordion').each(function(accordion) {
  209. new Fx.Accordion(accordion.getElements('.toggle'), accordion.getElements('.target'));
  210. });
  211. },
  212. visible: function(container){
  213. if(!container.get('html').contains('ccs-visible')) return;
  214. container.getElements('.ccs-visible').setStyle("display", "");
  215. }
  216. });
  217. this.addEvents({
  218. load: this.setup.bind(this)
  219. });
  220. this.jframe.addEvents({
  221. beforeRenderer: this.stripColumnTemplates.bind(this)
  222. });
  223. },
  224. setup: function(view) {
  225. switch(view) {
  226. case 'table-setup':
  227. this.setupTableForm();
  228. break;
  229. case 'execute':
  230. this.setupEditor();
  231. break;
  232. case 'choose-file':
  233. this.setupChooseFile();
  234. break;
  235. case 'choose-delimiter':
  236. this.setupChooseDelimiter();
  237. break;
  238. case 'define-columns':
  239. this.setupDefineColumns();
  240. break;
  241. }
  242. },
  243. setupChooseFile: function(){
  244. var importData = $(this).getElement('.bw-import_data');
  245. var impLabel = importData.getElement('label');
  246. var impInput = importData.getElement('input');
  247. var checkImportData = function(e){
  248. if (impInput.get('checked')) {
  249. impLabel.removeClass('ccs-bw-selected');
  250. impInput.setProperty('checked', 'false');
  251. } else {
  252. impLabel.addClass('ccs-bw-selected');
  253. impInput.setProperty('checked');
  254. }
  255. };
  256. impLabel.addEvent('click', checkImportData);
  257. if(impInput.get('checked')) impLabel.addClass('ccs-bw-selected');
  258. },
  259. setupDefineColumns: function(){
  260. var showHive = $(this).getElement('.bw-show_hive');
  261. var hiveLabel = showHive.getElement('label');
  262. var hiveInput = showHive.getElement('input');
  263. var checkShowHive = function(e){
  264. if (hiveInput.get('checked')) {
  265. hiveLabel.addClass('ccs-bw-selected');
  266. hiveInput.setProperty('checked', 'false');
  267. } else {
  268. hiveLabel.removeClass('ccs-bw-selected');
  269. hiveInput.setProperty('checked');
  270. }
  271. };
  272. hiveLabel.addEvent('click', checkShowHive);
  273. },
  274. setupChooseDelimiter: function(){
  275. var confirmDelim = $(this).getElement('.bw-confirm_delim');
  276. var selectDelim = $(this).getElement('.bw-select_delim');
  277. if(confirmDelim) {
  278. var yes = confirmDelim.getElement('input[name=Yes]');
  279. var no = confirmDelim.getElement('input[name=No]');
  280. confirmDelim.show();
  281. selectDelim.hide();
  282. no.addEvent('click', function(){
  283. confirmDelim.hide();
  284. selectDelim.show();
  285. });
  286. } else {
  287. selectDelim.show();
  288. }
  289. var otherInput = selectDelim.getElement('input[name=delimiter_1]');
  290. var delimiters = selectDelim.getElement('select[name=delimiter_0]');
  291. delimiters.addEvent('change', function(e){
  292. if(delimiters.getProperty('value') !== '__other__')
  293. {
  294. var form = delimiters.getParent('form');
  295. form.retrieve('form.request').setOptions({
  296. extraData:
  297. {'submit_preview': 'Preview'}
  298. }).send();
  299. }
  300. });
  301. },
  302. setupTableForm: function(){
  303. /* format inputs */
  304. var formatSection = $(this).getElement('.bw-format');
  305. //all the inputs in the format section have custom click behavior
  306. //to add a css class for styling purposes based on their checked state
  307. var checkFormSectionInput = function(input, validate) {
  308. if (input.get('checked')) {
  309. formatSection.getElements('dt').removeClass('ccs-bw-selected');
  310. input.getParent('dt').addClass('ccs-bw-selected');
  311. if (validate) $(this).getElement('form').validate();
  312. }
  313. }.bind(this);
  314. //when the user clicks, check the input and set up classes, validate the form, etc.
  315. formatSection.getElements('input').each(function(input) {
  316. input.addEvent('click', function(){
  317. checkFormSectionInput(input, true);
  318. });
  319. //do this on startup in case the input has a default value
  320. checkFormSectionInput(input);
  321. //if the input is checked, we have to invoke the data-group-toggle manually
  322. //otherwise the next slide shows both the delimited and serde options
  323. if (input.get('checked')) this.jframe.invokeLinker('[data-group-toggle]', input.getParent('[data-group-toggle]'));
  324. }, this);
  325. /* file formats */
  326. var fileSection = $(this).getElement('.bw-file_formats');
  327. var labels = fileSection.getElements('label');
  328. var inputFormatDetails = $(this).getElement('.bw-io_formats');
  329. //fix layout issues; django forces all choice elements to be in a ul
  330. fileSection.getElement('ul').addClass('clearfix');
  331. //private method to retrieve an overtext for an input or create one if there isn't one yet
  332. var getOT = function(input) {
  333. var ot = input.retrieve('OverText');
  334. if (!ot) ot = new OverText(input);
  335. return ot;
  336. };
  337. //get the inputs for the file section
  338. fileSection.getElements('input').each(function(input) {
  339. //whent he user chooses one, add some selected state for styling
  340. input.addEvent('click', function(){
  341. labels.removeClass('ccs-bw-selected');
  342. input.getParent('label').addClass('ccs-bw-selected');
  343. //if the user chooses InputFormat, show the details for that input
  344. //and configure them to be validated
  345. if (input.get('value') == 'InputFormat') {
  346. inputFormatDetails.reveal().get('reveal').chain(function(){
  347. inputFormatDetails.getElements('input').each(function(input){
  348. getOT(input).enable();
  349. input.removeClass('ignoreValidation').addClass('required');
  350. });
  351. });
  352. } else {
  353. //else hide the input format details and disable the validation
  354. inputFormatDetails.dissolve().getElements('input').each(function(input){
  355. getOT(input).disable();
  356. input.addClass('ignoreValidation');
  357. getValidator(input.getParent('form')).validate();
  358. });
  359. }
  360. });
  361. });
  362. /* file location */
  363. var fileLocation = $(this).getElement('.bw-file_location');
  364. var defLabel = fileLocation.getElement('.bw-default_location label');
  365. var fileLoc = fileLocation.getElement('.bw-external_loc');
  366. var fileLocInput = fileLoc.getElement('input').addClass('reqiured');
  367. var input = fileLocation.getElement('.bw-default_location input').set('checked', true);
  368. //private method to toggle style state, validation, and the file location input
  369. var checkFileLoc = function(e){
  370. if (input.get('checked')) {
  371. defLabel.addClass('ccs-bw-selected');
  372. getOT(fileLocInput).disable();
  373. fileLocInput.addClass('ignoreValidation');
  374. fileLoc.dissolve();
  375. //only validate if this was a user-initiated event
  376. if (e) getValidator(input.getParent('form')).validate();
  377. } else {
  378. defLabel.removeClass('ccs-bw-selected');
  379. fileLoc.removeClass('ignoreValidation').reveal().get('reveal').chain(function(){
  380. getOT(fileLocInput).enable();
  381. });
  382. }
  383. };
  384. //check the file state when the user changes the input value
  385. input.addEvent('click', checkFileLoc);
  386. //call it now to set it up.
  387. checkFileLoc();
  388. /* column tables */
  389. var columns = $(this).getElements('div.bw-column');
  390. var columnHeaders = $(this).getElements('.bw-columns dt.bw-column_header');
  391. var columnSections = $(this).getElements('.bw-columns dd.bw-column');
  392. //the column layout is an accordion; starts off with all closed.
  393. this.columnAccordion = new Fx.Accordion(columnHeaders, columnSections, {
  394. onActive: function(toggler, section) {
  395. toggler.addClass('bw-active').removeClass('bw-inactive');
  396. },
  397. onBackground: function(toggler, section) {
  398. toggler.removeClass('bw-active').addClass('bw-inactive');
  399. },
  400. display: -1
  401. });
  402. //create a column form instance for each column form.
  403. columns.each(function(column, i) {
  404. new ColumnForm(column, this.columnAccordion, {
  405. onRemove: this.removeColumn.bind(this)
  406. });
  407. }, this);
  408. //create buttons to add new columns
  409. ['column', 'partition'].each(function(type) {
  410. $(this).getElement('.bw-add_' + type + ' button').addEvent('click', function(e) {
  411. e.stop();
  412. this.addColumn(type, this.columnAccordion);
  413. }.bind(this));
  414. }, this);
  415. },
  416. /*
  417. finds the column templates in the jframe response and removes them before
  418. jframe sets them up like all the other forms
  419. container - (object) see JFrame's beforeRender event
  420. options - (object) ditto
  421. */
  422. stripColumnTemplates: function(container, options){
  423. this.columnTemplates = {};
  424. ['column', 'partition'].each(function(type) {
  425. var el;
  426. container.elements.some(function(section) {
  427. if (!el) el = section.getElement('.beeswax_' + type + '_form_template');
  428. return el;
  429. });
  430. if (el) {
  431. this.columnTemplates[type] = el.get('html');
  432. el.destroy();
  433. }
  434. }, this);
  435. },
  436. /*
  437. adds a new column form
  438. type - either "column" or "partition"
  439. accordion - the accordion that the column is controlled by
  440. */
  441. addColumn: function(type, accordion){
  442. //find the current count
  443. var count_input = $(this).getElement('input[name=' + type +'s-next_form_id]');
  444. //increment it and store it back into the input (as we're adding here)
  445. var count = count_input.get('value').toInt();
  446. count_input.set('value', count + 1);
  447. //create a new column from the template
  448. var column = Elements.from(this.columnTemplates[type].replace("TEMPLATE", count, 'g'))[0];
  449. //store some metadata about it, inject it into the list.
  450. column.store('bw:col-type', type).store('bw:col-count', count);
  451. column.hide().inject($(this).getElement('.bw-' + type + '-forms'));
  452. //apply JFrame magic
  453. this.jframe.applyFilters(column);
  454. //get the header and then add it to the accordion
  455. var columnHeader = column.getElement('dt.bw-column_header');
  456. accordion.addSection(columnHeader, column.getElement('dd.bw-column'));
  457. //expose the new section
  458. column.reveal();
  459. //get the header in that column and, now that it's visible, show its overtext
  460. var input = column.getElement('dt.bw-column_header input');
  461. var ot = input.retrieve('OverText');
  462. if (!ot) ot = new OverText(input);
  463. ot.enable();
  464. //create a new instance of ColumnForm with our new column
  465. new ColumnForm(column, accordion, {
  466. onRemove: this.removeColumn.bind(this)
  467. });
  468. //add the fields in our column to the validator for the form.
  469. getValidator(column.getParent('form')).watchFields(column.getElements('input, textarea, select'));
  470. },
  471. removeColumn: function(column){
  472. column.getElements('input, select, textarea').addClass('ignoreValidation');
  473. column.getParent('form').validate();
  474. column.dissolve();
  475. new Element('input', {
  476. 'type': 'hidden',
  477. 'name': 'columns-' + column.retrieve('bw:col-count') + '-_deleted',
  478. 'value': 'True'
  479. }).inject(column);
  480. },
  481. setupEditor: function(){
  482. //get the editor area, textarea input, & header
  483. var editor = $(this).getElement('.bw-query_bottom');
  484. var ta = editor.getElement('textarea');
  485. //this resizer method resizes the editor area to fill the remaining space below the header
  486. //the shade event is passed true/false if the shade is engaged or not
  487. var resizer = function(){
  488. var header = $(this).getElement('.bw-query_header').getSize().y;
  489. editor.setStyle('height', this.contentSize.y - header - 2);
  490. ta.setStyle('height', this.contentSize.y - header - 2);
  491. }.bind(this);
  492. //do so at startup
  493. resizer();
  494. //and any time the window is resized
  495. this.addEvent('unshade', resizer);
  496. //remove this resize handler on unload
  497. this.jframe.markForCleanup(function(){
  498. this.removeEvent('unshade', resizer);
  499. }.bind(this));
  500. //when the user clicks the save as link, show a popup with the save as form
  501. //when we fire the click manually, it means we want to submit the form
  502. var saveAs = $(this).getElement('.bw-query_save_as');
  503. var saving;
  504. saveAs.addEvent('click', function(e){
  505. //if we aren't trying to submit the form, show the popup
  506. if (!saving) {
  507. //this is the callback for when the user hits the "ok" button
  508. //TODO add some keyboard love for the enter button in fields?
  509. e.stop();
  510. //grab the container of the save as inputs and clone them
  511. //(clone them because our windows destroy themselves on hide)
  512. var saver = $(this).getElement('.bw-query_save_form');
  513. var form = saver.clone();
  514. //prompt the user w/ the form
  515. var prompt = this.prompt('Save This Query', form.show(), function(){
  516. //replace the saver form with the one the user filled out
  517. form.replaces(saver).hide();
  518. //now we're ready to submit the form
  519. saving = true;
  520. //and submit the form
  521. saveAs.click();
  522. //back to showing the popup
  523. saving = false;
  524. });
  525. }
  526. }.bind(this));
  527. //add the settings toggle
  528. var splitEl = $(this).getElement('div[data-filters*=SplitView]');
  529. if (splitEl) {
  530. //get the instance of splitview
  531. var split = splitEl.get('widget');
  532. if (split) {
  533. //let's not show the scrollbar over on the left side
  534. split.left.setStyle('overflow-x', 'hidden');
  535. split.addClass('bw-editor');
  536. //when the user clicks the toggle
  537. $(this).getElement('.bw-query_settings_toggle').addEvent('click', function(e){
  538. e.preventDefault();
  539. //expose or hide the left column
  540. split.chain(resizer).fold('left', split.left.getSize().x > 0 ? 0 : 230);
  541. }.bind(this));
  542. }
  543. }
  544. }
  545. });
  546. //returns an inline form validator for a form or creates one if there isn't one yet
  547. var getValidator = function(form) {
  548. var validator = form.retrieve('validator');
  549. if (!validator) {
  550. validator = new Form.Validator.Inline(form, {
  551. useTitles: true
  552. });
  553. var bwValidateNameRE = /^[a-zA-Z_]\w*/;
  554. validator.add('bw-validate-name', {
  555. errorMsg: 'Please enter a value only containing numbers, letters, underscores - the value cannot begin with a number.',
  556. test: function(element){
  557. return Form.Validator.getValidator('IsEmpty').test(element) ||
  558. (bwValidateNameRE).test(element.get('value'));
  559. }
  560. });
  561. }
  562. return validator;
  563. };
  564. /*
  565. column form class; needed because we add these dynamically
  566. column - the container of the form
  567. header - the header of the section
  568. */
  569. var ColumnForm = new Class({
  570. Implements: [Options, Events],
  571. initialize: function(column, accordion, options) {
  572. this.column = column;
  573. this.header = column.getElement('dt.bw-column_header');
  574. this.accordion = accordion;
  575. this.setOptions(options);
  576. this.setupHeader();
  577. this.setupMapData();
  578. column.getElement('.bw-remove_column button').addEvent('click', function(e){
  579. e.stop();
  580. this.fireEvent('remove', column);
  581. }.bind(this));
  582. },
  583. setupHeader: function(){
  584. //get the header and style it's input behaviors
  585. var input = this.header.getElement('input');
  586. input.addEvents({
  587. focus: function(){
  588. this.header.removeClass('bw-filled');
  589. input.addClass('bw-focused').select();
  590. this.accordion.display(this.accordion.togglers.indexOf(this.header));
  591. }.bind(this),
  592. blur: function(){
  593. if (input.get('value')) this.header.addClass('bw-filled');
  594. else this.header.removeClass('bw-filled');
  595. input.removeClass('bw-focused');
  596. }.bind(this)
  597. });
  598. this.header.getElement('label').addEvents({
  599. mouseover: function(){
  600. input.addClass('bw-focused');
  601. }.bind(this),
  602. mouseleave: function(){
  603. input.removeClass('bw-focused');
  604. }.bind(this)
  605. });
  606. },
  607. setupMapData: function(){
  608. var mapSection = this.column.getElement('.bw-map_data');
  609. if (!mapSection) return; //partition tables don't have these
  610. var arrayTypeSection = this.column.getElement('.bw-array_type');
  611. var typeSelect = this.column.getElement('.bw-col_type select');
  612. var arrayTypeSelect = this.column.getElement('.bw-array_type select');
  613. var checkType = function(){
  614. if (typeSelect.getSelected()[0].get('value') == 'array') arrayTypeSection.fade('in');
  615. else arrayTypeSection.fade('out');
  616. if (typeSelect.getSelected()[0].get('value') == 'map') mapSection.reveal();
  617. else mapSection.dissolve();
  618. };
  619. //for each column table, hide its mapdata config unless the user selects that in the array type
  620. arrayTypeSelect.addEvent('change', checkType.pass(true));
  621. typeSelect.addEvent('change', checkType.pass(true));
  622. checkType();
  623. }
  624. });
  625. })();