webui: added new component ItemSelector to select default language(s)
This commit is contained in:
parent
734cf843e9
commit
50d10d062e
13 changed files with 1272 additions and 64 deletions
|
@ -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;
|
||||
|
|
|
@ -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__ */
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
551
src/webui/static/multiselect/ddview.js
Normal file
551
src/webui/static/multiselect/ddview.js
Normal 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;
|
||||
}
|
||||
});
|
528
src/webui/static/multiselect/multiselect.js
Normal file
528
src/webui/static/multiselect/multiselect.js
Normal 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);
|
BIN
src/webui/static/multiselect/resources/bottom2.gif
Normal file
BIN
src/webui/static/multiselect/resources/bottom2.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 927 B |
BIN
src/webui/static/multiselect/resources/down2.gif
Normal file
BIN
src/webui/static/multiselect/resources/down2.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 920 B |
BIN
src/webui/static/multiselect/resources/left2.gif
Normal file
BIN
src/webui/static/multiselect/resources/left2.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 920 B |
19
src/webui/static/multiselect/resources/multiselect.css
Normal file
19
src/webui/static/multiselect/resources/multiselect.css
Normal 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;
|
||||
}
|
BIN
src/webui/static/multiselect/resources/right2.gif
Normal file
BIN
src/webui/static/multiselect/resources/right2.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 925 B |
BIN
src/webui/static/multiselect/resources/top2.gif
Normal file
BIN
src/webui/static/multiselect/resources/top2.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 927 B |
BIN
src/webui/static/multiselect/resources/up2.gif
Normal file
BIN
src/webui/static/multiselect/resources/up2.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 920 B |
Loading…
Add table
Reference in a new issue