feat: add DimensionFilter widget with h_data_url, v_measure_url, show_data_url support

New Bricks widget that provides:
- Search input bar with configurable placeholder
- Horizontally scrollable dimension buttons with left/right arrows
- Vertically scrollable measure buttons on the left side
- Display area on the right side loading show_data_url with params
- Single-selection for both horizontal and vertical dimensions
- '全部' (All) option default selected for both axes
- filter_changed event on every selection/search change
- Registered via bricks.Factory.register('DimensionFilter', ...)
This commit is contained in:
yumoqing 2026-07-30 15:52:36 +08:00
parent bc3c4525fd
commit 850d44ddb5
2 changed files with 246 additions and 0 deletions

View File

@ -7,6 +7,7 @@ SOURCES=" page_data_loader.js factory.js uitypesdef.js utils.js uitype.js \
tree.js multiple_state_image.js dynamiccolumn.js form.js message.js conform.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 \ paging.js datagrid.js iframe.js cols.js echartsext.js \
floaticonbar.js miniform.js wterm.js dynamicaccordion.js \ floaticonbar.js miniform.js wterm.js dynamicaccordion.js \
dimension_filter.js \
binstreaming.js streaming_audio.js vadtext.js rtc.js docxviewer.js \ binstreaming.js streaming_audio.js vadtext.js rtc.js docxviewer.js \
llm.js websocket.js datarow.js tabular.js continueaudio.js \ llm.js websocket.js datarow.js tabular.js continueaudio.js \
line.js pie.js bar.js gobang.js period.js iconbarpage.js \ line.js pie.js bar.js gobang.js period.js iconbarpage.js \

245
bricks/dimension_filter.js Normal file
View File

@ -0,0 +1,245 @@
var bricks = window.bricks || {};
bricks.DimensionFilter = class extends bricks.VBox {
/*
options:
{
h_data_url: URL to fetch horizontal dimension labels/values,
v_measure_url: URL to fetch vertical dimension labels/values,
show_data_url: base URL to fetch display data,
search_placeholder: placeholder text for search input
}
events:
filter_changed: {h_id: string, v_id: string, keyword: string}
*/
constructor(opts){
opts.width = '100%';
opts.height = '100%';
opts.overflow = 'hidden';
super(opts);
this.selected_h_id = 'all';
this.selected_v_id = 'all';
this.search_keyword = '';
this.h_buttons = [];
this.v_buttons = [];
this.h_data = [];
this.v_data = [];
schedule_once(this.build_all.bind(this), 0.1);
}
async build_all(){
this.build_search_bar();
this.build_horizontal_bar();
this.build_content_area();
await this.load_dimensions_data();
}
build_search_bar(){
this.searchbar = new bricks.SearchBar({
placeholder: this.opts.search_placeholder || '搜索...',
width: '100%',
search_label: this.opts.search_label || '搜索',
clear_label: this.opts.clear_label || '清除'
});
this.searchbar.bind('search', this.search_handle.bind(this));
this.add_widget(this.searchbar);
}
build_horizontal_bar(){
var bar = new bricks.HBox({width: '100%', height: 'auto', flexShrink: 0});
bar.set_style('alignItems', 'center');
this.left_arrow = new bricks.Button({
label: '◀',
width: 'auto',
height: 'auto',
flexShrink: 0,
nonepack: true
});
this.left_arrow.bind('click', this.scroll_left.bind(this));
bar.add_widget(this.left_arrow);
this.h_scroll = new bricks.HBox({
width: '100%',
flexGrow: 1,
overflow: 'hidden'
});
bar.add_widget(this.h_scroll);
this.right_arrow = new bricks.Button({
label: '▶',
width: 'auto',
height: 'auto',
flexShrink: 0,
nonepack: true
});
this.right_arrow.bind('click', this.scroll_right.bind(this));
bar.add_widget(this.right_arrow);
this.add_widget(bar);
}
build_content_area(){
var content = new bricks.HBox({width: '100%', height: '100%'});
/* Left: vertical measure buttons in a scroll panel */
this.v_scroll = new bricks.VScrollPanel({width: 'auto', height: '100%'});
this.v_scroll.set_style('maxWidth', '200px');
this.v_scroll.set_style('minWidth', '80px');
content.add_widget(this.v_scroll);
/* Right: display area for show_data_url results */
this.display_filler = new bricks.Filler({flexGrow: 1});
this.display_w = new bricks.VBox({width: '100%', height: '100%'});
this.display_filler.add_widget(this.display_w);
content.add_widget(this.display_filler);
this.add_widget(content);
}
scroll_left(){
var el = this.h_scroll.dom_element;
el.scrollLeft -= 150;
}
scroll_right(){
var el = this.h_scroll.dom_element;
el.scrollLeft += 150;
}
async load_dimensions_data(){
var jc = new bricks.HttpJson();
try {
var hd = await jc.get(this.opts.h_data_url);
this.h_data = (hd && hd.rows) ? hd.rows : [];
} catch(e) {
console.error('DimensionFilter: failed to load h_data_url', e);
}
try {
var vd = await jc.get(this.opts.v_measure_url);
this.v_data = (vd && vd.rows) ? vd.rows : [];
} catch(e) {
console.error('DimensionFilter: failed to load v_measure_url', e);
}
this.build_horizontal_buttons();
this.build_vertical_buttons();
}
build_horizontal_buttons(){
this.h_scroll.clear_widgets();
this.h_buttons = [];
var all_btn = this._make_dim_btn('全部', 'all', true);
all_btn._is_h = true;
this.h_scroll.add_widget(all_btn);
this.h_buttons.push(all_btn);
for (var i = 0; i < this.h_data.length; i++){
var dim = this.h_data[i];
var label = dim.name || dim.label || dim.id || String(dim);
var id = dim.id || dim.name || label;
var btn = this._make_dim_btn(label, id, false);
btn._is_h = true;
this.h_scroll.add_widget(btn);
this.h_buttons.push(btn);
if (this.selected_h_id == id){
this.select_h_button(btn);
}
}
}
build_vertical_buttons(){
this.v_scroll.clear_widgets();
this.v_buttons = [];
var all_btn = this._make_dim_btn('全部', 'all', true);
all_btn._is_h = false;
this.v_scroll.add_widget(all_btn);
this.v_buttons.push(all_btn);
for (var i = 0; i < this.v_data.length; i++){
var dim = this.v_data[i];
var label = dim.name || dim.label || dim.id || String(dim);
var id = dim.id || dim.name || label;
var btn = this._make_dim_btn(label, id, false);
btn._is_h = false;
this.v_scroll.add_widget(btn);
this.v_buttons.push(btn);
if (this.selected_v_id == id){
this.select_v_button(btn);
}
}
}
_make_dim_btn(label, id, is_selected){
var btn = new bricks.Button({
label: label,
width: 'auto',
height: 'auto',
flexShrink: 0
});
btn.dim_id = id;
if (is_selected){
btn.set_css('dimension-btn-active');
}
btn.bind('click', this.dim_button_clicked.bind(this, btn));
return btn;
}
dim_button_clicked(btn){
if (btn._is_h){
this.selected_h_id = btn.dim_id;
this.select_h_button(btn);
} else {
this.selected_v_id = btn.dim_id;
this.select_v_button(btn);
}
this.refresh_display();
}
select_h_button(btn){
for (var i = 0; i < this.h_buttons.length; i++){
this.h_buttons[i].set_css('dimension-btn-active', true);
}
btn.set_css('dimension-btn-active');
}
select_v_button(btn){
for (var i = 0; i < this.v_buttons.length; i++){
this.v_buttons[i].set_css('dimension-btn-active', true);
}
btn.set_css('dimension-btn-active');
}
search_handle(event){
var d = event.params || {};
this.search_keyword = d.keyword || '';
this.refresh_display();
}
async refresh_display(){
this.display_w.clear_widgets();
var params = {
h_id: this.selected_h_id,
v_id: this.selected_v_id
};
if (this.search_keyword){
params.keyword = this.search_keyword;
}
var desc = {
widgettype: 'urlwidget',
options: {
url: this.opts.show_data_url,
params: params,
method: this.opts.show_data_method || 'GET'
}
};
var w = await bricks.widgetBuild(desc, this);
if (w){
this.display_w.add_widget(w);
}
this.dispatch('filter_changed', params);
}
};
bricks.Factory.register('DimensionFilter', bricks.DimensionFilter);