From 0baf95a392e85074f9d902306ea1484e73cafaa5 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Wed, 3 Sep 2014 17:57:40 +0200 Subject: [PATCH] WEBUI JS: Add possibility to create tab panels dynamically (disabled by default) --- src/webui/static/app/acleditor.js | 16 +- src/webui/static/app/dvr.js | 4 +- src/webui/static/app/idnode.js | 570 +++++++++++++++++------------- src/webui/static/app/mpegts.js | 93 +++-- src/webui/static/app/tvheadend.js | 46 ++- 5 files changed, 415 insertions(+), 314 deletions(-) diff --git a/src/webui/static/app/acleditor.js b/src/webui/static/app/acleditor.js index c9c514ac..2f1426ad 100644 --- a/src/webui/static/app/acleditor.js +++ b/src/webui/static/app/acleditor.js @@ -4,17 +4,6 @@ tvheadend.acleditor = function(panel, index) { - panel2 = new Ext.TabPanel({ - activeTab: 0, - autoScroll: true, - title: 'Access Control', - iconCls: 'group', - tabIndex: index, - items: [] - }); - - tvheadend.paneladd(panel, panel2, index); - var list = 'enabled,username,password,prefix,streaming,adv_streaming,' + 'dvr,dvr_config,webui,admin,channel_min,channel_max,channel_tag,' + 'comment'; @@ -23,6 +12,7 @@ tvheadend.acleditor = function(panel, index) url: 'api/access/entry', titleS: 'Access Entry', titleP: 'Access Entries', + iconCls: 'group', columns: { username: { width: 250 }, password: { width: 250 }, @@ -35,7 +25,7 @@ tvheadend.acleditor = function(panel, index) channel_min: { width: 100 }, channel_max: { width: 100 }, }, - tabIndex: 0, + tabIndex: index, edit: { params: { list: list, @@ -55,6 +45,4 @@ tvheadend.acleditor = function(panel, index) new tvheadend.help('Access Control Entries', 'config_access.html'); }, }); - - return panel; }; diff --git a/src/webui/static/app/dvr.js b/src/webui/static/app/dvr.js index 90f8f089..2da2cc0b 100644 --- a/src/webui/static/app/dvr.js +++ b/src/webui/static/app/dvr.js @@ -71,7 +71,9 @@ tvheadend.dvrRowActions = function() { new tvheadend.dvrDetails(grid.getStore().getAt(row).id); } } - ] + ], + destroy: function() { + } }); } diff --git a/src/webui/static/app/idnode.js b/src/webui/static/app/idnode.js index 61a5faf4..75b7c9bd 100644 --- a/src/webui/static/app/idnode.js +++ b/src/webui/static/app/idnode.js @@ -572,7 +572,7 @@ tvheadend.idnode_editor_form = function(d, meta, panel, create) met[number].columns = columns; if (columns) { var p = newFieldSet({ title: m.name || "Settings", layout: 'column', border: false }); - cfs[number] = newFieldSet({ nocollapse: true }); + cfs[number] = newFieldSet({ nocollapse: true, style: 'border-width: 0px', bodyStyle: ' ' }); p.add(cfs[number]); fs[number] = p; mfs[number] = p; @@ -587,7 +587,7 @@ tvheadend.idnode_editor_form = function(d, meta, panel, create) parent = null; if (!m.columns) { if (parent) { - p = newFieldSet({ nocollapse: true }); + p = newFieldSet({ nocollapse: true, style: 'border-width: 0px', bodyStyle: ' ' }); fs[parent].add(p); } else { p = newFieldSet({ title: m.name }); @@ -658,9 +658,9 @@ tvheadend.idnode_editor = function(item, conf) bodyStyle: 'padding: 5px', labelAlign: 'left', labelWidth: conf.labelWidth || 200, - autoWidth: true, + autoWidth: conf.noautoWidth ? false : true, autoHeight: !conf.fixedHeight, - width: 600, + width: conf.width || 600, //defaults: {width: 330}, defaultType: 'textfield', buttonAlign: 'left', @@ -823,9 +823,22 @@ tvheadend.idnode_create = function(conf, onlyDefault) */ tvheadend.idnode_grid = function(panel, conf) { + var store = null; + var grid = null; + var event = null; + var auto = null; + + var update = function(o) { + if (auto.getValue()) + store.reload(); + }; + function build(d) { - var columns = conf.lcol || []; + if (conf.builder) + conf.builder(conf); + + var columns = []; var filters = []; var fields = []; var buttons = []; @@ -842,6 +855,11 @@ tvheadend.idnode_grid = function(panel, conf) if (conf.add && !conf.add.titleS && conf.titleS) conf.add.titleS = conf.titleS; + /* Left-hand columns (do copy, no reference!) */ + if (conf.lcol) + for (i = 0; i < conf.lcol.length; i++) + columns.push(conf.lcol[i]); + /* Model */ var idnode = new tvheadend.IdNode(d); for (var i = 0; i < idnode.length(); i++) { @@ -856,7 +874,7 @@ tvheadend.idnode_grid = function(panel, conf) /* Right-hand columns */ if (conf.rcol) for (i = 0; i < conf.rcol.length; i++) - columns.push(conf.rcol[i]); + columns.push(conf.rcol[i]); /* Filters */ var filter = new Ext.ux.grid.GridFilters({ @@ -865,12 +883,8 @@ tvheadend.idnode_grid = function(panel, conf) filters: filters }); - var sort = null; - if (conf.sort) - sort = conf.sort; - /* Store */ - var store = new Ext.data.JsonStore({ + store = new Ext.data.JsonStore({ root: 'entries', url: conf.gridURL || (conf.url + '/grid'), autoLoad: true, @@ -879,7 +893,7 @@ tvheadend.idnode_grid = function(panel, conf) fields: fields, remoteSort: true, pruneModifiedRecords: true, - sortInfo: sort + sortInfo: conf.sort ? conf.sort : null, }); /* Model */ @@ -1173,7 +1187,7 @@ tvheadend.idnode_grid = function(panel, conf) } /* Grid Panel */ - var auto = new Ext.form.Checkbox({ + auto = new Ext.form.Checkbox({ checked: true, listeners: { check: function(s, c) { @@ -1222,8 +1236,6 @@ tvheadend.idnode_grid = function(panel, conf) stateful: true, stateId: conf.gridURL || conf.url, stripeRows: true, - title: conf.titleP, - iconCls: conf.iconCls || '', store: store, cm: model, selModel: select, @@ -1234,41 +1246,73 @@ tvheadend.idnode_grid = function(panel, conf) tbar: buttons, bbar: page }; - var grid = conf.readonly ? new Ext.grid.GridPanel(gconf) : - new Ext.grid.EditorGridPanel(gconf); + grid = conf.readonly ? new Ext.grid.GridPanel(gconf) : + new Ext.grid.EditorGridPanel(gconf); grid.on('filterupdate', function() { page.changePage(0); }); - tvheadend.paneladd(panel, grid, conf.tabIndex); + dpanel.add(grid); + dpanel.doLayout(false, true); /* Add comet listeners */ - var update = function(o) { - if (auto.getValue()) - store.reload(); - }; if (conf.comet) tvheadend.comet.on(conf.comet, update); - if (idnode.event && idnode.event != conf.comet) + if (idnode.event && idnode.event != conf.comet) { + event = idnode.event; tvheadend.comet.on(idnode.event, update); + } } - /* Request data */ - if (!conf.fields) { - var p = {}; - if (conf.list) p['list'] = conf.list; - tvheadend.Ajax({ - url: conf.url + '/class', - params: p, - success: function(d) - { - var d = json_decode(d); - build(d); - } - }); - } else { - build(conf.fields); + function builder() { + if (grid) + return; + + /* Request data */ + if (!conf.fields) { + var p = {}; + if (conf.list) p['list'] = conf.list; + tvheadend.Ajax({ + url: conf.url + '/class', + params: p, + success: function(d) + { + var d = json_decode(d); + build(d); + } + }); + } else { + build(conf.fields); + } } + + function destroyer() { + if (grid === null || !tvheadend.dynamic) + return; + if (conf.comet) + tvheadend.comet.removeListener(conf.comet, update); + if (event) + tvheadend.comet.removeListener(event, update); + dpanel.removeAll(true); + store.destroy(); + grid = null; + store = null; + auto = null; + event = null; + if (conf.destroyer) + conf.destroyer(conf); + } + + var dpanel = new Ext.Panel({ + border: false, + header: false, + layout: 'fit', + title: conf.titleP || '', + iconCls: conf.iconCls || '' + }); + + tvheadend.paneladd(panel, dpanel, conf.tabIndex); + tvheadend.panelreg(panel, dpanel, builder, destroyer); }; /* @@ -1276,231 +1320,271 @@ tvheadend.idnode_grid = function(panel, conf) */ tvheadend.idnode_form_grid = function(panel, conf) { - var buttons = []; - var plugins = conf.plugins || []; - var saveBtn = null; - var undoBtn = null; - var addBtn = null; - var delBtn = null; - var current = null; - var grid = null; var mpanel = null; + var store = null; - /* Store */ - var store = new Ext.data.JsonStore({ - root: 'entries', - url: 'api/idnode/load', - baseParams: { - enum: 1, - 'class': conf.clazz - }, - autoLoad: true, - id: 'key', - totalProperty: 'total', - fields: ['key','val'], - remoteSort: false, - pruneModifiedRecords: true, - sortInfo: { - field: 'val', - direction: 'ASC' - }, - }); + var update = function(o) { + if (store) + store.reload(); + }; - /* Model */ - var model = new Ext.grid.ColumnModel({ - defaultSortable: true, - columns: [{ - width: 300, - id: 'val', - header: conf.titleC, - sortable: true, - dataIndex: 'val' - }] - }); + function builder() { + if (mpanel) + return; - /* Selection */ - var select = new Ext.grid.RowSelectionModel({ - singleSelect: true - }); + if (conf.builder) + conf.builder(conf); - /* Event handlers */ - select.on('selectionchange', function(s) { - roweditor(s.getSelected()); - if (conf.selected) - conf.selected(s); - }); + var buttons = []; + var plugins = conf.plugins || []; + var saveBtn = null; + var undoBtn = null; + var addBtn = null; + var delBtn = null; + var current = null; - /* Top bar */ - saveBtn = new Ext.Toolbar.Button({ - tooltip: 'Save pending changes (marked with red border)', - iconCls: 'save', - text: 'Save', - disabled: true, - handler: function() { - var node = current.editor.getForm().getFieldValues(); - node.uuid = current.uuid; + /* Store */ + store = new Ext.data.JsonStore({ + root: 'entries', + url: 'api/idnode/load', + baseParams: { + enum: 1, + 'class': conf.clazz + }, + autoLoad: true, + id: 'key', + totalProperty: 'total', + fields: ['key','val'], + remoteSort: false, + pruneModifiedRecords: true, + sortInfo: { + field: 'val', + direction: 'ASC' + } + }); + + store.on('load', function(st) { + if (!current) + grid.getSelectionModel().selectFirstRow(); + }); + + /* Model */ + var model = new Ext.grid.ColumnModel({ + defaultSortable: true, + columns: [{ + width: 300, + id: 'val', + header: conf.titleC, + sortable: true, + dataIndex: 'val' + }] + }); + + /* Selection */ + var select = new Ext.grid.RowSelectionModel({ + singleSelect: true + }); + + /* Event handlers */ + select.on('selectionchange', function(s) { + roweditor(s.getSelected()); + if (conf.selected) + conf.selected(s); + }); + + /* Top bar */ + saveBtn = new Ext.Toolbar.Button({ + tooltip: 'Save pending changes (marked with red border)', + iconCls: 'save', + text: 'Save', + disabled: true, + handler: function() { + var node = current.editor.getForm().getFieldValues(); + node.uuid = current.uuid; + tvheadend.Ajax({ + url: 'api/idnode/save', + params: { + node: Ext.encode(node) + }, + success: function() { + store.reload() + } + }); + } + }); + buttons.push(saveBtn); + undoBtn = new Ext.Toolbar.Button({ + tooltip: 'Revert pending changes (marked with red border)', + iconCls: 'undo', + text: 'Undo', + disabled: true, + handler: function() { + if (current) + current.editor.getForm().reset(); + } + }); + buttons.push(undoBtn); + buttons.push('-'); + if (conf.add) { + addBtn = new Ext.Toolbar.Button({ + tooltip: 'Add a new entry', + iconCls: 'add', + text: 'Add', + disabled: false, + handler: function() { + tvheadend.idnode_create(conf.add, true); + } + }); + buttons.push(addBtn); + } + if (conf.del) { + delBtn = new Ext.Toolbar.Button({ + tooltip: 'Delete selected entries', + iconCls: 'remove', + text: 'Delete', + disabled: true, + handler: function() { + if (current) { + tvheadend.Ajax({ + url: 'api/idnode/delete', + params: { + uuid: current.uuid + }, + success: function(d) + { + store.reload(); + grid.getSelectionModel().selectFirstRow(); + } + }); + } + } + }); + buttons.push(delBtn); + } + if (conf.add || conf.del) + buttons.push('-'); + + /* Extra buttons */ + if (conf.tbar) { + buttons.push('-'); + for (i = 0; i < conf.tbar.length; i++) { + if (conf.tbar[i].callback) { + conf.tbar[i].handler = function(b, e) { + this.callback(this, e, store, select); + }; + } + buttons.push(conf.tbar[i]); + } + } + + /* Help */ + if (conf.help) { + buttons.push('->'); + buttons.push({ + text: 'Help', + handler: conf.help + }); + } + + function roweditor(r) { + if (!r || !r.id) + return; tvheadend.Ajax({ - url: 'api/idnode/save', + url: 'api/idnode/load', params: { - node: Ext.encode(node) + uuid: r.id, + meta: 1 }, - success: function() { - store.reload() + success: function(d) { + d = json_decode(d); + if (current) + mpanel.remove(current.editor); + var editor = new tvheadend.idnode_editor(d[0], { + title: 'Parameters', + labelWidth: 300, + fixedHeight: true, + help: conf.help || null, + inTabPanel: true, + noButtons: true, + noautoWidth: true, + width: 730 + }); + current = { + uuid: d[0].id, + editor: editor + } + saveBtn.setDisabled(false); + undoBtn.setDisabled(false); + delBtn.setDisabled(false); + mpanel.add(editor); + mpanel.doLayout(); } }); } - }); - buttons.push(saveBtn); - undoBtn = new Ext.Toolbar.Button({ - tooltip: 'Revert pending changes (marked with red border)', - iconCls: 'undo', - text: 'Undo', - disabled: true, - handler: function() { - if (current) - current.editor.getForm().reset(); - } - }); - buttons.push(undoBtn); - buttons.push('-'); - if (conf.add) { - addBtn = new Ext.Toolbar.Button({ - tooltip: 'Add a new entry', - iconCls: 'add', - text: 'Add', - disabled: false, - handler: function() { - tvheadend.idnode_create(conf.add, true); - } - }); - buttons.push(addBtn); - } - if (conf.del) { - delBtn = new Ext.Toolbar.Button({ - tooltip: 'Delete selected entries', - iconCls: 'remove', - text: 'Delete', - disabled: true, - handler: function() { - if (current) { - tvheadend.Ajax({ - url: 'api/idnode/delete', - params: { - uuid: current.uuid - }, - success: function(d) - { - store.reload(); - grid.getSelectionModel().selectFirstRow(); - } - }); - } - } - }); - buttons.push(delBtn); - } - if (conf.add || conf.del) - buttons.push('-'); - /* Extra buttons */ - if (conf.tbar) { - buttons.push('-'); - for (i = 0; i < conf.tbar.length; i++) { - if (conf.tbar[i].callback) { - conf.tbar[i].handler = function(b, e) { - this.callback(this, e, store, select); - }; - } - buttons.push(conf.tbar[i]); - } - } - - /* Help */ - if (conf.help) { - buttons.push('->'); - buttons.push({ - text: 'Help', - handler: conf.help - }); - } - - function roweditor(r) { - if (!r || !r.id) - return; - tvheadend.Ajax({ - url: 'api/idnode/load', - params: { - uuid: r.id, - meta: 1 + /* Grid Panel (Selector) */ + var grid = new Ext.grid.GridPanel({ + width: 200, + stripeRows: true, + store: store, + cm: model, + selModel: select, + plugins: plugins, + border: false, + viewConfig: { + forceFit: true }, - success: function(d) { - d = json_decode(d); - if (current) - mpanel.remove(current.editor); - var editor = new tvheadend.idnode_editor(d[0], { - title: 'Parameters', - labelWidth: 300, - fixedHeight: true, - help: conf.help || null, - inTabPanel: true, - noButtons: true - }); - current = { - uuid: d[0].id, - editor: editor + listeners : { + render : { + fn : function() { + if (!current) + grid.getSelectionModel().selectFirstRow(); + } } - saveBtn.setDisabled(false); - undoBtn.setDisabled(false); - delBtn.setDisabled(false); - mpanel.add(editor); - mpanel.doLayout(); } }); + + var mpanel = new Ext.Panel({ + tbar: buttons, + layout: 'hbox', + padding: 5, + border: false, + layoutConfig: { + align: 'stretch' + }, + items: [grid] + }); + + dpanel.add(mpanel); + dpanel.doLayout(false, true); + + if (conf.comet) + tvheadend.comet.on(conf.comet, update); } - /* Grid Panel (Selector) */ - grid = new Ext.grid.GridPanel({ - width: 200, - stripeRows: true, - store: store, - cm: model, - selModel: select, - plugins: plugins, + function destroyer() { + if (mpanel === null || !tvheadend.dynamic) + return; + if (conf.comet) + tvheadend.comet.removeListener(conf.comet, update); + dpanel.removeAll(true); + store.destroy(); + mpanel = null; + store = null; + if (conf.destroyer) + conf.destroyer(conf); + } + + var dpanel = new Ext.Panel({ border: false, - viewConfig: { - forceFit: true - }, - listeners : { - render : { - fn : function() { - if (!current) - grid.getSelectionModel().selectFirstRow(); - } - } - } + header: false, + layout: 'fit', + title: conf.titleP || '', + iconCls: conf.iconCls || '' }); - mpanel = new Ext.Panel({ - tbar: buttons, - title: conf.titleP || '', - iconCls: conf.iconCls || '', - layout: 'hbox', - padding: 5, - border: false, - layoutConfig: { - align: 'stretch' - }, - items: [grid] - }); - - tvheadend.paneladd(panel, mpanel, conf.tabIndex); - - /* Add comet listeners */ - var update = function(o) { - store.reload(); - }; - if (conf.comet) - tvheadend.comet.on(conf.comet, update); + tvheadend.paneladd(panel, dpanel, conf.tabIndex); + tvheadend.panelreg(panel, dpanel, builder, destroyer); }; /* @@ -1540,8 +1624,10 @@ tvheadend.idnode_tree = function(panel, conf) }), listeners: { click: function(n) { - if (current) + if (current) { panel.remove(current); + current = null; + } if (!n.isRoot) current = panel.add(new tvheadend.idnode_editor(n.attributes, { title: 'Parameters', diff --git a/src/webui/static/app/mpegts.js b/src/webui/static/app/mpegts.js index 9a852d29..375fca5b 100644 --- a/src/webui/static/app/mpegts.js +++ b/src/webui/static/app/mpegts.js @@ -195,40 +195,54 @@ tvheadend.show_service_streams = function(data) { tvheadend.services = function(panel, index) { - var mapButton = new Ext.Toolbar.Button({ - tooltip: 'Map services to channels', - iconCls: 'clone', - text: 'Map All', - callback: tvheadend.service_mapper, - disabled: false - }); - var selected = function(s) - { - if (s.getCount() > 0) - mapButton.setText('Map Selected'); - else - mapButton.setText('Map All'); - }; - var actions = new Ext.ux.grid.RowActions({ - header: 'Details', - width: 10, - actions: [{ - iconCls: 'info', - qtip: 'Detailed stream info', - cb: function(grid, rec, act, row, col) { - Ext.Ajax.request({ - url: 'api/service/streams', - params: { - uuid: rec.id - }, - success: function(r, o) { - var d = Ext.util.JSON.decode(r.responseText); - tvheadend.show_service_streams(d); - } - }); - } - }] - }); + function builder(conf) { + var mapButton = new Ext.Toolbar.Button({ + tooltip: 'Map services to channels', + iconCls: 'clone', + text: 'Map All', + callback: tvheadend.service_mapper, + disabled: false + }); + var selected = function(s) + { + if (s.getCount() > 0) + mapButton.setText('Map Selected'); + else + mapButton.setText('Map All'); + }; + var actions = new Ext.ux.grid.RowActions({ + header: 'Details', + width: 10, + actions: [{ + iconCls: 'info', + qtip: 'Detailed stream info', + cb: function(grid, rec, act, row, col) { + Ext.Ajax.request({ + url: 'api/service/streams', + params: { + uuid: rec.id + }, + success: function(r, o) { + var d = Ext.util.JSON.decode(r.responseText); + tvheadend.show_service_streams(d); + } + }); + } + }], + destroy: function() { + } + }); + conf.tbar = [mapButton]; + conf.selected = selected; + conf.lcol[1] = actions; + conf.plugins = [actions]; + } + function destroyer(conf) { + delete conf.tbar; + delete conf.plugins; + conf.lcol[1] = {}; + conf.selected = null; + } tvheadend.idnode_grid(panel, { url: 'api/mpegts/service', titleS: 'Service', @@ -237,8 +251,6 @@ tvheadend.services = function(panel, index) hidemode: true, add: false, del: false, - selected: selected, - tbar: [mapButton], help: function() { new tvheadend.help('Services', 'config_services.html'); }, @@ -252,13 +264,16 @@ tvheadend.services = function(panel, index) "?title=" + encodeURIComponent(title) + "'>Play"; } }, - actions + { + /* placeholder for actions */ + } ], - plugins: [actions], sort: { field: 'svcname', direction: 'ASC' - } + }, + builder: builder, + destroyer: destroyer }); }; diff --git a/src/webui/static/app/tvheadend.js b/src/webui/static/app/tvheadend.js index 1ab34477..807e081e 100644 --- a/src/webui/static/app/tvheadend.js +++ b/src/webui/static/app/tvheadend.js @@ -1,3 +1,4 @@ +tvheadend.dynamic = false; tvheadend.accessupdate = null; tvheadend.capabilties = null; tvheadend.dvrpanel = null; @@ -39,27 +40,36 @@ tvheadend.help = function(title, pagename) { }; tvheadend.paneladd = function(dst, add, idx) { - if (idx != null) - dst.insert(idx, add); - else - dst.add(add); + if (idx != null) + dst.insert(idx, add); + else + dst.add(add); }; +tvheadend.panelreg = function(tabpanel, panel, builder, destroyer) { + /* the 'activate' event does not work in ExtJS 3.4 */ + tabpanel.on('beforetabchange', function(tp, p) { + if (p == panel) + builder(); + }); + panel.on('deactivate', destroyer); +} + tvheadend.Ajax = function(conf) { - var orig_success = conf.success; - var orig_failure = conf.failure; - conf.success = function(d) { - tvheadend.loading(0); - if (orig_success) - orig_success(d); - } - conf.failure = function(d) { - tvheadend.loading(0); - if (orig_failure) - orig_failure(d); - } - tvheadend.loading(1); - Ext.Ajax.request(conf); + var orig_success = conf.success; + var orig_failure = conf.failure; + conf.success = function(d) { + tvheadend.loading(0); + if (orig_success) + orig_success(d); + } + conf.failure = function(d) { + tvheadend.loading(0); + if (orig_failure) + orig_failure(d); + } + tvheadend.loading(1); + Ext.Ajax.request(conf); }; tvheadend.loading = function(on) {