* Channel editor has been reworked a bit. It uses an editorGrid, similar

to how other grids work in Tvheadend. Tags are mapped inline using
    a list-of-values combobox (http://lovcombo.extjs.eu/)
This commit is contained in:
Andreas Öman 2009-07-16 11:10:41 +00:00
parent 08466b212e
commit e8a7044f14
6 changed files with 606 additions and 1250 deletions

7
debian/changelog vendored
View file

@ -52,7 +52,12 @@ hts-tvheadend (2.3) hts; urgency=low
from local to nationwide broadcast (AC3 audio is only present
in nationwide broadcast)
Ticket #78
* Channel editor has been reworked a bit. It uses an editorGrid, similar
to how other grids work in Tvheadend. Tags are mapped inline using
a list-of-values combobox (http://lovcombo.extjs.eu/)
hts-tvheadend (2.2) hts; urgency=low
* Set $HOME so forked processes (XMLTV) will have correct environment

View file

@ -270,32 +270,106 @@ extjs_tablemgr(http_connection_t *hc, const char *remain, void *opaque)
/**
*
*/
static int
extjs_chlist(http_connection_t *hc, const char *remain, void *opaque)
static void
extjs_channels_delete(htsmsg_t *in)
{
htsbuf_queue_t *hq = &hc->hc_reply;
htsmsg_t *out, *array, *c;
htsmsg_field_t *f;
channel_t *ch;
out = htsmsg_create_map();
TAILQ_FOREACH(f, &in->hm_fields, hmf_link)
if(f->hmf_type == HMF_S64 &&
(ch = channel_find_by_identifier(f->hmf_s64)) != NULL)
channel_delete(ch);
}
array = htsmsg_create_list();
pthread_mutex_lock(&global_lock);
/**
*
*/
static void
extjs_channels_update(htsmsg_t *in)
{
htsmsg_field_t *f;
channel_t *ch;
htsmsg_t *c;
uint32_t id;
const char *s;
RB_FOREACH(ch, &channel_name_tree, ch_name_link) {
c = htsmsg_create_map();
htsmsg_add_str(c, "name", ch->ch_name);
htsmsg_add_u32(c, "chid", ch->ch_id);
htsmsg_add_msg(array, NULL, c);
TAILQ_FOREACH(f, &in->hm_fields, hmf_link) {
if((c = htsmsg_get_map_by_field(f)) == NULL ||
htsmsg_get_u32(c, "id", &id))
continue;
if((ch = channel_find_by_identifier(id)) == NULL)
continue;
if((s = htsmsg_get_str(c, "name")) != NULL)
channel_rename(ch, s);
if((s = htsmsg_get_str(c, "xmltvsrc")) != NULL)
channel_set_xmltv_source(ch, xmltv_channel_find_by_displayname(s));
if((s = htsmsg_get_str(c, "tags")) != NULL)
channel_set_tags_from_list(ch, s);
}
}
/**
*
*/
static int
extjs_channels(http_connection_t *hc, const char *remain, void *opaque)
{
htsbuf_queue_t *hq = &hc->hc_reply;
htsmsg_t *array, *c;
channel_t *ch;
char buf[1024];
channel_tag_mapping_t *ctm;
const char *op = http_arg_get(&hc->hc_req_args, "op");
const char *entries = http_arg_get(&hc->hc_req_args, "entries");
htsmsg_autodtor(in) =
entries != NULL ? htsmsg_json_deserialize(entries) : NULL;
htsmsg_autodtor(out) = htsmsg_create_map();
scopedgloballock();
if(!strcmp(op, "list")) {
array = htsmsg_create_list();
RB_FOREACH(ch, &channel_name_tree, ch_name_link) {
c = htsmsg_create_map();
htsmsg_add_str(c, "name", ch->ch_name);
htsmsg_add_u32(c, "chid", ch->ch_id);
if(ch->ch_xc != NULL)
htsmsg_add_str(c, "xmltvsrc", ch->ch_xc->xc_displayname);
buf[0] = 0;
LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link) {
snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
"%s%d", strlen(buf) == 0 ? "" : ",",
ctm->ctm_tag->ct_identifier);
}
htsmsg_add_str(c, "tags", buf);
htsmsg_add_msg(array, NULL, c);
}
htsmsg_add_msg(out, "entries", array);
} else if(!strcmp(op, "delete") && in != NULL) {
extjs_channels_delete(in);
} else if(!strcmp(op, "update") && in != NULL) {
extjs_channels_update(in);
} else {
return 400;
}
pthread_mutex_unlock(&global_lock);
htsmsg_add_msg(out, "entries", array);
htsmsg_json_serialize(out, hq, 0);
htsmsg_destroy(out);
http_output_content(hc, "text/x-json; charset=UTF-8");
return 0;
}
@ -388,229 +462,6 @@ json_single_record(htsmsg_t *rec, const char *root)
}
/**
*
*/
static htsmsg_t *
build_transport_msg(th_transport_t *t)
{
htsmsg_t *r = htsmsg_create_map();
th_stream_t *st;
const char *n;
char video[200];
char audio[200];
char subtitles[200];
char scrambling[200];
htsmsg_add_u32(r, "enabled", t->tht_enabled);
htsmsg_add_str(r, "name", t->tht_svcname);
htsmsg_add_str(r, "provider", t->tht_provider ?: "");
if((n = t->tht_networkname(t)) != NULL)
htsmsg_add_str(r, "network", n);
htsmsg_add_str(r, "source", t->tht_sourcename(t));
htsmsg_add_str(r, "status", "");
video[0] = 0;
audio[0] = 0;
subtitles[0] = 0;
scrambling[0] = 0;
LIST_FOREACH(st, &t->tht_components, st_link) {
switch(st->st_type) {
case SCT_TELETEXT:
case SCT_SUBTITLES:
case SCT_PAT:
case SCT_PMT:
break;
case SCT_MPEG2VIDEO:
snprintf(video + strlen(video), sizeof(video) - strlen(video),
"%sMPEG-2 (PID:%d", strlen(video) > 0 ? ", " : "",
st->st_pid);
video:
if(st->st_frame_duration) {
snprintf(video + strlen(video), sizeof(video) - strlen(video),
", %d Hz)", 90000 / st->st_frame_duration);
} else {
snprintf(video + strlen(video), sizeof(video) - strlen(video),
")");
}
break;
case SCT_H264:
snprintf(video + strlen(video), sizeof(video) - strlen(video),
"%sH.264 (PID:%d", strlen(video) > 0 ? ", " : "",
st->st_pid);
goto video;
case SCT_MPEG2AUDIO:
snprintf(audio + strlen(audio), sizeof(audio) - strlen(audio),
"%sMPEG-2 (PID:%d", strlen(audio) > 0 ? ", " : "",
st->st_pid);
audio:
if(st->st_lang[0]) {
snprintf(audio + strlen(audio), sizeof(audio) - strlen(audio),
", languange: \"%s\")", st->st_lang);
} else {
snprintf(audio + strlen(audio), sizeof(audio) - strlen(audio),
")");
}
break;
case SCT_AC3:
snprintf(audio + strlen(audio), sizeof(audio) - strlen(audio),
"%sAC3 (PID:%d", strlen(audio) > 0 ? ", " : "",
st->st_pid);
goto audio;
case SCT_AAC:
snprintf(audio + strlen(audio), sizeof(audio) - strlen(audio),
"%sAAC (PID:%d", strlen(audio) > 0 ? ", " : "",
st->st_pid);
goto audio;
case SCT_CA:
snprintf(scrambling + strlen(scrambling),
sizeof(scrambling) - strlen(scrambling),
"%s%s", strlen(scrambling) > 0 ? ", " : "",
psi_caid2name(st->st_caid));
break;
}
}
htsmsg_add_str(r, "video", video);
htsmsg_add_str(r, "audio", audio);
htsmsg_add_str(r, "scrambling", scrambling[0] ? scrambling : "none");
return r;
}
/**
*
*/
static int
extjs_channel(http_connection_t *hc, const char *remain, void *opaque)
{
htsbuf_queue_t *hq = &hc->hc_reply;
const char *s = http_arg_get(&hc->hc_req_args, "chid");
const char *op = http_arg_get(&hc->hc_req_args, "op");
channel_t *ch;
channel_t *ch2;
th_transport_t *t;
int reloadchlist = 0;
htsmsg_t *out, *array, *r;
channel_tag_mapping_t *ctm;
char buf[200];
pthread_mutex_lock(&global_lock);
if(http_access_verify(hc, ACCESS_ADMIN)) {
pthread_mutex_unlock(&global_lock);
return HTTP_STATUS_UNAUTHORIZED;
}
ch = s ? channel_find_by_identifier(atoi(s)) : NULL;
if(ch == NULL) {
pthread_mutex_unlock(&global_lock);
return HTTP_STATUS_BAD_REQUEST;
}
if(!strcmp(op, "load")) {
r = htsmsg_create_map();
htsmsg_add_u32(r, "id", ch->ch_id);
htsmsg_add_str(r, "name", ch->ch_name);
htsmsg_add_str(r, "comdetect", "tt192");
if(ch->ch_xc != NULL)
htsmsg_add_str(r, "xmltvchannel", ch->ch_xc->xc_displayname);
buf[0] = 0;
LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link) {
snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
"%s%d", strlen(buf) == 0 ? "" : ",",
ctm->ctm_tag->ct_identifier);
}
htsmsg_add_str(r, "tags", buf);
out = json_single_record(r, "channels");
} else if(!strcmp(op, "gettransports")) {
out = htsmsg_create_map();
array = htsmsg_create_list();
LIST_FOREACH(t, &ch->ch_transports, tht_ch_link)
htsmsg_add_msg(array, NULL, build_transport_msg(t));
htsmsg_add_msg(out, "entries", array);
} else if(!strcmp(op, "delete")) {
channel_delete(ch);
out = htsmsg_create_map();
htsmsg_add_u32(out, "reloadchlist", 1);
htsmsg_add_u32(out, "success", 1);
} else if(!strcmp(op, "mergefrom")) {
if((s = http_arg_get(&hc->hc_req_args, "srcch")) == NULL)
return HTTP_STATUS_BAD_REQUEST;
ch2 = channel_find_by_identifier(atoi(s));
if(ch2 == NULL || ch2 == ch)
return HTTP_STATUS_BAD_REQUEST;
channel_merge(ch, ch2); /* ch2 goes away here */
out = htsmsg_create_map();
htsmsg_add_u32(out, "reloadchlist", 1);
htsmsg_add_u32(out, "success", 1);
} else if(!strcmp(op, "save")) {
if((s = http_arg_get(&hc->hc_req_args, "tags")) != NULL)
channel_set_tags_from_list(ch, s);
s = http_arg_get(&hc->hc_req_args, "xmltvchannel");
channel_set_xmltv_source(ch, s?xmltv_channel_find_by_displayname(s):NULL);
if((s = http_arg_get(&hc->hc_req_args, "name")) != NULL &&
strcmp(s, ch->ch_name)) {
if(channel_rename(ch, s)) {
out = htsmsg_create_map();
htsmsg_add_u32(out, "success", 0);
htsmsg_add_str(out, "errormsg", "Channel name already exist");
goto response;
} else {
reloadchlist = 1;
}
}
out = htsmsg_create_map();
htsmsg_add_u32(out, "reloadchlist", 1);
htsmsg_add_u32(out, "success", 1);
} else {
pthread_mutex_unlock(&global_lock);
return HTTP_STATUS_BAD_REQUEST;
}
response:
pthread_mutex_unlock(&global_lock);
htsmsg_json_serialize(out, hq, 0);
htsmsg_destroy(out);
http_output_content(hc, "text/x-json; charset=UTF-8");
return 0;
}
/**
*
*/
@ -1528,8 +1379,7 @@ extjs_start(void)
http_path_add("/extjs.html", NULL, extjs_root, ACCESS_WEB_INTERFACE);
http_path_add("/tablemgr", NULL, extjs_tablemgr, ACCESS_WEB_INTERFACE);
http_path_add("/dvbnetworks", NULL, extjs_dvbnetworks, ACCESS_WEB_INTERFACE);
http_path_add("/chlist", NULL, extjs_chlist, ACCESS_WEB_INTERFACE);
http_path_add("/channel", NULL, extjs_channel, ACCESS_WEB_INTERFACE);
http_path_add("/channels", NULL, extjs_channels, ACCESS_WEB_INTERFACE);
http_path_add("/xmltv", NULL, extjs_xmltv, ACCESS_WEB_INTERFACE);
http_path_add("/channeltags", NULL, extjs_channeltags, ACCESS_WEB_INTERFACE);
http_path_add("/epg", NULL, extjs_epg, ACCESS_WEB_INTERFACE);

View file

@ -8,7 +8,9 @@ tvheadend.channelTags = new Ext.data.JsonStore({
fields: ['identifier', 'name'],
id: 'identifier',
url:'channeltags',
baseParams: {op: 'listTags'}
baseParams: {
op: 'listTags'
}
});
tvheadend.comet.on('channeltags', function(m) {
@ -23,9 +25,12 @@ tvheadend.comet.on('channeltags', function(m) {
tvheadend.channels = new Ext.data.JsonStore({
autoLoad: true,
root:'entries',
fields: ['name', 'chid'],
fields: ['name', 'chid', 'xmltvsrc', 'tags'],
id: 'chid',
url: "chlist"
url: "channels",
baseParams: {
op: 'list'
}
});
tvheadend.comet.on('channels', function(m) {
@ -34,343 +39,198 @@ tvheadend.comet.on('channels', function(m) {
});
/**
* Channel details
*/
tvheadend.channeldetails = function(chid, chname) {
var fm = Ext.form;
var xg = Ext.grid;
var expander = new xg.RowExpander({
tpl : new Ext.Template(
'<div><b width=100px>Video:</b>{video}</div>',
'<div><b>Audio:</b>{audio}</div>',
'<div><b>Subtitling:</b>{subtitles}</div>',
'<div><b>Scrambling:</b>{scrambling}</div>'
)
});
var enabledColumn = new Ext.grid.CheckColumn({
header: "Enabled",
dataIndex: 'enabled',
width: 60
});
var cm = new Ext.grid.ColumnModel([expander,
enabledColumn,
{
width: 125,
id:'name',
header: "Original name",
dataIndex: 'name'
},{
width: 125,
id:'status',
header: "Last status",
dataIndex: 'status'
},{
width: 125,
id:'provider',
header: "Provider",
dataIndex: 'provider'
},{
width: 125,
id:'network',
header: "Network",
dataIndex: 'network'
},{
width: 250,
id:'source',
header: "Source",
dataIndex: 'source'
}
]);
var transportRecord = Ext.data.Record.create([
{name: 'enabled'},
{name: 'status'},
{name: 'name'},
{name: 'provider'},
{name: 'network'},
{name: 'source'},
{name: 'video'},
{name: 'audio'},
{name: 'scrambling'},
{name: 'subtitles'}
]);
var transportsstore =
new Ext.data.JsonStore({root: 'entries',
fields: transportRecord,
url: "channel",
autoLoad: true,
id: 'id',
storeid: 'id',
baseParams: {chid: chid, op: "gettransports"}
});
var transportsgrid = new Ext.grid.EditorGridPanel({
title:'Transports',
anchor: '100% 50%',
stripeRows:true,
plugins:[enabledColumn, expander],
store: transportsstore,
clicksToEdit: 2,
viewConfig: {forceFit:true},
cm: cm,
selModel: new Ext.grid.RowSelectionModel({singleSelect:false})
});
var confreader = new Ext.data.JsonReader({
root: 'channels'
}, ['name','xmltvchannel','tags']);
var xmltvChannels = new Ext.data.JsonStore({
root:'entries',
fields: [{name: 'xcTitle'}, {name: 'xcIcon'}],
url:'xmltv',
baseParams: {op: 'listChannels'}
});
var confpanel = new Ext.FormPanel({
border:false,
disabled:true,
bodyStyle:'padding:15px',
anchor: '100% 50%',
labelAlign: 'right',
labelWidth: 150,
waitMsgTarget: true,
reader: confreader,
items: [{
layout:'column',
border:false,
items:[{
border:false,
columnWidth:.5,
layout: 'form',
defaultType: 'textfield',
items: [
{
fieldLabel: 'Channel name',
name: 'name'
},new Ext.form.ComboBox({
loadingText: 'Loading...',
fieldLabel: 'XML-TV Source',
name: 'xmltvchannel',
width: 200,
displayField:'xcTitle',
valueField:'xcTitle',
store: xmltvChannels,
forceSelection: true,
mode: 'remote',
editable: false,
triggerAction: 'all',
emptyText: 'None'
})
]
},{
border:false,
columnWidth:.5,
layout: 'form',
items: [{
fieldLabel: 'Tags',
xtype:"multiselect",
name:"tags",
valueField:"identifier",
displayField:"name",
width:200,
height:200,
store:tvheadend.channelTags
}]
}]
}]
});
confpanel.getForm().load({url:'channel',
params:{'chid': chid, 'op':'load'},
success:function(form, action) {
confpanel.enable();
}});
function saveChanges() {
confpanel.getForm().submit({url:'channel',
params:{'chid': chid, 'op':'save'},
waitMsg:'Saving Data...',
failure: function(form, action) {
Ext.Msg.alert('Save failed', action.result.errormsg);
}
});
}
function deleteChannel() {
Ext.MessageBox.confirm('Message',
'Do you really want to delete "' + chname + '"',
function(button) {
if(button == 'no')
return;
Ext.Ajax.request({url: 'channel',
params:{'chid': chid, 'op':'delete'},
success: function() {
panel.destroy();
}
});
}
);
}
var panel = new Ext.Panel({
title: chname,
border:false,
tbar: [{
tooltip: 'Delete channel "' + chname + '". All mapped transports will be unmapped',
iconCls:'remove',
text: "Delete channel",
handler: deleteChannel
}, '-', {
tooltip: 'Save changes made to channel configuration below and the mapped transports',
iconCls:'save',
text: "Save configuration",
handler: saveChanges
}, '->', {
text: 'Help',
handler: function() {
new tvheadend.help('Channel configuration',
'config_channels.html');
}
}],
defaults: {
border:false
},
layout:'anchor',
items: [confpanel,transportsgrid]
});
panel.on('afterlayout', function(parent, n) {
var DropTargetEl = parent.body.dom;
var DropTarget = new Ext.dd.DropTarget(DropTargetEl, {
ddGroup : 'chconfddgroup',
notifyEnter : function(ddSource, e, data) {
//Add some flare to invite drop.
parent.body.stopFx();
parent.body.highlight();
},
notifyDrop : function(ddSource, e, data){
// Reference the record (single selection) for readability
var selectedRecord = ddSource.dragData.selections[0];
Ext.MessageBox.confirm('Merge channels',
'Copy transport configuration from "' + selectedRecord.data.name +
'" to "' + chname + '". This will also remove the channel "' +
selectedRecord.data.name + '"',
function(button) {
if(button == 'no')
return;
Ext.Ajax.request({url: 'channel',
params:{chid: chid,
op:'mergefrom',
srcch: selectedRecord.data.chid},
success: function() {
transportsstore.reload();
}});
}
);
}
});
});
return panel;
}
/**
*
*/
tvheadend.chconf = function() {
var chlist = new Ext.grid.GridPanel({
viewConfig: {forceFit:true},
ddGroup: 'chconfddgroup',
enableDragDrop: true,
stripeRows:true,
region:'west',
width: 300,
columns: [{id:'name',
header: "Channel name",
width: 260,
dataIndex: 'name'}
],
selModel: new Ext.grid.RowSelectionModel({singleSelect:true}),
store: tvheadend.channels
});
var details = new Ext.Panel({
region:'center', layout:'fit',
items:[{border: false}]
tvheadend.chconf = function()
{
var xmltvChannels = new Ext.data.JsonStore({
root:'entries',
fields: ['xcTitle','xcIcon'],
url:'xmltv',
baseParams: {
op: 'listChannels'
}
});
var panel = new Ext.Panel({
border: false,
title:'Channels',
layout:'border',
items: [chlist, details]
});
var fm = Ext.form;
var cm = new Ext.grid.ColumnModel([
{
header: "Name",
dataIndex: 'name',
width: 150,
editor: new fm.TextField({
allowBlank: false
})
},
{
header: "XMLTV source",
dataIndex: 'xmltvsrc',
width: 150,
editor: new fm.ComboBox({
loadingText: 'Loading...',
store: xmltvChannels,
allowBlank: true,
typeAhead: true,
minChars: 2,
lazyRender: true,
triggerAction: 'all',
mode: 'remote',
displayField:'xcTitle',
valueField:'xcTitle'
})
},
{
header: "Tags",
dataIndex: 'tags',
width: 300,
renderer: function(value, metadata, record, row, col, store) {
if (typeof value === 'undefined' || value.length < 1) {
return '<span class="tvh-grid-unset">No tags</span>';
}
chlist.on('rowclick', function(grid, n) {
var rec = tvheadend.channels.getAt(n);
details.remove(details.getComponent(0));
details.doLayout();
var newpanel = new tvheadend.channeldetails(rec.data.chid,
rec.data.name);
details.add(newpanel);
details.doLayout();
});
/**
* Setup Drop Targets
*/
// This will make sure we only drop to the view container
/*
var DropTargetEl = details.getView();
var DropTarget = new Ext.dd.DropTarget(DropTargetEl, {
ddGroup : 'chconfddgroup',
notifyEnter : function(ddSource, e, data) {
//Add some flare to invite drop.
panel.body.stopFx();
panel.body.highlight();
ret = [];
tags = value.split(',');
for (var i = 0; i < tags.length; i++) {
var tag = tvheadend.channelTags.getById(tags[i]);
if (typeof tag !== 'undefined') {
ret.push(tag.data.name);
}
}
return ret.join(', ');
},
notifyDrop : function(ddSource, e, data){
// Reference the record (single selection) for readability
var selectedRecord = ddSource.dragData.selections[0];
console.log(selectedRecord);
}
});
editor: new Ext.ux.form.LovCombo({
store: tvheadend.channelTags,
mode:'local',
valueField: 'identifier',
displayField: 'name'
})
}
]);
*/
/*
details.on('afterlayout', function(parent, n) {
console.log(parent);
function delSelected() {
var selectedKeys = grid.selModel.selections.keys;
if(selectedKeys.length > 0) {
Ext.MessageBox.confirm('Message',
'Do you really want to delete selection?',
deleteRecord);
} else {
Ext.MessageBox.alert('Message',
'Please select at least one item to delete');
}
}
function deleteRecord(btn) {
if(btn=='yes') {
var selectedKeys = grid.selModel.selections.keys;
Ext.Ajax.request({
url: "channels",
params: {
op:"delete",
entries:Ext.encode(selectedKeys)
},
failure:function(response,options) {
Ext.MessageBox.alert('Server Error','Unable to delete');
}
})
}
}
function saveChanges() {
var mr = tvheadend.channels.getModifiedRecords();
var out = new Array();
for (var x = 0; x < mr.length; x++) {
v = mr[x].getChanges();
out[x] = v;
out[x].id = mr[x].id;
}
Ext.Ajax.request({
url: "channels",
params: {
op:"update",
entries:Ext.encode(out)
},
success:function(response,options) {
tvheadend.channels.commitChanges();
},
failure:function(response,options) {
Ext.MessageBox.alert('Message', response.statusText);
}
});
*/
return panel;
}
var selModel = new Ext.grid.RowSelectionModel({
singleSelect:false
});
var delBtn = new Ext.Toolbar.Button({
tooltip: 'Delete one or more selected channels',
iconCls:'remove',
text: 'Delete selected',
handler: delSelected,
disabled: true
});
selModel.on('selectionchange', function(s) {
delBtn.setDisabled(s.getCount() == 0);
});
var saveBtn = new Ext.Toolbar.Button({
tooltip: 'Save any changes made (Changed cells have red borders).',
iconCls:'save',
text: "Save changes",
handler: saveChanges,
disabled: true
});
var rejectBtn = new Ext.Toolbar.Button({
tooltip: 'Revert any changes made (Changed cells have red borders).',
iconCls:'undo',
text: "Revert changes",
handler: function() {
tvheadend.channels.rejectChanges();
},
disabled: true
});
var grid = new Ext.grid.EditorGridPanel({
stripeRows: true,
title: 'Channels',
store: tvheadend.channels,
clicksToEdit: 2,
cm: cm,
viewConfig: {
forceFit:true
},
selModel: selModel,
tbar: [
delBtn, '-', saveBtn, rejectBtn, '->', {
text: 'Help',
handler: function() {
new tvheadend.help(title, helpContent);
}
}
]
});
tvheadend.channels.on('update', function(s, r, o) {
d = s.getModifiedRecords().length == 0
saveBtn.setDisabled(d);
rejectBtn.setDisabled(d);
});
tvheadend.channelTags.on('load', function(s, r, o) {
if(grid.rendered)
grid.getView().refresh();
});
return grid;
}

View file

@ -273,4 +273,35 @@
float:left;
}
/* eof */
/** vim: ts=4:sw=4:nu:fdc=4:nospell
*
* Ext.ux.form.LovCombo CSS File
*
* @author Ing.Jozef Sakáloš
* @copyright (c) 2008, by Ing. Jozef Sakáloš
* @date 5. April 2008
* @version $Id: Ext.ux.form.LovCombo.css 189 2008-04-16 21:01:06Z jozo $
*
* @license Ext.ux.form.LovCombo.css is licensed under the terms of the Open Source
* LGPL 3.0 license. Commercial use is permitted to the extent that the
* code/component(s) do NOT become part of another Open Source or Commercially
* licensed development library or toolkit without explicit permission.
*
* License details: http://www.gnu.org/licenses/lgpl.html
*/
.ux-lovcombo-icon {
width:16px;
height:16px;
float:left;
background-position: -1px -1px ! important;
background-repeat:no-repeat ! important;
}
.ux-lovcombo-icon-checked {
background: transparent url(../extjs/resources/images/default/menu/checked.gif);
}
.ux-lovcombo-icon-unchecked {
background: transparent url(../extjs/resources/images/default/menu/unchecked.gif);
}
/* eof */

View file

@ -179,610 +179,6 @@ Ext.extend(Ext.grid.RowExpander, Ext.util.Observable, {
/*
* Software License Agreement (BSD License)
* Copyright (c) 2008, Nige "Animal" White
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of the original author nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @class Ext.ux.DDView
* <p>A DnD-enabled version of {@link Ext.DataView}. Drag/drop is implemented by adding
* {@link Ext.data.Record}s to the target DDView. If copying is not being performed,
* the original {@link Ext.data.Record} is removed from the source DDView.</p>
* @constructor
* Create a new DDView
* @param {Object} config The configuration properties.
*/
Ext.ux.DDView = function(config) {
if (!config.itemSelector) {
var tpl = config.tpl;
if (this.classRe.test(tpl)) {
config.tpl = tpl.replace(this.classRe, 'class=$1x-combo-list-item $2$1');
}
else {
config.tpl = tpl.replace(this.tagRe, '$1 class="x-combo-list-item" $2');
}
config.itemSelector = ".x-combo-list-item";
}
Ext.ux.DDView.superclass.constructor.call(this, Ext.apply(config, {
border: false
}));
};
Ext.extend(Ext.ux.DDView, Ext.DataView, {
/**
* @cfg {String/Array} dragGroup The ddgroup name(s) for the View's DragZone (defaults to undefined).
*/
/**
* @cfg {String/Array} dropGroup The ddgroup name(s) for the View's DropZone (defaults to undefined).
*/
/**
* @cfg {Boolean} copy Causes drag operations to copy nodes rather than move (defaults to false).
*/
/**
* @cfg {Boolean} allowCopy Causes ctrl/drag operations to copy nodes rather than move (defaults to false).
*/
/**
* @cfg {String} sortDir Sort direction for the view, 'ASC' or 'DESC' (defaults to 'ASC').
*/
sortDir: 'ASC',
// private
isFormField: true,
classRe: /class=(['"])(.*)\1/,
tagRe: /(<\w*)(.*?>)/,
reset: Ext.emptyFn,
clearInvalid: Ext.form.Field.prototype.clearInvalid,
// private
afterRender: function() {
Ext.ux.DDView.superclass.afterRender.call(this);
if (this.dragGroup) {
this.setDraggable(this.dragGroup.split(","));
}
if (this.dropGroup) {
this.setDroppable(this.dropGroup.split(","));
}
if (this.deletable) {
this.setDeletable();
}
this.isDirtyFlag = false;
this.addEvents(
"drop"
);
},
// private
validate: function() {
return true;
},
// private
destroy: function() {
this.purgeListeners();
this.getEl().removeAllListeners();
this.getEl().remove();
if (this.dragZone) {
if (this.dragZone.destroy) {
this.dragZone.destroy();
}
}
if (this.dropZone) {
if (this.dropZone.destroy) {
this.dropZone.destroy();
}
}
},
/**
* Allows this class to be an Ext.form.Field so it can be found using {@link Ext.form.BasicForm#findField}.
*/
getName: function() {
return this.name;
},
/**
* Loads the View from a JSON string representing the Records to put into the Store.
* @param {String} value The JSON string
*/
setValue: function(v) {
if (!this.store) {
throw "DDView.setValue(). DDView must be constructed with a valid Store";
}
var data = {};
data[this.store.reader.meta.root] = v ? [].concat(v) : [];
this.store.proxy = new Ext.data.MemoryProxy(data);
this.store.load();
},
/**
* Returns the view's data value as a list of ids.
* @return {String} A parenthesised list of the ids of the Records in the View, e.g. (1,3,8).
*/
getValue: function() {
var result = '(';
this.store.each(function(rec) {
result += rec.id + ',';
});
return result.substr(0, result.length - 1) + ')';
},
getIds: function() {
var i = 0, result = new Array(this.store.getCount());
this.store.each(function(rec) {
result[i++] = rec.id;
});
return result;
},
/**
* Returns true if the view's data has changed, else false.
* @return {Boolean}
*/
isDirty: function() {
return this.isDirtyFlag;
},
/**
* Part of the Ext.dd.DropZone interface. If no target node is found, the
* whole Element becomes the target, and this causes the drop gesture to append.
*/
getTargetFromEvent : function(e) {
var target = e.getTarget();
while ((target !== null) && (target.parentNode != this.el.dom)) {
target = target.parentNode;
}
if (!target) {
target = this.el.dom.lastChild || this.el.dom;
}
return target;
},
/**
* Create the drag data which consists of an object which has the property "ddel" as
* the drag proxy element.
*/
getDragData : function(e) {
var target = this.findItemFromChild(e.getTarget());
if(target) {
if (!this.isSelected(target)) {
delete this.ignoreNextClick;
this.onItemClick(target, this.indexOf(target), e);
this.ignoreNextClick = true;
}
var dragData = {
sourceView: this,
viewNodes: [],
records: [],
copy: this.copy || (this.allowCopy && e.ctrlKey)
};
if (this.getSelectionCount() == 1) {
var i = this.getSelectedIndexes()[0];
var n = this.getNode(i);
dragData.viewNodes.push(dragData.ddel = n);
dragData.records.push(this.store.getAt(i));
dragData.repairXY = Ext.fly(n).getXY();
} else {
dragData.ddel = document.createElement('div');
dragData.ddel.className = 'multi-proxy';
this.collectSelection(dragData);
}
return dragData;
}
return false;
},
// override the default repairXY.
getRepairXY : function(e){
return this.dragData.repairXY;
},
// private
collectSelection: function(data) {
data.repairXY = Ext.fly(this.getSelectedNodes()[0]).getXY();
if (this.preserveSelectionOrder === true) {
Ext.each(this.getSelectedIndexes(), function(i) {
var n = this.getNode(i);
var dragNode = n.cloneNode(true);
dragNode.id = Ext.id();
data.ddel.appendChild(dragNode);
data.records.push(this.store.getAt(i));
data.viewNodes.push(n);
}, this);
} else {
var i = 0;
this.store.each(function(rec){
if (this.isSelected(i)) {
var n = this.getNode(i);
var dragNode = n.cloneNode(true);
dragNode.id = Ext.id();
data.ddel.appendChild(dragNode);
data.records.push(this.store.getAt(i));
data.viewNodes.push(n);
}
i++;
}, this);
}
},
/**
* Specify to which ddGroup items in this DDView may be dragged.
* @param {String} ddGroup The DD group name to assign this view to.
*/
setDraggable: function(ddGroup) {
if (ddGroup instanceof Array) {
Ext.each(ddGroup, this.setDraggable, this);
return;
}
if (this.dragZone) {
this.dragZone.addToGroup(ddGroup);
} else {
this.dragZone = new Ext.dd.DragZone(this.getEl(), {
containerScroll: true,
ddGroup: ddGroup
});
// Draggability implies selection. DragZone's mousedown selects the element.
if (!this.multiSelect) { this.singleSelect = true; }
// Wire the DragZone's handlers up to methods in *this*
this.dragZone.getDragData = this.getDragData.createDelegate(this);
this.dragZone.getRepairXY = this.getRepairXY;
this.dragZone.onEndDrag = this.onEndDrag;
}
},
/**
* Specify from which ddGroup this DDView accepts drops.
* @param {String} ddGroup The DD group name from which to accept drops.
*/
setDroppable: function(ddGroup) {
if (ddGroup instanceof Array) {
Ext.each(ddGroup, this.setDroppable, this);
return;
}
if (this.dropZone) {
this.dropZone.addToGroup(ddGroup);
} else {
this.dropZone = new Ext.dd.DropZone(this.getEl(), {
owningView: this,
containerScroll: true,
ddGroup: ddGroup
});
// Wire the DropZone's handlers up to methods in *this*
this.dropZone.getTargetFromEvent = this.getTargetFromEvent.createDelegate(this);
this.dropZone.onNodeEnter = this.onNodeEnter.createDelegate(this);
this.dropZone.onNodeOver = this.onNodeOver.createDelegate(this);
this.dropZone.onNodeOut = this.onNodeOut.createDelegate(this);
this.dropZone.onNodeDrop = this.onNodeDrop.createDelegate(this);
}
},
// private
getDropPoint : function(e, n, dd){
if (n == this.el.dom) { return "above"; }
var t = Ext.lib.Dom.getY(n), b = t + n.offsetHeight;
var c = t + (b - t) / 2;
var y = Ext.lib.Event.getPageY(e);
if(y <= c) {
return "above";
}else{
return "below";
}
},
// private
isValidDropPoint: function(pt, n, data) {
if (!data.viewNodes || (data.viewNodes.length != 1)) {
return true;
}
var d = data.viewNodes[0];
if (d == n) {
return false;
}
if ((pt == "below") && (n.nextSibling == d)) {
return false;
}
if ((pt == "above") && (n.previousSibling == d)) {
return false;
}
return true;
},
// private
onNodeEnter : function(n, dd, e, data){
if (this.highlightColor && (data.sourceView != this)) {
this.el.highlight(this.highlightColor);
}
return false;
},
// private
onNodeOver : function(n, dd, e, data){
var dragElClass = this.dropNotAllowed;
var pt = this.getDropPoint(e, n, dd);
if (this.isValidDropPoint(pt, n, data)) {
if (this.appendOnly || this.sortField) {
return "x-tree-drop-ok-below";
}
// set the insert point style on the target node
if (pt) {
var targetElClass;
if (pt == "above"){
dragElClass = n.previousSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-above";
targetElClass = "x-view-drag-insert-above";
} else {
dragElClass = n.nextSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-below";
targetElClass = "x-view-drag-insert-below";
}
if (this.lastInsertClass != targetElClass){
Ext.fly(n).replaceClass(this.lastInsertClass, targetElClass);
this.lastInsertClass = targetElClass;
}
}
}
return dragElClass;
},
// private
onNodeOut : function(n, dd, e, data){
this.removeDropIndicators(n);
},
// private
onNodeDrop : function(n, dd, e, data){
if (this.fireEvent("drop", this, n, dd, e, data) === false) {
return false;
}
var pt = this.getDropPoint(e, n, dd);
var insertAt = (this.appendOnly || (n == this.el.dom)) ? this.store.getCount() : n.viewIndex;
if (pt == "below") {
insertAt++;
}
// Validate if dragging within a DDView
if (data.sourceView == this) {
// If the first element to be inserted below is the target node, remove it
if (pt == "below") {
if (data.viewNodes[0] == n) {
data.viewNodes.shift();
}
} else { // If the last element to be inserted above is the target node, remove it
if (data.viewNodes[data.viewNodes.length - 1] == n) {
data.viewNodes.pop();
}
}
// Nothing to drop...
if (!data.viewNodes.length) {
return false;
}
// If we are moving DOWN, then because a store.remove() takes place first,
// the insertAt must be decremented.
if (insertAt > this.store.indexOf(data.records[0])) {
insertAt--;
}
}
// Dragging from a Tree. Use the Tree's recordFromNode function.
if (data.node instanceof Ext.tree.TreeNode) {
var r = data.node.getOwnerTree().recordFromNode(data.node);
if (r) {
data.records = [ r ];
}
}
if (!data.records) {
alert("Programming problem. Drag data contained no Records");
return false;
}
for (var i = 0; i < data.records.length; i++) {
var r = data.records[i];
var dup = this.store.getById(r.id);
if (dup && (dd != this.dragZone)) {
if(!this.allowDup && !this.allowTrash){
Ext.fly(this.getNode(this.store.indexOf(dup))).frame("red", 1);
return true
}
var x=new Ext.data.Record();
r.id=x.id;
delete x;
}
if (data.copy) {
this.store.insert(insertAt++, r.copy());
} else {
if (data.sourceView) {
data.sourceView.isDirtyFlag = true;
data.sourceView.store.remove(r);
}
if(!this.allowTrash)this.store.insert(insertAt++, r);
}
if(this.sortField){
this.store.sort(this.sortField, this.sortDir);
}
this.isDirtyFlag = true;
}
this.dragZone.cachedTarget = null;
return true;
},
// private
onEndDrag: function(data, e) {
var d = Ext.get(this.dragData.ddel);
if (d && d.hasClass("multi-proxy")) {
d.remove();
//delete this.dragData.ddel;
}
},
// private
removeDropIndicators : function(n){
if(n){
Ext.fly(n).removeClass([
"x-view-drag-insert-above",
"x-view-drag-insert-left",
"x-view-drag-insert-right",
"x-view-drag-insert-below"]);
this.lastInsertClass = "_noclass";
}
},
/**
* Add a delete option to the DDView's context menu.
* @param {String} imageUrl The URL of the "delete" icon image.
*/
setDeletable: function(imageUrl) {
if (!this.singleSelect && !this.multiSelect) {
this.singleSelect = true;
}
var c = this.getContextMenu();
this.contextMenu.on("itemclick", function(item) {
switch (item.id) {
case "delete":
this.remove(this.getSelectedIndexes());
break;
}
}, this);
this.contextMenu.add({
icon: imageUrl || AU.resolveUrl("/images/delete.gif"),
id: "delete",
text: AU.getMessage("deleteItem")
});
},
/**
* Return the context menu for this DDView.
* @return {Ext.menu.Menu} The context menu
*/
getContextMenu: function() {
if (!this.contextMenu) {
// Create the View's context menu
this.contextMenu = new Ext.menu.Menu({
id: this.id + "-contextmenu"
});
this.el.on("contextmenu", this.showContextMenu, this);
}
return this.contextMenu;
},
/**
* Disables the view's context menu.
*/
disableContextMenu: function() {
if (this.contextMenu) {
this.el.un("contextmenu", this.showContextMenu, this);
}
},
// private
showContextMenu: function(e, item) {
item = this.findItemFromChild(e.getTarget());
if (item) {
e.stopEvent();
this.select(this.getNode(item), this.multiSelect && e.ctrlKey, true);
this.contextMenu.showAt(e.getXY());
}
},
/**
* Remove {@link Ext.data.Record}s at the specified indices.
* @param {Array/Number} selectedIndices The index (or Array of indices) of Records to remove.
*/
remove: function(selectedIndices) {
selectedIndices = [].concat(selectedIndices);
for (var i = 0; i < selectedIndices.length; i++) {
var rec = this.store.getAt(selectedIndices[i]);
this.store.remove(rec);
}
},
/**
* Double click fires the {@link #dblclick} event. Additionally, if this DDView is draggable, and there is only one other
* related DropZone that is in another DDView, it drops the selected node on that DDView.
*/
onDblClick : function(e){
var item = this.findItemFromChild(e.getTarget());
if(item){
if (this.fireEvent("dblclick", this, this.indexOf(item), item, e) === false) {
return false;
}
if (this.dragGroup) {
var targets = Ext.dd.DragDropMgr.getRelated(this.dragZone, true);
// Remove instances of this View's DropZone
while (targets.indexOf(this.dropZone) !== -1) {
targets.remove(this.dropZone);
}
// If there's only one other DropZone, and it is owned by a DDView, then drop it in
if ((targets.length == 1) && (targets[0].owningView)) {
this.dragZone.cachedTarget = null;
var el = Ext.get(targets[0].getEl());
var box = el.getBox(true);
targets[0].onNodeDrop(el.dom, {
target: el.dom,
xy: [box.x, box.y + box.height - 1]
}, null, this.getDragData(e));
}
}
}
},
// private
onItemClick : function(item, index, e){
// The DragZone's mousedown->getDragData already handled selection
if (this.ignoreNextClick) {
delete this.ignoreNextClick;
return;
}
if(this.fireEvent("beforeclick", this, index, item, e) === false){
return false;
}
if(this.multiSelect || this.singleSelect){
if(this.multiSelect && e.shiftKey && this.lastSelection){
this.select(this.getNodes(this.indexOf(this.lastSelection), index), false);
} else if (this.isSelected(item) && e.ctrlKey) {
this.deselect(item);
}else{
this.deselect(item);
this.select(item, this.multiSelect && e.ctrlKey);
this.lastSelection = item;
}
e.preventDefault();
}
return true;
}
});
/*
* Ext JS Library 2.2
* Copyright(c) 2006-2008, Ext JS, LLC.
@ -1602,4 +998,286 @@ Ext.extend(Ext.ux.grid.RowActions, Ext.util.Observable, {
// registre xtype
Ext.reg('rowactions', Ext.ux.grid.RowActions);
// eof
/**
* Ext.ux.form.LovCombo, List of Values Combo
*
* @author Ing. Jozef Sakáloš
* @copyright (c) 2008, by Ing. Jozef Sakáloš
* @date 16. April 2008
* @version $Id: Ext.ux.form.LovCombo.js 285 2008-06-06 09:22:20Z jozo $
*
* @license Ext.ux.form.LovCombo.js is licensed under the terms of the Open Source
* LGPL 3.0 license. Commercial use is permitted to the extent that the
* code/component(s) do NOT become part of another Open Source or Commercially
* licensed development library or toolkit without explicit permission.
*
* License details: http://www.gnu.org/licenses/lgpl.html
*/
/*global Ext */
// add RegExp.escape if it has not been already added
if('function' !== typeof RegExp.escape) {
RegExp.escape = function(s) {
if('string' !== typeof s) {
return s;
}
// Note: if pasting from forum, precede ]/\ with backslash manually
return s.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
}; // eo function escape
}
// create namespace
Ext.ns('Ext.ux.form');
/**
*
* @class Ext.ux.form.LovCombo
* @extends Ext.form.ComboBox
*/
Ext.ux.form.LovCombo = Ext.extend(Ext.form.ComboBox, {
// {{{
// configuration options
/**
* @cfg {String} checkField name of field used to store checked state.
* It is automatically added to existing fields.
* Change it only if it collides with your normal field.
*/
checkField:'checked'
/**
* @cfg {String} separator separator to use between values and texts
*/
,separator:','
/**
* @cfg {String/Array} tpl Template for items.
* Change it only if you know what you are doing.
*/
// }}}
// {{{
,initComponent:function() {
// template with checkbox
if(!this.tpl) {
this.tpl =
'<tpl for=".">'
+'<div class="x-combo-list-item">'
+'<img src="' + Ext.BLANK_IMAGE_URL + '" '
+'class="ux-lovcombo-icon ux-lovcombo-icon-'
+'{[values.' + this.checkField + '?"checked":"unchecked"' + ']}">'
+'<div class="ux-lovcombo-item-text">{' + (this.displayField || 'text' )+ '}</div>'
+'</div>'
+'</tpl>'
;
}
// call parent
Ext.ux.form.LovCombo.superclass.initComponent.apply(this, arguments);
// install internal event handlers
this.on({
scope:this
,beforequery:this.onBeforeQuery
,blur:this.onRealBlur
});
// remove selection from input field
this.onLoad = this.onLoad.createSequence(function() {
if(this.el) {
var v = this.el.dom.value;
this.el.dom.value = '';
this.el.dom.value = v;
}
});
} // e/o function initComponent
// }}}
// {{{
/**
* Disables default tab key bahavior
* @private
*/
,initEvents:function() {
Ext.ux.form.LovCombo.superclass.initEvents.apply(this, arguments);
// disable default tab handling - does no good
this.keyNav.tab = false;
} // eo function initEvents
// }}}
// {{{
/**
* clears value
*/
,clearValue:function() {
this.value = '';
this.setRawValue(this.value);
this.store.clearFilter();
this.store.each(function(r) {
r.set(this.checkField, false);
}, this);
if(this.hiddenField) {
this.hiddenField.value = '';
}
this.applyEmptyText();
} // eo function clearValue
// }}}
// {{{
/**
* @return {String} separator (plus space) separated list of selected displayFields
* @private
*/
,getCheckedDisplay:function() {
var re = new RegExp(this.separator, "g");
return this.getCheckedValue(this.displayField).replace(re, this.separator + ' ');
} // eo function getCheckedDisplay
// }}}
// {{{
/**
* @return {String} separator separated list of selected valueFields
* @private
*/
,getCheckedValue:function(field) {
field = field || this.valueField;
var c = [];
// store may be filtered so get all records
var snapshot = this.store.snapshot || this.store.data;
snapshot.each(function(r) {
if(r.get(this.checkField)) {
c.push(r.get(field));
}
}, this);
return c.join(this.separator);
} // eo function getCheckedValue
// }}}
// {{{
/**
* beforequery event handler - handles multiple selections
* @param {Object} qe query event
* @private
*/
,onBeforeQuery:function(qe) {
qe.query = qe.query.replace(new RegExp(this.getCheckedDisplay() + '[ ' + this.separator + ']*'), '');
} // eo function onBeforeQuery
// }}}
// {{{
/**
* blur event handler - runs only when real blur event is fired
*/
,onRealBlur:function() {
this.list.hide();
var rv = this.getRawValue();
var rva = rv.split(new RegExp(RegExp.escape(this.separator) + ' *'));
var va = [];
var snapshot = this.store.snapshot || this.store.data;
// iterate through raw values and records and check/uncheck items
Ext.each(rva, function(v) {
snapshot.each(function(r) {
if(v === r.get(this.displayField)) {
va.push(r.get(this.valueField));
}
}, this);
}, this);
this.setValue(va.join(this.separator));
this.store.clearFilter();
} // eo function onRealBlur
// }}}
// {{{
/**
* Combo's onSelect override
* @private
* @param {Ext.data.Record} record record that has been selected in the list
* @param {Number} index index of selected (clicked) record
*/
,onSelect:function(record, index) {
if(this.fireEvent('beforeselect', this, record, index) !== false){
// toggle checked field
record.set(this.checkField, !record.get(this.checkField));
// display full list
if(this.store.isFiltered()) {
this.doQuery(this.allQuery);
}
// set (update) value and fire event
this.setValue(this.getCheckedValue());
this.fireEvent('select', this, record, index);
}
} // eo function onSelect
// }}}
// {{{
/**
* Sets the value of the LovCombo
* @param {Mixed} v value
*/
,setValue:function(v) {
if(v) {
v = '' + v;
if(this.valueField) {
this.store.clearFilter();
this.store.each(function(r) {
var checked = !(!v.match(
'(^|' + this.separator + ')' + RegExp.escape(r.get(this.valueField))
+'(' + this.separator + '|$)'))
;
r.set(this.checkField, checked);
}, this);
this.value = this.getCheckedValue();
this.setRawValue(this.getCheckedDisplay());
if(this.hiddenField) {
this.hiddenField.value = this.value;
}
}
else {
this.value = v;
this.setRawValue(v);
if(this.hiddenField) {
this.hiddenField.value = v;
}
}
if(this.el) {
this.el.removeClass(this.emptyClass);
}
}
else {
this.clearValue();
}
} // eo function setValue
// }}}
// {{{
/**
* Selects all items
*/
,selectAll:function() {
this.store.each(function(record){
// toggle checked field
record.set(this.checkField, true);
}, this);
//display full list
this.doQuery(this.allQuery);
this.setValue(this.getCheckedValue());
} // eo full selectAll
// }}}
// {{{
/**
* Deselects all items. Synonym for clearValue
*/
,deselectAll:function() {
this.clearValue();
} // eo full deselectAll
// }}}
}); // eo extend
// register xtype
Ext.reg('lovcombo', Ext.ux.form.LovCombo);

View file

@ -2,79 +2,11 @@ tvheadend.grabberStore = new Ext.data.JsonStore({
root:'entries',
fields: ['identifier','name','version','apiconfig'],
url:'xmltv',
baseParams: {op: 'listGrabbers'}
baseParams: {
op: 'listGrabbers'
}
});
/*
tvheadend.xmltvsetup = function() {
var deck1info = new Ext.form.Label({
fieldLabel: 'Version',
html:'',
});
var deck1cb = new Ext.form.ComboBox({
loadingText: 'Scanning for XMLTV grabbers, please wait...',
fieldLabel: 'XML-TV Source',
name: 'xmltvchannel',
width: 350,
displayField:'name',
valueField:'identifier',
store: tvheadend.grabberStore,
forceSelection: true,
editable: false,
triggerAction: 'all',
mode: 'remote',
emptyText: 'Select grabber'
});
var deck1 = new Ext.FormPanel({
labelAlign: 'right',
labelWidth: 100,
bodyStyle: 'padding: 5px',
defaultType: 'label',
layout: 'form',
border:false,
items: [deck1cb, deck1info]
});
var win = new Ext.Window({
title: 'Configure XMLTV grabbers',
bodyStyle: 'padding: 5px',
layout: 'fit',
width: 500,
height: 500,
constrainHeader: true,
buttonAlign: 'center',
items: [deck1],
bbar: [
{
id: 'move-back',
text: 'Back',
disabled: true
},
'->',
{
id: 'move-next',
text: 'Next'
}
]
});
win.show();
deck1cb.on('select', function(c,r,i) {
deck1info.setText(r.data.version);
});
}
*/
tvheadend.xmltv = function() {
var confreader = new Ext.data.JsonReader({