webui: added new component ItemSelector to select default language(s)

This commit is contained in:
xhaggi 2012-09-14 15:54:25 +02:00 committed by Adam Sutton
parent 734cf843e9
commit 50d10d062e
13 changed files with 1272 additions and 64 deletions

View file

@ -554,9 +554,35 @@ const lang_code_t *lang_code_get3 ( const char *code )
}
const char **lang_code_split ( const char *codes )
{
int i = 0;
const lang_code_t **lcs = lang_code_split2(codes);
const char **ret;
if(!lcs) return NULL;
while(lcs[i])
i++;
ret = calloc(1+i, sizeof(char*));
i = 0;
while(lcs[i]) {
ret[i] = lcs[i]->code2b;
i++;
}
ret[i] = NULL;
free(lcs);
return ret;
}
const lang_code_t **lang_code_split2 ( const char *codes )
{
int n;
const char *c, *p, **ret;
const char *c, *p;
const lang_code_t **ret;
const lang_code_t *co;
/* Defaults */
if (!codes) codes = config_get_language();
@ -571,19 +597,21 @@ const char **lang_code_split ( const char *codes )
if (*c == ',') n++;
c++;
}
ret = calloc(2+n, sizeof(char*));
ret = calloc(2+n, sizeof(lang_code_t*));
/* Create list */
n = 0;
p = c = codes;
while (*c) {
if (*c == ',') {
ret[n++] = lang_code_get(p);
co = lang_code_get3(p);
if(co)
ret[n++] = co;
p = c + 1;
}
c++;
}
if (*p) ret[n++] = lang_code_get(p);
if (*p) ret[n++] = lang_code_get3(p);
ret[n] = NULL;
return ret;

View file

@ -36,5 +36,6 @@ const lang_code_t *lang_code_get3 ( const char *code );
/* Split list of codes as per HTTP Language-Accept spec */
const char **lang_code_split ( const char *codes );
const lang_code_t **lang_code_split2 ( const char *codes );
#endif /* __TVH_LANG_CODES_H__ */

View file

@ -1,6 +1,6 @@
/*
* tvheadend, EXTJS based interface
* Copyright (C) 2008 Andreas Öman
* Copyright (C) 2008 Andreas Öman
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -43,17 +43,19 @@
#include "epg.h"
#include "muxer.h"
#include "iptv_input.h"
#include "epggrab/private.h"
#include "config2.h"
#include "lang_codes.h"
/**
*
*/
static void
extjs_load(htsbuf_queue_t *hq, const char *script)
{
htsbuf_qprintf(hq,
"<script type=\"text/javascript\" "
"src=\"%s\">"
"</script>\n", script);
"<script type=\"text/javascript\" "
"src=\"%s\"></script>\n", script);
}
/**
@ -73,7 +75,6 @@ extjs_exec(htsbuf_queue_t *hq, const char *fmt, ...)
htsbuf_qprintf(hq, "\r\n</script>\r\n");
}
/**
* PVR info, deliver info about the given PVR entry
*/
@ -107,6 +108,8 @@ extjs_root(http_connection_t *hc, const char *remain, void *opaque)
extjs_load(hq, "static/app/extensions.js");
extjs_load(hq, "static/livegrid/livegrid-all.js");
extjs_load(hq, "static/lovcombo/lovcombo-all.js");
extjs_load(hq, "static/multiselect/multiselect.js");
extjs_load(hq, "static/multiselect/ddview.js");
/**
* Create a namespace for our app
@ -172,7 +175,6 @@ extjs_root(http_connection_t *hc, const char *remain, void *opaque)
return 0;
}
/**
*
*/
@ -205,7 +207,6 @@ page_about(http_connection_t *hc, const char *remain, void *opaque)
return 0;
}
/**
*
*/
@ -302,8 +303,6 @@ extjs_channels_delete(htsmsg_t *in)
channel_delete(ch);
}
#include "epggrab/private.h"
/**
*
*/
@ -483,7 +482,6 @@ extjs_channels(http_connection_t *hc, const char *remain, void *opaque)
return 0;
}
/**
* EPG Content Groups
*/
@ -519,7 +517,6 @@ json_single_record(htsmsg_t *rec, const char *root)
}
/**
*
*/
@ -618,7 +615,6 @@ extjs_epggrab(http_connection_t *hc, const char *remain, void *opaque)
return 0;
}
/**
*
*/
@ -711,6 +707,66 @@ skip:
}
/**
*
*/
static int
extjs_languages(http_connection_t *hc, const char *remain, void *opaque)
{
htsbuf_queue_t *hq = &hc->hc_reply;
const char *op = http_arg_get(&hc->hc_req_args, "op");
htsmsg_t *out, *array, *e;
pthread_mutex_lock(&global_lock);
if(op != NULL && !strcmp(op, "list")) {
out = htsmsg_create_map();
array = htsmsg_create_list();
const lang_code_t *c = lang_codes;
while (c->code2b) {
e = htsmsg_create_map();
htsmsg_add_str(e, "identifier", c->code2b);
htsmsg_add_str(e, "name", c->desc);
htsmsg_add_msg(array, NULL, e);
c++;
}
}
else if(op != NULL && !strcmp(op, "config")) {
out = htsmsg_create_map();
array = htsmsg_create_list();
const lang_code_t **c = lang_code_split2(NULL);
if(c) {
int i = 0;
while (c[i]) {
e = htsmsg_create_map();
htsmsg_add_str(e, "identifier", c[i]->code2b);
htsmsg_add_str(e, "name", c[i]->desc);
htsmsg_add_msg(array, NULL, e);
i++;
}
free(c);
}
}
else {
pthread_mutex_unlock(&global_lock);
return HTTP_STATUS_BAD_REQUEST;
}
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;
}
/**
*
*/
@ -909,6 +965,9 @@ extjs_epgrelated(http_connection_t *hc, const char *remain, void *opaque)
return 0;
}
/**
*
*/
static int
extjs_epgobject(http_connection_t *hc, const char *remain, void *opaque)
{
@ -1201,7 +1260,6 @@ extjs_dvr(http_connection_t *hc, const char *remain, void *opaque)
}
/**
*
*/
@ -1308,8 +1366,6 @@ extjs_dvrlist(http_connection_t *hc, const char *remain, void *opaque)
return 0;
}
/**
*
*/
@ -1327,7 +1383,6 @@ extjs_service_delete(htsmsg_t *in)
}
}
/**
*
*/
@ -1364,7 +1419,6 @@ service_update(htsmsg_t *in)
}
}
/**
*
*/
@ -1456,8 +1510,6 @@ extjs_servicedetails(http_connection_t *hc,
return 0;
}
/**
*
*/
@ -1501,8 +1553,6 @@ extjs_mergechannel(http_connection_t *hc, const char *remain, void *opaque)
return 0;
}
/**
*
*/
@ -1550,8 +1600,6 @@ service_update_iptv(htsmsg_t *in)
}
}
/**
*
*/
@ -1669,8 +1717,6 @@ extjs_iptvservices(http_connection_t *hc, const char *remain, void *opaque)
return 0;
}
/**
*
*/
@ -1707,7 +1753,6 @@ extjs_service_update(htsmsg_t *in)
}
}
/**
*
*/
@ -1801,32 +1846,25 @@ extjs_config(http_connection_t *hc, const char *remain, void *opaque)
void
extjs_start(void)
{
http_path_add("/about.html", NULL, page_about, ACCESS_WEB_INTERFACE);
http_path_add("/extjs.html", NULL, extjs_root, ACCESS_WEB_INTERFACE);
http_path_add("/tablemgr", NULL, extjs_tablemgr, ACCESS_WEB_INTERFACE);
http_path_add("/channels", NULL, extjs_channels, ACCESS_WEB_INTERFACE);
http_path_add("/epggrab", NULL, extjs_epggrab, ACCESS_WEB_INTERFACE);
http_path_add("/channeltags", NULL, extjs_channeltags, ACCESS_WEB_INTERFACE);
http_path_add("/confignames", NULL, extjs_confignames, ACCESS_WEB_INTERFACE);
http_path_add("/epg", NULL, extjs_epg, ACCESS_WEB_INTERFACE);
http_path_add("/epgrelated", NULL, extjs_epgrelated, ACCESS_WEB_INTERFACE);
http_path_add("/epgobject", NULL, extjs_epgobject, ACCESS_WEB_INTERFACE);
http_path_add("/dvr", NULL, extjs_dvr, ACCESS_WEB_INTERFACE);
http_path_add("/dvrlist", NULL, extjs_dvrlist, ACCESS_WEB_INTERFACE);
http_path_add("/ecglist", NULL, extjs_ecglist, ACCESS_WEB_INTERFACE);
http_path_add("/config", NULL, extjs_config, ACCESS_WEB_INTERFACE);
http_path_add("/mergechannel",
NULL, extjs_mergechannel, ACCESS_ADMIN);
http_path_add("/iptv/services",
NULL, extjs_iptvservices, ACCESS_ADMIN);
http_path_add("/servicedetails",
NULL, extjs_servicedetails, ACCESS_ADMIN);
http_path_add("/tv/adapter",
NULL, extjs_tvadapter, ACCESS_ADMIN);
http_path_add("/about.html", NULL, page_about, ACCESS_WEB_INTERFACE);
http_path_add("/extjs.html", NULL, extjs_root, ACCESS_WEB_INTERFACE);
http_path_add("/tablemgr", NULL, extjs_tablemgr, ACCESS_WEB_INTERFACE);
http_path_add("/channels", NULL, extjs_channels, ACCESS_WEB_INTERFACE);
http_path_add("/epggrab", NULL, extjs_epggrab, ACCESS_WEB_INTERFACE);
http_path_add("/channeltags", NULL, extjs_channeltags, ACCESS_WEB_INTERFACE);
http_path_add("/confignames", NULL, extjs_confignames, ACCESS_WEB_INTERFACE);
http_path_add("/epg", NULL, extjs_epg, ACCESS_WEB_INTERFACE);
http_path_add("/epgrelated", NULL, extjs_epgrelated, ACCESS_WEB_INTERFACE);
http_path_add("/epgobject", NULL, extjs_epgobject, ACCESS_WEB_INTERFACE);
http_path_add("/dvr", NULL, extjs_dvr, ACCESS_WEB_INTERFACE);
http_path_add("/dvrlist", NULL, extjs_dvrlist, ACCESS_WEB_INTERFACE);
http_path_add("/ecglist", NULL, extjs_ecglist, ACCESS_WEB_INTERFACE);
http_path_add("/config", NULL, extjs_config, ACCESS_WEB_INTERFACE);
http_path_add("/languages", NULL, extjs_languages, ACCESS_WEB_INTERFACE);
http_path_add("/mergechannel", NULL, extjs_mergechannel, ACCESS_ADMIN);
http_path_add("/iptv/services", NULL, extjs_iptvservices, ACCESS_ADMIN);
http_path_add("/servicedetails", NULL, extjs_servicedetails, ACCESS_ADMIN);
http_path_add("/tv/adapter", NULL, extjs_tvadapter, ACCESS_ADMIN);
#if ENABLE_LINUXDVB
extjs_start_dvb();

View file

@ -1,3 +1,36 @@
// Store: config languages
tvheadend.languages = new Ext.data.JsonStore({
autoLoad:true,
root:'entries',
fields: ['identifier','name'],
id: 'identifier',
url:'languages',
baseParams: {
op: 'list'
}
});
// Store: all languages
tvheadend.config_languages = new Ext.data.JsonStore({
autoLoad:true,
root:'entries',
fields: ['identifier','name'],
id: 'identifier',
url:'languages',
baseParams: {
op: 'config'
}
});
tvheadend.languages.setDefaultSort('name', 'ASC');
tvheadend.comet.on('config', function(m) {
if(m.reload != null) {
tvheadend.languages.reload();
tvheadend.config_languages.reload();
}
});
tvheadend.miscconf = function() {
/*
* Basic Config
@ -13,13 +46,23 @@ tvheadend.miscconf = function() {
var dvbscanPath = new Ext.form.TextField({
fieldLabel : 'DVB scan files path',
name : 'muxconfpath',
allowBlank : true
allowBlank : true,
width: 400
});
var language = new Ext.form.TextField({
fieldLabel : 'Default Language(s)',
name : 'language',
allowBlank : true
var language = new Ext.ux.ItemSelector({
name: 'language',
fromStore: tvheadend.languages,
toStore: tvheadend.config_languages,
fieldLabel: 'Default Language(s)',
dataFields:['identifier', 'name'],
msWidth: 190,
msHeight: 150,
valueField: 'identifier',
displayField: 'name',
imagePath: 'static/multiselect/resources',
toLegend: 'Selected',
fromLegend: 'Available'
});
/* ****************************************************************
@ -40,7 +83,7 @@ tvheadend.miscconf = function() {
}
});
var confpanel = new Ext.FormPanel({
var confpanel = new Ext.form.FormPanel({
title : 'General',
iconCls : 'wrench',
border : false,

View file

@ -0,0 +1,551 @@
Array.prototype.contains = function(element) {
return this.indexOf(element) !== -1;
};
Ext.namespace("Ext.ux");
/**
* @class Ext.ux.DDView
* A DnD enabled version of Ext.View.
* @param {Element/String} container The Element in which to create the View.
* @param {String} tpl The template string used to create the markup for each element of the View
* @param {Object} config The configuration properties. These include all the config options of
* {@link Ext.View} plus some specific to this class.<br>
* <p>
* 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.<br>
* <p>
* The following extra CSS rules are needed to provide insertion point highlighting:<pre><code>
.x-view-drag-insert-above {
border-top:1px dotted #3366cc;
}
.x-view-drag-insert-below {
border-bottom:1px dotted #3366cc;
}
</code></pre>
*
*/
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. */
/** @cfg {String/Array} dropGroup The ddgroup name(s) for the View's DropZone. */
/** @cfg {Boolean} copy Causes drag operations to copy nodes rather than move. */
/** @cfg {Boolean} allowCopy Causes ctrl/drag operations to copy nodes rather than move. */
sortDir: 'ASC',
isFormField: true,
classRe: /class=(['"])(.*)\1/,
tagRe: /(<\w*)(.*?>)/,
reset: Ext.emptyFn,
clearInvalid: Ext.form.Field.prototype.clearInvalid,
msgTarget: 'qtip',
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"
);
},
validate: function() {
return true;
},
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. */
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();
},
/** @return {String} a parenthesised list of the ids of the Records in the View. */
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;
},
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;
},
/** Put the selections into the records and viewNodes Arrays. */
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. */
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. */
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);
}
},
/** Decide whether to drop above or below a View node. */
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";
}
},
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;
},
onNodeEnter : function(n, dd, e, data){
if (this.highlightColor && (data.sourceView != this)) {
this.el.highlight(this.highlightColor);
}
return false;
},
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;
},
onNodeOut : function(n, dd, e, data){
this.removeDropIndicators(n);
},
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;
},
// Ensure the multi proxy is removed
onEndDrag: function(data, e) {
var d = Ext.get(this.dragData.ddel);
if (d && d.hasClass("multi-proxy")) {
d.remove();
//delete this.dragData.ddel;
}
},
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";
}
},
/**
* Utility method. 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. */
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;
},
disableContextMenu: function() {
if (this.contextMenu) {
this.el.un("contextmenu", this.showContextMenu, this);
}
},
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 event, but also, if this 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.contains(this.dropZone)) {
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));
}
}
}
},
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;
}
});

View file

@ -0,0 +1,528 @@
//version 3.0
Ext.ux.Multiselect = Ext.extend(Ext.form.Field, {
store:null,
dataFields:[],
data:[],
width:100,
height:100,
displayField:0,
valueField:1,
allowBlank:true,
minLength:0,
maxLength:Number.MAX_VALUE,
blankText:Ext.form.TextField.prototype.blankText,
minLengthText:'Minimum {0} item(s) required',
maxLengthText:'Maximum {0} item(s) allowed',
copy:false,
allowDup:false,
allowTrash:false,
legend:null,
focusClass:undefined,
delimiter:',',
view:null,
dragGroup:null,
dropGroup:null,
tbar:null,
appendOnly:false,
sortField:null,
sortDir:'ASC',
defaultAutoCreate : {tag: "div"},
initComponent: function(){
Ext.ux.Multiselect.superclass.initComponent.call(this);
this.addEvents({
'dblclick' : true,
'click' : true,
'change' : true,
'drop' : true
});
},
onRender: function(ct, position){
var fs, cls, tpl;
Ext.ux.Multiselect.superclass.onRender.call(this, ct, position);
cls = 'ux-mselect';
fs = new Ext.form.FieldSet({
renderTo:this.el,
title:this.legend,
height:this.height,
width:this.width,
style:"padding:1px;",
tbar:this.tbar
});
if(!this.legend)fs.el.down('.'+fs.headerCls).remove();
fs.body.addClass(cls);
tpl = '<tpl for="."><div class="' + cls + '-item';
if(Ext.isIE || Ext.isIE7)tpl+='" unselectable=on';
else tpl+=' x-unselectable"';
tpl+='>{' + this.displayField + '}</div></tpl>';
if(!this.store){
this.store = new Ext.data.SimpleStore({
fields: this.dataFields,
data : this.data
});
}
this.view = new Ext.ux.DDView({
multiSelect: true, store: this.store, selectedClass: cls+"-selected", tpl:tpl,
allowDup:this.allowDup, copy: this.copy, allowTrash: this.allowTrash,
dragGroup: this.dragGroup, dropGroup: this.dropGroup, itemSelector:"."+cls+"-item",
isFormField:false, applyTo:fs.body, appendOnly:this.appendOnly,
sortField:this.sortField, sortDir:this.sortDir
});
fs.add(this.view);
this.view.on('click', this.onViewClick, this);
this.view.on('beforeClick', this.onViewBeforeClick, this);
this.view.on('dblclick', this.onViewDblClick, this);
this.view.on('drop', function(ddView, n, dd, e, data){
return this.fireEvent("drop", ddView, n, dd, e, data);
}, this);
this.hiddenName = this.name;
var hiddenTag={tag: "input", type: "hidden", value: "", name:this.name};
if (this.isFormField) {
this.hiddenField = this.el.createChild(hiddenTag);
} else {
this.hiddenField = Ext.get(document.body).createChild(hiddenTag);
}
fs.doLayout();
},
initValue:Ext.emptyFn,
onViewClick: function(vw, index, node, e) {
var arrayIndex = this.preClickSelections.indexOf(index);
if (arrayIndex != -1)
{
this.preClickSelections.splice(arrayIndex, 1);
this.view.clearSelections(true);
this.view.select(this.preClickSelections);
}
this.fireEvent('change', this, this.getValue(), this.hiddenField.dom.value);
this.hiddenField.dom.value = this.getValue();
this.fireEvent('click', this, e);
this.validate();
},
onViewBeforeClick: function(vw, index, node, e) {
this.preClickSelections = this.view.getSelectedIndexes();
if (this.disabled) {return false;}
},
onViewDblClick : function(vw, index, node, e) {
return this.fireEvent('dblclick', vw, index, node, e);
},
getValue: function(valueField){
var returnArray = [];
var selectionsArray = this.view.getSelectedIndexes();
if (selectionsArray.length == 0) {return '';}
for (var i=0; i<selectionsArray.length; i++) {
returnArray.push(this.store.getAt(selectionsArray[i]).get(((valueField != null)? valueField : this.valueField)));
}
return returnArray.join(this.delimiter);
},
setValue: function(values) {
var index;
var selections = [];
this.view.clearSelections();
this.hiddenField.dom.value = '';
if (!values || (values == '')) { return; }
if (!(values instanceof Array)) { values = values.split(this.delimiter); }
for (var i=0; i<values.length; i++) {
index = this.view.store.indexOf(this.view.store.query(this.valueField,
new RegExp('^' + values[i] + '$', "i")).itemAt(0));
selections.push(index);
}
this.view.select(selections);
this.hiddenField.dom.value = this.getValue();
this.validate();
},
reset : function() {
this.setValue('');
},
getRawValue: function(valueField) {
var tmp = this.getValue(valueField);
if (tmp.length) {
tmp = tmp.split(this.delimiter);
}
else{
tmp = [];
}
return tmp;
},
setRawValue: function(values){
setValue(values);
},
validateValue : function(value){
if (value.length < 1) { // if it has no value
if (this.allowBlank) {
this.clearInvalid();
return true;
} else {
this.markInvalid(this.blankText);
return false;
}
}
if (value.length < this.minLength) {
this.markInvalid(String.format(this.minLengthText, this.minLength));
return false;
}
if (value.length > this.maxLength) {
this.markInvalid(String.format(this.maxLengthText, this.maxLength));
return false;
}
return true;
}
});
Ext.reg("multiselect", Ext.ux.Multiselect);
Ext.ux.ItemSelector = Ext.extend(Ext.form.Field, {
msWidth:200,
msHeight:300,
hideNavIcons:false,
imagePath:"",
iconUp:"up2.gif",
iconDown:"down2.gif",
iconLeft:"left2.gif",
iconRight:"right2.gif",
iconTop:"top2.gif",
iconBottom:"bottom2.gif",
drawUpIcon:true,
drawDownIcon:true,
drawLeftIcon:true,
drawRightIcon:true,
drawTopIcon:true,
drawBotIcon:true,
fromStore:null,
toStore:null,
fromData:null,
toData:null,
displayField:0,
valueField:1,
switchToFrom:false,
allowDup:false,
focusClass:undefined,
delimiter:',',
readOnly:false,
toLegend:null,
fromLegend:null,
toSortField:null,
fromSortField:null,
toSortDir:'ASC',
fromSortDir:'ASC',
toTBar:null,
fromTBar:null,
bodyStyle:null,
border:false,
defaultAutoCreate:{tag: "div"},
initComponent: function(){
Ext.ux.ItemSelector.superclass.initComponent.call(this);
this.addEvents({
'rowdblclick' : true,
'change' : true
});
},
onRender: function(ct, position){
Ext.ux.ItemSelector.superclass.onRender.call(this, ct, position);
this.fromMultiselect = new Ext.ux.Multiselect({
legend: this.fromLegend,
delimiter: this.delimiter,
allowDup: this.allowDup,
copy: this.allowDup,
allowTrash: this.allowDup,
dragGroup: this.readOnly ? null : "drop2-"+this.el.dom.id,
dropGroup: this.readOnly ? null : "drop1-"+this.el.dom.id,
width: this.msWidth,
height: this.msHeight,
dataFields: this.dataFields,
data: this.fromData,
displayField: this.displayField,
valueField: this.valueField,
store: this.fromStore,
isFormField: false,
tbar: this.fromTBar,
appendOnly: true,
sortField: this.fromSortField,
sortDir: this.fromSortDir
});
this.fromMultiselect.on('dblclick', this.onRowDblClick, this);
if (!this.toStore) {
this.toStore = new Ext.data.SimpleStore({
fields: this.dataFields,
data : this.toData
});
}
this.toStore.on('add', this.valueChanged, this);
this.toStore.on('remove', this.valueChanged, this);
this.toStore.on('load', this.valueChanged, this);
this.toMultiselect = new Ext.ux.Multiselect({
legend: this.toLegend,
delimiter: this.delimiter,
allowDup: this.allowDup,
dragGroup: this.readOnly ? null : "drop1-"+this.el.dom.id,
//dropGroup: this.readOnly ? null : "drop2-"+this.el.dom.id+(this.toSortField ? "" : ",drop1-"+this.el.dom.id),
dropGroup: this.readOnly ? null : "drop2-"+this.el.dom.id+",drop1-"+this.el.dom.id,
width: this.msWidth,
height: this.msHeight,
displayField: this.displayField,
valueField: this.valueField,
store: this.toStore,
isFormField: false,
tbar: this.toTBar,
sortField: this.toSortField,
sortDir: this.toSortDir
});
this.toMultiselect.on('dblclick', this.onRowDblClick, this);
var p = new Ext.Panel({
bodyStyle:this.bodyStyle,
border:this.border,
layout:"table",
layoutConfig:{columns:3}
});
p.add(this.switchToFrom ? this.toMultiselect : this.fromMultiselect);
var icons = new Ext.Panel({header:false});
p.add(icons);
p.add(this.switchToFrom ? this.fromMultiselect : this.toMultiselect);
p.render(this.el);
icons.el.down('.'+icons.bwrapCls).remove();
if (this.imagePath!="" && this.imagePath.charAt(this.imagePath.length-1)!="/")
this.imagePath+="/";
this.iconUp = this.imagePath + (this.iconUp || 'up2.gif');
this.iconDown = this.imagePath + (this.iconDown || 'down2.gif');
this.iconLeft = this.imagePath + (this.iconLeft || 'left2.gif');
this.iconRight = this.imagePath + (this.iconRight || 'right2.gif');
this.iconTop = this.imagePath + (this.iconTop || 'top2.gif');
this.iconBottom = this.imagePath + (this.iconBottom || 'bottom2.gif');
var el=icons.getEl();
if (!this.toSortField) {
this.toTopIcon = el.createChild({tag:'img', src:this.iconTop, style:{cursor:'pointer', margin:'2px'}});
el.createChild({tag: 'br'});
this.upIcon = el.createChild({tag:'img', src:this.iconUp, style:{cursor:'pointer', margin:'2px'}});
el.createChild({tag: 'br'});
}
this.addIcon = el.createChild({tag:'img', src:this.switchToFrom?this.iconLeft:this.iconRight, style:{cursor:'pointer', margin:'2px'}});
el.createChild({tag: 'br'});
this.removeIcon = el.createChild({tag:'img', src:this.switchToFrom?this.iconRight:this.iconLeft, style:{cursor:'pointer', margin:'2px'}});
el.createChild({tag: 'br'});
if (!this.toSortField) {
this.downIcon = el.createChild({tag:'img', src:this.iconDown, style:{cursor:'pointer', margin:'2px'}});
el.createChild({tag: 'br'});
this.toBottomIcon = el.createChild({tag:'img', src:this.iconBottom, style:{cursor:'pointer', margin:'2px'}});
}
if (!this.readOnly) {
if (!this.toSortField) {
this.toTopIcon.on('click', this.toTop, this);
this.upIcon.on('click', this.up, this);
this.downIcon.on('click', this.down, this);
this.toBottomIcon.on('click', this.toBottom, this);
}
this.addIcon.on('click', this.fromTo, this);
this.removeIcon.on('click', this.toFrom, this);
}
if (!this.drawUpIcon || this.hideNavIcons) { this.upIcon.dom.style.display='none'; }
if (!this.drawDownIcon || this.hideNavIcons) { this.downIcon.dom.style.display='none'; }
if (!this.drawLeftIcon || this.hideNavIcons) { this.addIcon.dom.style.display='none'; }
if (!this.drawRightIcon || this.hideNavIcons) { this.removeIcon.dom.style.display='none'; }
if (!this.drawTopIcon || this.hideNavIcons) { this.toTopIcon.dom.style.display='none'; }
if (!this.drawBotIcon || this.hideNavIcons) { this.toBottomIcon.dom.style.display='none'; }
var tb = p.body.first();
this.el.setWidth(p.body.first().getWidth());
p.body.removeClass();
this.hiddenName = this.name;
var hiddenTag={tag: "input", type: "hidden", value: "", name:this.name};
this.hiddenField = this.el.createChild(hiddenTag);
this.valueChanged(this.toStore);
},
initValue:Ext.emptyFn,
toTop : function() {
var selectionsArray = this.toMultiselect.view.getSelectedIndexes();
var records = [];
if (selectionsArray.length > 0) {
selectionsArray.sort();
for (var i=0; i<selectionsArray.length; i++) {
record = this.toMultiselect.view.store.getAt(selectionsArray[i]);
records.push(record);
}
selectionsArray = [];
for (var i=records.length-1; i>-1; i--) {
record = records[i];
this.toMultiselect.view.store.remove(record);
this.toMultiselect.view.store.insert(0, record);
selectionsArray.push(((records.length - 1) - i));
}
}
this.toMultiselect.view.refresh();
this.toMultiselect.view.select(selectionsArray);
},
toBottom : function() {
var selectionsArray = this.toMultiselect.view.getSelectedIndexes();
var records = [];
if (selectionsArray.length > 0) {
selectionsArray.sort();
for (var i=0; i<selectionsArray.length; i++) {
record = this.toMultiselect.view.store.getAt(selectionsArray[i]);
records.push(record);
}
selectionsArray = [];
for (var i=0; i<records.length; i++) {
record = records[i];
this.toMultiselect.view.store.remove(record);
this.toMultiselect.view.store.add(record);
selectionsArray.push((this.toMultiselect.view.store.getCount()) - (records.length - i));
}
}
this.toMultiselect.view.refresh();
this.toMultiselect.view.select(selectionsArray);
},
up : function() {
var record = null;
var selectionsArray = this.toMultiselect.view.getSelectedIndexes();
selectionsArray.sort();
var newSelectionsArray = [];
if (selectionsArray.length > 0) {
for (var i=0; i<selectionsArray.length; i++) {
record = this.toMultiselect.view.store.getAt(selectionsArray[i]);
if ((selectionsArray[i] - 1) >= 0) {
this.toMultiselect.view.store.remove(record);
this.toMultiselect.view.store.insert(selectionsArray[i] - 1, record);
newSelectionsArray.push(selectionsArray[i] - 1);
}
}
this.toMultiselect.view.refresh();
this.toMultiselect.view.select(newSelectionsArray);
}
},
down : function() {
var record = null;
var selectionsArray = this.toMultiselect.view.getSelectedIndexes();
selectionsArray.sort();
selectionsArray.reverse();
var newSelectionsArray = [];
if (selectionsArray.length > 0) {
for (var i=0; i<selectionsArray.length; i++) {
record = this.toMultiselect.view.store.getAt(selectionsArray[i]);
if ((selectionsArray[i] + 1) < this.toMultiselect.view.store.getCount()) {
this.toMultiselect.view.store.remove(record);
this.toMultiselect.view.store.insert(selectionsArray[i] + 1, record);
newSelectionsArray.push(selectionsArray[i] + 1);
}
}
this.toMultiselect.view.refresh();
this.toMultiselect.view.select(newSelectionsArray);
}
},
fromTo : function() {
var selectionsArray = this.fromMultiselect.view.getSelectedIndexes();
var records = [];
if (selectionsArray.length > 0) {
for (var i=0; i<selectionsArray.length; i++) {
record = this.fromMultiselect.view.store.getAt(selectionsArray[i]);
records.push(record);
}
if(!this.allowDup)selectionsArray = [];
for (var i=0; i<records.length; i++) {
record = records[i];
if(this.allowDup){
var x=new Ext.data.Record();
record.id=x.id;
delete x;
this.toMultiselect.view.store.add(record);
}else{
this.fromMultiselect.view.store.remove(record);
this.toMultiselect.view.store.add(record);
selectionsArray.push((this.toMultiselect.view.store.getCount() - 1));
}
}
}
this.toMultiselect.view.refresh();
this.fromMultiselect.view.refresh();
if(this.toSortField)this.toMultiselect.store.sort(this.toSortField, this.toSortDir);
if(this.allowDup)this.fromMultiselect.view.select(selectionsArray);
else this.toMultiselect.view.select(selectionsArray);
},
toFrom : function() {
var selectionsArray = this.toMultiselect.view.getSelectedIndexes();
var records = [];
if (selectionsArray.length > 0) {
for (var i=0; i<selectionsArray.length; i++) {
record = this.toMultiselect.view.store.getAt(selectionsArray[i]);
records.push(record);
}
selectionsArray = [];
for (var i=0; i<records.length; i++) {
record = records[i];
this.toMultiselect.view.store.remove(record);
if(!this.allowDup){
this.fromMultiselect.view.store.add(record);
selectionsArray.push((this.fromMultiselect.view.store.getCount() - 1));
}
}
}
this.fromMultiselect.view.refresh();
this.toMultiselect.view.refresh();
if(this.fromSortField)this.fromMultiselect.store.sort(this.fromSortField, this.fromSortDir);
this.fromMultiselect.view.select(selectionsArray);
},
valueChanged: function(store) {
var record = null;
var values = [];
for (var i=0; i<store.getCount(); i++) {
record = store.getAt(i);
values.push(record.get(this.valueField));
}
this.hiddenField.dom.value = values.join(this.delimiter);
this.fireEvent('change', this, this.getValue(), this.hiddenField.dom.value);
},
getValue : function() {
return this.hiddenField.dom.value;
},
onRowDblClick : function(vw, index, node, e) {
return this.fireEvent('rowdblclick', vw, index, node, e);
},
reset: function(){
range = this.toMultiselect.store.getRange();
this.toMultiselect.store.removeAll();
if (!this.allowDup) {
this.fromMultiselect.store.add(range);
this.fromMultiselect.store.sort(this.displayField,'ASC');
}
this.valueChanged(this.toMultiselect.store);
}
});
Ext.reg("itemselector", Ext.ux.ItemSelector);

Binary file not shown.

After

Width:  |  Height:  |  Size: 927 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 B

View file

@ -0,0 +1,19 @@
.ux-mselect{
overflow:auto;
background:white;
position:relative; /* for calculating scroll offsets */
zoom:1;
overflow:auto;
}
.ux-mselect-item{
font:normal 12px tahoma, arial, helvetica, sans-serif;
padding:2px;
border:1px solid #fff;
white-space: nowrap;
cursor:pointer;
}
.ux-mselect-selected{
border:1px dotted #a3bae9 !important;
background:#DFE8F6;
cursor:pointer;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 925 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 927 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 B