DataTable in GridReport (#5234)
* DataTable in GridReport
* Remove query_report_old
* Remove SlickGrid library 💃
|
|
@ -54,7 +54,7 @@ cur_frm.cscript.refresh = function(doc) {
|
|||
|
||||
frappe.ui.form.on('Report', {
|
||||
refresh: function(frm) {
|
||||
if(!frappe.boot.developer_mode && user != 'Administrator') {
|
||||
if(!frappe.boot.developer_mode && frappe.session.user != 'Administrator') {
|
||||
// make the document read-only
|
||||
frm.set_read_only();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,18 +8,16 @@ import json
|
|||
import copy
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_data(doctypes, last_modified):
|
||||
def get_data(doctypes, last_modified):
|
||||
data_map = {}
|
||||
for dump_report_map in frappe.get_hooks().dump_report_map:
|
||||
data_map.update(frappe.get_attr(dump_report_map))
|
||||
|
||||
import datetime
|
||||
|
||||
out = {}
|
||||
|
||||
|
||||
doctypes = json.loads(doctypes)
|
||||
last_modified = json.loads(last_modified)
|
||||
|
||||
start = datetime.datetime.now()
|
||||
|
||||
for d in doctypes:
|
||||
args = copy.deepcopy(data_map[d])
|
||||
dt = d.find("[") != -1 and d[:d.find("[")] or d
|
||||
|
|
@ -29,7 +27,7 @@ def get_data(doctypes, last_modified):
|
|||
modified_table = "item."
|
||||
else:
|
||||
modified_table = ""
|
||||
|
||||
|
||||
conditions = order_by = ""
|
||||
table = args.get("from") or ("`tab%s`" % dt)
|
||||
|
||||
|
|
@ -39,30 +37,30 @@ def get_data(doctypes, last_modified):
|
|||
args['conditions'].append(modified_table + "modified > '" + last_modified[d] + "'")
|
||||
out[dt]["modified_names"] = frappe.db.sql_list("""select %sname from %s
|
||||
where %smodified > %s""" % (modified_table, table, modified_table, "%s"), last_modified[d])
|
||||
|
||||
|
||||
if args.get("force_index"):
|
||||
conditions = " force index (%s) " % args["force_index"]
|
||||
if args.get("conditions"):
|
||||
conditions += " where " + " and ".join(args["conditions"])
|
||||
if args.get("order_by"):
|
||||
order_by = " order by " + args["order_by"]
|
||||
|
||||
|
||||
out[dt]["data"] = [list(t) for t in frappe.db.sql("""select %s from %s %s %s""" \
|
||||
% (",".join(args["columns"]), table, conditions, order_by))]
|
||||
|
||||
|
||||
# last modified
|
||||
modified_table = table
|
||||
if "," in table:
|
||||
modified_table = " ".join(table.split(",")[0].split(" ")[:-1])
|
||||
|
||||
tmp = frappe.db.sql("""select `modified`
|
||||
|
||||
tmp = frappe.db.sql("""select `modified`
|
||||
from %s order by modified desc limit 1""" % modified_table)
|
||||
out[dt]["last_modified"] = tmp and tmp[0][0] or ""
|
||||
out[dt]["columns"] = map(lambda c: c.split(" as ")[-1], args["columns"])
|
||||
|
||||
out[dt]["columns"] = list(map(lambda c: c.split(" as ")[-1], args["columns"]))
|
||||
|
||||
if args.get("links"):
|
||||
out[dt]["links"] = args["links"]
|
||||
|
||||
|
||||
for d in out:
|
||||
unused_links = []
|
||||
# only compress full dumps (not partial)
|
||||
|
|
@ -70,25 +68,28 @@ def get_data(doctypes, last_modified):
|
|||
for link_key in out[d]["links"]:
|
||||
link = out[d]["links"][link_key]
|
||||
if link[0] in out and (link[0] not in last_modified):
|
||||
|
||||
|
||||
# make a map of link ids
|
||||
# to index
|
||||
link_map = {}
|
||||
doctype_data = out[link[0]]
|
||||
|
||||
col_idx = doctype_data["columns"].index(link[1])
|
||||
for row_idx in range(len(doctype_data["data"])):
|
||||
row = doctype_data["data"][row_idx]
|
||||
link_map[row[col_idx]] = row_idx
|
||||
|
||||
|
||||
for row in out[d]["data"]:
|
||||
col_idx = out[d]["columns"].index(link_key)
|
||||
# replace by id
|
||||
if row[col_idx]:
|
||||
row[col_idx] = link_map.get(row[col_idx])
|
||||
columns = list(out[d]["columns"])
|
||||
if link_key in columns:
|
||||
col_idx = columns.index(link_key)
|
||||
# replace by id
|
||||
if row[col_idx]:
|
||||
row[col_idx] = link_map.get(row[col_idx])
|
||||
else:
|
||||
unused_links.append(link_key)
|
||||
|
||||
|
||||
for link in unused_links:
|
||||
del out[d]["links"][link]
|
||||
|
||||
|
||||
return out
|
||||
|
|
|
|||
|
|
@ -357,7 +357,6 @@
|
|||
],
|
||||
"js/report.min.js": [
|
||||
"public/js/lib/clusterize.min.js",
|
||||
"public/js/lib/frappe-datatable.js",
|
||||
"public/js/frappe/views/reports/report_factory.js",
|
||||
"public/js/frappe/views/reports/report_view.js",
|
||||
"public/js/frappe/views/reports/reportview_footer.html",
|
||||
|
|
|
|||
|
|
@ -1,62 +0,0 @@
|
|||
.slick-header-column,
|
||||
.slick-cell {
|
||||
-webkit-box-sizing: content-box;
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
.slick-wrapper,
|
||||
.slick-header {
|
||||
border: none !important;
|
||||
}
|
||||
.slick-headerrow {
|
||||
border: none;
|
||||
border-bottom: 1px solid #d1d8dd;
|
||||
}
|
||||
.slick-headerrow-column {
|
||||
background-color: #F7FAFC !important;
|
||||
text-overflow: clip;
|
||||
}
|
||||
.slick-headerrow-column input {
|
||||
border: 1px solid #d1d8dd;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
padding: 0px 3px !important;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
min-height: 20px;
|
||||
}
|
||||
.slick-cell,
|
||||
.slick-headerrow-column {
|
||||
font-size: 12px;
|
||||
border-color: transparent #d1d8dd #d1d8dd transparent !important;
|
||||
border-style: solid;
|
||||
color: inherit !important;
|
||||
margin-top: -1px;
|
||||
}
|
||||
.slick-cell pre {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
padding: 3px;
|
||||
}
|
||||
.slick-header-column,
|
||||
.slick-header-columns {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
background-color: #F7FAFC;
|
||||
border-color: #d1d8dd !important;
|
||||
color: #8D99A6 !important;
|
||||
}
|
||||
.slick-header-column:hover,
|
||||
.slick-header-column-active {
|
||||
background-image: none;
|
||||
background-color: #d9e7f1;
|
||||
}
|
||||
.slick-row.odd .slick-cell {
|
||||
background-color: #fafbfc;
|
||||
}
|
||||
.frappe-rtl .slick-wrapper {
|
||||
direction: ltr;
|
||||
}
|
||||
.slick-cell > span[data-field="_comments"] * {
|
||||
display: inline-block;
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// MIT License. See license.txt
|
||||
|
||||
import DataTable from 'frappe-datatable';
|
||||
frappe.provide("frappe.report_dump");
|
||||
|
||||
$.extend(frappe.report_dump, {
|
||||
|
|
@ -34,15 +35,15 @@ $.extend(frappe.report_dump, {
|
|||
} else {
|
||||
row[link_key] = null;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
callback();
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
set_data: function(doctype, doctype_data) {
|
||||
var data = [];
|
||||
|
|
@ -55,7 +56,7 @@ $.extend(frappe.report_dump, {
|
|||
row.id = row.name;
|
||||
row.doctype = doctype;
|
||||
return row;
|
||||
}
|
||||
};
|
||||
if(frappe.report_dump.last_modified[doctype]) {
|
||||
// partial loading, make a name dict
|
||||
$.each(doctype_data.data, function(i, d) {
|
||||
|
|
@ -79,7 +80,7 @@ $.extend(frappe.report_dump, {
|
|||
// add new records
|
||||
$.each(replace_dict, function(name, d) {
|
||||
data.push(d);
|
||||
})
|
||||
});
|
||||
} else {
|
||||
|
||||
// first loading
|
||||
|
|
@ -98,7 +99,6 @@ frappe.views.GridReport = Class.extend({
|
|||
this.filter_inputs = {};
|
||||
this.preset_checks = [];
|
||||
this.tree_grid = {show: false};
|
||||
var me = this;
|
||||
$.extend(this, opts);
|
||||
|
||||
this.wrapper = $('<div class="grid-report"></div>').appendTo(this.page.main);
|
||||
|
|
@ -149,11 +149,9 @@ frappe.views.GridReport = Class.extend({
|
|||
$.each(me.filter_inputs, function(i, v) {
|
||||
var opts = v.get(0).opts;
|
||||
if(opts.fieldtype == "Select" && in_list(me.doctypes, opts.link)) {
|
||||
$(v).add_options($.map(frappe.report_dump.data[opts.link],
|
||||
function(d) { return d.name; }));
|
||||
$(v).add_options(frappe.report_dump.data[opts.link].map(d => d.name));
|
||||
} else if(opts.fieldtype == "Link" && in_list(me.doctypes, opts.link)) {
|
||||
opts.list = $.map(frappe.report_dump.data[opts.link],
|
||||
function(d) { return d.name; });
|
||||
opts.list = frappe.report_dump.data[opts.link].map(d => d.name);
|
||||
me.set_autocomplete(v, opts.list);
|
||||
}
|
||||
});
|
||||
|
|
@ -184,10 +182,6 @@ frappe.views.GridReport = Class.extend({
|
|||
this.filter_inputs.range && this.filter_inputs.range.on("change", function() {
|
||||
me.refresh();
|
||||
});
|
||||
|
||||
// chart check
|
||||
if(this.setup_chart_check)
|
||||
this.setup_chart_check();
|
||||
},
|
||||
set_filter: function(key, value) {
|
||||
var filters = this.filter_inputs[key];
|
||||
|
|
@ -201,7 +195,7 @@ frappe.views.GridReport = Class.extend({
|
|||
filters.val(value);
|
||||
}
|
||||
} else {
|
||||
frappe.msgprint(__("Invalid Filter: {0}", [key]))
|
||||
frappe.msgprint(__("Invalid Filter: {0}", [key]));
|
||||
}
|
||||
},
|
||||
set_autocomplete: function($filter, list) {
|
||||
|
|
@ -216,7 +210,6 @@ frappe.views.GridReport = Class.extend({
|
|||
});
|
||||
},
|
||||
init_filter_values: function() {
|
||||
var me = this;
|
||||
$.each(this.filter_inputs, function(key, filter) {
|
||||
var opts = filter.get(0).opts;
|
||||
if(frappe.sys_defaults[key]) {
|
||||
|
|
@ -237,13 +230,13 @@ frappe.views.GridReport = Class.extend({
|
|||
var values = {
|
||||
from_date: frappe.datetime.str_to_user(frappe.sys_defaults.year_start_date),
|
||||
to_date: frappe.datetime.str_to_user(frappe.sys_defaults.year_end_date)
|
||||
}
|
||||
};
|
||||
|
||||
var me = this;
|
||||
$.each(values, function(i, v) {
|
||||
if(me.filter_inputs[i] && !me.filter_inputs[i].val())
|
||||
me.filter_inputs[i].val(v);
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
make_filters: function() {
|
||||
|
|
@ -281,7 +274,7 @@ frappe.views.GridReport = Class.extend({
|
|||
if(e.which==13) {
|
||||
me.refresh();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
me.filter_inputs[v.fieldname] = input;
|
||||
});
|
||||
|
|
@ -317,7 +310,7 @@ frappe.views.GridReport = Class.extend({
|
|||
key = key || "name";
|
||||
$.each(data, function(i, v) {
|
||||
map[v[key]] = v;
|
||||
})
|
||||
});
|
||||
return map;
|
||||
},
|
||||
|
||||
|
|
@ -350,7 +343,7 @@ frappe.views.GridReport = Class.extend({
|
|||
this.waiting.toggle(false);
|
||||
if(!this.grid_wrapper)
|
||||
this.make();
|
||||
this.show_zero = $('.show-zero input:checked').length;
|
||||
// this.show_zero = $('.show-zero input:checked').length;
|
||||
this.load_filter_values();
|
||||
this.setup_columns();
|
||||
this.setup_dataview_columns();
|
||||
|
|
@ -364,29 +357,28 @@ frappe.views.GridReport = Class.extend({
|
|||
this.setup_chart && this.setup_chart();
|
||||
},
|
||||
setup_dataview_columns: function() {
|
||||
this.dataview_columns = $.map(this.columns, function(col) {
|
||||
return !col.hidden ? col : null;
|
||||
this.columns = this.columns.filter(col => !col.hidden);
|
||||
this.datatable_columns = this.columns.map(column => {
|
||||
return Object.assign(column, {
|
||||
format: (value, row, column, data) => {
|
||||
return column.formatter ?
|
||||
column.formatter(row, {}, value, column, data) :
|
||||
value || '';
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
make: function() {
|
||||
var me = this;
|
||||
|
||||
// chart wrapper
|
||||
this.chart_area = $('<div class="chart" style="padding-bottom: 1px"></div>').appendTo(this.wrapper);
|
||||
|
||||
this.page.add_menu_item(__("Export"), function() { return me.export(); }, true);
|
||||
this.page.add_menu_item(__("Export"), () => this.export(), true);
|
||||
|
||||
// grid wrapper
|
||||
this.grid_wrapper = $("<div style='height: 500px; border: 1px solid #aaa; \
|
||||
background-color: #eee; '>")
|
||||
this.grid_wrapper = $("<div style='height: 500px;'>")
|
||||
.appendTo(this.wrapper);
|
||||
this.id = frappe.dom.set_unique_id(this.grid_wrapper.get(0));
|
||||
|
||||
// zero-value check
|
||||
$('<div class="checkbox show-zero">\
|
||||
<label><input type="checkbox"> '+__('Show rows with zero values')
|
||||
+'</label></div>').appendTo(this.wrapper);
|
||||
|
||||
this.bind_show();
|
||||
|
||||
frappe.cur_grid_report = this;
|
||||
|
|
@ -412,40 +404,33 @@ frappe.views.GridReport = Class.extend({
|
|||
enableColumnReorder: false
|
||||
},
|
||||
render: function() {
|
||||
// new slick grid
|
||||
this.grid = new Slick.Grid("#"+this.id, this.dataView, this.dataview_columns, this.options);
|
||||
var me = this;
|
||||
this.datatable = new DataTable('#' + this.id, {
|
||||
columns: this.datatable_columns,
|
||||
data: this.data,
|
||||
layout: 'fixed',
|
||||
inlineFilters: true,
|
||||
treeView: true,
|
||||
checkboxColumn: true,
|
||||
checkedRowStatus: false,
|
||||
events: {
|
||||
onCheckRow: (row) => {
|
||||
const rowIndex = row.meta.rowIndex;
|
||||
const checked = this.datatable.rowmanager.checkMap[rowIndex];
|
||||
const data = this.datatable.datamanager.getData(rowIndex);
|
||||
data.checked = Boolean(checked);
|
||||
|
||||
if (!frappe.dom.is_touchscreen()) {
|
||||
this.grid.setSelectionModel(new Slick.CellSelectionModel());
|
||||
this.grid.registerPlugin(new Slick.CellExternalCopyManager({
|
||||
dataItemColumnValueExtractor: function(item, columnDef, value) {
|
||||
return item[columnDef.field];
|
||||
this.setup_chart && this.setup_chart();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// bind events
|
||||
this.dataView.onRowsChanged.subscribe(function (e, args) {
|
||||
me.grid.invalidateRows(args.rows);
|
||||
me.grid.render();
|
||||
}
|
||||
});
|
||||
|
||||
this.dataView.onRowCountChanged.subscribe(function (e, args) {
|
||||
me.grid.updateRowCount();
|
||||
me.grid.render();
|
||||
this.data.forEach((d, i) => {
|
||||
if (d.checked) {
|
||||
this.datatable.rowmanager.checkRow(i, true);
|
||||
}
|
||||
});
|
||||
|
||||
this.tree_grid.show && this.add_tree_grid_events();
|
||||
},
|
||||
prepare_data_view: function() {
|
||||
// initialize the model
|
||||
this.dataView = new Slick.Data.DataView({ inlineFilters: true });
|
||||
this.dataView.beginUpdate();
|
||||
this.dataView.setItems(this.data);
|
||||
if(this.dataview_filter) this.dataView.setFilter(this.dataview_filter);
|
||||
if(this.tree_grid.show) this.dataView.setFilter(this.tree_dataview_filter);
|
||||
this.dataView.endUpdate();
|
||||
},
|
||||
export: function() {
|
||||
frappe.tools.downloadify(frappe.slickgrid_tools.get_view_data(this.columns, this.dataView),
|
||||
|
|
@ -501,10 +486,11 @@ frappe.views.GridReport = Class.extend({
|
|||
is_default: function(fieldname) {
|
||||
return this[fieldname]==this[fieldname + "_default"];
|
||||
},
|
||||
date_formatter: function(row, cell, value, columnDef, dataContext) {
|
||||
date_formatter: function(row, cell, value) {
|
||||
return frappe.datetime.str_to_user(value);
|
||||
},
|
||||
currency_formatter: function(row, cell, value, columnDef, dataContext) {
|
||||
if (isNaN(value)) value = '';
|
||||
return repl('<div style="text-align: right; %(_style)s">%(value)s</div>', {
|
||||
_style: dataContext._style || "",
|
||||
value: ((value==null || value==="") ? "" : format_number(value))
|
||||
|
|
@ -513,7 +499,7 @@ frappe.views.GridReport = Class.extend({
|
|||
text_formatter: function(row, cell, value, columnDef, dataContext) {
|
||||
return repl('<span style="%(_style)s" title="%(esc_value)s">%(value)s</span>', {
|
||||
_style: dataContext._style || "",
|
||||
esc_value: cstr(value).replace(/"/g, '\"'),
|
||||
esc_value: cstr(value).replace(/"/g, '\\"'),
|
||||
value: cstr(value)
|
||||
});
|
||||
},
|
||||
|
|
@ -522,11 +508,10 @@ frappe.views.GridReport = Class.extend({
|
|||
class="chart-check" %(checked)s>', {
|
||||
"id": dataContext.id,
|
||||
"checked": dataContext.checked ? 'checked="checked"' : ""
|
||||
})
|
||||
});
|
||||
},
|
||||
apply_link_formatters: function() {
|
||||
var me = this;
|
||||
$.each(this.dataview_columns, function(i, col) {
|
||||
$.each(this.columns, function(i, col) {
|
||||
if(col.link_formatter) {
|
||||
col.formatter = function(row, cell, value, columnDef, dataContext, for_print) {
|
||||
// added link and open button to links
|
||||
|
|
@ -548,9 +533,10 @@ frappe.views.GridReport = Class.extend({
|
|||
}
|
||||
|
||||
// make link to add a filter
|
||||
var html;
|
||||
var link_formatter = me.dataview_columns[cell].link_formatter;
|
||||
if (link_formatter.filter_input) {
|
||||
var html = repl('<a href="#" \
|
||||
html = repl('<a href="#" \
|
||||
onclick="frappe.cur_grid_report.set_filter(\'%(col_name)s\', \'%(value)s\'); \
|
||||
frappe.cur_grid_report.refresh(); return false;">\
|
||||
%(value)s</a>', {
|
||||
|
|
@ -559,7 +545,7 @@ frappe.views.GridReport = Class.extend({
|
|||
page_name: frappe.container.page.page_name
|
||||
});
|
||||
} else {
|
||||
var html = value;
|
||||
html = value;
|
||||
}
|
||||
|
||||
// make icon to open form
|
||||
|
|
@ -570,9 +556,9 @@ frappe.views.GridReport = Class.extend({
|
|||
html += me.get_link_open_icon(doctype, value);
|
||||
}
|
||||
return html;
|
||||
}
|
||||
};
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
get_link_open_icon: function(doctype, name) {
|
||||
return repl(' <a href="#Form/%(doctype)s/%(name)s">\
|
||||
|
|
@ -601,13 +587,13 @@ frappe.views.GridReport = Class.extend({
|
|||
formatter: me.currency_formatter,
|
||||
width: 100
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var build_columns = function(condition) {
|
||||
// add column for each date range
|
||||
for(var i=0; i <= date_diff; i++) {
|
||||
var date = frappe.datetime.add_days(me.from_date, i);
|
||||
if(!condition) condition = function() { return true; }
|
||||
if(!condition) condition = () => true;
|
||||
|
||||
if(condition(date)) add_column(date);
|
||||
me.last_date = date;
|
||||
|
|
@ -616,7 +602,7 @@ frappe.views.GridReport = Class.extend({
|
|||
me.column_map[date] = me.columns[me.columns.length-1];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// make columns for all date ranges
|
||||
if(range=='Daily') {
|
||||
|
|
@ -624,17 +610,17 @@ frappe.views.GridReport = Class.extend({
|
|||
} else if(range=='Weekly') {
|
||||
build_columns(function(date) {
|
||||
if(!me.last_date) return true;
|
||||
return !(frappe.datetime.get_diff(date, me.from_date) % 7)
|
||||
return !(frappe.datetime.get_diff(date, me.from_date) % 7);
|
||||
});
|
||||
} else if(range=='Monthly') {
|
||||
build_columns(function(date) {
|
||||
if(!me.last_date) return true;
|
||||
return frappe.datetime.str_to_obj(me.last_date).getMonth() != frappe.datetime.str_to_obj(date).getMonth()
|
||||
return frappe.datetime.str_to_obj(me.last_date).getMonth() != frappe.datetime.str_to_obj(date).getMonth();
|
||||
});
|
||||
} else if(range=='Quarterly') {
|
||||
build_columns(function(date) {
|
||||
if(!me.last_date) return true;
|
||||
return frappe.datetime.str_to_obj(date).getDate()==1 && in_list([0,3,6,9], frappe.datetime.str_to_obj(date).getMonth())
|
||||
return frappe.datetime.str_to_obj(date).getDate()==1 && in_list([0,3,6,9], frappe.datetime.str_to_obj(date).getMonth());
|
||||
});
|
||||
} else if(range=='Yearly') {
|
||||
build_columns(function(date) {
|
||||
|
|
@ -665,7 +651,6 @@ frappe.views.GridReport = Class.extend({
|
|||
|
||||
frappe.views.GridReportWithPlot = frappe.views.GridReport.extend({
|
||||
setup_chart: function() {
|
||||
var me = this;
|
||||
if (in_list(["Daily", "Weekly"], this.filter_inputs.range.val())) {
|
||||
this.chart_area.toggle(false);
|
||||
return;
|
||||
|
|
@ -674,33 +659,14 @@ frappe.views.GridReportWithPlot = frappe.views.GridReport.extend({
|
|||
}
|
||||
var chart_data = this.get_chart_data ? this.get_chart_data() : null;
|
||||
|
||||
this.chart = new Chart(".chart", {
|
||||
const parent = this.wrapper.find('.chart')[0];
|
||||
this.chart = new Chart(parent, {
|
||||
height: 200,
|
||||
data: chart_data,
|
||||
type: 'line'
|
||||
});
|
||||
},
|
||||
|
||||
setup_chart_check: function() {
|
||||
var me = this;
|
||||
me.wrapper.bind('make', function() {
|
||||
me.wrapper.on("click", ".chart-check", function() {
|
||||
var checked = $(this).prop("checked");
|
||||
var id = $(this).attr("data-id");
|
||||
if(me.item_by_name) {
|
||||
if(me.item_by_name[id]) {
|
||||
me.item_by_name[id].checked = checked ? true : false;
|
||||
}
|
||||
} else {
|
||||
$.each(me.data, function(i, d) {
|
||||
if(d.id==id) d.checked = checked;
|
||||
});
|
||||
}
|
||||
me.setup_chart();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
get_chart_data: function() {
|
||||
var me = this;
|
||||
var plottable_cols = [];
|
||||
|
|
@ -752,7 +718,7 @@ frappe.views.TreeGridReport = frappe.views.GridReportWithPlot.extend({
|
|||
},
|
||||
add_tree_grid_events: function() {
|
||||
var me = this;
|
||||
this.grid.onClick.subscribe(function (e, args) {
|
||||
this.grid.onClick.subscribe(function(e, args) {
|
||||
if ($(e.target).hasClass("toggle")) {
|
||||
var item = me.dataView.getItem(args.row);
|
||||
if (item) {
|
||||
|
|
@ -768,7 +734,7 @@ frappe.views.TreeGridReport = frappe.views.GridReportWithPlot.extend({
|
|||
}
|
||||
});
|
||||
},
|
||||
tree_formatter: function (row, cell, value, columnDef, dataContext) {
|
||||
tree_formatter: function(row, cell, value, columnDef, dataContext) {
|
||||
var me = frappe.cur_grid_report;
|
||||
var data = me.data;
|
||||
var spacer = "<span style='display:inline-block;height:1px;width:" +
|
||||
|
|
@ -810,7 +776,8 @@ frappe.views.TreeGridReport = frappe.views.GridReportWithPlot.extend({
|
|||
// prepare map with child in respective group
|
||||
var me = this;
|
||||
var item_group_map = {};
|
||||
var group_ids = $.map(group_data, function(v) { return v.id; });
|
||||
var group_ids = group_data.map(v => v.id);
|
||||
|
||||
$.each(item_data, function(i, item) {
|
||||
var parent = item[me.tree_grid.parent_field];
|
||||
if(!item_group_map[parent]) item_group_map[parent] = [];
|
||||
|
|
@ -880,7 +847,7 @@ frappe.views.TreeGridReport = frappe.views.GridReportWithPlot.extend({
|
|||
|
||||
frappe.tools.downloadify(data, ["Report Manager", "System Manager"], me.title);
|
||||
return false;
|
||||
})
|
||||
});
|
||||
|
||||
return false;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,965 +0,0 @@
|
|||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// MIT License. See license.txt
|
||||
|
||||
frappe.provide("frappe.views");
|
||||
frappe.provide("frappe.query_reports");
|
||||
frappe.provide("frappe.ui.graphs");
|
||||
|
||||
frappe.standard_pages["query-report"] = function() {
|
||||
var wrapper = frappe.container.add_page('query-report');
|
||||
|
||||
frappe.ui.make_app_page({
|
||||
parent: wrapper,
|
||||
title: __('Query Report'),
|
||||
single_column: true,
|
||||
});
|
||||
|
||||
frappe.query_report = new frappe.views.QueryReport({
|
||||
parent: wrapper,
|
||||
});
|
||||
|
||||
$(wrapper).bind("show", function() {
|
||||
frappe.query_report.load();
|
||||
});
|
||||
}
|
||||
|
||||
frappe.views.QueryReport = Class.extend({
|
||||
init: function(opts) {
|
||||
$.extend(this, opts);
|
||||
this.flags = {};
|
||||
// globalify for slickgrid
|
||||
this.page = this.parent.page;
|
||||
this.parent.query_report = this;
|
||||
this.make();
|
||||
},
|
||||
slickgrid_options: {
|
||||
enableColumnReorder: false,
|
||||
showHeaderRow: true,
|
||||
headerRowHeight: 30,
|
||||
explicitInitialization: true,
|
||||
multiColumnSort: true
|
||||
},
|
||||
make: function() {
|
||||
var me = this;
|
||||
this.wrapper = $("<div>").appendTo(this.page.main);
|
||||
$('<div class="waiting-area" style="display: none;"></div>\
|
||||
<div class="no-report-area msg-box no-border" style="display: none;"></div>\
|
||||
<div class="chart-area" style="border-bottom: 1px solid #d1d8dd; margin: 0px 3%"></div>\
|
||||
<div class="results" style="display: none;">\
|
||||
<div class="result-area" style="height:70vh;"></div>\
|
||||
<button class="btn btn-secondary btn-default btn-xs expand-all hidden" style="margin: 10px;">'+__('Expand All')+'</button>\
|
||||
<button class="btn btn-secondary btn-default btn-xs collapse-all hidden" style="margin: 10px; margin-left: 0px;">'+__('Collapse All')+'</button>\
|
||||
<p class="help-msg alert alert-warning text-center" style="margin: 15px; margin-top: 0px;"></p>\
|
||||
<p class="msg-box small">\
|
||||
'+__('For comparative filters, start with')+' ">" or "<" or "!", e.g. >5 or >01-02-2012 or !0\
|
||||
<br>'+__('For ranges')+' ('+__('values and dates')+') use ":", \
|
||||
e.g. "5:10" (' + __("to filter values between 5 & 10") + ')</p>\
|
||||
</div>').appendTo(this.wrapper);
|
||||
this.wrapper.find(".expand-all").on("click", function() { me.toggle_all(false);});
|
||||
this.wrapper.find(".collapse-all").on("click", function() { me.toggle_all(true);});
|
||||
this.chart_area = this.wrapper.find(".chart-area");
|
||||
this.make_toolbar();
|
||||
},
|
||||
toggle_expand_collapse_buttons: function(show) {
|
||||
this.wrapper.find(".expand-all, .collapse-all").toggleClass('hidden', !!!show);
|
||||
},
|
||||
make_toolbar: function() {
|
||||
var me = this;
|
||||
this.page.set_secondary_action(__('Refresh'), function() { me.refresh(); });
|
||||
|
||||
// Edit
|
||||
this.page.add_menu_item(__('Edit'), function() {
|
||||
if(!frappe.user.is_report_manager()) {
|
||||
frappe.msgprint(__("You are not allowed to create / edit reports"));
|
||||
return false;
|
||||
}
|
||||
frappe.set_route("Form", "Report", me.report_name);
|
||||
}, true);
|
||||
|
||||
this.page.add_menu_item(__("Print"), function() {
|
||||
frappe.ui.get_print_settings(false, function(print_settings) {
|
||||
me.print_settings = print_settings;
|
||||
me.print_report();
|
||||
}, me.report_doc.letter_head);
|
||||
}, true);
|
||||
|
||||
this.page.add_menu_item(__("PDF"), function() {
|
||||
frappe.ui.get_print_settings(true, function(print_settings) {
|
||||
me.print_settings = print_settings;
|
||||
me.pdf_report();
|
||||
}, me.report_doc.letter_head);
|
||||
}, true);
|
||||
|
||||
this.page.add_menu_item(__('Export'), function() {
|
||||
me.make_export();
|
||||
}, true);
|
||||
|
||||
this.page.add_menu_item(__("Setup Auto Email"), function() {
|
||||
frappe.set_route('List', 'Auto Email Report', {'report' : me.report_name});
|
||||
}, true);
|
||||
|
||||
if(frappe.model.can_set_user_permissions("Report")) {
|
||||
this.page.add_menu_item(__("User Permissions"), function() {
|
||||
frappe.route_options = {
|
||||
doctype: "Report",
|
||||
name: me.report_name
|
||||
};
|
||||
frappe.set_route('List', 'User Permission');
|
||||
}, true);
|
||||
}
|
||||
|
||||
// add to desktop
|
||||
this.page.add_menu_item(__("Add to Desktop"), function() {
|
||||
frappe.add_to_desktop(me.report_name, null, me.report_name);
|
||||
}, true);
|
||||
},
|
||||
load: function() {
|
||||
// load from route
|
||||
var route = frappe.get_route();
|
||||
var me = this;
|
||||
if(route[1]) {
|
||||
if((me.report_name!=route[1]) || frappe.route_options) {
|
||||
me.report_name = route[1];
|
||||
this.wrapper.find(".no-report-area").toggle(false);
|
||||
me.page.set_title(__(me.report_name));
|
||||
|
||||
frappe.model.with_doc("Report", me.report_name, function() {
|
||||
|
||||
me.report_doc = frappe.get_doc("Report", me.report_name);
|
||||
|
||||
frappe.model.with_doctype(me.report_doc.ref_doctype, function() {
|
||||
var module = locals.DocType[me.report_doc.ref_doctype].module;
|
||||
frappe.breadcrumbs.add(module)
|
||||
|
||||
if(!frappe.query_reports[me.report_name]) {
|
||||
return frappe.call({
|
||||
method:"frappe.desk.query_report.get_script",
|
||||
args: {
|
||||
report_name: me.report_name
|
||||
},
|
||||
callback: function(r) {
|
||||
frappe.dom.eval(r.message.script || "");
|
||||
|
||||
frappe.after_ajax(function() {
|
||||
var report_settings = frappe.query_reports[me.report_name];
|
||||
me.html_format = r.message.html_format;
|
||||
report_settings["html_format"] = r.message.html_format;
|
||||
|
||||
me.setup_report();
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
me.setup_report();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
var msg = __("No Report Loaded. Please use query-report/[Report Name] to run a report.")
|
||||
this.wrapper.find(".no-report-area").html(msg).toggle(true);
|
||||
}
|
||||
},
|
||||
setup_report: function() {
|
||||
var me = this;
|
||||
this.page.set_title(__(this.report_name));
|
||||
this.page.clear_inner_toolbar();
|
||||
this.setup_filters();
|
||||
this.chart_area.toggle(false);
|
||||
this.toggle_expand_collapse_buttons(false);
|
||||
this.is_tree_report = false;
|
||||
|
||||
var report_settings = frappe.query_reports[this.report_name];
|
||||
|
||||
$.when(function() {
|
||||
if (report_settings.onload) {
|
||||
return report_settings.onload(me);
|
||||
}
|
||||
|
||||
}()).then(function() {
|
||||
me.refresh();
|
||||
});
|
||||
},
|
||||
print_report: function() {
|
||||
if(!frappe.model.can_print(this.report_doc.ref_doctype)) {
|
||||
frappe.msgprint(__("You are not allowed to print this report"));
|
||||
return false;
|
||||
}
|
||||
if(this.html_format) {
|
||||
var content = frappe.render(this.html_format, {
|
||||
data: frappe.slickgrid_tools.get_filtered_items(this.dataView),
|
||||
filters: this.get_values(),
|
||||
report: this,
|
||||
data_to_be_printed: this.data_to_be_printed
|
||||
});
|
||||
|
||||
frappe.render_grid({
|
||||
content: content,
|
||||
title: __(this.report_name),
|
||||
print_settings: this.print_settings,
|
||||
columns: this.columns
|
||||
});
|
||||
} else {
|
||||
frappe.render_grid({
|
||||
grid: this.grid,
|
||||
report: this,
|
||||
title: __(this.report_name),
|
||||
print_settings: this.print_settings,
|
||||
});
|
||||
}
|
||||
},
|
||||
pdf_report: function() {
|
||||
var me = this;
|
||||
var base_url = frappe.urllib.get_base_url();
|
||||
var print_css = frappe.boot.print_css;
|
||||
|
||||
if(!frappe.model.can_print(this.report_doc.ref_doctype)) {
|
||||
frappe.msgprint(__("You are not allowed to make PDF for this report"));
|
||||
return false;
|
||||
}
|
||||
|
||||
var orientation = this.print_settings.orientation;
|
||||
var landscape = orientation == "Landscape" ? true: false
|
||||
var columns = this.grid.getColumns();
|
||||
if(this.html_format) {
|
||||
var content = frappe.render(this.html_format, {
|
||||
data: frappe.slickgrid_tools.get_filtered_items(this.dataView),
|
||||
filters:this.get_values(),
|
||||
report:this,
|
||||
data_to_be_printed: this.data_to_be_printed
|
||||
});
|
||||
|
||||
//Render Report in HTML
|
||||
var html = frappe.render_template("print_template", {
|
||||
content:content,
|
||||
title:__(this.report_name),
|
||||
base_url: base_url,
|
||||
print_css: print_css,
|
||||
print_settings: this.print_settings,
|
||||
landscape: landscape,
|
||||
columns: columns
|
||||
});
|
||||
} else {
|
||||
// rows filtered by inline_filter of slickgrid
|
||||
var visible_idx = frappe.slickgrid_tools
|
||||
.get_view_data(this.columns, this.dataView)
|
||||
.map(row => row[0]).filter(idx => idx !== 'Sr No');
|
||||
|
||||
var data = this.grid.getData().getItems();
|
||||
data = data.filter(d => visible_idx.includes(d._id));
|
||||
|
||||
var content = frappe.render_template("print_grid", {
|
||||
columns:columns,
|
||||
data:data,
|
||||
title:__(this.report_name)
|
||||
})
|
||||
|
||||
//Render Report in HTML
|
||||
var html = frappe.render_template("print_template",{
|
||||
content:content,
|
||||
title:__(this.report_name),
|
||||
base_url: base_url,
|
||||
print_css: print_css,
|
||||
print_settings: this.print_settings,
|
||||
landscape: landscape,
|
||||
columns: columns
|
||||
});
|
||||
}
|
||||
|
||||
var orientation = this.print_settings.orientation;
|
||||
this.open_pdf_report(html, orientation)
|
||||
},
|
||||
open_pdf_report: function(html, orientation) {
|
||||
//Create a form to place the HTML content
|
||||
var formData = new FormData();
|
||||
|
||||
//Push the HTML content into an element
|
||||
formData.append("html", html);
|
||||
formData.append("orientation", orientation);
|
||||
var blob = new Blob([], { type: "text/xml"});
|
||||
//formData.append("webmasterfile", blob);
|
||||
formData.append("blob", blob);
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", '/api/method/frappe.utils.print_format.report_to_pdf');
|
||||
xhr.setRequestHeader("X-Frappe-CSRF-Token", frappe.csrf_token);
|
||||
xhr.responseType = "arraybuffer";
|
||||
|
||||
xhr.onload = function(success) {
|
||||
if (this.status === 200) {
|
||||
var blob = new Blob([success.currentTarget.response], {type: "application/pdf"});
|
||||
var objectUrl = URL.createObjectURL(blob);
|
||||
|
||||
//Open report in a new window
|
||||
window.open(objectUrl);
|
||||
}
|
||||
};
|
||||
xhr.send(formData);
|
||||
},
|
||||
setup_filters: function() {
|
||||
if(this.setting_filters) return;
|
||||
|
||||
this.clear_filters();
|
||||
var me = this;
|
||||
$.each(frappe.query_reports[this.report_name].filters || [], function(i, df) {
|
||||
if(df.fieldtype==="Break") {
|
||||
me.page.add_break();
|
||||
} else {
|
||||
var f = me.page.add_field(df);
|
||||
$(f.wrapper).addClass("filters pull-left");
|
||||
me.filters.push(f);
|
||||
|
||||
if(df["default"]) {
|
||||
f.set_input(df["default"]);
|
||||
}
|
||||
if(df.fieldtype=="Check") {
|
||||
$(f.wrapper).find("input[type='checkbox']");
|
||||
}
|
||||
|
||||
if(df.get_query) f.get_query = df.get_query;
|
||||
if(df.on_change) f.on_change = df.on_change;
|
||||
df.onchange = () => {
|
||||
if(!me.flags.filters_set) {
|
||||
// don't trigger change while setting filters
|
||||
return;
|
||||
}
|
||||
if (f.on_change) {
|
||||
f.on_change(me);
|
||||
} else {
|
||||
me.trigger_refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// hide page form if no filters
|
||||
var $filters = $(this.parent).find('.page-form .filters');
|
||||
$(this.parent).find('.page-form').toggle($filters.length ? true : false);
|
||||
|
||||
// set the field 'query_report_filters_by_name' first as they can be used in
|
||||
// setting/triggering the filters
|
||||
this.set_filters_by_name();
|
||||
|
||||
this.setting_filters = true;
|
||||
this.set_route_filters();
|
||||
this.setting_filters = false;
|
||||
|
||||
this.flags.filters_set = true;
|
||||
},
|
||||
clear_filters: function() {
|
||||
this.filters = [];
|
||||
$(this.parent).find('.page-form .filters').remove();
|
||||
},
|
||||
set_filters_by_name: function() {
|
||||
frappe.query_report_filters_by_name = {};
|
||||
for(var i in this.filters) {
|
||||
frappe.query_report_filters_by_name[this.filters[i].df.fieldname] = this.filters[i];
|
||||
}
|
||||
},
|
||||
set_route_filters: function() {
|
||||
var me = this;
|
||||
if(frappe.route_options) {
|
||||
const fields = Object.keys(frappe.route_options);
|
||||
const filters_to_set = this.filters.filter(f => fields.includes(f.df.fieldname));
|
||||
|
||||
const promises = filters_to_set.map(f => {
|
||||
return () => {
|
||||
const value = frappe.route_options[f.df.fieldname];
|
||||
return f.set_value(value);
|
||||
}
|
||||
});
|
||||
promises.push(() => {
|
||||
frappe.route_options = null;
|
||||
});
|
||||
|
||||
return frappe.run_serially(promises);
|
||||
}
|
||||
|
||||
},
|
||||
refresh: function() {
|
||||
// throttle
|
||||
// stop refresh from being called multiple times (from triggers ?)
|
||||
if (!this.request_refresh) {
|
||||
this.request_refresh = setTimeout(() => {
|
||||
this._refresh();
|
||||
this.request_refresh = null;
|
||||
}, 300);
|
||||
}
|
||||
},
|
||||
_refresh: function() {
|
||||
// Run
|
||||
var me = this;
|
||||
|
||||
this.wrapper.find(".results").toggle(false);
|
||||
try {
|
||||
var filters = this.get_values(true);
|
||||
} catch(e) {
|
||||
// don't run report
|
||||
return;
|
||||
}
|
||||
|
||||
this.waiting = frappe.messages.waiting(this.wrapper.find(".waiting-area").empty().toggle(true),
|
||||
__("Loading Report") + "...");
|
||||
this.wrapper.find(".no-report-area").toggle(false);
|
||||
|
||||
if (this.report_ajax) {
|
||||
// abort previous request
|
||||
this.report_ajax.abort();
|
||||
}
|
||||
|
||||
this.chart_area.toggle(false);
|
||||
|
||||
this.report_ajax = frappe.call({
|
||||
method: "frappe.desk.query_report.run",
|
||||
type: "GET",
|
||||
args: {
|
||||
"report_name": me.report_name,
|
||||
filters: filters
|
||||
},
|
||||
callback: function(r) {
|
||||
me.report_ajax = undefined;
|
||||
me.make_results(r.message);
|
||||
}
|
||||
});
|
||||
|
||||
return this.report_ajax;
|
||||
},
|
||||
trigger_refresh: function() {
|
||||
var me = this;
|
||||
var filters = me.get_values();
|
||||
|
||||
// check if required filters are not missing
|
||||
var missing = false;
|
||||
$.each(me.filters, function(k, _f) {
|
||||
if (_f.df.reqd && !filters[_f.df.fieldname]) {
|
||||
missing = true;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
if (!missing) {
|
||||
me.refresh();
|
||||
}
|
||||
},
|
||||
get_values: function(raise) {
|
||||
var filters = {};
|
||||
var mandatory_fields = [];
|
||||
$.each(this.filters || [], function(i, f) {
|
||||
var v = f.get_value();
|
||||
// TODO: hidden fields dont have $input
|
||||
if(f.df.hidden) v = f.value;
|
||||
if(v === '%') v = null;
|
||||
if(f.df.reqd && !v) mandatory_fields.push(f.df.label);
|
||||
if(v) filters[f.df.fieldname] = v;
|
||||
})
|
||||
if(raise && mandatory_fields.length) {
|
||||
this.chart_area.hide();
|
||||
this.wrapper.find(".waiting-area").empty().toggle(false);
|
||||
this.wrapper.find(".no-report-area").html(__("Please set filters")).toggle(true);
|
||||
if(raise) {
|
||||
console.log('filter missing: ' + mandatory_fields);
|
||||
throw "Filters required";
|
||||
}
|
||||
}
|
||||
|
||||
return filters;
|
||||
},
|
||||
make_results: function(res) {
|
||||
this.wrapper.find(".waiting-area, .no-report-area").empty().toggle(false);
|
||||
this.wrapper.find(".results").toggle(true);
|
||||
this.make_columns(res.columns);
|
||||
this.make_data(res.result, res.columns);
|
||||
this.filter_hidden_columns();
|
||||
this.render(res);
|
||||
},
|
||||
render: function(res) {
|
||||
this.columnFilters = {};
|
||||
this.make_dataview();
|
||||
this.id = frappe.dom.set_unique_id(this.wrapper.find(".result-area").addClass("slick-wrapper").get(0));
|
||||
|
||||
this.grid = new Slick.Grid("#"+this.id, this.dataView, this.columns,
|
||||
this.slickgrid_options);
|
||||
|
||||
if (!frappe.dom.is_touchscreen()) {
|
||||
this.grid.setSelectionModel(new Slick.CellSelectionModel());
|
||||
this.grid.registerPlugin(new Slick.CellExternalCopyManager({
|
||||
dataItemColumnValueExtractor: function(item, columnDef, value) {
|
||||
return item[columnDef.field];
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
this.setup_header_row();
|
||||
this.grid.init();
|
||||
this.setup_sort();
|
||||
|
||||
// further setup of grid like click subscription for tree
|
||||
if (this.get_query_report_opts().tree) {
|
||||
this.setup_tree();
|
||||
}
|
||||
|
||||
this.set_message(res.message);
|
||||
this.setup_chart(res);
|
||||
this.set_print_data(res.data_to_be_printed);
|
||||
|
||||
this.toggle_expand_collapse_buttons(this.is_tree_report);
|
||||
},
|
||||
|
||||
make_columns: function(columns) {
|
||||
var me = this;
|
||||
var formatter = this.get_formatter();
|
||||
|
||||
this.columns = [{id: "_id", field: "_id", name: __("Sr No"), width: 60}]
|
||||
.concat($.map(columns, function(c, i) {
|
||||
if ($.isPlainObject(c)) {
|
||||
var df = c;
|
||||
} else if (c.indexOf(":")!==-1) {
|
||||
var opts = c.split(":");
|
||||
var df = {
|
||||
label: opts.length<=2 ? opts[0] : opts.slice(0, opts.length - 2).join(":"),
|
||||
fieldtype: opts.length<=2 ? opts[1] : opts[opts.length - 2],
|
||||
width: opts.length<=2 ? opts[2] : opts[opts.length - 1]
|
||||
};
|
||||
if (df.fieldtype.indexOf("/")!==-1) {
|
||||
var tmp = df.fieldtype.split("/");
|
||||
df.fieldtype = tmp[0];
|
||||
df.options = tmp[1];
|
||||
}
|
||||
df.width = cint(df.width);
|
||||
} else {
|
||||
var df = {
|
||||
label: c,
|
||||
fieldtype: "Data"
|
||||
};
|
||||
}
|
||||
|
||||
if (!df.fieldtype) df.fieldtype = "Data";
|
||||
if (!cint(df.width)) df.width = 80;
|
||||
|
||||
var col = $.extend({}, df, {
|
||||
label: df.label || (df.fieldname && __(toTitle(df.fieldname.replace(/_/g, " ")))) || "",
|
||||
sortable: true,
|
||||
df: df,
|
||||
formatter: formatter
|
||||
});
|
||||
|
||||
col.field = df.fieldname || df.label;
|
||||
df.label = __(df.label);
|
||||
col.name = col.id = col.label = df.label;
|
||||
|
||||
if(df.width < 0) {
|
||||
col.hidden = true;
|
||||
}
|
||||
|
||||
return col
|
||||
}));
|
||||
},
|
||||
filter_hidden_columns: function() {
|
||||
this.columns = $.map(this.columns, function(c, i) {
|
||||
return (c.hidden==1 ? null : c);
|
||||
});
|
||||
},
|
||||
get_query_report_opts: function() {
|
||||
return frappe.query_reports[this.report_name] || {};
|
||||
},
|
||||
get_formatter: function() {
|
||||
var formatter = function(row, cell, value, columnDef, dataContext, for_print) {
|
||||
var value = frappe.format(value, columnDef.df, {for_print: for_print, always_show_decimals: true}, dataContext);
|
||||
|
||||
if (columnDef.df.is_tree) {
|
||||
value = frappe.query_report.tree_formatter(row, cell, value, columnDef, dataContext);
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
var query_report_opts = this.get_query_report_opts();
|
||||
if (query_report_opts.formatter) {
|
||||
var default_formatter = formatter;
|
||||
|
||||
// custom formatter
|
||||
formatter = function(row, cell, value, columnDef, dataContext) {
|
||||
return query_report_opts.formatter(row, cell, value, columnDef, dataContext, default_formatter);
|
||||
}
|
||||
}
|
||||
|
||||
return formatter;
|
||||
},
|
||||
make_data: function(result, columns) {
|
||||
var me = this;
|
||||
this.data = [];
|
||||
for(var row_idx=0, l=result.length; row_idx < l; row_idx++) {
|
||||
var row = result[row_idx];
|
||||
if ($.isPlainObject(row)) {
|
||||
var newrow = row;
|
||||
} else {
|
||||
var newrow = {};
|
||||
for(var i=1, j=this.columns.length; i<j; i++) {
|
||||
newrow[this.columns[i].field] = row[i-1];
|
||||
}
|
||||
}
|
||||
newrow._id = row_idx + 1;
|
||||
newrow.id = newrow.name ? newrow.name : ("_" + newrow._id);
|
||||
this.data.push(newrow);
|
||||
}
|
||||
if(this.data.length && this.report_doc.add_total_row) {
|
||||
this.total_row_id = this.data[this.data.length - 1].id;
|
||||
}
|
||||
},
|
||||
make_dataview: function() {
|
||||
// initialize the model
|
||||
this.dataView = new Slick.Data.DataView({ inlineFilters: true });
|
||||
this.dataView.beginUpdate();
|
||||
|
||||
if (this.get_query_report_opts().tree) {
|
||||
this.setup_item_by_name();
|
||||
this.dataView.setFilter(this.tree_filter);
|
||||
} else {
|
||||
this.dataView.setFilter(this.inline_filter);
|
||||
}
|
||||
|
||||
this.dataView.setItems(this.data);
|
||||
this.dataView.endUpdate();
|
||||
|
||||
var me = this;
|
||||
this.dataView.onRowCountChanged.subscribe(function (e, args) {
|
||||
me.update_totals_row();
|
||||
me.grid.updateRowCount();
|
||||
me.grid.render();
|
||||
});
|
||||
|
||||
this.dataView.onRowsChanged.subscribe(function (e, args) {
|
||||
me.grid.invalidateRows(args.rows);
|
||||
me.grid.render();
|
||||
});
|
||||
},
|
||||
update_totals_row: function() {
|
||||
if(!this.report_doc.add_total_row) return;
|
||||
|
||||
const number_fields = ['Currency', 'Float', 'Int'];
|
||||
const fields = this.columns
|
||||
.filter(col => number_fields.includes(col.fieldtype))
|
||||
.map(col => col.field);
|
||||
|
||||
// reset numeric fields
|
||||
let updated_totals = Object.assign({}, this.dataView.getItemById(this.total_row_id));
|
||||
fields.map(field => {
|
||||
updated_totals[field] = 0.0;
|
||||
});
|
||||
|
||||
const data_length = this.dataView.getLength();
|
||||
// loop all the rows except the last Total row
|
||||
for (let i = 0; i < data_length - 1; i++) {
|
||||
const item = this.dataView.getItem(i);
|
||||
fields.map(field => {
|
||||
updated_totals[field] += item[field];
|
||||
});
|
||||
}
|
||||
this.dataView.updateItem(updated_totals.id, updated_totals);
|
||||
},
|
||||
inline_filter: function (item) {
|
||||
var me = frappe.container.page.query_report;
|
||||
if(me.report_doc.add_total_row) {
|
||||
// always show totals row
|
||||
if(item.id === me.total_row_id) return true;
|
||||
}
|
||||
for (var columnId in me.columnFilters) {
|
||||
if (columnId !== undefined && me.columnFilters[columnId] !== "") {
|
||||
var c = me.grid.getColumns()[me.grid.getColumnIndex(columnId)];
|
||||
if (!me.compare_values(item[c.field], me.columnFilters[columnId],
|
||||
me.columns[me.grid.getColumnIndex(columnId)])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
setup_item_by_name: function() {
|
||||
this.item_by_name = {};
|
||||
this.name_field = this.get_query_report_opts().name_field;
|
||||
this.parent_field = this.get_query_report_opts().parent_field;
|
||||
var initial_depth = this.get_query_report_opts().initial_depth;
|
||||
for (var i=0, l=this.data.length; i<l; i++) {
|
||||
var item = this.data[i];
|
||||
|
||||
// only if name field has value
|
||||
if (item[this.name_field]) {
|
||||
this.item_by_name[item[this.name_field]] = item;
|
||||
}
|
||||
|
||||
// set collapsed if initial depth is specified
|
||||
if (initial_depth && item.indent && item.indent>=(initial_depth - 1)) {
|
||||
item._collapsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
toggle_all: function(collapse) {
|
||||
var me = this;
|
||||
for(var i=0, l=this.data.length; i<l; i++) {
|
||||
var item = this.data[i];
|
||||
item._collapsed = collapse;
|
||||
me.dataView.updateItem(item.id, item);
|
||||
}
|
||||
},
|
||||
tree_filter: function(item) {
|
||||
var me = frappe.query_report;
|
||||
|
||||
// apply inline filters
|
||||
if (!me.inline_filter(item)) return false;
|
||||
|
||||
try {
|
||||
var parent_name = item[me.parent_field];
|
||||
while (parent_name) {
|
||||
if (!me.item_by_name[parent_name] || me.item_by_name[parent_name]._collapsed) {
|
||||
return false;
|
||||
}
|
||||
parent_name = me.item_by_name[parent_name][me.parent_field];
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (e.message.indexOf("[parent_name] is undefined")!==-1) {
|
||||
frappe.msgprint(__("Unable to display this tree report, due to missing data. Most likely, it is being filtered out due to permissions."));
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
tree_formatter: function(row, cell, value, columnDef, dataContext) {
|
||||
var me = frappe.query_report;
|
||||
me.is_tree_report = true;
|
||||
var $span = $("<span></span>")
|
||||
.css("padding-left", (cint(dataContext.indent) * 21) + "px")
|
||||
.html(value);
|
||||
|
||||
var idx = me.dataView.getIdxById(dataContext.id);
|
||||
var show_toggle = me.data[idx + 1] && (me.data[idx + 1].indent > me.data[idx].indent)
|
||||
|
||||
if (dataContext[me.name_field] && show_toggle) {
|
||||
$('<span class="toggle"></span>')
|
||||
.addClass(dataContext._collapsed ? "expand" : "collapse")
|
||||
.css("margin-right", "7px")
|
||||
.prependTo($span);
|
||||
}
|
||||
|
||||
return $span.wrap("<p></p>").parent().html();
|
||||
},
|
||||
compare_values: function(value, filter, columnDef) {
|
||||
var invert = false;
|
||||
|
||||
// check if invert
|
||||
if(filter[0]=="!") {
|
||||
invert = true;
|
||||
filter = filter.substr(1);
|
||||
}
|
||||
|
||||
var out = false;
|
||||
var cond = "=="
|
||||
|
||||
// parse condition
|
||||
if(filter[0]==">") {
|
||||
filter = filter.substr(1);
|
||||
cond = ">"
|
||||
} else if(filter[0]=="<") {
|
||||
filter = filter.substr(1);
|
||||
cond = "<"
|
||||
}
|
||||
|
||||
if(in_list(['Float', 'Currency', 'Int', 'Date'], columnDef.df.fieldtype)) {
|
||||
// non strings
|
||||
if(filter.indexOf(":")==-1) {
|
||||
if(columnDef.df.fieldtype=="Date") {
|
||||
filter = frappe.datetime.user_to_str(filter);
|
||||
}
|
||||
|
||||
if(in_list(["Float", "Currency", "Int"], columnDef.df.fieldtype)) {
|
||||
value = flt(value);
|
||||
filter = flt(filter);
|
||||
}
|
||||
|
||||
out = eval("value" + cond + "filter");
|
||||
} else {
|
||||
// range
|
||||
filter = filter.split(":");
|
||||
if(columnDef.df.fieldtype=="Date") {
|
||||
filter[0] = frappe.datetime.user_to_str(filter[0]);
|
||||
filter[1] = frappe.datetime.user_to_str(filter[1]);
|
||||
}
|
||||
|
||||
if(in_list(["Float", "Currency", "Int"], columnDef.df.fieldtype)) {
|
||||
value = flt(value);
|
||||
filter[0] = flt(filter[0]);
|
||||
filter[1] = flt(filter[1]);
|
||||
}
|
||||
|
||||
out = value >= filter[0] && value <= filter[1];
|
||||
}
|
||||
} else {
|
||||
// string
|
||||
value = value + "";
|
||||
value = value.toLowerCase();
|
||||
filter = filter.toLowerCase();
|
||||
out = value.indexOf(filter) != -1;
|
||||
}
|
||||
|
||||
if(invert)
|
||||
return !out;
|
||||
else
|
||||
return out;
|
||||
},
|
||||
setup_header_row: function() {
|
||||
var me = this;
|
||||
|
||||
$(this.grid.getHeaderRow()).delegate(":input", "change keyup", function (e) {
|
||||
var columnId = $(this).data("columnId");
|
||||
if (columnId != null) {
|
||||
me.columnFilters[columnId] = $.trim($(this).val());
|
||||
me.dataView.refresh();
|
||||
}
|
||||
});
|
||||
|
||||
this.grid.onHeaderRowCellRendered.subscribe(function(e, args) {
|
||||
$(args.node).empty();
|
||||
$("<input type='text'>")
|
||||
.data("columnId", args.column.id)
|
||||
.val(me.columnFilters[args.column.id])
|
||||
.appendTo(args.node);
|
||||
});
|
||||
},
|
||||
setup_sort: function() {
|
||||
var me = this;
|
||||
this.grid.onSort.subscribe(function (e, args) {
|
||||
var cols = args.sortCols;
|
||||
|
||||
me.data.sort(function (dataRow1, dataRow2) {
|
||||
// Totals row should always be last
|
||||
if(me.report_doc.add_total_row) {
|
||||
if(dataRow1.id === me.total_row_id) {
|
||||
return 1;
|
||||
}
|
||||
if(dataRow2.id === me.total_row_id) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0, l = cols.length; i < l; i++) {
|
||||
var field = cols[i].sortCol.field;
|
||||
var sign = cols[i].sortAsc ? 1 : -1;
|
||||
var value1 = dataRow1[field], value2 = dataRow2[field];
|
||||
var result = (value1 == value2 ? 0 : (value1 > value2 ? 1 : -1)) * sign;
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
me.dataView.beginUpdate();
|
||||
me.dataView.setItems(me.data);
|
||||
me.dataView.endUpdate();
|
||||
me.dataView.refresh();
|
||||
});
|
||||
},
|
||||
setup_tree: function() {
|
||||
// set these in frappe.query_reports[report_name]
|
||||
// "tree": true,
|
||||
// "name_field": "account",
|
||||
// "parent_field": "parent_account",
|
||||
// "initial_depth": 3
|
||||
|
||||
// also set "is_tree" true for ColumnDef
|
||||
|
||||
var me = this;
|
||||
this.grid.onClick.subscribe(function (e, args) {
|
||||
if ($(e.target).hasClass("toggle")) {
|
||||
var item = me.dataView.getItem(args.row);
|
||||
if (item) {
|
||||
if (!item._collapsed) {
|
||||
item._collapsed = true;
|
||||
} else {
|
||||
item._collapsed = false;
|
||||
}
|
||||
|
||||
me.dataView.updateItem(item.id, item);
|
||||
}
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
make_export: function() {
|
||||
|
||||
var me = this;
|
||||
this.title = this.report_name;
|
||||
|
||||
if(!frappe.model.can_export(this.report_doc.ref_doctype)) {
|
||||
frappe.msgprint(__("You are not allowed to export this report"));
|
||||
return false;
|
||||
}
|
||||
|
||||
frappe.prompt({fieldtype:"Select", label: __("Select File Type"), fieldname:"file_format_type",
|
||||
options:"Excel\nCSV", default:"Excel", reqd: 1},
|
||||
function(data) {
|
||||
var view_data = frappe.slickgrid_tools.get_view_data(me.columns, me.dataView);
|
||||
var result = view_data.map(row => row.splice(1));
|
||||
|
||||
// to download only visible rows
|
||||
var visible_idx = view_data.map(row => row[0]).filter(sr_no => sr_no !== 'Sr No');
|
||||
|
||||
if (data.file_format_type == "CSV") {
|
||||
frappe.tools.downloadify(result, null, me.title);
|
||||
}
|
||||
|
||||
else if (data.file_format_type == "Excel") {
|
||||
try {
|
||||
var filters = me.get_values(true);
|
||||
} catch(e) {
|
||||
return;
|
||||
}
|
||||
var args = {
|
||||
cmd: 'frappe.desk.query_report.export_query',
|
||||
report_name: me.report_name,
|
||||
file_format_type: data.file_format_type,
|
||||
filters: filters,
|
||||
visible_idx: visible_idx,
|
||||
}
|
||||
|
||||
open_url_post(frappe.request.url, args);
|
||||
}
|
||||
}, __("Export Report: "+ me.title), __("Download"));
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
set_message: function(msg) {
|
||||
if(msg) {
|
||||
this.wrapper.find(".help-msg").html(msg).toggle(true);
|
||||
} else {
|
||||
this.wrapper.find(".help-msg").empty().toggle(false);
|
||||
}
|
||||
},
|
||||
|
||||
setup_chart: function(res) {
|
||||
this.chart_area.toggle(false);
|
||||
|
||||
if (this.get_query_report_opts().get_chart_data) {
|
||||
var opts = this.get_query_report_opts().get_chart_data(res.columns, res.result);
|
||||
} else if (res.chart) {
|
||||
var opts = res.chart;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
$.extend(opts, {
|
||||
height: 200
|
||||
});
|
||||
|
||||
if(opts.data && opts.data.labels && opts.data.labels.length) {
|
||||
this.chart_area.toggle(true);
|
||||
this.chart = new Chart(".chart-area", opts);
|
||||
}
|
||||
},
|
||||
|
||||
set_print_data: function(data_to_be_printed) {
|
||||
this.data_to_be_printed = data_to_be_printed;
|
||||
}
|
||||
})
|
||||
|
|
@ -147,9 +147,6 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
this.datatable = new DataTable(this.$datatable_wrapper[0], {
|
||||
columns: this.columns,
|
||||
data: this.get_data(values),
|
||||
enableClusterize: true,
|
||||
addCheckbox: this.can_delete,
|
||||
takeAvailableSpace: true,
|
||||
getEditor: this.get_editing_object.bind(this),
|
||||
events: {
|
||||
onRemoveColumn: (column) => {
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 170 B |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 572 B |
|
Before Width: | Height: | Size: 128 B |
|
Before Width: | Height: | Size: 128 B |
|
Before Width: | Height: | Size: 578 B |
|
Before Width: | Height: | Size: 241 B |
|
Before Width: | Height: | Size: 279 B |
|
Before Width: | Height: | Size: 154 B |
|
Before Width: | Height: | Size: 156 B |
|
Before Width: | Height: | Size: 1 KiB |
|
Before Width: | Height: | Size: 846 B |
|
Before Width: | Height: | Size: 257 B |
|
Before Width: | Height: | Size: 59 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 851 B |
|
Before Width: | Height: | Size: 872 B |
|
Before Width: | Height: | Size: 836 B |
|
Before Width: | Height: | Size: 823 B |
|
Before Width: | Height: | Size: 345 B |
|
Before Width: | Height: | Size: 80 B |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 914 B |
|
Before Width: | Height: | Size: 823 B |
|
Before Width: | Height: | Size: 830 B |
|
Before Width: | Height: | Size: 105 B |
|
Before Width: | Height: | Size: 833 B |
|
Before Width: | Height: | Size: 107 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 537 B |
|
Before Width: | Height: | Size: 484 B |
|
Before Width: | Height: | Size: 905 B |
|
Before Width: | Height: | Size: 546 B |
|
|
@ -1,408 +0,0 @@
|
|||
/*!
|
||||
* jquery.event.drag - v 2.2
|
||||
* Copyright (c) 2010 Three Dub Media - http://threedubmedia.com
|
||||
* Open Source MIT License - http://threedubmedia.com/code/license
|
||||
*/
|
||||
// Created: 2008-06-04
|
||||
// Updated: 2012-05-21
|
||||
// REQUIRES: jquery 1.7.x
|
||||
|
||||
;(function( $ ){
|
||||
|
||||
// add the jquery instance method
|
||||
$.fn.drag = function( str, arg, opts ){
|
||||
// figure out the event type
|
||||
var type = typeof str == "string" ? str : "",
|
||||
// figure out the event handler...
|
||||
fn = $.isFunction( str ) ? str : $.isFunction( arg ) ? arg : null;
|
||||
// fix the event type
|
||||
if ( type.indexOf("drag") !== 0 )
|
||||
type = "drag"+ type;
|
||||
// were options passed
|
||||
opts = ( str == fn ? arg : opts ) || {};
|
||||
// trigger or bind event handler
|
||||
return fn ? this.bind( type, opts, fn ) : this.trigger( type );
|
||||
};
|
||||
|
||||
// local refs (increase compression)
|
||||
var $event = $.event,
|
||||
$special = $event.special,
|
||||
// configure the drag special event
|
||||
drag = $special.drag = {
|
||||
|
||||
// these are the default settings
|
||||
defaults: {
|
||||
which: 1, // mouse button pressed to start drag sequence
|
||||
distance: 0, // distance dragged before dragstart
|
||||
not: ':input', // selector to suppress dragging on target elements
|
||||
handle: null, // selector to match handle target elements
|
||||
relative: false, // true to use "position", false to use "offset"
|
||||
drop: true, // false to suppress drop events, true or selector to allow
|
||||
click: false // false to suppress click events after dragend (no proxy)
|
||||
},
|
||||
|
||||
// the key name for stored drag data
|
||||
datakey: "dragdata",
|
||||
|
||||
// prevent bubbling for better performance
|
||||
noBubble: true,
|
||||
|
||||
// count bound related events
|
||||
add: function( obj ){
|
||||
// read the interaction data
|
||||
var data = $.data( this, drag.datakey ),
|
||||
// read any passed options
|
||||
opts = obj.data || {};
|
||||
// count another realted event
|
||||
data.related += 1;
|
||||
// extend data options bound with this event
|
||||
// don't iterate "opts" in case it is a node
|
||||
$.each( drag.defaults, function( key, def ){
|
||||
if ( opts[ key ] !== undefined )
|
||||
data[ key ] = opts[ key ];
|
||||
});
|
||||
},
|
||||
|
||||
// forget unbound related events
|
||||
remove: function(){
|
||||
$.data( this, drag.datakey ).related -= 1;
|
||||
},
|
||||
|
||||
// configure interaction, capture settings
|
||||
setup: function(){
|
||||
|
||||
// check for related events
|
||||
if ( $.data( this, drag.datakey ) )
|
||||
return;
|
||||
// initialize the drag data with copied defaults
|
||||
var data = $.extend({ related:0 }, drag.defaults );
|
||||
// store the interaction data
|
||||
$.data( this, drag.datakey, data );
|
||||
// bind the mousedown event, which starts drag interactions
|
||||
|
||||
// don't attached drag event via special for fullcalendar
|
||||
// return false to attach the normal way
|
||||
if(this===document) return false;
|
||||
|
||||
$event.add( this, "touchstart mousedown", drag.init, data );
|
||||
// prevent image dragging in IE...
|
||||
if ( this.attachEvent )
|
||||
this.attachEvent("ondragstart", drag.dontstart );
|
||||
},
|
||||
|
||||
// destroy configured interaction
|
||||
teardown: function(){
|
||||
var data = $.data( this, drag.datakey ) || {};
|
||||
// check for related events
|
||||
if ( data.related )
|
||||
return;
|
||||
// remove the stored data
|
||||
$.removeData( this, drag.datakey );
|
||||
// remove the mousedown event
|
||||
$event.remove( this, "touchstart mousedown", drag.init );
|
||||
// enable text selection
|
||||
drag.textselect( true );
|
||||
// un-prevent image dragging in IE...
|
||||
if ( this.detachEvent )
|
||||
this.detachEvent("ondragstart", drag.dontstart );
|
||||
},
|
||||
|
||||
// initialize the interaction
|
||||
init: function( event ){
|
||||
// sorry, only one touch at a time
|
||||
if ( drag.touched )
|
||||
return;
|
||||
// the drag/drop interaction data
|
||||
var dd = event.data, results;
|
||||
// check the which directive
|
||||
if ( event.which != 0 && dd.which > 0 && event.which != dd.which )
|
||||
return;
|
||||
// check for suppressed selector
|
||||
if ( $( event.target ).is( dd.not ) )
|
||||
return;
|
||||
// check for handle selector
|
||||
if ( dd.handle && !$( event.target ).closest( dd.handle, event.currentTarget ).length )
|
||||
return;
|
||||
|
||||
drag.touched = event.type == 'touchstart' ? this : null;
|
||||
dd.propagates = 1;
|
||||
dd.mousedown = this;
|
||||
dd.interactions = [ drag.interaction( this, dd ) ];
|
||||
dd.target = event.target;
|
||||
dd.pageX = event.pageX;
|
||||
dd.pageY = event.pageY;
|
||||
dd.dragging = null;
|
||||
// handle draginit event...
|
||||
results = drag.hijack( event, "draginit", dd );
|
||||
// early cancel
|
||||
if ( !dd.propagates )
|
||||
return;
|
||||
// flatten the result set
|
||||
results = drag.flatten( results );
|
||||
// insert new interaction elements
|
||||
if ( results && results.length ){
|
||||
dd.interactions = [];
|
||||
$.each( results, function(){
|
||||
dd.interactions.push( drag.interaction( this, dd ) );
|
||||
});
|
||||
}
|
||||
// remember how many interactions are propagating
|
||||
dd.propagates = dd.interactions.length;
|
||||
// locate and init the drop targets
|
||||
if ( dd.drop !== false && $special.drop )
|
||||
$special.drop.handler( event, dd );
|
||||
// disable text selection
|
||||
drag.textselect( false );
|
||||
// bind additional events...
|
||||
if ( drag.touched )
|
||||
$event.add( drag.touched, "touchmove touchend", drag.handler, dd );
|
||||
else
|
||||
$event.add( document, "mousemove mouseup", drag.handler, dd );
|
||||
// helps prevent text selection or scrolling
|
||||
if ( !drag.touched || dd.live )
|
||||
return false;
|
||||
},
|
||||
|
||||
// returns an interaction object
|
||||
interaction: function( elem, dd ){
|
||||
var offset = $( elem )[ dd.relative ? "position" : "offset" ]() || { top:0, left:0 };
|
||||
return {
|
||||
drag: elem,
|
||||
callback: new drag.callback(),
|
||||
droppable: [],
|
||||
offset: offset
|
||||
};
|
||||
},
|
||||
|
||||
// handle drag-releatd DOM events
|
||||
handler: function( event ){
|
||||
// read the data before hijacking anything
|
||||
var dd = event.data;
|
||||
// handle various events
|
||||
switch ( event.type ){
|
||||
// mousemove, check distance, start dragging
|
||||
case !dd.dragging && 'touchmove':
|
||||
event.preventDefault();
|
||||
case !dd.dragging && 'mousemove':
|
||||
// drag tolerance, x² + y² = distance²
|
||||
if ( Math.pow( event.pageX-dd.pageX, 2 ) + Math.pow( event.pageY-dd.pageY, 2 ) < Math.pow( dd.distance, 2 ) )
|
||||
break; // distance tolerance not reached
|
||||
event.target = dd.target; // force target from "mousedown" event (fix distance issue)
|
||||
drag.hijack( event, "dragstart", dd ); // trigger "dragstart"
|
||||
if ( dd.propagates ) // "dragstart" not rejected
|
||||
dd.dragging = true; // activate interaction
|
||||
// mousemove, dragging
|
||||
case 'touchmove':
|
||||
event.preventDefault();
|
||||
case 'mousemove':
|
||||
if ( dd.dragging ){
|
||||
// trigger "drag"
|
||||
drag.hijack( event, "drag", dd );
|
||||
if ( dd.propagates ){
|
||||
// manage drop events
|
||||
if ( dd.drop !== false && $special.drop )
|
||||
$special.drop.handler( event, dd ); // "dropstart", "dropend"
|
||||
break; // "drag" not rejected, stop
|
||||
}
|
||||
event.type = "mouseup"; // helps "drop" handler behave
|
||||
}
|
||||
// mouseup, stop dragging
|
||||
case 'touchend':
|
||||
case 'mouseup':
|
||||
default:
|
||||
if ( drag.touched )
|
||||
$event.remove( drag.touched, "touchmove touchend", drag.handler ); // remove touch events
|
||||
else
|
||||
$event.remove( document, "mousemove mouseup", drag.handler ); // remove page events
|
||||
if ( dd.dragging ){
|
||||
if ( dd.drop !== false && $special.drop )
|
||||
$special.drop.handler( event, dd ); // "drop"
|
||||
drag.hijack( event, "dragend", dd ); // trigger "dragend"
|
||||
}
|
||||
drag.textselect( true ); // enable text selection
|
||||
// if suppressing click events...
|
||||
if ( dd.click === false && dd.dragging )
|
||||
$.data( dd.mousedown, "suppress.click", new Date().getTime() + 5 );
|
||||
dd.dragging = drag.touched = false; // deactivate element
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
// re-use event object for custom events
|
||||
hijack: function( event, type, dd, x, elem ){
|
||||
// not configured
|
||||
if ( !dd )
|
||||
return;
|
||||
// remember the original event and type
|
||||
var orig = { event:event.originalEvent, type:event.type },
|
||||
// is the event drag related or drog related?
|
||||
mode = type.indexOf("drop") ? "drag" : "drop",
|
||||
// iteration vars
|
||||
result, i = x || 0, ia, $elems, callback,
|
||||
len = !isNaN( x ) ? x : dd.interactions.length;
|
||||
// modify the event type
|
||||
event.type = type;
|
||||
// remove the original event
|
||||
event.originalEvent = null;
|
||||
// initialize the results
|
||||
dd.results = [];
|
||||
// handle each interacted element
|
||||
do if ( ia = dd.interactions[ i ] ){
|
||||
// validate the interaction
|
||||
if ( type !== "dragend" && ia.cancelled )
|
||||
continue;
|
||||
// set the dragdrop properties on the event object
|
||||
callback = drag.properties( event, dd, ia );
|
||||
// prepare for more results
|
||||
ia.results = [];
|
||||
// handle each element
|
||||
$( elem || ia[ mode ] || dd.droppable ).each(function( p, subject ){
|
||||
// identify drag or drop targets individually
|
||||
callback.target = subject;
|
||||
// force propagtion of the custom event
|
||||
event.isPropagationStopped = function(){ return false; };
|
||||
// handle the event
|
||||
result = subject ? $event.dispatch.call( subject, event, callback ) : null;
|
||||
// stop the drag interaction for this element
|
||||
if ( result === false ){
|
||||
if ( mode == "drag" ){
|
||||
ia.cancelled = true;
|
||||
dd.propagates -= 1;
|
||||
}
|
||||
if ( type == "drop" ){
|
||||
ia[ mode ][p] = null;
|
||||
}
|
||||
}
|
||||
// assign any dropinit elements
|
||||
else if ( type == "dropinit" )
|
||||
ia.droppable.push( drag.element( result ) || subject );
|
||||
// accept a returned proxy element
|
||||
if ( type == "dragstart" )
|
||||
ia.proxy = $( drag.element( result ) || ia.drag )[0];
|
||||
// remember this result
|
||||
ia.results.push( result );
|
||||
// forget the event result, for recycling
|
||||
delete event.result;
|
||||
// break on cancelled handler
|
||||
if ( type !== "dropinit" )
|
||||
return result;
|
||||
});
|
||||
// flatten the results
|
||||
dd.results[ i ] = drag.flatten( ia.results );
|
||||
// accept a set of valid drop targets
|
||||
if ( type == "dropinit" )
|
||||
ia.droppable = drag.flatten( ia.droppable );
|
||||
// locate drop targets
|
||||
if ( type == "dragstart" && !ia.cancelled )
|
||||
callback.update();
|
||||
}
|
||||
while ( ++i < len )
|
||||
// restore the original event & type
|
||||
event.type = orig.type;
|
||||
event.originalEvent = orig.event;
|
||||
// return all handler results
|
||||
return drag.flatten( dd.results );
|
||||
},
|
||||
|
||||
// extend the callback object with drag/drop properties...
|
||||
properties: function( event, dd, ia ){
|
||||
var obj = ia.callback;
|
||||
// elements
|
||||
obj.drag = ia.drag;
|
||||
obj.proxy = ia.proxy || ia.drag;
|
||||
// starting mouse position
|
||||
obj.startX = dd.pageX;
|
||||
obj.startY = dd.pageY;
|
||||
// current distance dragged
|
||||
obj.deltaX = event.pageX - dd.pageX;
|
||||
obj.deltaY = event.pageY - dd.pageY;
|
||||
// original element position
|
||||
obj.originalX = ia.offset.left;
|
||||
obj.originalY = ia.offset.top;
|
||||
// adjusted element position
|
||||
obj.offsetX = obj.originalX + obj.deltaX;
|
||||
obj.offsetY = obj.originalY + obj.deltaY;
|
||||
// assign the drop targets information
|
||||
obj.drop = drag.flatten( ( ia.drop || [] ).slice() );
|
||||
obj.available = drag.flatten( ( ia.droppable || [] ).slice() );
|
||||
return obj;
|
||||
},
|
||||
|
||||
// determine is the argument is an element or jquery instance
|
||||
element: function( arg ){
|
||||
if ( arg && ( arg.jquery || arg.nodeType == 1 ) )
|
||||
return arg;
|
||||
},
|
||||
|
||||
// flatten nested jquery objects and arrays into a single dimension array
|
||||
flatten: function( arr ){
|
||||
return $.map( arr, function( member ){
|
||||
return member && member.jquery ? $.makeArray( member ) :
|
||||
member && member.length ? drag.flatten( member ) : member;
|
||||
});
|
||||
},
|
||||
|
||||
// toggles text selection attributes ON (true) or OFF (false)
|
||||
textselect: function( bool ){
|
||||
$( document )[ bool ? "unbind" : "bind" ]("selectstart", drag.dontstart )
|
||||
.css("MozUserSelect", bool ? "" : "none" );
|
||||
// .attr("unselectable", bool ? "off" : "on" )
|
||||
document.unselectable = bool ? "off" : "on";
|
||||
},
|
||||
|
||||
// suppress "selectstart" and "ondragstart" events
|
||||
dontstart: function(){
|
||||
return false;
|
||||
},
|
||||
|
||||
// a callback instance contructor
|
||||
callback: function(){}
|
||||
|
||||
};
|
||||
|
||||
// callback methods
|
||||
drag.callback.prototype = {
|
||||
update: function(){
|
||||
if ( $special.drop && this.available.length )
|
||||
$.each( this.available, function( i ){
|
||||
$special.drop.locate( this, i );
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// patch $.event.$dispatch to allow suppressing clicks
|
||||
var $dispatch = $event.dispatch;
|
||||
$event.dispatch = function( event ){
|
||||
if ( $.data( this, "suppress."+ event.type ) - new Date().getTime() > 0 ){
|
||||
$.removeData( this, "suppress."+ event.type );
|
||||
return;
|
||||
}
|
||||
return $dispatch.apply( this, arguments );
|
||||
};
|
||||
|
||||
// event fix hooks for touch events...
|
||||
var touchHooks =
|
||||
$event.fixHooks.touchstart =
|
||||
$event.fixHooks.touchmove =
|
||||
$event.fixHooks.touchend =
|
||||
$event.fixHooks.touchcancel = {
|
||||
props: "clientX clientY pageX pageY screenX screenY".split( " " ),
|
||||
filter: function( event, orig ) {
|
||||
if ( orig ){
|
||||
var touched = ( orig.touches && orig.touches[0] )
|
||||
|| ( orig.changedTouches && orig.changedTouches[0] )
|
||||
|| null;
|
||||
// iOS webkit: touchstart, touchmove, touchend
|
||||
if ( touched )
|
||||
$.each( touchHooks.props, function( i, prop ){
|
||||
event[ prop ] = touched[ prop ];
|
||||
});
|
||||
}
|
||||
return event;
|
||||
}
|
||||
};
|
||||
|
||||
// share the same special event configuration with related events...
|
||||
$special.draginit = $special.dragstart = $special.dragend = drag;
|
||||
|
||||
})( jQuery );
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
(function ($) {
|
||||
// Register namespace
|
||||
$.extend(true, window, {
|
||||
"Slick": {
|
||||
"AutoTooltips": AutoTooltips
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* AutoTooltips plugin to show/hide tooltips when columns are too narrow to fit content.
|
||||
* @constructor
|
||||
* @param {boolean} [options.enableForCells=true] - Enable tooltip for grid cells
|
||||
* @param {boolean} [options.enableForHeaderCells=false] - Enable tooltip for header cells
|
||||
* @param {number} [options.maxToolTipLength=null] - The maximum length for a tooltip
|
||||
*/
|
||||
function AutoTooltips(options) {
|
||||
var _grid;
|
||||
var _self = this;
|
||||
var _defaults = {
|
||||
enableForCells: true,
|
||||
enableForHeaderCells: false,
|
||||
maxToolTipLength: null
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize plugin.
|
||||
*/
|
||||
function init(grid) {
|
||||
options = $.extend(true, {}, _defaults, options);
|
||||
_grid = grid;
|
||||
if (options.enableForCells) _grid.onMouseEnter.subscribe(handleMouseEnter);
|
||||
if (options.enableForHeaderCells) _grid.onHeaderMouseEnter.subscribe(handleHeaderMouseEnter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy plugin.
|
||||
*/
|
||||
function destroy() {
|
||||
if (options.enableForCells) _grid.onMouseEnter.unsubscribe(handleMouseEnter);
|
||||
if (options.enableForHeaderCells) _grid.onHeaderMouseEnter.unsubscribe(handleHeaderMouseEnter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle mouse entering grid cell to add/remove tooltip.
|
||||
* @param {jQuery.Event} e - The event
|
||||
*/
|
||||
function handleMouseEnter(e) {
|
||||
var cell = _grid.getCellFromEvent(e);
|
||||
if (cell) {
|
||||
var $node = $(_grid.getCellNode(cell.row, cell.cell));
|
||||
var text;
|
||||
if ($node.innerWidth() < $node[0].scrollWidth) {
|
||||
text = $.trim($node.text());
|
||||
if (options.maxToolTipLength && text.length > options.maxToolTipLength) {
|
||||
text = text.substr(0, options.maxToolTipLength - 3) + "...";
|
||||
}
|
||||
} else {
|
||||
text = "";
|
||||
}
|
||||
$node.attr("title", text);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle mouse entering header cell to add/remove tooltip.
|
||||
* @param {jQuery.Event} e - The event
|
||||
* @param {object} args.column - The column definition
|
||||
*/
|
||||
function handleHeaderMouseEnter(e, args) {
|
||||
var column = args.column,
|
||||
$node = $(e.target).closest(".slick-header-column");
|
||||
if (!column.toolTip) {
|
||||
$node.attr("title", ($node.innerWidth() < $node[0].scrollWidth) ? column.name : "");
|
||||
}
|
||||
}
|
||||
|
||||
// Public API
|
||||
$.extend(this, {
|
||||
"init": init,
|
||||
"destroy": destroy
|
||||
});
|
||||
}
|
||||
})(jQuery);
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
(function ($) {
|
||||
// register namespace
|
||||
$.extend(true, window, {
|
||||
"Slick": {
|
||||
"CellCopyManager": CellCopyManager
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function CellCopyManager() {
|
||||
var _grid;
|
||||
var _self = this;
|
||||
var _copiedRanges;
|
||||
|
||||
function init(grid) {
|
||||
_grid = grid;
|
||||
_grid.onKeyDown.subscribe(handleKeyDown);
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
_grid.onKeyDown.unsubscribe(handleKeyDown);
|
||||
}
|
||||
|
||||
function handleKeyDown(e, args) {
|
||||
var ranges;
|
||||
if (!_grid.getEditorLock().isActive()) {
|
||||
if (e.which == frappe.ui.keyCode.ESCAPE) {
|
||||
if (_copiedRanges) {
|
||||
e.preventDefault();
|
||||
clearCopySelection();
|
||||
_self.onCopyCancelled.notify({ranges: _copiedRanges});
|
||||
_copiedRanges = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.which == 67 && (e.ctrlKey || e.metaKey)) {
|
||||
ranges = _grid.getSelectionModel().getSelectedRanges();
|
||||
if (ranges.length != 0) {
|
||||
e.preventDefault();
|
||||
_copiedRanges = ranges;
|
||||
markCopySelection(ranges);
|
||||
_self.onCopyCells.notify({ranges: ranges});
|
||||
}
|
||||
}
|
||||
|
||||
if (e.which == 86 && (e.ctrlKey || e.metaKey)) {
|
||||
if (_copiedRanges) {
|
||||
e.preventDefault();
|
||||
clearCopySelection();
|
||||
ranges = _grid.getSelectionModel().getSelectedRanges();
|
||||
_self.onPasteCells.notify({from: _copiedRanges, to: ranges});
|
||||
_copiedRanges = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function markCopySelection(ranges) {
|
||||
var columns = _grid.getColumns();
|
||||
var hash = {};
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
for (var j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
|
||||
hash[j] = {};
|
||||
for (var k = ranges[i].fromCell; k <= ranges[i].toCell; k++) {
|
||||
hash[j][columns[k].id] = "copied";
|
||||
}
|
||||
}
|
||||
}
|
||||
_grid.setCellCssStyles("copy-manager", hash);
|
||||
}
|
||||
|
||||
function clearCopySelection() {
|
||||
_grid.removeCellCssStyles("copy-manager");
|
||||
}
|
||||
|
||||
$.extend(this, {
|
||||
"init": init,
|
||||
"destroy": destroy,
|
||||
"clearCopySelection": clearCopySelection,
|
||||
|
||||
"onCopyCells": new Slick.Event(),
|
||||
"onCopyCancelled": new Slick.Event(),
|
||||
"onPasteCells": new Slick.Event()
|
||||
});
|
||||
}
|
||||
})(jQuery);
|
||||
|
|
@ -1,274 +0,0 @@
|
|||
(function ($) {
|
||||
// register namespace
|
||||
$.extend(true, window, {
|
||||
"Slick": {
|
||||
"CellExternalCopyManager": CellExternalCopyManager
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function CellExternalCopyManager(options) {
|
||||
/*
|
||||
This manager enables users to copy/paste data from/to an external Spreadsheet application
|
||||
|
||||
Since it is not possible to access directly the clipboard in javascript, the plugin uses
|
||||
a trick to do it's job. After detecting the keystroke, we dynamically create a textarea
|
||||
where the browser copies/pastes the serialized data.
|
||||
|
||||
options:
|
||||
copiedCellStyle : sets the css className used for copied cells. default : "copied"
|
||||
copiedCellStyleLayerKey : sets the layer key for setting css values of copied cells. default : "copy-manager"
|
||||
dataItemColumnValueExtractor : option to specify a custom column value extractor function
|
||||
dataItemColumnValueSetter : option to specify a custom column value setter function
|
||||
*/
|
||||
var _grid;
|
||||
var _self = this;
|
||||
var _copiedRanges;
|
||||
var _options = options || {};
|
||||
var _copiedCellStyleLayerKey = _options.copiedCellStyleLayerKey || "copy-manager";
|
||||
var _copiedCellStyle = _options.copiedCellStyle || "copied";
|
||||
var _clearCopyTI = 0;
|
||||
|
||||
var keyCodes = {
|
||||
'C':67,
|
||||
'V':86
|
||||
}
|
||||
|
||||
function init(grid) {
|
||||
_grid = grid;
|
||||
_grid.onKeyDown.subscribe(handleKeyDown);
|
||||
|
||||
// we need a cell selection model
|
||||
var cellSelectionModel = grid.getSelectionModel();
|
||||
if (!cellSelectionModel){
|
||||
throw new Error("Selection model is mandatory for this plugin. Please set a selection model on the grid before adding this plugin: grid.setSelectionModel(new Slick.CellSelectionModel())");
|
||||
}
|
||||
// we give focus on the grid when a selection is done on it.
|
||||
// without this, if the user selects a range of cell without giving focus on a particular cell, the grid doesn't get the focus and key stroke handles (ctrl+c) don't work
|
||||
cellSelectionModel.onSelectedRangesChanged.subscribe(function(e, args){
|
||||
_grid.focus();
|
||||
});
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
_grid.onKeyDown.unsubscribe(handleKeyDown);
|
||||
}
|
||||
|
||||
function getDataItemValueForColumn(item, columnDef) {
|
||||
if (_options.dataItemColumnValueExtractor) {
|
||||
return _options.dataItemColumnValueExtractor(item, columnDef);
|
||||
}
|
||||
// if a custom getter is not defined, we call serializeValue of the editor to serialize
|
||||
var editorArgs = {
|
||||
'container':$(document), // a dummy container
|
||||
'column':columnDef
|
||||
};
|
||||
var editor = new columnDef.editor(editorArgs);
|
||||
var retVal = '';
|
||||
editor.loadValue(item);
|
||||
retVal = editor.serializeValue();
|
||||
editor.destroy();
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
function setDataItemValueForColumn(item, columnDef, value) {
|
||||
if (_options.dataItemColumnValueSetter) {
|
||||
return _options.dataItemColumnValueSetter(item, columnDef, value);
|
||||
}
|
||||
// if a custom setter is not defined, we call applyValue of the editor to unserialize
|
||||
var editorArgs = {
|
||||
'container':$(document), // a dummy container
|
||||
'column':columnDef
|
||||
};
|
||||
var editor = new columnDef.editor(editorArgs);
|
||||
editor.loadValue(item);
|
||||
editor.applyValue(item, value);
|
||||
editor.destroy();
|
||||
}
|
||||
|
||||
|
||||
function _createTextBox(innerText){
|
||||
var ta = document.createElement('textarea');
|
||||
ta.style.position = 'absolute';
|
||||
ta.style.left = '-1000px';
|
||||
ta.style.top = '-1000px';
|
||||
ta.value = innerText;
|
||||
document.body.appendChild(ta);
|
||||
ta.focus();
|
||||
|
||||
return ta;
|
||||
}
|
||||
|
||||
function _decodeTabularData(_grid, ta){
|
||||
var columns = _grid.getColumns();
|
||||
var clipText = ta.value;
|
||||
var clipRows = clipText.split(/[\n\f\r]/);
|
||||
var clippedRange = [];
|
||||
|
||||
document.body.removeChild(ta);
|
||||
|
||||
for (var i=0; i<clipRows.length; i++) {
|
||||
if (clipRows[i]!="")
|
||||
clippedRange[i] = clipRows[i].split("\t");
|
||||
}
|
||||
|
||||
var selectedCell = _grid.getActiveCell();
|
||||
var ranges = _grid.getSelectionModel().getSelectedRanges();
|
||||
var selectedRange = ranges && ranges.length ? ranges[0] : null; // pick only one selection
|
||||
var activeRow = null;
|
||||
var activeCell = null;
|
||||
|
||||
if (selectedRange){
|
||||
activeRow = selectedRange.fromRow;
|
||||
activeCell = selectedRange.fromCell;
|
||||
} else if (selectedCell){
|
||||
activeRow = selectedCell.row;
|
||||
activeCell = selectedCell.cell;
|
||||
} else {
|
||||
// we don't know where to paste
|
||||
return;
|
||||
}
|
||||
|
||||
var oneCellToMultiple = false;
|
||||
var destH = clippedRange.length;
|
||||
var destW = clippedRange.length ? clippedRange[0].length : 0;
|
||||
if (clippedRange.length == 1 && clippedRange[0].length == 1 && selectedRange){
|
||||
oneCellToMultiple = true;
|
||||
destH = selectedRange.toRow - selectedRange.fromRow +1;
|
||||
destW = selectedRange.toCell - selectedRange.fromCell +1;
|
||||
}
|
||||
|
||||
var desty = activeRow;
|
||||
var destx = activeCell;
|
||||
var h = 0;
|
||||
var w = 0;
|
||||
|
||||
for (var y = 0; y < destH; y++){
|
||||
h++;
|
||||
w=0;
|
||||
for (var x = 0; x < destW; x++){
|
||||
w++;
|
||||
var desty = activeRow + y;
|
||||
var destx = activeCell + x;
|
||||
|
||||
if (desty < data.length && destx < grid.getColumns().length ) {
|
||||
var nd = _grid.getCellNode(desty, destx);
|
||||
var dt = _grid.getDataItem(desty);
|
||||
if (oneCellToMultiple)
|
||||
setDataItemValueForColumn(dt, columns[destx], clippedRange[0][0]);
|
||||
else
|
||||
setDataItemValueForColumn(dt, columns[destx], clippedRange[y][x]);
|
||||
_grid.updateCell(desty, destx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var bRange = {
|
||||
'fromCell': activeCell,
|
||||
'fromRow': activeRow,
|
||||
'toCell': activeCell+w-1,
|
||||
'toRow': activeRow+h-1
|
||||
}
|
||||
|
||||
markCopySelection([bRange]);
|
||||
_grid.getSelectionModel().setSelectedRanges([bRange]);
|
||||
_self.onPasteCells.notify({ranges: [bRange]});
|
||||
}
|
||||
|
||||
|
||||
function handleKeyDown(e, args) {
|
||||
var ranges;
|
||||
if (!_grid.getEditorLock().isActive()) {
|
||||
if (e.which == frappe.ui.keyCode.ESCAPE) {
|
||||
if (_copiedRanges) {
|
||||
e.preventDefault();
|
||||
clearCopySelection();
|
||||
_self.onCopyCancelled.notify({ranges: _copiedRanges});
|
||||
_copiedRanges = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.which == keyCodes.C && (e.ctrlKey || e.metaKey)) { // CTRL + C
|
||||
ranges = _grid.getSelectionModel().getSelectedRanges();
|
||||
if (ranges.length != 0) {
|
||||
_copiedRanges = ranges;
|
||||
markCopySelection(ranges);
|
||||
_self.onCopyCells.notify({ranges: ranges});
|
||||
|
||||
var columns = _grid.getColumns();
|
||||
var clipTextArr = [];
|
||||
|
||||
for (var rg = 0; rg < ranges.length; rg++){
|
||||
var range = ranges[rg];
|
||||
var clipTextRows = [];
|
||||
for (var i=range.fromRow; i< range.toRow+1 ; i++){
|
||||
var clipTextCells = [];
|
||||
var dt = _grid.getDataItem(i);
|
||||
|
||||
for (var j=range.fromCell; j< range.toCell+1 ; j++){
|
||||
clipTextCells.push(getDataItemValueForColumn(dt, columns[j]));
|
||||
}
|
||||
clipTextRows.push(clipTextCells.join("\t"));
|
||||
}
|
||||
clipTextArr.push(clipTextRows.join("\r\n"));
|
||||
}
|
||||
var clipText = clipTextArr.join('');
|
||||
var ta = _createTextBox(clipText);
|
||||
$(ta).select();
|
||||
|
||||
setTimeout(function(){
|
||||
document.body.removeChild(ta);
|
||||
}, 100);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.which == keyCodes.V && (e.ctrlKey || e.metaKey)) { // CTRL + V
|
||||
var ta = _createTextBox('');
|
||||
|
||||
setTimeout(function(){
|
||||
_decodeTabularData(_grid, ta);
|
||||
}, 100);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function markCopySelection(ranges) {
|
||||
clearCopySelection();
|
||||
|
||||
var columns = _grid.getColumns();
|
||||
var hash = {};
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
for (var j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
|
||||
hash[j] = {};
|
||||
for (var k = ranges[i].fromCell; k <= ranges[i].toCell && k<columns.length; k++) {
|
||||
hash[j][columns[k].id] = _copiedCellStyle;
|
||||
}
|
||||
}
|
||||
}
|
||||
_grid.setCellCssStyles(_copiedCellStyleLayerKey, hash);
|
||||
clearTimeout(_clearCopyTI);
|
||||
_clearCopyTI = setTimeout(function(){
|
||||
_self.clearCopySelection();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function clearCopySelection() {
|
||||
_grid.removeCellCssStyles(_copiedCellStyleLayerKey);
|
||||
}
|
||||
|
||||
$.extend(this, {
|
||||
"init": init,
|
||||
"destroy": destroy,
|
||||
"clearCopySelection": clearCopySelection,
|
||||
"handleKeyDown":handleKeyDown,
|
||||
"onCopyCells": new Slick.Event(),
|
||||
"onCopyCancelled": new Slick.Event(),
|
||||
"onPasteCells": new Slick.Event()
|
||||
});
|
||||
}
|
||||
})(jQuery);
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
(function ($) {
|
||||
// register namespace
|
||||
$.extend(true, window, {
|
||||
"Slick": {
|
||||
"CellRangeDecorator": CellRangeDecorator
|
||||
}
|
||||
});
|
||||
|
||||
/***
|
||||
* Displays an overlay on top of a given cell range.
|
||||
*
|
||||
* TODO:
|
||||
* Currently, it blocks mouse events to DOM nodes behind it.
|
||||
* Use FF and WebKit-specific "pointer-events" CSS style, or some kind of event forwarding.
|
||||
* Could also construct the borders separately using 4 individual DIVs.
|
||||
*
|
||||
* @param {Grid} grid
|
||||
* @param {Object} options
|
||||
*/
|
||||
function CellRangeDecorator(grid, options) {
|
||||
var _elem;
|
||||
var _defaults = {
|
||||
selectionCssClass: 'slick-range-decorator',
|
||||
selectionCss: {
|
||||
"zIndex": "9999",
|
||||
"border": "2px dashed red"
|
||||
}
|
||||
};
|
||||
|
||||
options = $.extend(true, {}, _defaults, options);
|
||||
|
||||
|
||||
function show(range) {
|
||||
if (!_elem) {
|
||||
_elem = $("<div></div>", {css: options.selectionCss})
|
||||
.addClass(options.selectionCssClass)
|
||||
.css("position", "absolute")
|
||||
.appendTo(grid.getCanvasNode());
|
||||
}
|
||||
|
||||
var from = grid.getCellNodeBox(range.fromRow, range.fromCell);
|
||||
var to = grid.getCellNodeBox(range.toRow, range.toCell);
|
||||
|
||||
_elem.css({
|
||||
top: from.top - 1,
|
||||
left: from.left - 1,
|
||||
height: to.bottom - from.top - 2,
|
||||
width: to.right - from.left - 2
|
||||
});
|
||||
|
||||
return _elem;
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (_elem) {
|
||||
_elem.remove();
|
||||
_elem = null;
|
||||
}
|
||||
}
|
||||
|
||||
$.extend(this, {
|
||||
"show": show,
|
||||
"hide": hide
|
||||
});
|
||||
}
|
||||
})(jQuery);
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
(function ($) {
|
||||
// register namespace
|
||||
$.extend(true, window, {
|
||||
"Slick": {
|
||||
"CellRangeSelector": CellRangeSelector
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function CellRangeSelector(options) {
|
||||
var _grid;
|
||||
var _canvas;
|
||||
var _dragging;
|
||||
var _decorator;
|
||||
var _self = this;
|
||||
var _handler = new Slick.EventHandler();
|
||||
var _defaults = {
|
||||
selectionCss: {
|
||||
"border": "2px dashed blue"
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function init(grid) {
|
||||
options = $.extend(true, {}, _defaults, options);
|
||||
_decorator = new Slick.CellRangeDecorator(grid, options);
|
||||
_grid = grid;
|
||||
_canvas = _grid.getCanvasNode();
|
||||
_handler
|
||||
.subscribe(_grid.onDragInit, handleDragInit)
|
||||
.subscribe(_grid.onDragStart, handleDragStart)
|
||||
.subscribe(_grid.onDrag, handleDrag)
|
||||
.subscribe(_grid.onDragEnd, handleDragEnd);
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
_handler.unsubscribeAll();
|
||||
}
|
||||
|
||||
function handleDragInit(e, dd) {
|
||||
// prevent the grid from cancelling drag'n'drop by default
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
|
||||
function handleDragStart(e, dd) {
|
||||
var cell = _grid.getCellFromEvent(e);
|
||||
if (_self.onBeforeCellRangeSelected.notify(cell) !== false) {
|
||||
if (_grid.canCellBeSelected(cell.row, cell.cell)) {
|
||||
_dragging = true;
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
if (!_dragging) {
|
||||
return;
|
||||
}
|
||||
|
||||
_grid.focus();
|
||||
|
||||
var start = _grid.getCellFromPoint(
|
||||
dd.startX - $(_canvas).offset().left,
|
||||
dd.startY - $(_canvas).offset().top);
|
||||
|
||||
dd.range = {start: start, end: {}};
|
||||
|
||||
return _decorator.show(new Slick.Range(start.row, start.cell));
|
||||
}
|
||||
|
||||
function handleDrag(e, dd) {
|
||||
if (!_dragging) {
|
||||
return;
|
||||
}
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
var end = _grid.getCellFromPoint(
|
||||
e.pageX - $(_canvas).offset().left,
|
||||
e.pageY - $(_canvas).offset().top);
|
||||
|
||||
if (!_grid.canCellBeSelected(end.row, end.cell)) {
|
||||
return;
|
||||
}
|
||||
|
||||
dd.range.end = end;
|
||||
_decorator.show(new Slick.Range(dd.range.start.row, dd.range.start.cell, end.row, end.cell));
|
||||
}
|
||||
|
||||
function handleDragEnd(e, dd) {
|
||||
if (!_dragging) {
|
||||
return;
|
||||
}
|
||||
|
||||
_dragging = false;
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
_decorator.hide();
|
||||
_self.onCellRangeSelected.notify({
|
||||
range: new Slick.Range(
|
||||
dd.range.start.row,
|
||||
dd.range.start.cell,
|
||||
dd.range.end.row,
|
||||
dd.range.end.cell
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
$.extend(this, {
|
||||
"init": init,
|
||||
"destroy": destroy,
|
||||
|
||||
"onBeforeCellRangeSelected": new Slick.Event(),
|
||||
"onCellRangeSelected": new Slick.Event()
|
||||
});
|
||||
}
|
||||
})(jQuery);
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
(function ($) {
|
||||
// register namespace
|
||||
$.extend(true, window, {
|
||||
"Slick": {
|
||||
"CellSelectionModel": CellSelectionModel
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function CellSelectionModel(options) {
|
||||
var _grid;
|
||||
var _canvas;
|
||||
var _ranges = [];
|
||||
var _self = this;
|
||||
var _selector = new Slick.CellRangeSelector({
|
||||
"selectionCss": {
|
||||
"border": "2px solid black"
|
||||
}
|
||||
});
|
||||
var _options;
|
||||
var _defaults = {
|
||||
selectActiveCell: true
|
||||
};
|
||||
|
||||
|
||||
function init(grid) {
|
||||
_options = $.extend(true, {}, _defaults, options);
|
||||
_grid = grid;
|
||||
_canvas = _grid.getCanvasNode();
|
||||
_grid.onActiveCellChanged.subscribe(handleActiveCellChange);
|
||||
_grid.onKeyDown.subscribe(handleKeyDown);
|
||||
grid.registerPlugin(_selector);
|
||||
_selector.onCellRangeSelected.subscribe(handleCellRangeSelected);
|
||||
_selector.onBeforeCellRangeSelected.subscribe(handleBeforeCellRangeSelected);
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
_grid.onActiveCellChanged.unsubscribe(handleActiveCellChange);
|
||||
_grid.onKeyDown.unsubscribe(handleKeyDown);
|
||||
_selector.onCellRangeSelected.unsubscribe(handleCellRangeSelected);
|
||||
_selector.onBeforeCellRangeSelected.unsubscribe(handleBeforeCellRangeSelected);
|
||||
_grid.unregisterPlugin(_selector);
|
||||
}
|
||||
|
||||
function removeInvalidRanges(ranges) {
|
||||
var result = [];
|
||||
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
var r = ranges[i];
|
||||
if (_grid.canCellBeSelected(r.fromRow, r.fromCell) && _grid.canCellBeSelected(r.toRow, r.toCell)) {
|
||||
result.push(r);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function setSelectedRanges(ranges) {
|
||||
_ranges = removeInvalidRanges(ranges);
|
||||
_self.onSelectedRangesChanged.notify(_ranges);
|
||||
}
|
||||
|
||||
function getSelectedRanges() {
|
||||
return _ranges;
|
||||
}
|
||||
|
||||
function handleBeforeCellRangeSelected(e, args) {
|
||||
if (_grid.getEditorLock().isActive()) {
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleCellRangeSelected(e, args) {
|
||||
setSelectedRanges([args.range]);
|
||||
}
|
||||
|
||||
function handleActiveCellChange(e, args) {
|
||||
if (_options.selectActiveCell && args.row != null && args.cell != null) {
|
||||
setSelectedRanges([new Slick.Range(args.row, args.cell)]);
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyDown(e) {
|
||||
/***
|
||||
* Кey codes
|
||||
* 37 left
|
||||
* 38 up
|
||||
* 39 right
|
||||
* 40 down
|
||||
*/
|
||||
var ranges, last;
|
||||
var active = _grid.getActiveCell();
|
||||
|
||||
if ( active && e.shiftKey && !e.ctrlKey && !e.altKey &&
|
||||
(e.which == 37 || e.which == 39 || e.which == 38 || e.which == 40) ) {
|
||||
|
||||
ranges = getSelectedRanges();
|
||||
if (!ranges.length)
|
||||
ranges.push(new Slick.Range(active.row, active.cell));
|
||||
|
||||
// keyboard can work with last range only
|
||||
last = ranges.pop();
|
||||
|
||||
// can't handle selection out of active cell
|
||||
if (!last.contains(active.row, active.cell))
|
||||
last = new Slick.Range(active.row, active.cell);
|
||||
|
||||
var dRow = last.toRow - last.fromRow,
|
||||
dCell = last.toCell - last.fromCell,
|
||||
// walking direction
|
||||
dirRow = active.row == last.fromRow ? 1 : -1,
|
||||
dirCell = active.cell == last.fromCell ? 1 : -1;
|
||||
|
||||
if (e.which == 37) {
|
||||
dCell -= dirCell;
|
||||
} else if (e.which == 39) {
|
||||
dCell += dirCell ;
|
||||
} else if (e.which == 38) {
|
||||
dRow -= dirRow;
|
||||
} else if (e.which == 40) {
|
||||
dRow += dirRow;
|
||||
}
|
||||
|
||||
// define new selection range
|
||||
var new_last = new Slick.Range(active.row, active.cell, active.row + dirRow*dRow, active.cell + dirCell*dCell);
|
||||
if (removeInvalidRanges([new_last]).length) {
|
||||
ranges.push(new_last);
|
||||
var viewRow = dirRow > 0 ? new_last.toRow : new_last.fromRow;
|
||||
var viewCell = dirCell > 0 ? new_last.toCell : new_last.fromCell;
|
||||
_grid.scrollRowIntoView(viewRow);
|
||||
_grid.scrollCellIntoView(viewRow, viewCell);
|
||||
}
|
||||
else
|
||||
ranges.push(last);
|
||||
|
||||
setSelectedRanges(ranges);
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
$.extend(this, {
|
||||
"getSelectedRanges": getSelectedRanges,
|
||||
"setSelectedRanges": setSelectedRanges,
|
||||
|
||||
"init": init,
|
||||
"destroy": destroy,
|
||||
|
||||
"onSelectedRangesChanged": new Slick.Event()
|
||||
});
|
||||
}
|
||||
})(jQuery);
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
(function ($) {
|
||||
// register namespace
|
||||
$.extend(true, window, {
|
||||
"Slick": {
|
||||
"CheckboxSelectColumn": CheckboxSelectColumn
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function CheckboxSelectColumn(options) {
|
||||
var _grid;
|
||||
var _self = this;
|
||||
var _handler = new Slick.EventHandler();
|
||||
var _selectedRowsLookup = {};
|
||||
var _defaults = {
|
||||
columnId: "_checkbox_selector",
|
||||
cssClass: null,
|
||||
toolTip: "Select/Deselect All",
|
||||
width: 30
|
||||
};
|
||||
|
||||
var _options = $.extend(true, {}, _defaults, options);
|
||||
|
||||
function init(grid) {
|
||||
_grid = grid;
|
||||
_handler
|
||||
.subscribe(_grid.onSelectedRowsChanged, handleSelectedRowsChanged)
|
||||
.subscribe(_grid.onClick, handleClick)
|
||||
.subscribe(_grid.onHeaderClick, handleHeaderClick)
|
||||
.subscribe(_grid.onKeyDown, handleKeyDown);
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
_handler.unsubscribeAll();
|
||||
}
|
||||
|
||||
function handleSelectedRowsChanged(e, args) {
|
||||
var selectedRows = _grid.getSelectedRows();
|
||||
var lookup = {}, row, i;
|
||||
for (i = 0; i < selectedRows.length; i++) {
|
||||
row = selectedRows[i];
|
||||
lookup[row] = true;
|
||||
if (lookup[row] !== _selectedRowsLookup[row]) {
|
||||
_grid.invalidateRow(row);
|
||||
delete _selectedRowsLookup[row];
|
||||
}
|
||||
}
|
||||
for (i in _selectedRowsLookup) {
|
||||
_grid.invalidateRow(i);
|
||||
}
|
||||
_selectedRowsLookup = lookup;
|
||||
_grid.render();
|
||||
|
||||
if (selectedRows.length && selectedRows.length == _grid.getDataLength()) {
|
||||
_grid.updateColumnHeader(_options.columnId, "<input type='checkbox' checked='checked'>", _options.toolTip);
|
||||
} else {
|
||||
_grid.updateColumnHeader(_options.columnId, "<input type='checkbox'>", _options.toolTip);
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyDown(e, args) {
|
||||
if (e.which == 32) {
|
||||
if (_grid.getColumns()[args.cell].id === _options.columnId) {
|
||||
// if editing, try to commit
|
||||
if (!_grid.getEditorLock().isActive() || _grid.getEditorLock().commitCurrentEdit()) {
|
||||
toggleRowSelection(args.row);
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleClick(e, args) {
|
||||
// clicking on a row select checkbox
|
||||
if (_grid.getColumns()[args.cell].id === _options.columnId && $(e.target).is(":checkbox")) {
|
||||
// if editing, try to commit
|
||||
if (_grid.getEditorLock().isActive() && !_grid.getEditorLock().commitCurrentEdit()) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
toggleRowSelection(args.row);
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleRowSelection(row) {
|
||||
if (_selectedRowsLookup[row]) {
|
||||
_grid.setSelectedRows($.grep(_grid.getSelectedRows(), function (n) {
|
||||
return n != row
|
||||
}));
|
||||
} else {
|
||||
_grid.setSelectedRows(_grid.getSelectedRows().concat(row));
|
||||
}
|
||||
}
|
||||
|
||||
function handleHeaderClick(e, args) {
|
||||
if (args.column.id == _options.columnId && $(e.target).is(":checkbox")) {
|
||||
// if editing, try to commit
|
||||
if (_grid.getEditorLock().isActive() && !_grid.getEditorLock().commitCurrentEdit()) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($(e.target).is(":checked")) {
|
||||
var rows = [];
|
||||
for (var i = 0; i < _grid.getDataLength(); i++) {
|
||||
rows.push(i);
|
||||
}
|
||||
_grid.setSelectedRows(rows);
|
||||
} else {
|
||||
_grid.setSelectedRows([]);
|
||||
}
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
|
||||
function getColumnDefinition() {
|
||||
return {
|
||||
id: _options.columnId,
|
||||
name: "<input type='checkbox'>",
|
||||
toolTip: _options.toolTip,
|
||||
field: "sel",
|
||||
width: _options.width,
|
||||
resizable: false,
|
||||
sortable: false,
|
||||
cssClass: _options.cssClass,
|
||||
formatter: checkboxSelectionFormatter
|
||||
};
|
||||
}
|
||||
|
||||
function checkboxSelectionFormatter(row, cell, value, columnDef, dataContext) {
|
||||
if (dataContext) {
|
||||
return _selectedRowsLookup[row]
|
||||
? "<input type='checkbox' checked='checked'>"
|
||||
: "<input type='checkbox'>";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
$.extend(this, {
|
||||
"init": init,
|
||||
"destroy": destroy,
|
||||
|
||||
"getColumnDefinition": getColumnDefinition
|
||||
});
|
||||
}
|
||||
})(jQuery);
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
.slick-column-name,
|
||||
.slick-sort-indicator {
|
||||
/**
|
||||
* This makes all "float:right" elements after it that spill over to the next line
|
||||
* display way below the lower boundary of the column thus hiding them.
|
||||
*/
|
||||
display: inline-block;
|
||||
float: left;
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
|
||||
.slick-header-button {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
vertical-align: top;
|
||||
margin: 1px;
|
||||
/**
|
||||
* This makes all "float:right" elements after it that spill over to the next line
|
||||
* display way below the lower boundary of the column thus hiding them.
|
||||
*/
|
||||
margin-bottom: 100px;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.slick-header-button-hidden {
|
||||
width: 0;
|
||||
|
||||
-webkit-transition: 0.2s width;
|
||||
-ms-transition: 0.2s width;
|
||||
transition: 0.2s width;
|
||||
}
|
||||
|
||||
.slick-header-column:hover > .slick-header-button {
|
||||
width: 15px;
|
||||
}
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
(function ($) {
|
||||
// register namespace
|
||||
$.extend(true, window, {
|
||||
"Slick": {
|
||||
"Plugins": {
|
||||
"HeaderButtons": HeaderButtons
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/***
|
||||
* A plugin to add custom buttons to column headers.
|
||||
*
|
||||
* USAGE:
|
||||
*
|
||||
* Add the plugin .js & .css files and register it with the grid.
|
||||
*
|
||||
* To specify a custom button in a column header, extend the column definition like so:
|
||||
*
|
||||
* var columns = [
|
||||
* {
|
||||
* id: 'myColumn',
|
||||
* name: 'My column',
|
||||
*
|
||||
* // This is the relevant part
|
||||
* header: {
|
||||
* buttons: [
|
||||
* {
|
||||
* // button options
|
||||
* },
|
||||
* {
|
||||
* // button options
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* ];
|
||||
*
|
||||
* Available button options:
|
||||
* cssClass: CSS class to add to the button.
|
||||
* image: Relative button image path.
|
||||
* tooltip: Button tooltip.
|
||||
* showOnHover: Only show the button on hover.
|
||||
* handler: Button click handler.
|
||||
* command: A command identifier to be passed to the onCommand event handlers.
|
||||
*
|
||||
* The plugin exposes the following events:
|
||||
* onCommand: Fired on button click for buttons with 'command' specified.
|
||||
* Event args:
|
||||
* grid: Reference to the grid.
|
||||
* column: Column definition.
|
||||
* command: Button command identified.
|
||||
* button: Button options. Note that you can change the button options in your
|
||||
* event handler, and the column header will be automatically updated to
|
||||
* reflect them. This is useful if you want to implement something like a
|
||||
* toggle button.
|
||||
*
|
||||
*
|
||||
* @param options {Object} Options:
|
||||
* buttonCssClass: a CSS class to use for buttons (default 'slick-header-button')
|
||||
* @class Slick.Plugins.HeaderButtons
|
||||
* @constructor
|
||||
*/
|
||||
function HeaderButtons(options) {
|
||||
var _grid;
|
||||
var _self = this;
|
||||
var _handler = new Slick.EventHandler();
|
||||
var _defaults = {
|
||||
buttonCssClass: "slick-header-button"
|
||||
};
|
||||
|
||||
|
||||
function init(grid) {
|
||||
options = $.extend(true, {}, _defaults, options);
|
||||
_grid = grid;
|
||||
_handler
|
||||
.subscribe(_grid.onHeaderCellRendered, handleHeaderCellRendered)
|
||||
.subscribe(_grid.onBeforeHeaderCellDestroy, handleBeforeHeaderCellDestroy);
|
||||
|
||||
// Force the grid to re-render the header now that the events are hooked up.
|
||||
_grid.setColumns(_grid.getColumns());
|
||||
}
|
||||
|
||||
|
||||
function destroy() {
|
||||
_handler.unsubscribeAll();
|
||||
}
|
||||
|
||||
|
||||
function handleHeaderCellRendered(e, args) {
|
||||
var column = args.column;
|
||||
|
||||
if (column.header && column.header.buttons) {
|
||||
// Append buttons in reverse order since they are floated to the right.
|
||||
var i = column.header.buttons.length;
|
||||
while (i--) {
|
||||
var button = column.header.buttons[i];
|
||||
var btn = $("<div></div>")
|
||||
.addClass(options.buttonCssClass)
|
||||
.data("column", column)
|
||||
.data("button", button);
|
||||
|
||||
if (button.showOnHover) {
|
||||
btn.addClass("slick-header-button-hidden");
|
||||
}
|
||||
|
||||
if (button.image) {
|
||||
btn.css("backgroundImage", "url(" + button.image + ")");
|
||||
}
|
||||
|
||||
if (button.cssClass) {
|
||||
btn.addClass(button.cssClass);
|
||||
}
|
||||
|
||||
if (button.tooltip) {
|
||||
btn.attr("title", button.tooltip);
|
||||
}
|
||||
|
||||
if (button.command) {
|
||||
btn.data("command", button.command);
|
||||
}
|
||||
|
||||
if (button.handler) {
|
||||
btn.bind("click", button.handler);
|
||||
}
|
||||
|
||||
btn
|
||||
.bind("click", handleButtonClick)
|
||||
.appendTo(args.node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleBeforeHeaderCellDestroy(e, args) {
|
||||
var column = args.column;
|
||||
|
||||
if (column.header && column.header.buttons) {
|
||||
// Removing buttons via jQuery will also clean up any event handlers and data.
|
||||
// NOTE: If you attach event handlers directly or using a different framework,
|
||||
// you must also clean them up here to avoid memory leaks.
|
||||
$(args.node).find("." + options.buttonCssClass).remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleButtonClick(e) {
|
||||
var command = $(this).data("command");
|
||||
var columnDef = $(this).data("column");
|
||||
var button = $(this).data("button");
|
||||
|
||||
if (command != null) {
|
||||
_self.onCommand.notify({
|
||||
"grid": _grid,
|
||||
"column": columnDef,
|
||||
"command": command,
|
||||
"button": button
|
||||
}, e, _self);
|
||||
|
||||
// Update the header in case the user updated the button definition in the handler.
|
||||
_grid.updateColumnHeader(columnDef.id);
|
||||
}
|
||||
|
||||
// Stop propagation so that it doesn't register as a header click event.
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
$.extend(this, {
|
||||
"init": init,
|
||||
"destroy": destroy,
|
||||
|
||||
"onCommand": new Slick.Event()
|
||||
});
|
||||
}
|
||||
})(jQuery);
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
/* Menu button */
|
||||
.slick-header-menubutton {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 14px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: left center;
|
||||
background-image: url(../images/down.gif);
|
||||
cursor: pointer;
|
||||
|
||||
display: none;
|
||||
border-left: thin ridge silver;
|
||||
}
|
||||
|
||||
.slick-header-column:hover > .slick-header-menubutton,
|
||||
.slick-header-column-active .slick-header-menubutton {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Menu */
|
||||
.slick-header-menu {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
padding: 2px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
|
||||
/* Menu items */
|
||||
.slick-header-menuitem {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.slick-header-menuicon {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: middle;
|
||||
margin-right: 4px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
}
|
||||
|
||||
.slick-header-menucontent {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
||||
/* Disabled */
|
||||
.slick-header-menuitem-disabled {
|
||||
color: silver;
|
||||
}
|
||||
|
|
@ -1,275 +0,0 @@
|
|||
(function ($) {
|
||||
// register namespace
|
||||
$.extend(true, window, {
|
||||
"Slick": {
|
||||
"Plugins": {
|
||||
"HeaderMenu": HeaderMenu
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/***
|
||||
* A plugin to add drop-down menus to column headers.
|
||||
*
|
||||
* USAGE:
|
||||
*
|
||||
* Add the plugin .js & .css files and register it with the grid.
|
||||
*
|
||||
* To specify a menu in a column header, extend the column definition like so:
|
||||
*
|
||||
* var columns = [
|
||||
* {
|
||||
* id: 'myColumn',
|
||||
* name: 'My column',
|
||||
*
|
||||
* // This is the relevant part
|
||||
* header: {
|
||||
* menu: {
|
||||
* items: [
|
||||
* {
|
||||
* // menu item options
|
||||
* },
|
||||
* {
|
||||
* // menu item options
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ];
|
||||
*
|
||||
*
|
||||
* Available menu options:
|
||||
* tooltip: Menu button tooltip.
|
||||
*
|
||||
*
|
||||
* Available menu item options:
|
||||
* title: Menu item text.
|
||||
* disabled: Whether the item is disabled.
|
||||
* tooltip: Item tooltip.
|
||||
* command: A command identifier to be passed to the onCommand event handlers.
|
||||
* iconCssClass: A CSS class to be added to the menu item icon.
|
||||
* iconImage: A url to the icon image.
|
||||
*
|
||||
*
|
||||
* The plugin exposes the following events:
|
||||
* onBeforeMenuShow: Fired before the menu is shown. You can customize the menu or dismiss it by returning false.
|
||||
* Event args:
|
||||
* grid: Reference to the grid.
|
||||
* column: Column definition.
|
||||
* menu: Menu options. Note that you can change the menu items here.
|
||||
*
|
||||
* onCommand: Fired on menu item click for buttons with 'command' specified.
|
||||
* Event args:
|
||||
* grid: Reference to the grid.
|
||||
* column: Column definition.
|
||||
* command: Button command identified.
|
||||
* button: Button options. Note that you can change the button options in your
|
||||
* event handler, and the column header will be automatically updated to
|
||||
* reflect them. This is useful if you want to implement something like a
|
||||
* toggle button.
|
||||
*
|
||||
*
|
||||
* @param options {Object} Options:
|
||||
* buttonCssClass: an extra CSS class to add to the menu button
|
||||
* buttonImage: a url to the menu button image (default '../images/down.gif')
|
||||
* @class Slick.Plugins.HeaderButtons
|
||||
* @constructor
|
||||
*/
|
||||
function HeaderMenu(options) {
|
||||
var _grid;
|
||||
var _self = this;
|
||||
var _handler = new Slick.EventHandler();
|
||||
var _defaults = {
|
||||
buttonCssClass: null,
|
||||
buttonImage: null
|
||||
};
|
||||
var $menu;
|
||||
var $activeHeaderColumn;
|
||||
|
||||
|
||||
function init(grid) {
|
||||
options = $.extend(true, {}, _defaults, options);
|
||||
_grid = grid;
|
||||
_handler
|
||||
.subscribe(_grid.onHeaderCellRendered, handleHeaderCellRendered)
|
||||
.subscribe(_grid.onBeforeHeaderCellDestroy, handleBeforeHeaderCellDestroy);
|
||||
|
||||
// Force the grid to re-render the header now that the events are hooked up.
|
||||
_grid.setColumns(_grid.getColumns());
|
||||
|
||||
// Hide the menu on outside click.
|
||||
$(document.body).bind("mousedown", handleBodyMouseDown);
|
||||
}
|
||||
|
||||
|
||||
function destroy() {
|
||||
_handler.unsubscribeAll();
|
||||
$(document.body).unbind("mousedown", handleBodyMouseDown);
|
||||
}
|
||||
|
||||
|
||||
function handleBodyMouseDown(e) {
|
||||
if ($menu && $menu[0] != e.target && !$.contains($menu[0], e.target)) {
|
||||
hideMenu();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function hideMenu() {
|
||||
if ($menu) {
|
||||
$menu.remove();
|
||||
$menu = null;
|
||||
$activeHeaderColumn
|
||||
.removeClass("slick-header-column-active");
|
||||
}
|
||||
}
|
||||
|
||||
function handleHeaderCellRendered(e, args) {
|
||||
var column = args.column;
|
||||
var menu = column.header && column.header.menu;
|
||||
|
||||
if (menu) {
|
||||
var $el = $("<div></div>")
|
||||
.addClass("slick-header-menubutton")
|
||||
.data("column", column)
|
||||
.data("menu", menu);
|
||||
|
||||
if (options.buttonCssClass) {
|
||||
$el.addClass(options.buttonCssClass);
|
||||
}
|
||||
|
||||
if (options.buttonImage) {
|
||||
$el.css("background-image", "url(" + options.buttonImage + ")");
|
||||
}
|
||||
|
||||
if (menu.tooltip) {
|
||||
$el.attr("title", menu.tooltip);
|
||||
}
|
||||
|
||||
$el
|
||||
.bind("click", showMenu)
|
||||
.appendTo(args.node);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleBeforeHeaderCellDestroy(e, args) {
|
||||
var column = args.column;
|
||||
|
||||
if (column.header && column.header.menu) {
|
||||
$(args.node).find(".slick-header-menubutton").remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function showMenu(e) {
|
||||
var $menuButton = $(this);
|
||||
var menu = $menuButton.data("menu");
|
||||
var columnDef = $menuButton.data("column");
|
||||
|
||||
// Let the user modify the menu or cancel altogether,
|
||||
// or provide alternative menu implementation.
|
||||
if (_self.onBeforeMenuShow.notify({
|
||||
"grid": _grid,
|
||||
"column": columnDef,
|
||||
"menu": menu
|
||||
}, e, _self) == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!$menu) {
|
||||
$menu = $("<div class='slick-header-menu'></div>")
|
||||
.appendTo(_grid.getContainerNode());
|
||||
}
|
||||
$menu.empty();
|
||||
|
||||
|
||||
// Construct the menu items.
|
||||
for (var i = 0; i < menu.items.length; i++) {
|
||||
var item = menu.items[i];
|
||||
|
||||
var $li = $("<div class='slick-header-menuitem'></div>")
|
||||
.data("command", item.command || '')
|
||||
.data("column", columnDef)
|
||||
.data("item", item)
|
||||
.bind("click", handleMenuItemClick)
|
||||
.appendTo($menu);
|
||||
|
||||
if (item.disabled) {
|
||||
$li.addClass("slick-header-menuitem-disabled");
|
||||
}
|
||||
|
||||
if (item.tooltip) {
|
||||
$li.attr("title", item.tooltip);
|
||||
}
|
||||
|
||||
var $icon = $("<div class='slick-header-menuicon'></div>")
|
||||
.appendTo($li);
|
||||
|
||||
if (item.iconCssClass) {
|
||||
$icon.addClass(item.iconCssClass);
|
||||
}
|
||||
|
||||
if (item.iconImage) {
|
||||
$icon.css("background-image", "url(" + item.iconImage + ")");
|
||||
}
|
||||
|
||||
$("<span class='slick-header-menucontent'></span>")
|
||||
.text(item.title)
|
||||
.appendTo($li);
|
||||
}
|
||||
|
||||
|
||||
// Position the menu.
|
||||
$menu
|
||||
.offset({ top: $(this).offset().top + $(this).height(), left: $(this).offset().left });
|
||||
|
||||
|
||||
// Mark the header as active to keep the highlighting.
|
||||
$activeHeaderColumn = $menuButton.closest(".slick-header-column");
|
||||
$activeHeaderColumn
|
||||
.addClass("slick-header-column-active");
|
||||
|
||||
// Stop propagation so that it doesn't register as a header click event.
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
|
||||
function handleMenuItemClick(e) {
|
||||
var command = $(this).data("command");
|
||||
var columnDef = $(this).data("column");
|
||||
var item = $(this).data("item");
|
||||
|
||||
if (item.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
hideMenu();
|
||||
|
||||
if (command != null && command != '') {
|
||||
_self.onCommand.notify({
|
||||
"grid": _grid,
|
||||
"column": columnDef,
|
||||
"command": command,
|
||||
"item": item
|
||||
}, e, _self);
|
||||
}
|
||||
|
||||
// Stop propagation so that it doesn't register as a header click event.
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
$.extend(this, {
|
||||
"init": init,
|
||||
"destroy": destroy,
|
||||
|
||||
"onBeforeMenuShow": new Slick.Event(),
|
||||
"onCommand": new Slick.Event()
|
||||
});
|
||||
}
|
||||
})(jQuery);
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
(function ($) {
|
||||
// register namespace
|
||||
$.extend(true, window, {
|
||||
"Slick": {
|
||||
"RowMoveManager": RowMoveManager
|
||||
}
|
||||
});
|
||||
|
||||
function RowMoveManager(options) {
|
||||
var _grid;
|
||||
var _canvas;
|
||||
var _dragging;
|
||||
var _self = this;
|
||||
var _handler = new Slick.EventHandler();
|
||||
var _defaults = {
|
||||
cancelEditOnDrag: false
|
||||
};
|
||||
|
||||
function init(grid) {
|
||||
options = $.extend(true, {}, _defaults, options);
|
||||
_grid = grid;
|
||||
_canvas = _grid.getCanvasNode();
|
||||
_handler
|
||||
.subscribe(_grid.onDragInit, handleDragInit)
|
||||
.subscribe(_grid.onDragStart, handleDragStart)
|
||||
.subscribe(_grid.onDrag, handleDrag)
|
||||
.subscribe(_grid.onDragEnd, handleDragEnd);
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
_handler.unsubscribeAll();
|
||||
}
|
||||
|
||||
function handleDragInit(e, dd) {
|
||||
// prevent the grid from cancelling drag'n'drop by default
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
|
||||
function handleDragStart(e, dd) {
|
||||
var cell = _grid.getCellFromEvent(e);
|
||||
|
||||
if (options.cancelEditOnDrag && _grid.getEditorLock().isActive()) {
|
||||
_grid.getEditorLock().cancelCurrentEdit();
|
||||
}
|
||||
|
||||
if (_grid.getEditorLock().isActive() || !/move|selectAndMove/.test(_grid.getColumns()[cell.cell].behavior)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_dragging = true;
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
var selectedRows = _grid.getSelectedRows();
|
||||
|
||||
if (selectedRows.length == 0 || $.inArray(cell.row, selectedRows) == -1) {
|
||||
selectedRows = [cell.row];
|
||||
_grid.setSelectedRows(selectedRows);
|
||||
}
|
||||
|
||||
var rowHeight = _grid.getOptions().rowHeight;
|
||||
|
||||
dd.selectedRows = selectedRows;
|
||||
|
||||
dd.selectionProxy = $("<div class='slick-reorder-proxy'/>")
|
||||
.css("position", "absolute")
|
||||
.css("zIndex", "99999")
|
||||
.css("width", $(_canvas).innerWidth())
|
||||
.css("height", rowHeight * selectedRows.length)
|
||||
.appendTo(_canvas);
|
||||
|
||||
dd.guide = $("<div class='slick-reorder-guide'/>")
|
||||
.css("position", "absolute")
|
||||
.css("zIndex", "99998")
|
||||
.css("width", $(_canvas).innerWidth())
|
||||
.css("top", -1000)
|
||||
.appendTo(_canvas);
|
||||
|
||||
dd.insertBefore = -1;
|
||||
}
|
||||
|
||||
function handleDrag(e, dd) {
|
||||
if (!_dragging) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
var top = e.pageY - $(_canvas).offset().top;
|
||||
dd.selectionProxy.css("top", top - 5);
|
||||
|
||||
var insertBefore = Math.max(0, Math.min(Math.round(top / _grid.getOptions().rowHeight), _grid.getDataLength()));
|
||||
if (insertBefore !== dd.insertBefore) {
|
||||
var eventData = {
|
||||
"rows": dd.selectedRows,
|
||||
"insertBefore": insertBefore
|
||||
};
|
||||
|
||||
if (_self.onBeforeMoveRows.notify(eventData) === false) {
|
||||
dd.guide.css("top", -1000);
|
||||
dd.canMove = false;
|
||||
} else {
|
||||
dd.guide.css("top", insertBefore * _grid.getOptions().rowHeight);
|
||||
dd.canMove = true;
|
||||
}
|
||||
|
||||
dd.insertBefore = insertBefore;
|
||||
}
|
||||
}
|
||||
|
||||
function handleDragEnd(e, dd) {
|
||||
if (!_dragging) {
|
||||
return;
|
||||
}
|
||||
_dragging = false;
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
dd.guide.remove();
|
||||
dd.selectionProxy.remove();
|
||||
|
||||
if (dd.canMove) {
|
||||
var eventData = {
|
||||
"rows": dd.selectedRows,
|
||||
"insertBefore": dd.insertBefore
|
||||
};
|
||||
// TODO: _grid.remapCellCssClasses ?
|
||||
_self.onMoveRows.notify(eventData);
|
||||
}
|
||||
}
|
||||
|
||||
$.extend(this, {
|
||||
"onBeforeMoveRows": new Slick.Event(),
|
||||
"onMoveRows": new Slick.Event(),
|
||||
|
||||
"init": init,
|
||||
"destroy": destroy
|
||||
});
|
||||
}
|
||||
})(jQuery);
|
||||
|
|
@ -1,187 +0,0 @@
|
|||
(function ($) {
|
||||
// register namespace
|
||||
$.extend(true, window, {
|
||||
"Slick": {
|
||||
"RowSelectionModel": RowSelectionModel
|
||||
}
|
||||
});
|
||||
|
||||
function RowSelectionModel(options) {
|
||||
var _grid;
|
||||
var _ranges = [];
|
||||
var _self = this;
|
||||
var _handler = new Slick.EventHandler();
|
||||
var _inHandler;
|
||||
var _options;
|
||||
var _defaults = {
|
||||
selectActiveRow: true
|
||||
};
|
||||
|
||||
function init(grid) {
|
||||
_options = $.extend(true, {}, _defaults, options);
|
||||
_grid = grid;
|
||||
_handler.subscribe(_grid.onActiveCellChanged,
|
||||
wrapHandler(handleActiveCellChange));
|
||||
_handler.subscribe(_grid.onKeyDown,
|
||||
wrapHandler(handleKeyDown));
|
||||
_handler.subscribe(_grid.onClick,
|
||||
wrapHandler(handleClick));
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
_handler.unsubscribeAll();
|
||||
}
|
||||
|
||||
function wrapHandler(handler) {
|
||||
return function () {
|
||||
if (!_inHandler) {
|
||||
_inHandler = true;
|
||||
handler.apply(this, arguments);
|
||||
_inHandler = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function rangesToRows(ranges) {
|
||||
var rows = [];
|
||||
for (var i = 0; i < ranges.length; i++) {
|
||||
for (var j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
|
||||
rows.push(j);
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
function rowsToRanges(rows) {
|
||||
var ranges = [];
|
||||
var lastCell = _grid.getColumns().length - 1;
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
ranges.push(new Slick.Range(rows[i], 0, rows[i], lastCell));
|
||||
}
|
||||
return ranges;
|
||||
}
|
||||
|
||||
function getRowsRange(from, to) {
|
||||
var i, rows = [];
|
||||
for (i = from; i <= to; i++) {
|
||||
rows.push(i);
|
||||
}
|
||||
for (i = to; i < from; i++) {
|
||||
rows.push(i);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
function getSelectedRows() {
|
||||
return rangesToRows(_ranges);
|
||||
}
|
||||
|
||||
function setSelectedRows(rows) {
|
||||
setSelectedRanges(rowsToRanges(rows));
|
||||
}
|
||||
|
||||
function setSelectedRanges(ranges) {
|
||||
_ranges = ranges;
|
||||
_self.onSelectedRangesChanged.notify(_ranges);
|
||||
}
|
||||
|
||||
function getSelectedRanges() {
|
||||
return _ranges;
|
||||
}
|
||||
|
||||
function handleActiveCellChange(e, data) {
|
||||
if (_options.selectActiveRow && data.row != null) {
|
||||
setSelectedRanges([new Slick.Range(data.row, 0, data.row, _grid.getColumns().length - 1)]);
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyDown(e) {
|
||||
var activeRow = _grid.getActiveCell();
|
||||
if (activeRow && e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey && (e.which == 38 || e.which == 40)) {
|
||||
var selectedRows = getSelectedRows();
|
||||
selectedRows.sort(function (x, y) {
|
||||
return x - y
|
||||
});
|
||||
|
||||
if (!selectedRows.length) {
|
||||
selectedRows = [activeRow.row];
|
||||
}
|
||||
|
||||
var top = selectedRows[0];
|
||||
var bottom = selectedRows[selectedRows.length - 1];
|
||||
var active;
|
||||
|
||||
if (e.which == 40) {
|
||||
active = activeRow.row < bottom || top == bottom ? ++bottom : ++top;
|
||||
} else {
|
||||
active = activeRow.row < bottom ? --bottom : --top;
|
||||
}
|
||||
|
||||
if (active >= 0 && active < _grid.getDataLength()) {
|
||||
_grid.scrollRowIntoView(active);
|
||||
_ranges = rowsToRanges(getRowsRange(top, bottom));
|
||||
setSelectedRanges(_ranges);
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
function handleClick(e) {
|
||||
var cell = _grid.getCellFromEvent(e);
|
||||
if (!cell || !_grid.canCellBeActive(cell.row, cell.cell)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_grid.getOptions().multiSelect || (
|
||||
!e.ctrlKey && !e.shiftKey && !e.metaKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var selection = rangesToRows(_ranges);
|
||||
var idx = $.inArray(cell.row, selection);
|
||||
|
||||
if (idx === -1 && (e.ctrlKey || e.metaKey)) {
|
||||
selection.push(cell.row);
|
||||
_grid.setActiveCell(cell.row, cell.cell);
|
||||
} else if (idx !== -1 && (e.ctrlKey || e.metaKey)) {
|
||||
selection = $.grep(selection, function (o, i) {
|
||||
return (o !== cell.row);
|
||||
});
|
||||
_grid.setActiveCell(cell.row, cell.cell);
|
||||
} else if (selection.length && e.shiftKey) {
|
||||
var last = selection.pop();
|
||||
var from = Math.min(cell.row, last);
|
||||
var to = Math.max(cell.row, last);
|
||||
selection = [];
|
||||
for (var i = from; i <= to; i++) {
|
||||
if (i !== last) {
|
||||
selection.push(i);
|
||||
}
|
||||
}
|
||||
selection.push(last);
|
||||
_grid.setActiveCell(cell.row, cell.cell);
|
||||
}
|
||||
|
||||
_ranges = rowsToRanges(selection);
|
||||
setSelectedRanges(_ranges);
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$.extend(this, {
|
||||
"getSelectedRows": getSelectedRows,
|
||||
"setSelectedRows": setSelectedRows,
|
||||
|
||||
"getSelectedRanges": getSelectedRanges,
|
||||
"setSelectedRanges": setSelectedRanges,
|
||||
|
||||
"init": init,
|
||||
"destroy": destroy,
|
||||
|
||||
"onSelectedRangesChanged": new Slick.Event()
|
||||
});
|
||||
}
|
||||
})(jQuery);
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
/*
|
||||
IMPORTANT:
|
||||
In order to preserve the uniform grid appearance, all cell styles need to have padding, margin and border sizes.
|
||||
No built-in (selected, editable, highlight, flashing, invalid, loading, :focus) or user-specified CSS
|
||||
classes should alter those!
|
||||
*/
|
||||
|
||||
.slick-header-columns {
|
||||
/*background: url('images/header-columns-bg.gif') repeat-x center bottom;*/
|
||||
border-bottom: 1px solid silver;
|
||||
}
|
||||
|
||||
.slick-header-column {
|
||||
/*background: url('images/header-columns-bg.gif') repeat-x center bottom;*/
|
||||
border-right: 1px solid silver;
|
||||
}
|
||||
|
||||
.slick-header-column:hover, .slick-header-column-active {
|
||||
/*background: white url('images/header-columns-over-bg.gif') repeat-x center bottom;*/
|
||||
}
|
||||
|
||||
.slick-headerrow {
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.slick-headerrow-column {
|
||||
background: #fafafa;
|
||||
border-bottom: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.slick-row.ui-state-active {
|
||||
background: #F5F7D7;
|
||||
}
|
||||
|
||||
.slick-row {
|
||||
position: absolute;
|
||||
background: white;
|
||||
border: 0px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.slick-row.selected {
|
||||
z-index: 10;
|
||||
background: #DFE8F6;
|
||||
}
|
||||
|
||||
.slick-cell {
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.slick-group {
|
||||
border-bottom: 2px solid silver;
|
||||
}
|
||||
|
||||
.slick-group-toggle {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.slick-group-toggle.expanded {
|
||||
background: url(images/collapse.gif) no-repeat center center;
|
||||
}
|
||||
|
||||
.slick-group-toggle.collapsed {
|
||||
background: url(images/expand.gif) no-repeat center center;
|
||||
}
|
||||
|
||||
.slick-group-totals {
|
||||
color: gray;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.slick-cell.selected {
|
||||
background-color: #fffce7 !important;
|
||||
}
|
||||
|
||||
.slick-cell.active {
|
||||
border-color: gray;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.slick-sortable-placeholder {
|
||||
background: silver !important;
|
||||
}
|
||||
|
||||
.slick-row.odd {
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.slick-row.ui-state-active {
|
||||
background: #F5F7D7;
|
||||
}
|
||||
|
||||
.slick-row.loading {
|
||||
opacity: 0.5;
|
||||
/* filter: alpha(opacity = 50); */
|
||||
}
|
||||
|
||||
.slick-cell.invalid {
|
||||
border-color: red;
|
||||
-moz-animation-duration: 0.2s;
|
||||
-webkit-animation-duration: 0.2s;
|
||||
-moz-animation-name: slickgrid-invalid-hilite;
|
||||
-webkit-animation-name: slickgrid-invalid-hilite;
|
||||
}
|
||||
|
||||
@-moz-keyframes slickgrid-invalid-hilite {
|
||||
from { box-shadow: 0 0 6px red; }
|
||||
to { box-shadow: none; }
|
||||
}
|
||||
|
||||
@-webkit-keyframes slickgrid-invalid-hilite {
|
||||
from { box-shadow: 0 0 6px red; }
|
||||
to { box-shadow: none; }
|
||||
}
|
||||
467
frappe/public/js/lib/slickgrid/slick.core.js
vendored
|
|
@ -1,467 +0,0 @@
|
|||
/***
|
||||
* Contains core SlickGrid classes.
|
||||
* @module Core
|
||||
* @namespace Slick
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
// register namespace
|
||||
$.extend(true, window, {
|
||||
"Slick": {
|
||||
"Event": Event,
|
||||
"EventData": EventData,
|
||||
"EventHandler": EventHandler,
|
||||
"Range": Range,
|
||||
"NonDataRow": NonDataItem,
|
||||
"Group": Group,
|
||||
"GroupTotals": GroupTotals,
|
||||
"EditorLock": EditorLock,
|
||||
|
||||
/***
|
||||
* A global singleton editor lock.
|
||||
* @class GlobalEditorLock
|
||||
* @static
|
||||
* @constructor
|
||||
*/
|
||||
"GlobalEditorLock": new EditorLock()
|
||||
}
|
||||
});
|
||||
|
||||
/***
|
||||
* An event object for passing data to event handlers and letting them control propagation.
|
||||
* <p>This is pretty much identical to how W3C and jQuery implement events.</p>
|
||||
* @class EventData
|
||||
* @constructor
|
||||
*/
|
||||
function EventData() {
|
||||
var isPropagationStopped = false;
|
||||
var isImmediatePropagationStopped = false;
|
||||
|
||||
/***
|
||||
* Stops event from propagating up the DOM tree.
|
||||
* @method stopPropagation
|
||||
*/
|
||||
this.stopPropagation = function () {
|
||||
isPropagationStopped = true;
|
||||
};
|
||||
|
||||
/***
|
||||
* Returns whether stopPropagation was called on this event object.
|
||||
* @method isPropagationStopped
|
||||
* @return {Boolean}
|
||||
*/
|
||||
this.isPropagationStopped = function () {
|
||||
return isPropagationStopped;
|
||||
};
|
||||
|
||||
/***
|
||||
* Prevents the rest of the handlers from being executed.
|
||||
* @method stopImmediatePropagation
|
||||
*/
|
||||
this.stopImmediatePropagation = function () {
|
||||
isImmediatePropagationStopped = true;
|
||||
};
|
||||
|
||||
/***
|
||||
* Returns whether stopImmediatePropagation was called on this event object.\
|
||||
* @method isImmediatePropagationStopped
|
||||
* @return {Boolean}
|
||||
*/
|
||||
this.isImmediatePropagationStopped = function () {
|
||||
return isImmediatePropagationStopped;
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* A simple publisher-subscriber implementation.
|
||||
* @class Event
|
||||
* @constructor
|
||||
*/
|
||||
function Event() {
|
||||
var handlers = [];
|
||||
|
||||
/***
|
||||
* Adds an event handler to be called when the event is fired.
|
||||
* <p>Event handler will receive two arguments - an <code>EventData</code> and the <code>data</code>
|
||||
* object the event was fired with.<p>
|
||||
* @method subscribe
|
||||
* @param fn {Function} Event handler.
|
||||
*/
|
||||
this.subscribe = function (fn) {
|
||||
handlers.push(fn);
|
||||
};
|
||||
|
||||
/***
|
||||
* Removes an event handler added with <code>subscribe(fn)</code>.
|
||||
* @method unsubscribe
|
||||
* @param fn {Function} Event handler to be removed.
|
||||
*/
|
||||
this.unsubscribe = function (fn) {
|
||||
for (var i = handlers.length - 1; i >= 0; i--) {
|
||||
if (handlers[i] === fn) {
|
||||
handlers.splice(i, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/***
|
||||
* Fires an event notifying all subscribers.
|
||||
* @method notify
|
||||
* @param args {Object} Additional data object to be passed to all handlers.
|
||||
* @param e {EventData}
|
||||
* Optional.
|
||||
* An <code>EventData</code> object to be passed to all handlers.
|
||||
* For DOM events, an existing W3C/jQuery event object can be passed in.
|
||||
* @param scope {Object}
|
||||
* Optional.
|
||||
* The scope ("this") within which the handler will be executed.
|
||||
* If not specified, the scope will be set to the <code>Event</code> instance.
|
||||
*/
|
||||
this.notify = function (args, e, scope) {
|
||||
e = e || new EventData();
|
||||
scope = scope || this;
|
||||
|
||||
var returnValue;
|
||||
for (var i = 0; i < handlers.length && !(e.isPropagationStopped() || e.isImmediatePropagationStopped()); i++) {
|
||||
returnValue = handlers[i].call(scope, e, args);
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
};
|
||||
}
|
||||
|
||||
function EventHandler() {
|
||||
var handlers = [];
|
||||
|
||||
this.subscribe = function (event, handler) {
|
||||
handlers.push({
|
||||
event: event,
|
||||
handler: handler
|
||||
});
|
||||
event.subscribe(handler);
|
||||
|
||||
return this; // allow chaining
|
||||
};
|
||||
|
||||
this.unsubscribe = function (event, handler) {
|
||||
var i = handlers.length;
|
||||
while (i--) {
|
||||
if (handlers[i].event === event &&
|
||||
handlers[i].handler === handler) {
|
||||
handlers.splice(i, 1);
|
||||
event.unsubscribe(handler);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return this; // allow chaining
|
||||
};
|
||||
|
||||
this.unsubscribeAll = function () {
|
||||
var i = handlers.length;
|
||||
while (i--) {
|
||||
handlers[i].event.unsubscribe(handlers[i].handler);
|
||||
}
|
||||
handlers = [];
|
||||
|
||||
return this; // allow chaining
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* A structure containing a range of cells.
|
||||
* @class Range
|
||||
* @constructor
|
||||
* @param fromRow {Integer} Starting row.
|
||||
* @param fromCell {Integer} Starting cell.
|
||||
* @param toRow {Integer} Optional. Ending row. Defaults to <code>fromRow</code>.
|
||||
* @param toCell {Integer} Optional. Ending cell. Defaults to <code>fromCell</code>.
|
||||
*/
|
||||
function Range(fromRow, fromCell, toRow, toCell) {
|
||||
if (toRow === undefined && toCell === undefined) {
|
||||
toRow = fromRow;
|
||||
toCell = fromCell;
|
||||
}
|
||||
|
||||
/***
|
||||
* @property fromRow
|
||||
* @type {Integer}
|
||||
*/
|
||||
this.fromRow = Math.min(fromRow, toRow);
|
||||
|
||||
/***
|
||||
* @property fromCell
|
||||
* @type {Integer}
|
||||
*/
|
||||
this.fromCell = Math.min(fromCell, toCell);
|
||||
|
||||
/***
|
||||
* @property toRow
|
||||
* @type {Integer}
|
||||
*/
|
||||
this.toRow = Math.max(fromRow, toRow);
|
||||
|
||||
/***
|
||||
* @property toCell
|
||||
* @type {Integer}
|
||||
*/
|
||||
this.toCell = Math.max(fromCell, toCell);
|
||||
|
||||
/***
|
||||
* Returns whether a range represents a single row.
|
||||
* @method isSingleRow
|
||||
* @return {Boolean}
|
||||
*/
|
||||
this.isSingleRow = function () {
|
||||
return this.fromRow == this.toRow;
|
||||
};
|
||||
|
||||
/***
|
||||
* Returns whether a range represents a single cell.
|
||||
* @method isSingleCell
|
||||
* @return {Boolean}
|
||||
*/
|
||||
this.isSingleCell = function () {
|
||||
return this.fromRow == this.toRow && this.fromCell == this.toCell;
|
||||
};
|
||||
|
||||
/***
|
||||
* Returns whether a range contains a given cell.
|
||||
* @method contains
|
||||
* @param row {Integer}
|
||||
* @param cell {Integer}
|
||||
* @return {Boolean}
|
||||
*/
|
||||
this.contains = function (row, cell) {
|
||||
return row >= this.fromRow && row <= this.toRow &&
|
||||
cell >= this.fromCell && cell <= this.toCell;
|
||||
};
|
||||
|
||||
/***
|
||||
* Returns a readable representation of a range.
|
||||
* @method toString
|
||||
* @return {String}
|
||||
*/
|
||||
this.toString = function () {
|
||||
if (this.isSingleCell()) {
|
||||
return "(" + this.fromRow + ":" + this.fromCell + ")";
|
||||
}
|
||||
else {
|
||||
return "(" + this.fromRow + ":" + this.fromCell + " - " + this.toRow + ":" + this.toCell + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* A base class that all special / non-data rows (like Group and GroupTotals) derive from.
|
||||
* @class NonDataItem
|
||||
* @constructor
|
||||
*/
|
||||
function NonDataItem() {
|
||||
this.__nonDataRow = true;
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Information about a group of rows.
|
||||
* @class Group
|
||||
* @extends Slick.NonDataItem
|
||||
* @constructor
|
||||
*/
|
||||
function Group() {
|
||||
this.__group = true;
|
||||
|
||||
/**
|
||||
* Grouping level, starting with 0.
|
||||
* @property level
|
||||
* @type {Number}
|
||||
*/
|
||||
this.level = 0;
|
||||
|
||||
/***
|
||||
* Number of rows in the group.
|
||||
* @property count
|
||||
* @type {Integer}
|
||||
*/
|
||||
this.count = 0;
|
||||
|
||||
/***
|
||||
* Grouping value.
|
||||
* @property value
|
||||
* @type {Object}
|
||||
*/
|
||||
this.value = null;
|
||||
|
||||
/***
|
||||
* Formatted display value of the group.
|
||||
* @property title
|
||||
* @type {String}
|
||||
*/
|
||||
this.title = null;
|
||||
|
||||
/***
|
||||
* Whether a group is collapsed.
|
||||
* @property collapsed
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.collapsed = false;
|
||||
|
||||
/***
|
||||
* GroupTotals, if any.
|
||||
* @property totals
|
||||
* @type {GroupTotals}
|
||||
*/
|
||||
this.totals = null;
|
||||
|
||||
/**
|
||||
* Rows that are part of the group.
|
||||
* @property rows
|
||||
* @type {Array}
|
||||
*/
|
||||
this.rows = [];
|
||||
|
||||
/**
|
||||
* Sub-groups that are part of the group.
|
||||
* @property groups
|
||||
* @type {Array}
|
||||
*/
|
||||
this.groups = null;
|
||||
|
||||
/**
|
||||
* A unique key used to identify the group. This key can be used in calls to DataView
|
||||
* collapseGroup() or expandGroup().
|
||||
* @property groupingKey
|
||||
* @type {Object}
|
||||
*/
|
||||
this.groupingKey = null;
|
||||
}
|
||||
|
||||
Group.prototype = new NonDataItem();
|
||||
|
||||
/***
|
||||
* Compares two Group instances.
|
||||
* @method equals
|
||||
* @return {Boolean}
|
||||
* @param group {Group} Group instance to compare to.
|
||||
*/
|
||||
Group.prototype.equals = function (group) {
|
||||
return this.value === group.value &&
|
||||
this.count === group.count &&
|
||||
this.collapsed === group.collapsed &&
|
||||
this.title === group.title;
|
||||
};
|
||||
|
||||
/***
|
||||
* Information about group totals.
|
||||
* An instance of GroupTotals will be created for each totals row and passed to the aggregators
|
||||
* so that they can store arbitrary data in it. That data can later be accessed by group totals
|
||||
* formatters during the display.
|
||||
* @class GroupTotals
|
||||
* @extends Slick.NonDataItem
|
||||
* @constructor
|
||||
*/
|
||||
function GroupTotals() {
|
||||
this.__groupTotals = true;
|
||||
|
||||
/***
|
||||
* Parent Group.
|
||||
* @param group
|
||||
* @type {Group}
|
||||
*/
|
||||
this.group = null;
|
||||
|
||||
/***
|
||||
* Whether the totals have been fully initialized / calculated.
|
||||
* Will be set to false for lazy-calculated group totals.
|
||||
* @param initialized
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
GroupTotals.prototype = new NonDataItem();
|
||||
|
||||
/***
|
||||
* A locking helper to track the active edit controller and ensure that only a single controller
|
||||
* can be active at a time. This prevents a whole class of state and validation synchronization
|
||||
* issues. An edit controller (such as SlickGrid) can query if an active edit is in progress
|
||||
* and attempt a commit or cancel before proceeding.
|
||||
* @class EditorLock
|
||||
* @constructor
|
||||
*/
|
||||
function EditorLock() {
|
||||
var activeEditController = null;
|
||||
|
||||
/***
|
||||
* Returns true if a specified edit controller is active (has the edit lock).
|
||||
* If the parameter is not specified, returns true if any edit controller is active.
|
||||
* @method isActive
|
||||
* @param editController {EditController}
|
||||
* @return {Boolean}
|
||||
*/
|
||||
this.isActive = function (editController) {
|
||||
return (editController ? activeEditController === editController : activeEditController !== null);
|
||||
};
|
||||
|
||||
/***
|
||||
* Sets the specified edit controller as the active edit controller (acquire edit lock).
|
||||
* If another edit controller is already active, and exception will be thrown.
|
||||
* @method activate
|
||||
* @param editController {EditController} edit controller acquiring the lock
|
||||
*/
|
||||
this.activate = function (editController) {
|
||||
if (editController === activeEditController) { // already activated?
|
||||
return;
|
||||
}
|
||||
if (activeEditController !== null) {
|
||||
throw "SlickGrid.EditorLock.activate: an editController is still active, can't activate another editController";
|
||||
}
|
||||
if (!editController.commitCurrentEdit) {
|
||||
throw "SlickGrid.EditorLock.activate: editController must implement .commitCurrentEdit()";
|
||||
}
|
||||
if (!editController.cancelCurrentEdit) {
|
||||
throw "SlickGrid.EditorLock.activate: editController must implement .cancelCurrentEdit()";
|
||||
}
|
||||
activeEditController = editController;
|
||||
};
|
||||
|
||||
/***
|
||||
* Unsets the specified edit controller as the active edit controller (release edit lock).
|
||||
* If the specified edit controller is not the active one, an exception will be thrown.
|
||||
* @method deactivate
|
||||
* @param editController {EditController} edit controller releasing the lock
|
||||
*/
|
||||
this.deactivate = function (editController) {
|
||||
if (activeEditController !== editController) {
|
||||
throw "SlickGrid.EditorLock.deactivate: specified editController is not the currently active one";
|
||||
}
|
||||
activeEditController = null;
|
||||
};
|
||||
|
||||
/***
|
||||
* Attempts to commit the current edit by calling "commitCurrentEdit" method on the active edit
|
||||
* controller and returns whether the commit attempt was successful (commit may fail due to validation
|
||||
* errors, etc.). Edit controller's "commitCurrentEdit" must return true if the commit has succeeded
|
||||
* and false otherwise. If no edit controller is active, returns true.
|
||||
* @method commitCurrentEdit
|
||||
* @return {Boolean}
|
||||
*/
|
||||
this.commitCurrentEdit = function () {
|
||||
return (activeEditController ? activeEditController.commitCurrentEdit() : true);
|
||||
};
|
||||
|
||||
/***
|
||||
* Attempts to cancel the current edit by calling "cancelCurrentEdit" method on the active edit
|
||||
* controller and returns whether the edit was successfully cancelled. If no edit controller is
|
||||
* active, returns true.
|
||||
* @method cancelCurrentEdit
|
||||
* @return {Boolean}
|
||||
*/
|
||||
this.cancelCurrentEdit = function cancelCurrentEdit() {
|
||||
return (activeEditController ? activeEditController.cancelCurrentEdit() : true);
|
||||
};
|
||||
}
|
||||
})(jQuery);
|
||||
|
||||
|
||||
1126
frappe/public/js/lib/slickgrid/slick.dataview.js
vendored
512
frappe/public/js/lib/slickgrid/slick.editors.js
vendored
|
|
@ -1,512 +0,0 @@
|
|||
/***
|
||||
* Contains basic SlickGrid editors.
|
||||
* @module Editors
|
||||
* @namespace Slick
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
// register namespace
|
||||
$.extend(true, window, {
|
||||
"Slick": {
|
||||
"Editors": {
|
||||
"Text": TextEditor,
|
||||
"Integer": IntegerEditor,
|
||||
"Date": DateEditor,
|
||||
"YesNoSelect": YesNoSelectEditor,
|
||||
"Checkbox": CheckboxEditor,
|
||||
"PercentComplete": PercentCompleteEditor,
|
||||
"LongText": LongTextEditor
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function TextEditor(args) {
|
||||
var $input;
|
||||
var defaultValue;
|
||||
var scope = this;
|
||||
|
||||
this.init = function () {
|
||||
$input = $("<INPUT type=text class='editor-text' />")
|
||||
.appendTo(args.container)
|
||||
.bind("keydown.nav", function (e) {
|
||||
if (e.keyCode === frappe.ui.keyCode.LEFT || e.keyCode === frappe.ui.keyCode.RIGHT) {
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
})
|
||||
.focus()
|
||||
.select();
|
||||
};
|
||||
|
||||
this.destroy = function () {
|
||||
$input.remove();
|
||||
};
|
||||
|
||||
this.focus = function () {
|
||||
$input.focus();
|
||||
};
|
||||
|
||||
this.getValue = function () {
|
||||
return $input.val();
|
||||
};
|
||||
|
||||
this.setValue = function (val) {
|
||||
$input.val(val);
|
||||
};
|
||||
|
||||
this.loadValue = function (item) {
|
||||
defaultValue = item[args.column.field] || "";
|
||||
$input.val(defaultValue);
|
||||
$input[0].defaultValue = defaultValue;
|
||||
$input.select();
|
||||
};
|
||||
|
||||
this.serializeValue = function () {
|
||||
return $input.val();
|
||||
};
|
||||
|
||||
this.applyValue = function (item, state) {
|
||||
item[args.column.field] = state;
|
||||
};
|
||||
|
||||
this.isValueChanged = function () {
|
||||
return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue);
|
||||
};
|
||||
|
||||
this.validate = function () {
|
||||
if (args.column.validator) {
|
||||
var validationResults = args.column.validator($input.val());
|
||||
if (!validationResults.valid) {
|
||||
return validationResults;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
msg: null
|
||||
};
|
||||
};
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
function IntegerEditor(args) {
|
||||
var $input;
|
||||
var defaultValue;
|
||||
var scope = this;
|
||||
|
||||
this.init = function () {
|
||||
$input = $("<INPUT type=text class='editor-text' />");
|
||||
|
||||
$input.bind("keydown.nav", function (e) {
|
||||
if (e.keyCode === frappe.ui.keyCode.LEFT || e.keyCode === frappe.ui.keyCode.RIGHT) {
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
});
|
||||
|
||||
$input.appendTo(args.container);
|
||||
$input.focus().select();
|
||||
};
|
||||
|
||||
this.destroy = function () {
|
||||
$input.remove();
|
||||
};
|
||||
|
||||
this.focus = function () {
|
||||
$input.focus();
|
||||
};
|
||||
|
||||
this.loadValue = function (item) {
|
||||
defaultValue = item[args.column.field];
|
||||
$input.val(defaultValue);
|
||||
$input[0].defaultValue = defaultValue;
|
||||
$input.select();
|
||||
};
|
||||
|
||||
this.serializeValue = function () {
|
||||
return parseInt($input.val(), 10) || 0;
|
||||
};
|
||||
|
||||
this.applyValue = function (item, state) {
|
||||
item[args.column.field] = state;
|
||||
};
|
||||
|
||||
this.isValueChanged = function () {
|
||||
return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue);
|
||||
};
|
||||
|
||||
this.validate = function () {
|
||||
if (isNaN($input.val())) {
|
||||
return {
|
||||
valid: false,
|
||||
msg: "Please enter a valid integer"
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
msg: null
|
||||
};
|
||||
};
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
function DateEditor(args) {
|
||||
var $input;
|
||||
var defaultValue;
|
||||
var scope = this;
|
||||
var calendarOpen = false;
|
||||
|
||||
this.init = function () {
|
||||
$input = $("<INPUT type=text class='editor-text' />");
|
||||
$input.appendTo(args.container);
|
||||
$input.focus().select();
|
||||
$input.datepicker({
|
||||
showOn: "button",
|
||||
buttonImageOnly: true,
|
||||
buttonImage: "../images/calendar.gif",
|
||||
beforeShow: function () {
|
||||
calendarOpen = true
|
||||
},
|
||||
onClose: function () {
|
||||
calendarOpen = false
|
||||
}
|
||||
});
|
||||
$input.width($input.width() - 18);
|
||||
};
|
||||
|
||||
this.destroy = function () {
|
||||
$.datepicker.dpDiv.stop(true, true);
|
||||
$input.datepicker("hide");
|
||||
$input.datepicker("destroy");
|
||||
$input.remove();
|
||||
};
|
||||
|
||||
this.show = function () {
|
||||
if (calendarOpen) {
|
||||
$.datepicker.dpDiv.stop(true, true).show();
|
||||
}
|
||||
};
|
||||
|
||||
this.hide = function () {
|
||||
if (calendarOpen) {
|
||||
$.datepicker.dpDiv.stop(true, true).hide();
|
||||
}
|
||||
};
|
||||
|
||||
this.position = function (position) {
|
||||
if (!calendarOpen) {
|
||||
return;
|
||||
}
|
||||
$.datepicker.dpDiv
|
||||
.css("top", position.top + 30)
|
||||
.css("left", position.left);
|
||||
};
|
||||
|
||||
this.focus = function () {
|
||||
$input.focus();
|
||||
};
|
||||
|
||||
this.loadValue = function (item) {
|
||||
defaultValue = item[args.column.field];
|
||||
$input.val(defaultValue);
|
||||
$input[0].defaultValue = defaultValue;
|
||||
$input.select();
|
||||
};
|
||||
|
||||
this.serializeValue = function () {
|
||||
return $input.val();
|
||||
};
|
||||
|
||||
this.applyValue = function (item, state) {
|
||||
item[args.column.field] = state;
|
||||
};
|
||||
|
||||
this.isValueChanged = function () {
|
||||
return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue);
|
||||
};
|
||||
|
||||
this.validate = function () {
|
||||
return {
|
||||
valid: true,
|
||||
msg: null
|
||||
};
|
||||
};
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
function YesNoSelectEditor(args) {
|
||||
var $select;
|
||||
var defaultValue;
|
||||
var scope = this;
|
||||
|
||||
this.init = function () {
|
||||
$select = $("<SELECT tabIndex='0' class='editor-yesno'><OPTION value='yes'>Yes</OPTION><OPTION value='no'>No</OPTION></SELECT>");
|
||||
$select.appendTo(args.container);
|
||||
$select.focus();
|
||||
};
|
||||
|
||||
this.destroy = function () {
|
||||
$select.remove();
|
||||
};
|
||||
|
||||
this.focus = function () {
|
||||
$select.focus();
|
||||
};
|
||||
|
||||
this.loadValue = function (item) {
|
||||
$select.val((defaultValue = item[args.column.field]) ? "yes" : "no");
|
||||
$select.select();
|
||||
};
|
||||
|
||||
this.serializeValue = function () {
|
||||
return ($select.val() == "yes");
|
||||
};
|
||||
|
||||
this.applyValue = function (item, state) {
|
||||
item[args.column.field] = state;
|
||||
};
|
||||
|
||||
this.isValueChanged = function () {
|
||||
return ($select.val() != defaultValue);
|
||||
};
|
||||
|
||||
this.validate = function () {
|
||||
return {
|
||||
valid: true,
|
||||
msg: null
|
||||
};
|
||||
};
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
function CheckboxEditor(args) {
|
||||
var $select;
|
||||
var defaultValue;
|
||||
var scope = this;
|
||||
|
||||
this.init = function () {
|
||||
$select = $("<INPUT type=checkbox value='true' class='editor-checkbox' hideFocus>");
|
||||
$select.appendTo(args.container);
|
||||
$select.focus();
|
||||
};
|
||||
|
||||
this.destroy = function () {
|
||||
$select.remove();
|
||||
};
|
||||
|
||||
this.focus = function () {
|
||||
$select.focus();
|
||||
};
|
||||
|
||||
this.loadValue = function (item) {
|
||||
defaultValue = !!item[args.column.field];
|
||||
if (defaultValue) {
|
||||
$select.prop('checked', true);
|
||||
} else {
|
||||
$select.prop('checked', false);
|
||||
}
|
||||
};
|
||||
|
||||
this.serializeValue = function () {
|
||||
return $select.prop('checked');
|
||||
};
|
||||
|
||||
this.applyValue = function (item, state) {
|
||||
item[args.column.field] = state;
|
||||
};
|
||||
|
||||
this.isValueChanged = function () {
|
||||
return (this.serializeValue() !== defaultValue);
|
||||
};
|
||||
|
||||
this.validate = function () {
|
||||
return {
|
||||
valid: true,
|
||||
msg: null
|
||||
};
|
||||
};
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
function PercentCompleteEditor(args) {
|
||||
var $input, $picker;
|
||||
var defaultValue;
|
||||
var scope = this;
|
||||
|
||||
this.init = function () {
|
||||
$input = $("<INPUT type=text class='editor-percentcomplete' />");
|
||||
$input.width($(args.container).innerWidth() - 25);
|
||||
$input.appendTo(args.container);
|
||||
|
||||
$picker = $("<div class='editor-percentcomplete-picker' />").appendTo(args.container);
|
||||
$picker.append("<div class='editor-percentcomplete-helper'><div class='editor-percentcomplete-wrapper'><div class='editor-percentcomplete-slider' /><div class='editor-percentcomplete-buttons' /></div></div>");
|
||||
|
||||
$picker.find(".editor-percentcomplete-buttons").append("<button val=0>Not started</button><br/><button val=50>In Progress</button><br/><button val=100>Complete</button>");
|
||||
|
||||
$input.focus().select();
|
||||
|
||||
$picker.find(".editor-percentcomplete-slider").slider({
|
||||
orientation: "vertical",
|
||||
range: "min",
|
||||
value: defaultValue,
|
||||
slide: function (event, ui) {
|
||||
$input.val(ui.value)
|
||||
}
|
||||
});
|
||||
|
||||
$picker.find(".editor-percentcomplete-buttons button").bind("click", function (e) {
|
||||
$input.val($(this).attr("val"));
|
||||
$picker.find(".editor-percentcomplete-slider").slider("value", $(this).attr("val"));
|
||||
})
|
||||
};
|
||||
|
||||
this.destroy = function () {
|
||||
$input.remove();
|
||||
$picker.remove();
|
||||
};
|
||||
|
||||
this.focus = function () {
|
||||
$input.focus();
|
||||
};
|
||||
|
||||
this.loadValue = function (item) {
|
||||
$input.val(defaultValue = item[args.column.field]);
|
||||
$input.select();
|
||||
};
|
||||
|
||||
this.serializeValue = function () {
|
||||
return parseInt($input.val(), 10) || 0;
|
||||
};
|
||||
|
||||
this.applyValue = function (item, state) {
|
||||
item[args.column.field] = state;
|
||||
};
|
||||
|
||||
this.isValueChanged = function () {
|
||||
return (!($input.val() == "" && defaultValue == null)) && ((parseInt($input.val(), 10) || 0) != defaultValue);
|
||||
};
|
||||
|
||||
this.validate = function () {
|
||||
if (isNaN(parseInt($input.val(), 10))) {
|
||||
return {
|
||||
valid: false,
|
||||
msg: "Please enter a valid positive number"
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
msg: null
|
||||
};
|
||||
};
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
/*
|
||||
* An example of a "detached" editor.
|
||||
* The UI is added onto document BODY and .position(), .show() and .hide() are implemented.
|
||||
* KeyDown events are also handled to provide handling for Tab, Shift-Tab, Esc and Ctrl-Enter.
|
||||
*/
|
||||
function LongTextEditor(args) {
|
||||
var $input, $wrapper;
|
||||
var defaultValue;
|
||||
var scope = this;
|
||||
|
||||
this.init = function () {
|
||||
var $container = $("body");
|
||||
|
||||
$wrapper = $("<DIV style='z-index:10000;position:absolute;background:white;padding:5px;border:3px solid gray; -moz-border-radius:10px; border-radius:10px;'/>")
|
||||
.appendTo($container);
|
||||
|
||||
$input = $("<TEXTAREA hidefocus rows=5 style='backround:white;width:250px;height:80px;border:0;outline:0'>")
|
||||
.appendTo($wrapper);
|
||||
|
||||
$("<DIV style='text-align:right'><BUTTON>Save</BUTTON><BUTTON>Cancel</BUTTON></DIV>")
|
||||
.appendTo($wrapper);
|
||||
|
||||
$wrapper.find("button:first").bind("click", this.save);
|
||||
$wrapper.find("button:last").bind("click", this.cancel);
|
||||
$input.bind("keydown", this.handleKeyDown);
|
||||
|
||||
scope.position(args.position);
|
||||
$input.focus().select();
|
||||
};
|
||||
|
||||
this.handleKeyDown = function (e) {
|
||||
if (e.which == frappe.ui.keyCode.ENTER && e.ctrlKey) {
|
||||
scope.save();
|
||||
} else if (e.which == frappe.ui.keyCode.ESCAPE) {
|
||||
e.preventDefault();
|
||||
scope.cancel();
|
||||
} else if (e.which == frappe.ui.keyCode.TAB && e.shiftKey) {
|
||||
e.preventDefault();
|
||||
args.grid.navigatePrev();
|
||||
} else if (e.which == frappe.ui.keyCode.TAB) {
|
||||
e.preventDefault();
|
||||
args.grid.navigateNext();
|
||||
}
|
||||
};
|
||||
|
||||
this.save = function () {
|
||||
args.commitChanges();
|
||||
};
|
||||
|
||||
this.cancel = function () {
|
||||
$input.val(defaultValue);
|
||||
args.cancelChanges();
|
||||
};
|
||||
|
||||
this.hide = function () {
|
||||
$wrapper.hide();
|
||||
};
|
||||
|
||||
this.show = function () {
|
||||
$wrapper.show();
|
||||
};
|
||||
|
||||
this.position = function (position) {
|
||||
$wrapper
|
||||
.css("top", position.top - 5)
|
||||
.css("left", position.left - 5)
|
||||
};
|
||||
|
||||
this.destroy = function () {
|
||||
$wrapper.remove();
|
||||
};
|
||||
|
||||
this.focus = function () {
|
||||
$input.focus();
|
||||
};
|
||||
|
||||
this.loadValue = function (item) {
|
||||
$input.val(defaultValue = item[args.column.field]);
|
||||
$input.select();
|
||||
};
|
||||
|
||||
this.serializeValue = function () {
|
||||
return $input.val();
|
||||
};
|
||||
|
||||
this.applyValue = function (item, state) {
|
||||
item[args.column.field] = state;
|
||||
};
|
||||
|
||||
this.isValueChanged = function () {
|
||||
return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue);
|
||||
};
|
||||
|
||||
this.validate = function () {
|
||||
return {
|
||||
valid: true,
|
||||
msg: null
|
||||
};
|
||||
};
|
||||
|
||||
this.init();
|
||||
}
|
||||
})(jQuery);
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
/***
|
||||
* Contains basic SlickGrid formatters.
|
||||
*
|
||||
* NOTE: These are merely examples. You will most likely need to implement something more
|
||||
* robust/extensible/localizable/etc. for your use!
|
||||
*
|
||||
* @module Formatters
|
||||
* @namespace Slick
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
// register namespace
|
||||
$.extend(true, window, {
|
||||
"Slick": {
|
||||
"Formatters": {
|
||||
"PercentComplete": PercentCompleteFormatter,
|
||||
"PercentCompleteBar": PercentCompleteBarFormatter,
|
||||
"YesNo": YesNoFormatter,
|
||||
"Checkmark": CheckmarkFormatter
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function PercentCompleteFormatter(row, cell, value, columnDef, dataContext) {
|
||||
if (value == null || value === "") {
|
||||
return "-";
|
||||
} else if (value < 50) {
|
||||
return "<span style='color:red;font-weight:bold;'>" + value + "%</span>";
|
||||
} else {
|
||||
return "<span style='color:green'>" + value + "%</span>";
|
||||
}
|
||||
}
|
||||
|
||||
function PercentCompleteBarFormatter(row, cell, value, columnDef, dataContext) {
|
||||
if (value == null || value === "") {
|
||||
return "";
|
||||
}
|
||||
|
||||
var color;
|
||||
|
||||
if (value < 30) {
|
||||
color = "red";
|
||||
} else if (value < 70) {
|
||||
color = "silver";
|
||||
} else {
|
||||
color = "green";
|
||||
}
|
||||
|
||||
return "<span class='percent-complete-bar' style='background:" + color + ";width:" + value + "%'></span>";
|
||||
}
|
||||
|
||||
function YesNoFormatter(row, cell, value, columnDef, dataContext) {
|
||||
return value ? "Yes" : "No";
|
||||
}
|
||||
|
||||
function CheckmarkFormatter(row, cell, value, columnDef, dataContext) {
|
||||
return value ? "<img src='../images/tick.png'>" : "";
|
||||
}
|
||||
})(jQuery);
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
/*
|
||||
IMPORTANT:
|
||||
In order to preserve the uniform grid appearance, all cell styles need to have padding, margin and border sizes.
|
||||
No built-in (selected, editable, highlight, flashing, invalid, loading, :focus) or user-specified CSS
|
||||
classes should alter those!
|
||||
*/
|
||||
|
||||
.slick-header.ui-state-default, .slick-headerrow.ui-state-default {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
border-left: 0px;
|
||||
}
|
||||
|
||||
.slick-header-columns, .slick-headerrow-columns {
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
cursor: default;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.slick-header-column.ui-state-default {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
-o-text-overflow: ellipsis;
|
||||
text-overflow: ellipsis;
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
margin: 0;
|
||||
padding: 4px;
|
||||
border-right: 1px solid silver;
|
||||
border-left: 0px;
|
||||
border-top: 0px;
|
||||
border-bottom: 0px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.slick-headerrow-column.ui-state-default {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.slick-header-column-sorted {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.slick-sort-indicator {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 5px;
|
||||
margin-left: 4px;
|
||||
margin-top: 6px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.slick-sort-indicator-desc {
|
||||
background: url(images/sort-desc.gif);
|
||||
}
|
||||
|
||||
.slick-sort-indicator-asc {
|
||||
background: url(images/sort-asc.gif);
|
||||
}
|
||||
|
||||
.slick-resizable-handle {
|
||||
position: absolute;
|
||||
font-size: 0.1px;
|
||||
display: block;
|
||||
cursor: col-resize;
|
||||
width: 4px;
|
||||
right: 0px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.slick-sortable-placeholder {
|
||||
background: silver;
|
||||
}
|
||||
|
||||
.grid-canvas {
|
||||
position: relative;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.slick-row.ui-widget-content, .slick-row.ui-state-active {
|
||||
position: absolute;
|
||||
border: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.slick-cell, .slick-headerrow-column {
|
||||
position: absolute;
|
||||
border: 1px solid transparent;
|
||||
border-right: 1px dotted silver;
|
||||
border-bottom-color: silver;
|
||||
overflow: hidden;
|
||||
-o-text-overflow: ellipsis;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
z-index: 1;
|
||||
padding: 1px 2px 2px 1px;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.slick-group {
|
||||
}
|
||||
|
||||
.slick-group-toggle {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.slick-cell.highlighted {
|
||||
background: lightskyblue;
|
||||
background: rgba(0, 0, 255, 0.2);
|
||||
-webkit-transition: all 0.5s;
|
||||
-moz-transition: all 0.5s;
|
||||
-o-transition: all 0.5s;
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.slick-cell.flashing {
|
||||
border: 1px solid red !important;
|
||||
}
|
||||
|
||||
.slick-cell.editable {
|
||||
z-index: 11;
|
||||
overflow: visible;
|
||||
background: white;
|
||||
border-color: black;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.slick-cell:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.slick-reorder-proxy {
|
||||
display: inline-block;
|
||||
background: blue;
|
||||
opacity: 0.15;
|
||||
/* filter: alpha(opacity = 15); */
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.slick-reorder-guide {
|
||||
display: inline-block;
|
||||
height: 2px;
|
||||
background: blue;
|
||||
opacity: 0.7;
|
||||
/* filter: alpha(opacity = 70); */
|
||||
}
|
||||
|
||||
.slick-selection {
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
border: 2px dashed black;
|
||||
}
|
||||
3422
frappe/public/js/lib/slickgrid/slick.grid.js
vendored
|
|
@ -1,158 +0,0 @@
|
|||
(function ($) {
|
||||
$.extend(true, window, {
|
||||
Slick: {
|
||||
Data: {
|
||||
GroupItemMetadataProvider: GroupItemMetadataProvider
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/***
|
||||
* Provides item metadata for group (Slick.Group) and totals (Slick.Totals) rows produced by the DataView.
|
||||
* This metadata overrides the default behavior and formatting of those rows so that they appear and function
|
||||
* correctly when processed by the grid.
|
||||
*
|
||||
* This class also acts as a grid plugin providing event handlers to expand & collapse groups.
|
||||
* If "grid.registerPlugin(...)" is not called, expand & collapse will not work.
|
||||
*
|
||||
* @class GroupItemMetadataProvider
|
||||
* @module Data
|
||||
* @namespace Slick.Data
|
||||
* @constructor
|
||||
* @param options
|
||||
*/
|
||||
function GroupItemMetadataProvider(options) {
|
||||
var _grid;
|
||||
var _defaults = {
|
||||
groupCssClass: "slick-group",
|
||||
groupTitleCssClass: "slick-group-title",
|
||||
totalsCssClass: "slick-group-totals",
|
||||
groupFocusable: true,
|
||||
totalsFocusable: false,
|
||||
toggleCssClass: "slick-group-toggle",
|
||||
toggleExpandedCssClass: "expanded",
|
||||
toggleCollapsedCssClass: "collapsed",
|
||||
enableExpandCollapse: true,
|
||||
groupFormatter: defaultGroupCellFormatter,
|
||||
totalsFormatter: defaultTotalsCellFormatter
|
||||
};
|
||||
|
||||
options = $.extend(true, {}, _defaults, options);
|
||||
|
||||
|
||||
function defaultGroupCellFormatter(row, cell, value, columnDef, item) {
|
||||
if (!options.enableExpandCollapse) {
|
||||
return item.title;
|
||||
}
|
||||
|
||||
var indentation = item.level * 15 + "px";
|
||||
|
||||
return "<span class='" + options.toggleCssClass + " " +
|
||||
(item.collapsed ? options.toggleCollapsedCssClass : options.toggleExpandedCssClass) +
|
||||
"' style='margin-left:" + indentation +"'>" +
|
||||
"</span>" +
|
||||
"<span class='" + options.groupTitleCssClass + "' level='" + item.level + "'>" +
|
||||
item.title +
|
||||
"</span>";
|
||||
}
|
||||
|
||||
function defaultTotalsCellFormatter(row, cell, value, columnDef, item) {
|
||||
return (columnDef.groupTotalsFormatter && columnDef.groupTotalsFormatter(item, columnDef)) || "";
|
||||
}
|
||||
|
||||
|
||||
function init(grid) {
|
||||
_grid = grid;
|
||||
_grid.onClick.subscribe(handleGridClick);
|
||||
_grid.onKeyDown.subscribe(handleGridKeyDown);
|
||||
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
if (_grid) {
|
||||
_grid.onClick.unsubscribe(handleGridClick);
|
||||
_grid.onKeyDown.unsubscribe(handleGridKeyDown);
|
||||
}
|
||||
}
|
||||
|
||||
function handleGridClick(e, args) {
|
||||
var item = this.getDataItem(args.row);
|
||||
if (item && item instanceof Slick.Group && $(e.target).hasClass(options.toggleCssClass)) {
|
||||
var range = _grid.getRenderedRange();
|
||||
this.getData().setRefreshHints({
|
||||
ignoreDiffsBefore: range.top,
|
||||
ignoreDiffsAfter: range.bottom
|
||||
});
|
||||
|
||||
if (item.collapsed) {
|
||||
this.getData().expandGroup(item.groupingKey);
|
||||
} else {
|
||||
this.getData().collapseGroup(item.groupingKey);
|
||||
}
|
||||
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add -/+ handling
|
||||
function handleGridKeyDown(e, args) {
|
||||
if (options.enableExpandCollapse && (e.which == frappe.ui.keyCode.SPACE)) {
|
||||
var activeCell = this.getActiveCell();
|
||||
if (activeCell) {
|
||||
var item = this.getDataItem(activeCell.row);
|
||||
if (item && item instanceof Slick.Group) {
|
||||
var range = _grid.getRenderedRange();
|
||||
this.getData().setRefreshHints({
|
||||
ignoreDiffsBefore: range.top,
|
||||
ignoreDiffsAfter: range.bottom
|
||||
});
|
||||
|
||||
if (item.collapsed) {
|
||||
this.getData().expandGroup(item.groupingKey);
|
||||
} else {
|
||||
this.getData().collapseGroup(item.groupingKey);
|
||||
}
|
||||
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getGroupRowMetadata(item) {
|
||||
return {
|
||||
selectable: false,
|
||||
focusable: options.groupFocusable,
|
||||
cssClasses: options.groupCssClass,
|
||||
columns: {
|
||||
0: {
|
||||
colspan: "*",
|
||||
formatter: options.groupFormatter,
|
||||
editor: null
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getTotalsRowMetadata(item) {
|
||||
return {
|
||||
selectable: false,
|
||||
focusable: options.totalsFocusable,
|
||||
cssClasses: options.totalsCssClass,
|
||||
formatter: options.totalsFormatter,
|
||||
editor: null
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
"init": init,
|
||||
"destroy": destroy,
|
||||
"getGroupRowMetadata": getGroupRowMetadata,
|
||||
"getTotalsRowMetadata": getTotalsRowMetadata
|
||||
};
|
||||
}
|
||||
})(jQuery);
|
||||
173
frappe/public/js/lib/slickgrid/slick.remotemodel.js
vendored
|
|
@ -1,173 +0,0 @@
|
|||
(function ($) {
|
||||
/***
|
||||
* A sample AJAX data store implementation.
|
||||
* Right now, it's hooked up to load Hackernews stories, but can
|
||||
* easily be extended to support any JSONP-compatible backend that accepts paging parameters.
|
||||
*/
|
||||
function RemoteModel() {
|
||||
// private
|
||||
var PAGESIZE = 50;
|
||||
var data = {length: 0};
|
||||
var searchstr = "";
|
||||
var sortcol = null;
|
||||
var sortdir = 1;
|
||||
var h_request = null;
|
||||
var req = null; // ajax request
|
||||
|
||||
// events
|
||||
var onDataLoading = new Slick.Event();
|
||||
var onDataLoaded = new Slick.Event();
|
||||
|
||||
|
||||
function init() {
|
||||
}
|
||||
|
||||
|
||||
function isDataLoaded(from, to) {
|
||||
for (var i = from; i <= to; i++) {
|
||||
if (data[i] == undefined || data[i] == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function clear() {
|
||||
for (var key in data) {
|
||||
delete data[key];
|
||||
}
|
||||
data.length = 0;
|
||||
}
|
||||
|
||||
|
||||
function ensureData(from, to) {
|
||||
if (req) {
|
||||
req.abort();
|
||||
for (var i = req.fromPage; i <= req.toPage; i++)
|
||||
data[i * PAGESIZE] = undefined;
|
||||
}
|
||||
|
||||
if (from < 0) {
|
||||
from = 0;
|
||||
}
|
||||
|
||||
if (data.length > 0) {
|
||||
to = Math.min(to, data.length - 1);
|
||||
}
|
||||
|
||||
var fromPage = Math.floor(from / PAGESIZE);
|
||||
var toPage = Math.floor(to / PAGESIZE);
|
||||
|
||||
while (data[fromPage * PAGESIZE] !== undefined && fromPage < toPage)
|
||||
fromPage++;
|
||||
|
||||
while (data[toPage * PAGESIZE] !== undefined && fromPage < toPage)
|
||||
toPage--;
|
||||
|
||||
if (fromPage > toPage || ((fromPage == toPage) && data[fromPage * PAGESIZE] !== undefined)) {
|
||||
// TODO: look-ahead
|
||||
onDataLoaded.notify({from: from, to: to});
|
||||
return;
|
||||
}
|
||||
|
||||
var url = "http://api.thriftdb.com/api.hnsearch.com/items/_search?filter[fields][type][]=submission&q=" + searchstr + "&start=" + (fromPage * PAGESIZE) + "&limit=" + (((toPage - fromPage) * PAGESIZE) + PAGESIZE);
|
||||
|
||||
if (sortcol != null) {
|
||||
url += ("&sortby=" + sortcol + ((sortdir > 0) ? "+asc" : "+desc"));
|
||||
}
|
||||
|
||||
if (h_request != null) {
|
||||
clearTimeout(h_request);
|
||||
}
|
||||
|
||||
h_request = setTimeout(function () {
|
||||
for (var i = fromPage; i <= toPage; i++)
|
||||
data[i * PAGESIZE] = null; // null indicates a 'requested but not available yet'
|
||||
|
||||
onDataLoading.notify({from: from, to: to});
|
||||
|
||||
req = $.jsonp({
|
||||
url: url,
|
||||
callbackParameter: "callback",
|
||||
cache: true,
|
||||
success: onSuccess,
|
||||
error: function () {
|
||||
onError(fromPage, toPage)
|
||||
}
|
||||
});
|
||||
req.fromPage = fromPage;
|
||||
req.toPage = toPage;
|
||||
}, 50);
|
||||
}
|
||||
|
||||
|
||||
function onError(fromPage, toPage) {
|
||||
alert("error loading pages " + fromPage + " to " + toPage);
|
||||
}
|
||||
|
||||
function onSuccess(resp) {
|
||||
var from = resp.request.start, to = from + resp.results.length;
|
||||
data.length = Math.min(parseInt(resp.hits),1000); // limitation of the API
|
||||
|
||||
for (var i = 0; i < resp.results.length; i++) {
|
||||
var item = resp.results[i].item;
|
||||
|
||||
// Old IE versions can't parse ISO dates, so change to universally-supported format.
|
||||
item.create_ts = item.create_ts.replace(/^(\d+)-(\d+)-(\d+)T(\d+:\d+:\d+)Z$/, "$2/$3/$1 $4 UTC");
|
||||
item.create_ts = new Date(item.create_ts);
|
||||
|
||||
data[from + i] = item;
|
||||
data[from + i].index = from + i;
|
||||
}
|
||||
|
||||
req = null;
|
||||
|
||||
onDataLoaded.notify({from: from, to: to});
|
||||
}
|
||||
|
||||
|
||||
function reloadData(from, to) {
|
||||
for (var i = from; i <= to; i++)
|
||||
delete data[i];
|
||||
|
||||
ensureData(from, to);
|
||||
}
|
||||
|
||||
|
||||
function setSort(column, dir) {
|
||||
sortcol = column;
|
||||
sortdir = dir;
|
||||
clear();
|
||||
}
|
||||
|
||||
function setSearch(str) {
|
||||
searchstr = str;
|
||||
clear();
|
||||
}
|
||||
|
||||
|
||||
init();
|
||||
|
||||
return {
|
||||
// properties
|
||||
"data": data,
|
||||
|
||||
// methods
|
||||
"clear": clear,
|
||||
"isDataLoaded": isDataLoaded,
|
||||
"ensureData": ensureData,
|
||||
"reloadData": reloadData,
|
||||
"setSort": setSort,
|
||||
"setSearch": setSearch,
|
||||
|
||||
// events
|
||||
"onDataLoading": onDataLoading,
|
||||
"onDataLoaded": onDataLoaded
|
||||
};
|
||||
}
|
||||
|
||||
// Slick.Data.RemoteModel
|
||||
$.extend(true, window, { Slick: { Data: { RemoteModel: RemoteModel }}});
|
||||
})(jQuery);
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
@import "variables.less";
|
||||
|
||||
.slick-header-column, .slick-cell {
|
||||
-webkit-box-sizing: content-box;
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.slick-wrapper, .slick-header {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.slick-headerrow {
|
||||
border: none;
|
||||
border-bottom: 1px solid @border-color;
|
||||
}
|
||||
|
||||
.slick-headerrow-column {
|
||||
background-color: @panel-bg !important;
|
||||
text-overflow: clip;
|
||||
}
|
||||
|
||||
.slick-headerrow-column input {
|
||||
border: 1px solid @border-color;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
padding: 0px 3px !important;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
.slick-cell,
|
||||
.slick-headerrow-column {
|
||||
font-size: 12px;
|
||||
border-color: transparent @border-color @border-color transparent !important;
|
||||
border-style: solid;
|
||||
color: inherit !important;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
.slick-cell pre {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.slick-header-column, .slick-header-columns {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
background-color: @panel-bg;
|
||||
border-color: @border-color !important;
|
||||
color: @text-muted !important;
|
||||
}
|
||||
.slick-header-column:hover, .slick-header-column-active {
|
||||
background-image: none;
|
||||
background-color: darken(@panel-bg, 8%);
|
||||
}
|
||||
|
||||
.slick-row.odd .slick-cell {
|
||||
background-color: @light-bg;
|
||||
}
|
||||
|
||||
.frappe-rtl .slick-wrapper {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.slick-cell > span[data-field="_comments"] * {
|
||||
display: inline-block;
|
||||
}
|
||||
|
|
@ -867,6 +867,7 @@ frappe-datatable@frappe/datatable:
|
|||
resolved "https://codeload.github.com/frappe/datatable/tar.gz/88486b5a078387464426922d340124f92a30f2ef"
|
||||
dependencies:
|
||||
clusterize.js "^0.18.0"
|
||||
lodash "^4.17.5"
|
||||
sortablejs "^1.7.0"
|
||||
|
||||
frappe-gantt@^0.1.0:
|
||||
|
|
@ -1370,6 +1371,10 @@ lodash.uniq@^4.5.0:
|
|||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||
|
||||
lodash@^4.17.5:
|
||||
version "4.17.5"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
|
||||
|
||||
lru-cache@^4.0.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55"
|
||||
|
|
|
|||