// Custom List component for bricks-framework // This component renders a list of items with custom item templates bricks.List = class extends bricks.VBox { constructor(opts) { super(opts); this.data_url = opts.data_url || null; this.items = opts.items || []; this.itemHeight = opts.itemHeight || 50; this._loading = false; // Set default dimensions if not provided if (!this.options.width) this.options.width = '100%'; if (!this.options.height) this.options.height = '100%'; // Load data if data_url is provided if (this.data_url) { this.loadData(); } else if (this.items.length > 0) { this.renderItems(); } } async loadData() { if (this._loading || !this.data_url) return; this._loading = true; try { const response = await fetch(this.data_url); if (response.ok) { const data = await response.json(); this.items = Array.isArray(data) ? data : (data.items || []); this.renderItems(); } else { console.error('Failed to load list data:', response.status); } } catch (error) { console.error('Error loading list data:', error); } finally { this._loading = false; } } renderItems() { // Clear existing children this.subwidgets = []; this.children = []; // Render each item using the template defined in subwidgets this.items.forEach((item, index) => { const itemWidget = this.createItemWidget(item, index); if (itemWidget) { this.appendChild(itemWidget); } }); // Refresh the display this.refresh(); } createItemWidget(itemData, index) { if (!this.template || this.template.length === 0) { return null; } // Clone the template and process it with item data const processedTemplate = this.processTemplate(this.template[0], itemData, index); if (processedTemplate) { const widget = bricks.Factory.create(processedTemplate); if (widget) { // Store reference to item data for event handling widget._listItemData = itemData; widget._listItemIndex = index; return widget; } } return null; } processTemplate(template, itemData, index) { // This is a simplified template processor // In a real implementation, you would need proper Jinja2-like processing const processed = JSON.parse(JSON.stringify(template)); // Process options recursively this.processObject(processed, itemData, index); return processed; } processObject(obj, itemData, index) { if (typeof obj !== 'object' || obj === null) { return; } for (const key in obj) { if (typeof obj[key] === 'string') { // Simple string replacement for {{item.xxx}} patterns if (obj[key].includes('{{item.')) { const match = obj[key].match(/{{item\.([^}]+)}}/); if (match && match[1]) { const fieldName = match[1]; obj[key] = obj[key].replace(`{{item.${fieldName}}}`, itemData[fieldName] || ''); } } // Handle conditional expressions like {{ '#22C55E' if item.status == 'Connected' else '#EF4444' }} if (obj[key].includes('{{ ') && obj[key].includes(' if item.')) { const fullMatch = obj[key].match(/{{\s*(.*?)\s+if\s+item\.(\w+)\s*==\s*'([^']+)'\s+else\s+(.*?)\s*}}/); if (fullMatch) { const trueValue = fullMatch[1].trim().replace(/^['"]|['"]$/g, ''); const fieldName = fullMatch[2]; const conditionValue = fullMatch[3]; const falseValue = fullMatch[4].trim().replace(/^['"]|['"]$/g, ''); obj[key] = itemData[fieldName] === conditionValue ? trueValue : falseValue; } } } else if (typeof obj[key] === 'object' && obj[key] !== null) { this.processObject(obj[key], itemData, index); } } } // Public method to reload data reload() { if (this.data_url) { this.loadData(); } } // Public method to set items directly setItems(items) { this.items = items || []; this.renderItems(); } // Override appendChild to capture the template appendChild(widget) { if (!this.template) { this.template = this.subwidgets || []; } super.appendChild(widget); } }; // Register the List component bricks.register('List', bricks.List); console.log('Custom List component registered with bricks framework');