Kanban board fixes (#5933)
* class.extend -> es6 class * fix(kanban): Fix kanban creation from sidebar Creating custom fields from client side breaks in cases when DDL queries and normal queries were run simultaneously. Now, we do the heavy lifting on the server side Also, moved kanban specific code from list_sidebar to kanban_view * style: replace function with arrow functions * style: semicolon
This commit is contained in:
parent
bece82d6ab
commit
a406e5db68
4 changed files with 204 additions and 196 deletions
|
|
@ -8,6 +8,7 @@ import json
|
|||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from six import iteritems
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
|
||||
|
||||
class KanbanBoard(Document):
|
||||
|
|
@ -36,6 +37,14 @@ def has_permission(doc, ptype, user):
|
|||
|
||||
return False
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_kanban_boards(doctype):
|
||||
'''Get Kanban Boards for doctype to show in List View'''
|
||||
return frappe.get_list('Kanban Board',
|
||||
fields=['name', 'filters', 'reference_doctype', 'private'],
|
||||
filters={ 'reference_doctype': doctype }
|
||||
)
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_column(board_name, column_title):
|
||||
'''Adds new column to Kanban Board'''
|
||||
|
|
@ -120,6 +129,14 @@ def quick_kanban_board(doctype, board_name, field_name, project=None):
|
|||
'''Create new KanbanBoard quickly with default options'''
|
||||
doc = frappe.new_doc('Kanban Board')
|
||||
|
||||
if field_name == 'kanban_column':
|
||||
create_custom_field(doctype, {
|
||||
'label': 'Kanban Column',
|
||||
'fieldname': 'kanban_column',
|
||||
'fieldtype': 'Select',
|
||||
'hidden': 1
|
||||
})
|
||||
|
||||
meta = frappe.get_meta(doctype)
|
||||
|
||||
options = ''
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class FormMeta(Meta):
|
|||
for k in ("__js", "__css", "__list_js", "__calendar_js", "__map_js",
|
||||
"__linked_with", "__messages", "__print_formats", "__workflow_docs",
|
||||
"__form_grid_templates", "__listview_template", "__tree_js",
|
||||
"__dashboard", "__kanban_boards", "__kanban_column_fields", '__templates',
|
||||
"__dashboard", "__kanban_column_fields", '__templates',
|
||||
'__custom_js'):
|
||||
d[k] = self.get(k)
|
||||
|
||||
|
|
@ -185,14 +185,8 @@ class FormMeta(Meta):
|
|||
self.set('__dashboard', self.get_dashboard_data())
|
||||
|
||||
def load_kanban_meta(self):
|
||||
self.load_kanban_boards()
|
||||
self.load_kanban_column_fields()
|
||||
|
||||
def load_kanban_boards(self):
|
||||
kanban_boards = frappe.get_list(
|
||||
'Kanban Board', fields=['name', 'filters', 'reference_doctype', 'private'], filters={'reference_doctype': self.name})
|
||||
self.set("__kanban_boards", kanban_boards, as_value=True)
|
||||
|
||||
def load_kanban_column_fields(self):
|
||||
values = frappe.get_list(
|
||||
'Kanban Board', fields=['field_name'],
|
||||
|
|
|
|||
|
|
@ -9,14 +9,15 @@ frappe.provide('frappe.views');
|
|||
// parent
|
||||
// set_filter = function called on click
|
||||
|
||||
frappe.views.ListSidebar = Class.extend({
|
||||
init: function(opts) {
|
||||
frappe.views.ListSidebar = class ListSidebar {
|
||||
constructor(opts) {
|
||||
$.extend(this, opts);
|
||||
this.make();
|
||||
this.get_stats();
|
||||
this.cat_tags = [];
|
||||
},
|
||||
make: function() {
|
||||
}
|
||||
|
||||
make() {
|
||||
var sidebar_content = frappe.render_template("list_sidebar", { doctype: this.doctype });
|
||||
|
||||
this.sidebar = $('<div class="list-sidebar overlay-sidebar hidden-xs hidden-sm"></div>')
|
||||
|
|
@ -36,8 +37,9 @@ frappe.views.ListSidebar = Class.extend({
|
|||
if (limits.upgrade_url && limits.expiry && !frappe.flags.upgrade_dismissed) {
|
||||
this.setup_upgrade_box();
|
||||
}
|
||||
},
|
||||
setup_views: function() {
|
||||
}
|
||||
|
||||
setup_views() {
|
||||
var show_list_link = false;
|
||||
|
||||
if (frappe.views.calendar[this.doctype]) {
|
||||
|
|
@ -85,8 +87,9 @@ frappe.views.ListSidebar = Class.extend({
|
|||
if (show_list_link) {
|
||||
this.sidebar.find('.list-link[data-view="List"]').removeClass('hide');
|
||||
}
|
||||
},
|
||||
setup_reports: function() {
|
||||
}
|
||||
|
||||
setup_reports() {
|
||||
// add reports linked to this doctype to the dropdown
|
||||
var me = this;
|
||||
var added = [];
|
||||
|
|
@ -124,167 +127,22 @@ frappe.views.ListSidebar = Class.extend({
|
|||
|
||||
// from specially tagged reports
|
||||
add_reports(frappe.boot.user.all_reports || []);
|
||||
},
|
||||
}
|
||||
|
||||
setup_list_filter: function() {
|
||||
setup_list_filter() {
|
||||
this.list_filter = new ListFilter({
|
||||
wrapper: this.page.sidebar.find('.list-filters'),
|
||||
doctype: this.doctype,
|
||||
list_view: this.list_view
|
||||
});
|
||||
},
|
||||
|
||||
setup_kanban_boards: function() {
|
||||
// add kanban boards linked to this doctype to the dropdown
|
||||
var me = this;
|
||||
var $dropdown = this.page.sidebar.find('.kanban-dropdown');
|
||||
var divider = false;
|
||||
|
||||
var meta = frappe.get_meta(this.doctype);
|
||||
var boards = meta && meta.__kanban_boards;
|
||||
if (!boards) return;
|
||||
|
||||
boards.forEach(function(board) {
|
||||
var route = ["List", board.reference_doctype, "Kanban", board.name].join('/');
|
||||
if (!divider) {
|
||||
me.get_divider().appendTo($dropdown);
|
||||
divider = true;
|
||||
}
|
||||
$(`<li><a href="#${route}">
|
||||
<span>${__(board.name)}</span>
|
||||
${board.private ? '<i class="fa fa-lock fa-fw text-warning"></i>' : ''}
|
||||
</a></li>`).appendTo($dropdown);
|
||||
});
|
||||
|
||||
$dropdown.find('.new-kanban-board').click(function() {
|
||||
// frappe.new_doc('Kanban Board', {reference_doctype: me.doctype});
|
||||
var select_fields = frappe.get_meta(me.doctype)
|
||||
.fields.filter(function(df) {
|
||||
return df.fieldtype === 'Select' &&
|
||||
df.fieldname !== 'kanban_column';
|
||||
});
|
||||
|
||||
var fields = [{
|
||||
fieldtype: 'Data',
|
||||
fieldname: 'board_name',
|
||||
label: __('Kanban Board Name'),
|
||||
reqd: 1
|
||||
}];
|
||||
|
||||
if(me.doctype === 'Task') {
|
||||
fields.push({
|
||||
fieldtype: 'Link',
|
||||
fieldname: 'project',
|
||||
label: __('Project'),
|
||||
options: 'Project'
|
||||
});
|
||||
}
|
||||
|
||||
if(select_fields.length > 0) {
|
||||
fields = fields.concat([{
|
||||
fieldtype: 'Select',
|
||||
fieldname: 'field_name',
|
||||
label: __('Columns based on'),
|
||||
options: select_fields.map(df => df.label).join('\n'),
|
||||
default: select_fields[0],
|
||||
depends_on: 'eval:doc.custom_column===0'
|
||||
},
|
||||
{
|
||||
fieldtype: 'Check',
|
||||
fieldname: 'custom_column',
|
||||
label: __('Custom Column'),
|
||||
default: 0
|
||||
}]);
|
||||
setup_kanban_boards() {
|
||||
const $dropdown = this.page.sidebar.find('.kanban-dropdown');
|
||||
frappe.views.KanbanView.setup_dropdown_in_sidebar(this.doctype, $dropdown);
|
||||
}
|
||||
|
||||
if(['Note', 'ToDo'].includes(me.doctype)) {
|
||||
fields[0].description = __('This Kanban Board will be private');
|
||||
}
|
||||
|
||||
var d = new frappe.ui.Dialog({
|
||||
title: __('New Kanban Board'),
|
||||
fields: fields,
|
||||
primary_action_label: __('Save'),
|
||||
primary_action: function(values) {
|
||||
|
||||
var custom_column = values.custom_column !== undefined ?
|
||||
values.custom_column : 1;
|
||||
var field_name;
|
||||
if (custom_column) {
|
||||
field_name = 'kanban_column';
|
||||
} else {
|
||||
|
||||
if (!values.field_name) {
|
||||
frappe.throw(__('Please select Columns Based On'));
|
||||
}
|
||||
var field_name =
|
||||
select_fields
|
||||
.find(df => df.label === values.field_name)
|
||||
.fieldname;
|
||||
}
|
||||
|
||||
me.add_custom_column_field(custom_column)
|
||||
.then(function(custom_column) {
|
||||
return me.make_kanban_board(values.board_name, field_name, values.project);
|
||||
})
|
||||
.then(function() {
|
||||
d.hide();
|
||||
}, function(err) {
|
||||
frappe.msgprint(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
d.show();
|
||||
});
|
||||
},
|
||||
add_custom_column_field: function(flag) {
|
||||
var me = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (!flag) resolve(false);
|
||||
frappe.call({
|
||||
method: 'frappe.custom.doctype.custom_field.custom_field.add_custom_field',
|
||||
args: {
|
||||
doctype: me.doctype,
|
||||
df: {
|
||||
label: 'Kanban Column',
|
||||
fieldname: 'kanban_column',
|
||||
fieldtype: 'Select',
|
||||
hidden: 1
|
||||
}
|
||||
}
|
||||
}).success(function() {
|
||||
resolve(true);
|
||||
}).error(function(err) {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
},
|
||||
make_kanban_board: function(board_name, field_name, project) {
|
||||
var me = this;
|
||||
return frappe.call({
|
||||
method: 'frappe.desk.doctype.kanban_board.kanban_board.quick_kanban_board',
|
||||
args: {
|
||||
doctype: me.doctype,
|
||||
board_name,
|
||||
field_name,
|
||||
project
|
||||
},
|
||||
callback: function(r) {
|
||||
var kb = r.message;
|
||||
if (kb.filters) {
|
||||
frappe.provide('frappe.kanban_filters');
|
||||
frappe.kanban_filters[kb.kanban_board_name] = kb.filters;
|
||||
}
|
||||
frappe.set_route(
|
||||
'List',
|
||||
me.doctype,
|
||||
'Kanban',
|
||||
kb.kanban_board_name
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
setup_calendar_view: function() {
|
||||
setup_calendar_view() {
|
||||
const doctype = this.doctype;
|
||||
|
||||
frappe.db.get_list('Calendar View', {
|
||||
|
|
@ -322,8 +180,9 @@ frappe.views.ListSidebar = Class.extend({
|
|||
$link_calendar.removeClass('hide');
|
||||
$link_calendar.html(dropdown_html);
|
||||
});
|
||||
},
|
||||
setup_email_inbox: function() {
|
||||
}
|
||||
|
||||
setup_email_inbox() {
|
||||
// get active email account for the user and add in dropdown
|
||||
if (this.doctype != "Communication")
|
||||
return;
|
||||
|
|
@ -352,13 +211,15 @@ frappe.views.ListSidebar = Class.extend({
|
|||
$dropdown.find('.new-email-account').click(function() {
|
||||
frappe.new_doc("Email Account");
|
||||
});
|
||||
},
|
||||
setup_assigned_to_me: function() {
|
||||
}
|
||||
|
||||
setup_assigned_to_me() {
|
||||
this.page.sidebar.find(".assigned-to-me a").on("click", () => {
|
||||
this.list_view.filter_area.add(this.list_view.doctype, "_assign", "like", `%${frappe.session.user}%`);
|
||||
});
|
||||
},
|
||||
setup_upgrade_box: function() {
|
||||
}
|
||||
|
||||
setup_upgrade_box() {
|
||||
let upgrade_list = $(`<ul class="list-unstyled sidebar-menu"></ul>`).appendTo(this.sidebar);
|
||||
|
||||
// Show Renew/Upgrade button,
|
||||
|
|
@ -386,11 +247,13 @@ frappe.views.ListSidebar = Class.extend({
|
|||
frappe.flags.upgrade_dismissed = 1;
|
||||
});
|
||||
}
|
||||
},
|
||||
get_cat_tags: function() {
|
||||
}
|
||||
|
||||
get_cat_tags() {
|
||||
return this.cat_tags;
|
||||
},
|
||||
get_stats: function() {
|
||||
}
|
||||
|
||||
get_stats() {
|
||||
var me = this;
|
||||
frappe.call({
|
||||
method: 'frappe.desk.reportview.get_sidebar_stats',
|
||||
|
|
@ -425,8 +288,9 @@ frappe.views.ListSidebar = Class.extend({
|
|||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
render_stat: function(field, stat, tags) {
|
||||
}
|
||||
|
||||
render_stat(field, stat, tags) {
|
||||
var me = this;
|
||||
var sum = 0;
|
||||
var stats = [];
|
||||
|
|
@ -485,8 +349,9 @@ frappe.views.ListSidebar = Class.extend({
|
|||
});
|
||||
})
|
||||
.insertBefore(this.sidebar.find(".close-sidebar-button"));
|
||||
},
|
||||
set_fieldtype: function(df) {
|
||||
}
|
||||
|
||||
set_fieldtype(df) {
|
||||
|
||||
// scrub
|
||||
if (df.fieldname == "docstatus") {
|
||||
|
|
@ -511,12 +376,14 @@ frappe.views.ListSidebar = Class.extend({
|
|||
if (df.fieldtype === "Data" && (df.options || "").toLowerCase() === "email") {
|
||||
df.options = null;
|
||||
}
|
||||
},
|
||||
reload_stats: function() {
|
||||
}
|
||||
|
||||
reload_stats() {
|
||||
this.sidebar.find(".sidebar-stat").remove();
|
||||
this.get_stats();
|
||||
},
|
||||
get_divider: function() {
|
||||
}
|
||||
|
||||
get_divider() {
|
||||
return $('<li role="separator" class="divider"></li>');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
|
|||
|
||||
render() {
|
||||
const board_name = this.board_name;
|
||||
if(this.kanban && board_name === this.kanban.board_name) {
|
||||
if (this.kanban && board_name === this.kanban.board_name) {
|
||||
this.kanban.update(this.data);
|
||||
return;
|
||||
}
|
||||
|
|
@ -95,11 +95,11 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
|
|||
var title_field = null;
|
||||
var quick_entry = false;
|
||||
|
||||
if(this.meta.title_field) {
|
||||
if (this.meta.title_field) {
|
||||
title_field = frappe.meta.get_field(this.doctype, this.meta.title_field);
|
||||
}
|
||||
|
||||
this.meta.fields.forEach(function(df) {
|
||||
this.meta.fields.forEach((df) => {
|
||||
const is_valid_field =
|
||||
in_list(['Data', 'Text', 'Small Text', 'Text Editor'], df.fieldtype)
|
||||
&& !df.hidden;
|
||||
|
|
@ -111,15 +111,13 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
|
|||
});
|
||||
|
||||
// quick entry
|
||||
var mandatory = meta.fields.filter(function(df) {
|
||||
return df.reqd && !doc[df.fieldname];
|
||||
});
|
||||
var mandatory = meta.fields.filter((df) => df.reqd && !doc[df.fieldname]);
|
||||
|
||||
if(mandatory.some(df => df.fieldtype === 'Table') || mandatory.length > 1) {
|
||||
if (mandatory.some(df => df.fieldtype === 'Table') || mandatory.length > 1) {
|
||||
quick_entry = true;
|
||||
}
|
||||
|
||||
if(!title_field) {
|
||||
if (!title_field) {
|
||||
title_field = frappe.meta.get_field(this.doctype, 'name');
|
||||
}
|
||||
|
||||
|
|
@ -136,3 +134,135 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
|
|||
];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
frappe.views.KanbanView.setup_dropdown_in_sidebar = function(doctype, $dropdown) {
|
||||
// get kanban boards and append to dropdown
|
||||
get_kanban_boards()
|
||||
.then((kanban_boards) => {
|
||||
if (!kanban_boards) return;
|
||||
|
||||
$('<li role="separator" class="divider"></li>').appendTo($dropdown);
|
||||
|
||||
kanban_boards.forEach(board => {
|
||||
const route = ['List', board.reference_doctype, 'Kanban', board.name].join('/');
|
||||
|
||||
$(`<li>
|
||||
<a href="#${route}">
|
||||
<span>${__(board.name)}</span>
|
||||
${board.private ? '<i class="fa fa-lock fa-fw text-warning"></i>' : ''}
|
||||
</a>
|
||||
</li>
|
||||
`).appendTo($dropdown);
|
||||
});
|
||||
});
|
||||
|
||||
$dropdown.on('click', '.new-kanban-board', () => {
|
||||
const dialog = new_kanban_dialog();
|
||||
dialog.show();
|
||||
});
|
||||
|
||||
function make_kanban_board(board_name, field_name, project) {
|
||||
return frappe.call({
|
||||
method: 'frappe.desk.doctype.kanban_board.kanban_board.quick_kanban_board',
|
||||
args: {
|
||||
doctype,
|
||||
board_name,
|
||||
field_name,
|
||||
project
|
||||
},
|
||||
callback: function(r) {
|
||||
var kb = r.message;
|
||||
if (kb.filters) {
|
||||
frappe.provide('frappe.kanban_filters');
|
||||
frappe.kanban_filters[kb.kanban_board_name] = kb.filters;
|
||||
}
|
||||
frappe.set_route('List', doctype, 'Kanban', kb.kanban_board_name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let dialog = null;
|
||||
|
||||
function new_kanban_dialog() {
|
||||
if (dialog) return dialog;
|
||||
|
||||
const fields = get_fields_for_dialog();
|
||||
|
||||
dialog = new frappe.ui.Dialog({
|
||||
title: __('New Kanban Board'),
|
||||
fields: fields,
|
||||
|
||||
primary_action_label: __('Save'),
|
||||
primary_action(values) {
|
||||
const custom_column =
|
||||
values.custom_column !== undefined ?
|
||||
values.custom_column : 1;
|
||||
|
||||
let field_name = custom_column ? 'kanban_column' : values.field_name;
|
||||
|
||||
make_kanban_board(values.board_name, field_name, values.project)
|
||||
.then(() => dialog.hide(), (err) => frappe.msgprint(err));
|
||||
}
|
||||
});
|
||||
return dialog;
|
||||
}
|
||||
|
||||
function get_fields_for_dialog() {
|
||||
|
||||
let fields = [{
|
||||
fieldtype: 'Data',
|
||||
fieldname: 'board_name',
|
||||
label: __('Kanban Board Name'),
|
||||
reqd: 1,
|
||||
description: ['Note', 'ToDo'].includes(doctype) ?
|
||||
__('This Kanban Board will be private') : ''
|
||||
}];
|
||||
|
||||
if (doctype === 'Task') {
|
||||
fields.push({
|
||||
fieldtype: 'Link',
|
||||
fieldname: 'project',
|
||||
label: __('Project'),
|
||||
options: 'Project'
|
||||
});
|
||||
}
|
||||
|
||||
const select_fields =
|
||||
frappe.get_meta(doctype).fields
|
||||
.filter(df => {
|
||||
return df.fieldtype === 'Select' &&
|
||||
df.fieldname !== 'kanban_column';
|
||||
});
|
||||
|
||||
if (select_fields.length > 0) {
|
||||
fields = fields.concat([{
|
||||
fieldtype: 'Select',
|
||||
fieldname: 'field_name',
|
||||
label: __('Columns based on'),
|
||||
options: select_fields.map(df => ({label: df.label, value: df.fieldname})),
|
||||
default: select_fields[0],
|
||||
depends_on: 'eval:doc.custom_column===0',
|
||||
reqd: 1
|
||||
},
|
||||
|
||||
{
|
||||
fieldtype: 'Check',
|
||||
fieldname: 'custom_column',
|
||||
label: __('Custom Column'),
|
||||
default: 0,
|
||||
onchange() {
|
||||
const value = this.get_value();
|
||||
this.layout.set_df_property('field_name', 'reqd', !value);
|
||||
}
|
||||
}]);
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
function get_kanban_boards() {
|
||||
return frappe.call('frappe.desk.doctype.kanban_board.kanban_board.get_kanban_boards', { doctype })
|
||||
.then(r => r.message);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue