diff --git a/bricks/build.sh b/bricks/build.sh index fafe5d7..a59afea 100755 --- a/bricks/build.sh +++ b/bricks/build.sh @@ -3,7 +3,7 @@ SOURCES=" page_data_loader.js factory.js uitypesdef.js utils.js uitype.js \ jsoncall.js myoperator.js scroll.js menu.js popup.js recorder.js \ modal.js running.js llmout.js glbviewer.js \ markdown_viewer.js audio.js toolbar.js tab.js \ - input.js registerfunction.js button.js accordion.js searchbar.js data_filter.js dataviewer.js \ + input.js registerfunction.js button.js accordion.js searchbar.js dataviewer.js \ tree.js multiple_state_image.js dynamiccolumn.js form.js message.js conform.js \ paging.js datagrid.js iframe.js cols.js echartsext.js \ floaticonbar.js miniform.js wterm.js dynamicaccordion.js \ diff --git a/bricks/data_filter.js b/bricks/data_filter.js deleted file mode 100644 index ab70aca..0000000 --- a/bricks/data_filter.js +++ /dev/null @@ -1,247 +0,0 @@ -var bricks = window.bricks || {}; - -/* - * DataFilter widget — renders a search form from a data_filter definition. - * - * Usage: - * new bricks.DataFilter({ - * data_filter: { "AND": [ - * {"field": "name", "op": "LIKE", "var": "name_input"}, - * {"field": "status", "op": "=", "var": "status_input"} - * ]}, - * browserfields: { alters: { status: { uitype: "code", data: [...] } } } // optional - * }); - * - * Events: - * filter_search: { values: { name_input: "x", status_input: "y" } } - * filter_clear: {} - */ -bricks.DataFilter = class extends bricks.VBox { - constructor(opts){ - opts = opts || {}; - opts.width = opts.width || '100%'; - opts.height = opts.height || 'auto'; - super(opts); - this.set_style('alignItems', 'center'); - this.set_style('gap', opts.gap || '0.5rem'); - this.set_style('flexWrap', 'wrap'); - this.filter_values = {}; - this.var_fields = []; - this._parse_filter(); - this._build_inputs(); - this._build_buttons(); - } - - /* Recursively extract all {var, op, field} from the filter tree */ - _parse_filter(){ - this.var_fields = []; - if (!this.opts.data_filter) return; - this._extract_vars(this.opts.data_filter); - } - - _extract_vars(node){ - if (!node || typeof node !== 'object') return; - var self = this; - var keys = Object.keys(node); - for (var i = 0; i < keys.length; i++){ - var key = keys[i]; - var val = node[key]; - if (key === 'AND' || key === 'OR'){ - if (Array.isArray(val)){ - val.forEach(function(item){ self._extract_vars(item); }); - } - } else if (key === 'NOT'){ - this._extract_vars(val); - } else if (key === 'field'){ - // This node is a leaf condition: {field, op, var|const} - if (node.var){ - this.var_fields.push({ - var_name: node.var, - field_name: node.field, - op: node.op || '=' - }); - } - return; // leaf node, don't recurse into siblings - } - } - } - - _build_inputs(){ - var self = this; - var alters = (this.opts.browserfields && this.opts.browserfields.alters) || {}; - this.var_fields.forEach(function(vf){ - var wrapper = new bricks.HBox({ - width: 'auto', - height: 'auto' - }); - wrapper.set_style('alignItems', 'center'); - wrapper.set_style('gap', '0.25rem'); - - // Label - var label_text = self.opts.field_labels && self.opts.field_labels[vf.var_name] - ? self.opts.field_labels[vf.var_name] - : vf.field_name; - var label_w = new bricks.Text({ - text: label_text, - fontSize: '14px' - }); - wrapper.add_widget(label_w); - - // Input — choose type based on op and alters - var alter = alters[vf.field_name]; - var input_w; - if (alter && alter.uitype === 'code'){ - // Dropdown - input_w = self._build_code_input(vf, alter); - } else if (vf.op === 'IN' || vf.op === 'NOT IN'){ - input_w = self._build_text_input(vf, 'a,b,c'); - } else if (vf.op === 'LIKE' || vf.op === 'NOT LIKE'){ - input_w = self._build_text_input(vf, ''); - } else { - // =, !=, >, >=, <, <= etc. - input_w = self._build_text_input(vf, ''); - } - input_w.set_style('minWidth', '120px'); - input_w.set_style('maxWidth', '200px'); - wrapper.add_widget(input_w); - vf.input_widget = input_w; - - self.add_widget(wrapper); - }); - } - - _build_text_input(vf, placeholder){ - var input_w = new bricks.UiStr({ - name: vf.var_name, - value: '', - placeholder: placeholder || '', - width: '100%' - }); - input_w.set_style('padding', '0.35rem 0.5rem'); - input_w.set_style('boxSizing', 'border-box'); - input_w.set_style('height', 'auto'); - input_w.bind('changed', this._input_changed.bind(this)); - input_w.dom_element.addEventListener('keydown', this._keydown_handle.bind(this)); - return input_w; - } - - _build_code_input(vf, alter){ - var input_opts = { - name: vf.var_name, - value: '', - placeholder: '', - width: '100%', - dataurl: alter.dataurl || null, - datamethod: alter.datamethod || 'GET', - dataparams: alter.dataparams || {} - }; - if (alter.data && Array.isArray(alter.data)){ - input_opts.data = alter.data; - } - if (alter.data_field){ - input_opts.data_field = alter.data_field; - } - var input_w = new bricks.UiCode(input_opts); - input_w.set_style('padding', '0.25rem'); - input_w.set_style('boxSizing', 'border-box'); - input_w.set_style('height', 'auto'); - input_w.bind('changed', this._input_changed.bind(this)); - return input_w; - } - - _build_buttons(){ - var btn_box = new bricks.HBox({ - width: 'auto', - height: 'auto' - }); - btn_box.set_style('alignItems', 'center'); - btn_box.set_style('gap', '0.5rem'); - - this.search_btn = new bricks.Button({ - label: this.opts.search_label || '搜索', - orientation: 'horizontal', - width: 'auto', - height: 'auto' - }); - this.search_btn.bind('click', this._do_search.bind(this)); - btn_box.add_widget(this.search_btn); - - this.clear_btn = new bricks.Button({ - label: this.opts.clear_label || '清空', - orientation: 'horizontal', - width: 'auto', - height: 'auto' - }); - this.clear_btn.bind('click', this._clear_search.bind(this)); - btn_box.add_widget(this.clear_btn); - - this.add_widget(btn_box); - } - - _input_changed(event){ - var d = event.params || {}; - for (var k in d){ - this.filter_values[k] = d[k]; - } - } - - _keydown_handle(event){ - if (event.key === 'Enter'){ - this._do_search(event); - } - } - - _get_all_values(){ - var values = {}; - var self = this; - this.var_fields.forEach(function(vf){ - var w = vf.input_widget; - if (!w) return; - if (w.resultValue){ - values[vf.var_name] = w.resultValue() || ''; - } else if (w.getValue){ - values[vf.var_name] = w.getValue() || ''; - } else { - values[vf.var_name] = ''; - } - }); - return values; - } - - _do_search(event){ - var values = this._get_all_values(); - this.filter_values = values; - this.dispatch('filter_search', { values: values }); - if (event && event.stopPropagation){ - event.stopPropagation(); - } - } - - _clear_search(event){ - var self = this; - this.var_fields.forEach(function(vf){ - var w = vf.input_widget; - if (!w) return; - if (w.setValue){ - w.setValue(''); - } - }); - this.filter_values = {}; - this.dispatch('filter_clear', {}); - this._do_search(event); - } - - /* Programmatically set a filter value */ - set_filter_value(var_name, value){ - var self = this; - this.var_fields.forEach(function(vf){ - if (vf.var_name === var_name && vf.input_widget){ - if (vf.input_widget.setValue){ - vf.input_widget.setValue(value); - } - } - }); - } -}; - -bricks.Factory.register('DataFilter', bricks.DataFilter); diff --git a/bricks/dataviewer.js b/bricks/dataviewer.js index 059466c..892e85d 100644 --- a/bricks/dataviewer.js +++ b/bricks/dataviewer.js @@ -32,7 +32,6 @@ bricks.DataViewer = class extends bricks.VBox { this.build_description_widget(); this.build_toolbar_widget(); this.build_searchbar_widget(); - this.build_datafilter_widget(); this.build_records_area(); await this.build_other(); this.check_changed_row = null; @@ -108,37 +107,6 @@ bricks.DataViewer = class extends bricks.VBox { this.searchbar_w.bind('search', this.search_event_handle.bind(this)); this.add_widget(this.searchbar_w); } - build_datafilter_widget(){ - if (!this.opts.data_filter){ - return; - } - var opts = { - data_filter: this.opts.data_filter, - browserfields: this.opts.browserfields || {}, - field_labels: this.opts.filter_labels || {}, - search_label: this.opts.filter_search_label || '搜索', - clear_label: this.opts.filter_clear_label || '清空' - }; - this.datafilter_w = new bricks.DataFilter(opts); - this.datafilter_w.bind('filter_search', this.filter_event_handle.bind(this)); - this.datafilter_w.bind('filter_clear', this.filter_clear_handle.bind(this)); - this.add_widget(this.datafilter_w); - } - async filter_event_handle(event){ - var d = event.params || {}; - this.filter_values = d.values || {}; - this.old_params = null; - this.select_row = null; - this.active_item = null; - this.data_offset = 0; - if (this.loader){ - this.loader.pages = []; - } - await this.render({}); - } - async filter_clear_handle(event){ - this.filter_values = {}; - } merge_search_params(params){ var merged = bricks.extend({}, params || {}); if (this.searchable){ @@ -239,9 +207,18 @@ bricks.DataViewer = class extends bricks.VBox { edit_names.push(t.name); }); } + /* data_filter: add search tool to toolbar */ + if (this.opts.data_filter){ + tbdesc.tools.push({ + name:'filter', + label: this.opts.filter_label || '搜索', + tip: 'filter records', + icon: this.opts.filter_icon || bricks_resource('imgs/search.svg') + }); + } if (this.toolbar){ this.toolbar.tools.forEach(t => { - if (! edit_names.includes(t.name)){ + if (! edit_names.includes(t.name) && t.name !== 'filter'){ tbdesc.tools.push(t); } }); @@ -275,6 +252,10 @@ bricks.DataViewer = class extends bricks.VBox { this.delete_record(this.select_row); return; } + if (tdesc.name == 'filter'){ + await this.show_filter_form(); + return; + } var data = null; if (this.select_row){ var r = this.select_row; @@ -283,6 +264,93 @@ bricks.DataViewer = class extends bricks.VBox { console.log(tdesc.name, 'clicked ==================', tdesc.name, data) this.dispatch(tdesc.name, data); } + /* Recursively extract {var, field, op} from data_filter JSON tree */ + get_filter_fields(){ + var fields = []; + var self = this; + var alters = (this.opts.browserfields && this.opts.browserfields.alters) || {}; + var labels = this.opts.filter_labels || {}; + + function extract(node){ + if (!node || typeof node !== 'object') return; + var keys = Object.keys(node); + for (var i = 0; i < keys.length; i++){ + var key = keys[i]; + var val = node[key]; + if (key === 'AND' || key === 'OR'){ + if (Array.isArray(val)){ + val.forEach(extract); + } + } else if (key === 'NOT'){ + extract(val); + } else if (key === 'field'){ + if (node.var){ + var alter = alters[node.field] || {}; + var f = { + name: node.var, + label: labels[node.var] || node.field, + op: node.op || '=' + }; + if (alter.uitype === 'code'){ + f.uitype = 'code'; + if (alter.data && Array.isArray(alter.data)){ + f.data = alter.data; + } + if (alter.dataurl){ + f.dataurl = alter.dataurl; + f.datamethod = alter.datamethod || 'GET'; + f.dataparams = alter.dataparams || {}; + } + if (alter.data_field){ + f.data_field = alter.data_field; + } + } + fields.push(f); + } + return; + } + } + } + extract(this.opts.data_filter); + return fields; + } + async show_filter_form(){ + var fields = this.get_filter_fields(); + var submit_url = this.opts.data_url || ''; + var form_opts = { + submit_url: '#', + width: '100%', + height: '100%', + fields: fields + }; + var form = new bricks.Form(form_opts); + var popup = new bricks.PopupWindow({ + title: this.opts.filter_title || '搜索过滤', + icon: bricks_resource('imgs/search.svg'), + widget: this, + archor: 'cc', + movable: true, + resizable: true, + width: '50%', + height: 'auto' + }); + form.bind('cancel', popup.dismiss.bind(popup)); + form.bind('submited', this.filter_form_submited.bind(this, popup, form)); + popup.add_widget(form); + popup.open(); + } + async filter_form_submited(popup, form, event){ + popup.dismiss(); + this.filter_values = form._getValue(); + this.old_params = null; + this.select_row = null; + this.active_item = null; + this.data_offset = 0; + if (this.loader){ + this.loader.pages = []; + } + await this.render({}); + } get_edit_fields(){ var fs = this.row_options.fields; this.fields = [];