List view bulk edit option (#5107)

* added actions button for bulk operations

also included bulk edit option

* page html refactor and added show/hide action option

* copy field object to avoid mutation

* commonify update method and other refactor

* fixed bug with progress bar level and minor fix with 'value' field display

* fixed frappe/erpnext#13063

* commonify field control method and removed unwanted code

* separate method for submit and cancel

* [minor] msgprint change

* refresh list on complete

* requested changes and refactor

* codacy fix

* code formatting, changed var name

* conflict fix

* description option for progress dialog

* extracted bulk operations to a separate file

* Refactor

commonified redundant method
get_json -> parse_json

* rename change_df -> replace_field
This commit is contained in:
Suraj Shetty 2018-03-05 11:02:50 +05:30 committed by Rushabh Mehta
parent 2fc96e1419
commit 1202ff1fdb
15 changed files with 527 additions and 282 deletions

View file

@ -1468,4 +1468,8 @@ def get_version(doctype, name, limit = None, head = False, raise_err = True):
@whitelist(allow_guest = True)
def ping():
return "pong"
return "pong"
def parse_json(val):
from frappe.utils import parse_json
return parse_json(val)

View file

@ -47,8 +47,8 @@ def remove_old_task_logs():
def is_file_old(file_path):
return ((time.time() - os.stat(file_path).st_mtime) > TASK_LOG_MAX_AGE)
def publish_progress(percent, title=None, doctype=None, docname=None):
publish_realtime('progress', {'percent': percent, 'title': title},
def publish_progress(percent, title=None, doctype=None, docname=None, description=None):
publish_realtime('progress', {'percent': percent, 'title': title, 'description': description},
user=frappe.session.user, doctype=doctype, docname=docname)
def publish_realtime(event=None, message=None, room=None,
@ -200,6 +200,6 @@ def get_task_progress_room(task_id):
# frappe.chat
def get_chat_room(room):
room = ''.join([frappe.local.site, ":room:", room])
return room
# end frappe.chat room

View file

@ -33,10 +33,13 @@ frappe.ui.form.on('Data Import', {
frm.disable_save();
frm.dashboard.clear_headline();
if (frm.doc.reference_doctype && !frm.doc.import_file) {
frm.dashboard.add_comment(__('Please attach a file to import'));
frm.page.set_indicator(__('Please attach a file to import'), 'orange');
} else {
if (frm.doc.import_status) {
frm.dashboard.add_comment(frm.doc.import_status);
const listview_settings = frappe.listview_settings['Data Import'];
const indicator = listview_settings.get_indicator(frm.doc);
frm.page.set_indicator(indicator[0], indicator[1]);
if (frm.doc.import_status==="In Progress") {
frm.dashboard.add_progress("Data Import Progress", "0");

View file

@ -4,10 +4,9 @@
frappe.ui.form.on('Bulk Update', {
refresh: function(frm) {
frm.page.set_primary_action(__('Update'), function() {
if(!frm.doc.update_value){
if (!frm.doc.update_value) {
frappe.throw(__('Field "value" is mandatory. Please specify value to be updated'));
}
else{
} else {
frappe.call({
method: 'frappe.desk.doctype.bulk_update.bulk_update.update',
args: {
@ -17,13 +16,19 @@ frappe.ui.form.on('Bulk Update', {
condition: frm.doc.condition,
limit: frm.doc.limit
},
callback: function() {
frappe.hide_progress();
}).then(r => {
let failed = r.message;
if (!failed) failed = [];
if (failed.length && !r._server_messages) {
frappe.throw(__('Cannot update {0}', [failed.map(f => f.bold ? f.bold(): f).join(', ')]));
}
frappe.hide_progress();
});
}
});
},
document_type: function(frm) {
// set field options
if(!frm.doc.document_type) return;

View file

@ -22,23 +22,47 @@ def update(doctype, field, value, condition='', limit=500):
if ';' in condition:
frappe.throw(_('; not allowed in condition'))
items = frappe.db.sql_list('''select name from `tab{0}`{1} limit 0, {2}'''.format(doctype,
condition, limit), debug=1)
n = len(items)
docnames = frappe.db.sql_list(
'''select name from `tab{0}`{1} limit 0, {2}'''.format(doctype, condition, limit)
)
data = {}
data[field] = value
return submit_cancel_or_update_docs(doctype, docnames, 'update', data)
for i, d in enumerate(items):
@frappe.whitelist()
def submit_cancel_or_update_docs(doctype, docnames, action='submit', data=None):
docnames = frappe.parse_json(docnames)
if data:
data = frappe.parse_json(data)
failed = []
for i, d in enumerate(docnames, 1):
doc = frappe.get_doc(doctype, d)
doc.set(field, value)
try:
doc.save()
except Exception as e:
frappe.msgprint(_("Validation failed for {0}").format(frappe.bold(doc.name)))
raise e
if action == 'submit':
doc.submit()
message = _('Submiting {0}').format(doctype)
elif action == 'cancel':
doc.cancel()
message = _('Cancelling {0}').format(doctype)
elif action == 'update':
doc.update(data)
doc.save()
message = _('Updating {0}').format(doctype)
frappe.publish_progress(float(i)*100/n,
title = _('Updating Records'), doctype='Bulk Update', docname='Bulk Update')
show_progress(docnames, message, i, d)
# clear messages
frappe.local.message_log = []
frappe.msgprint(_('{0} records updated').format(n), title=_('Success'), indicator='green')
except Exception:
failed.append(d)
return failed
def show_progress(docnames, message, i, description):
n = len(docnames)
if n >= 10:
frappe.publish_progress(
float(i) * 100 / n,
title = message,
description = description
)

View file

@ -224,8 +224,8 @@ def delete_items():
frappe.delete_doc(doctype, d)
if len(il) >= 5:
frappe.publish_realtime("progress",
dict(progress=[i+1, len(il)], title=_('Deleting {0}').format(doctype)),
user=frappe.session.user)
dict(progress=[i+1, len(il)], title=_('Deleting {0}').format(doctype), description=d),
user=frappe.session.user)
except Exception:
failed.append(d)

View file

@ -119,19 +119,27 @@ frappe.ui.form.Layout = Class.extend({
});
},
make_field: function(df, colspan, render = false) {
replace_field: function(fieldname, df, render) {
df.fieldname = fieldname; // change of fieldname is avoided
if (this.fields_dict[fieldname] && this.fields_dict[fieldname].df) {
const fieldobj = this.init_field(df, render);
this.fields_dict[fieldname].$wrapper.remove();
this.fields_list.splice(this.fields_dict[fieldname], 1, fieldobj);
this.fields_dict[fieldname] = fieldobj;
if (this.frm) {
fieldobj.perm = this.frm.perm;
}
this.section.fields_list.splice(this.section.fields_dict[fieldname], 1, fieldobj);
this.section.fields_dict[fieldname] = fieldobj;
this.refresh();
}
},
make_field: function(df, colspan, render) {
!this.section && this.make_section();
!this.column && this.make_column();
var fieldobj = frappe.ui.form.make_control({
df: df,
doctype: this.doctype,
parent: this.column.wrapper.get(0),
frm: this.frm,
render_input: render
});
fieldobj.layout = this;
const fieldobj = this.init_field(df, render);
this.fields_list.push(fieldobj);
this.fields_dict[df.fieldname] = fieldobj;
if(this.frm) {
@ -141,6 +149,20 @@ frappe.ui.form.Layout = Class.extend({
this.section.fields_list.push(fieldobj);
this.section.fields_dict[df.fieldname] = fieldobj;
},
init_field: function(df, render = false) {
const fieldobj = frappe.ui.form.make_control({
df: df,
doctype: this.doctype,
parent: this.column.wrapper.get(0),
frm: this.frm,
render_input: render
});
fieldobj.layout = this;
return fieldobj;
},
make_page: function(df) {
var me = this,
head = $('<div class="form-clickable-section text-center">\
@ -234,7 +256,7 @@ frappe.ui.form.Layout = Class.extend({
if(cnt % 2) {
$this.addClass("shaded-section");
}
cnt ++;
cnt++;
}
});
},
@ -414,11 +436,9 @@ frappe.ui.form.Layout = Class.extend({
} else {
field.grid.grid_rows[0].toggle_view(true);
}
}
else if(field.editor) {
} else if(field.editor) {
field.editor.set_focus();
}
else if(field.$input) {
} else if(field.$input) {
field.$input.focus();
}
},
@ -632,11 +652,13 @@ frappe.ui.form.Column = Class.extend({
</form>\
</div>').appendTo(this.section.body)
.find("form")
.on("submit", function() { return false; });
.on("submit", function() {
return false;
});
if(this.df.label) {
$('<label class="control-label">'+ __(this.df.label)
+'</label>').appendTo(this.wrapper);
if (this.df.label) {
$('<label class="control-label">' + __(this.df.label)
+ '</label>').appendTo(this.wrapper);
}
},
resize_all_columns: function() {

View file

@ -41,7 +41,6 @@ frappe.views.BaseList = class BaseList {
this.method = 'frappe.desk.reportview.get';
this.can_create = frappe.model.can_create(this.doctype);
this.can_delete = frappe.model.can_delete(this.doctype);
this.can_write = frappe.model.can_write(this.doctype);
this.fields = [];
@ -146,34 +145,9 @@ frappe.views.BaseList = class BaseList {
setup_page_head() {
this.page.set_title(this.page_title);
this.set_menu_items();
this.set_breadcrumbs();
}
set_menu_items() {
this.page.clear_menu();
const $secondary_action = this.page.set_secondary_action(
this.secondary_action.label,
this.secondary_action.action,
this.secondary_action.icon
);
if (!this.secondary_action.icon) {
$secondary_action.addClass('hidden-xs');
} else {
$secondary_action.addClass('visible-xs');
}
this.menu_items.map(item => {
if (item.condition && item.condition() === false) {
return;
}
const $item = this.page.add_menu_item(item.label, item.action, item.standard);
if (item.class) {
$item && $item.addClass(item.class);
}
});
}
set_breadcrumbs() {
frappe.breadcrumbs.add(this.meta.module, this.doctype);
}

View file

@ -0,0 +1,187 @@
export default class BulkOperations {
constructor({ doctype }) {
if (!doctype) frappe.throw(__('Doctype required'));
this.doctype = doctype;
}
print(docs) {
const print_settings = frappe.model.get_doc(':Print Settings', 'Print Settings');
const allow_print_for_draft = cint(print_settings.allow_print_for_draft);
const is_submittable = frappe.model.is_submittable(this.doctype);
const allow_print_for_cancelled = cint(print_settings.allow_print_for_cancelled);
const valid_docs = docs.filter(doc => {
return !is_submittable || doc.docstatus === 1 ||
(allow_print_for_cancelled && doc.docstatus == 2) ||
(allow_print_for_draft && doc.docstatus == 0) ||
frappe.user.has_role('Administrator');
}).map(doc => doc.name);
const invalid_docs = docs.filter(doc => !valid_docs.includes(doc.name));
if (invalid_docs.length > 0) {
frappe.msgprint(__('You selected Draft or Cancelled documents'));
return;
}
if (valid_docs.length > 0) {
const dialog = new frappe.ui.Dialog({
title: __('Print Documents'),
fields: [{
'fieldtype': 'Check',
'label': __('With Letterhead'),
'fieldname': 'with_letterhead'
},
{
'fieldtype': 'Select',
'label': __('Print Format'),
'fieldname': 'print_sel',
options: frappe.meta.get_print_formats(this.doctype)
}]
});
dialog.set_primary_action(__('Print'), args => {
if (!args) return;
const default_print_format = frappe.get_meta(this.doctype).default_print_format;
const with_letterhead = args.with_letterhead ? 1 : 0;
const print_format = args.print_sel ? args.print_sel : default_print_format;
const json_string = JSON.stringify(valid_docs);
const w = window.open('/api/method/frappe.utils.print_format.download_multi_pdf?' +
'doctype=' + encodeURIComponent(this.doctype) +
'&name=' + encodeURIComponent(json_string) +
'&format=' + encodeURIComponent(print_format) +
'&no_letterhead=' + (with_letterhead ? '0' : '1'));
if (!w) {
frappe.msgprint(__('Please enable pop-ups'));
return;
}
});
dialog.show();
} else {
frappe.msgprint(__('Select atleast 1 record for printing'));
}
}
delete(docnames, done = null) {
frappe
.call({
method: 'frappe.desk.reportview.delete_items',
freeze: true,
args: {
items: docnames,
doctype: this.doctype
}
})
.then((r) => {
let failed = r.message;
if (!failed) failed = [];
if (failed.length && !r._server_messages) {
frappe.throw(__('Cannot delete {0}', [failed.map(f => f.bold()).join(', ')]));
}
if (failed.length < docnames.length) {
frappe.utils.play_sound('delete');
if (done) done();
}
});
}
assign(docnames, done) {
if (docnames.length > 0) {
const dialog = new frappe.ui.form.AssignToDialog({
obj: this,
method: 'frappe.desk.form.assign_to.add_multiple',
doctype: this.doctype,
docname: docnames,
bulk_assign: true,
re_assign: true,
callback: done
});
dialog.clear();
dialog.show();
} else {
frappe.msgprint(__('Select records for assignment'));
}
}
submit_or_cancel(docnames, action='submit', done=null) {
action = action.toLowerCase();
frappe
.call({
method: 'frappe.desk.doctype.bulk_update.bulk_update.submit_cancel_or_update_docs',
args: {
doctype: this.doctype,
action: action,
docnames: docnames
},
})
.then((r) => {
let failed = r.message;
if (!failed) failed = [];
if (failed.length && !r._server_messages) {
frappe.throw(__('Cannot {0} {1}', [action, failed.map(f => f.bold()).join(', ')]));
}
if (failed.length < docnames.length) {
frappe.utils.play_sound(action);
if (done) done();
}
});
}
edit(docnames, field_mappings, done) {
let field_options = Object.keys(field_mappings).sort();
const dialog = new frappe.ui.Dialog({
title: __('Edit'),
fields: [
{
'fieldtype': 'Select', 'options': field_options,
'label': __('Field'), 'fieldname': 'field', 'reqd': 1,
'onchange': () => {
const new_df = Object.assign({},
field_mappings[dialog.get_value('field')]);
new_df.label = __('Value');
new_df.reqd = 1;
delete new_df.depends_on;
dialog.replace_field('value', new_df);
}
},
{
'fieldtype': 'Data',
'label': __('Value'),
'fieldname': 'value',
'reqd': 1
}
],
primary_action: ({ value }) => {
const fieldname = field_mappings[dialog.get_value('field')].fieldname;
frappe.call({
method: 'frappe.desk.doctype.bulk_update.bulk_update.submit_cancel_or_update_docs',
args: {
doctype: this.doctype,
docnames: docnames,
action: 'update',
data: {
[fieldname]: value
}
}
}).then(r => {
let failed = r.message || [];
if (failed.length && !r._server_messages) {
frappe.throw(__('Cannot update {0}', [failed.map(f => f.bold ? f.bold() : f).join(', ')]));
}
done();
dialog.hide();
});
},
primary_action_label: __('Update')
});
dialog.refresh();
dialog.show();
}
}

View file

@ -1,3 +1,5 @@
import BulkOperations from "./bulk_operations";
frappe.provide('frappe.views');
frappe.views.ListView = class ListView extends frappe.views.BaseList {
@ -55,10 +57,12 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
}
// initialize with saved order by
this.order_by = this.view_user_settings.order_by || 'modified desc';
// buld menu items)
// build menu items
this.menu_items = this.menu_items.concat(this.get_menu_items());
this.freeze_on_refresh = true;
this.actions_menu_items = this.get_actions_menu_items();
this.patch_refresh_and_load_lib();
}
@ -68,6 +72,45 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
super.setup_page();
}
setup_page_head() {
super.setup_page_head();
this.set_primary_action();
this.set_menu_items();
this.set_actions_menu_items();
}
set_menu_items() {
const $secondary_action = this.page.set_secondary_action(
this.secondary_action.label,
this.secondary_action.action,
this.secondary_action.icon
);
if (!this.secondary_action.icon) {
$secondary_action.addClass('hidden-xs');
} else {
$secondary_action.addClass('visible-xs');
}
this.menu_items.map(item => {
if (item.condition && item.condition() === false) {
return;
}
const $item = this.page.add_menu_item(item.label, item.action, item.standard);
if (item.class) {
$item && $item.addClass(item.class);
}
});
}
set_actions_menu_items() {
this.actions_menu_items.map(item => {
const $item = this.page.add_actions_menu_item(item.label, item.action, item.standard);
if (item.class) {
$item.addClass(item.class);
}
});
}
set_fields() {
let fields = [].concat(
frappe.model.std_fields_list,
@ -103,10 +146,6 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
}, interval);
}
setup_page_head() {
super.setup_page_head();
this.set_primary_action();
}
set_primary_action() {
if (this.can_create) {
@ -220,43 +259,23 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
});
}
// freeze(toggle) {
// if (this.view_name !== 'List') return;
// this.$freeze.toggle(toggle);
// this.$result.toggle(!toggle);
// const columns = this.columns;
// if (toggle) {
// if (this.$freeze.find('.freeze-row').length > 0) return;
// const html = `
// ${this.get_header_html()}
// ${Array.from(new Array(10)).map(loading_row).join('')}
// `;
// this.$freeze.html(html);
// }
// function loading_row() {
// return `
// <div class="list-row freeze-row level">
// <div class="level-left">
// <div class="list-row-col list-subject"></div>
// ${columns.slice(1).map(c => `<div class="list-row-col"></div>`).join('')}
// </div>
// <div class="level-right"></div>
// </div>
// `;
// }
// }
toggle_result_area() {
super.toggle_result_area();
this.toggle_delete_button(
this.toggle_actions_menu_button(
this.$result.find('.list-row-check:checked').length > 0
);
}
toggle_actions_menu_button(toggle) {
if (toggle) {
this.page.show_actions_menu();
this.page.clear_primary_action();
} else {
this.page.hide_actions_menu();
this.set_primary_action();
}
}
before_render() {
this.settings.before_render && this.settings.before_render();
frappe.model.user_settings.save(this.doctype, 'last_view', this.view_name);
@ -734,56 +753,13 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
this.$checkbox_actions.show();
this.$list_head_subject.hide();
}
if (this.can_delete) {
this.toggle_delete_button(this.$checks.length > 0);
}
}
toggle_delete_button(toggle) {
if (toggle) {
this.page.set_primary_action(__('Delete'),
() => this.delete_items(),
'octicon octicon-trashcan'
).addClass('btn-danger');
} else {
this.page.btn_primary.removeClass('btn-danger');
this.set_primary_action();
}
this.toggle_actions_menu_button(this.$checks.length > 0);
}
toggle_tags() {
this.$result.toggleClass('tags-shown');
}
delete_items() {
const docnames = this.get_checked_items(true);
frappe.confirm(__('Delete {0} items permanently?', [docnames.length]),
() => {
frappe.call({
method: 'frappe.desk.reportview.delete_items',
freeze: true,
args: {
items: docnames,
doctype: this.doctype
}
}).then((r) => {
let failed = r.message;
if (!failed) failed = [];
if (failed.length && !r._server_messages) {
frappe.throw(__('Cannot delete {0}', [failed.map(f => f.bold()).join(', ')]));
}
if (failed.length < docnames.length) {
frappe.utils.play_sound('delete');
this.refresh(true);
}
});
}
);
}
get_checked_items(only_docnames) {
const docnames = Array.from(this.$checks || [])
.map(check => $(check).data().name);
@ -816,6 +792,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
standard: true
});
}
if (frappe.model.can_set_user_permissions(doctype)) {
items.push({
label: __('User Permissions'),
@ -825,6 +802,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
standard: true
});
}
if (frappe.user_roles.includes('System Manager')) {
items.push({
label: __('Role Permissions Manager'),
@ -849,107 +827,6 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
standard: true
});
// utility
const bulk_assignment = () => {
return {
label: __('Assign To'),
action: () => {
const docnames = this.get_checked_items(true);
if (docnames.length > 0) {
const dialog = new frappe.ui.form.AssignToDialog({
obj: this,
method: 'frappe.desk.form.assign_to.add_multiple',
doctype: this.doctype,
docname: docnames,
bulk_assign: true,
re_assign: true,
callback: () => this.refresh(true)
});
dialog.clear();
dialog.show();
} else {
frappe.msgprint(__('Select records for assignment'));
}
},
standard: true
};
};
const bulk_printing = () => {
const print_settings = frappe.model.get_doc(':Print Settings', 'Print Settings');
const allow_print_for_draft = cint(print_settings.allow_print_for_draft);
const is_submittable = frappe.model.is_submittable(this.doctype);
const allow_print_for_cancelled = cint(print_settings.allow_print_for_cancelled);
return {
label: __('Print'),
action: () => {
const items = this.get_checked_items();
const valid_docs = items.filter(doc => {
return !is_submittable || doc.docstatus === 1 ||
(allow_print_for_cancelled && doc.docstatus == 2) ||
(allow_print_for_draft && doc.docstatus == 0) ||
frappe.user.has_role('Administrator');
}).map(doc => doc.name);
var invalid_docs = items.filter(doc => !valid_docs.includes(doc.name));
if (invalid_docs.length > 0) {
frappe.msgprint(__('You selected Draft or Cancelled documents'));
return;
}
if (valid_docs.length > 0) {
const dialog = new frappe.ui.Dialog({
title: __('Print Documents'),
fields: [{
'fieldtype': 'Check',
'label': __('With Letterhead'),
'fieldname': 'with_letterhead'
},
{
'fieldtype': 'Select',
'label': __('Print Format'),
'fieldname': 'print_sel',
options: frappe.meta.get_print_formats(this.doctype)
}]
});
dialog.set_primary_action(__('Print'), args => {
if (!args) return;
const default_print_format = frappe.get_meta(this.doctype).default_print_format;
const with_letterhead = args.with_letterhead ? 1 : 0;
const print_format = args.print_sel ? args.print_sel : default_print_format;
const json_string = JSON.stringify(valid_docs);
const w = window.open('/api/method/frappe.utils.print_format.download_multi_pdf?' +
'doctype=' + encodeURIComponent(this.doctype) +
'&name=' + encodeURIComponent(json_string) +
'&format=' + encodeURIComponent(print_format) +
'&no_letterhead=' + (with_letterhead ? '0' : '1'));
if (!w) {
frappe.msgprint(__('Please enable pop-ups'));
return;
}
});
dialog.show();
} else {
frappe.msgprint(__('Select atleast 1 record for printing'));
}
},
standard: true
};
};
// bulk assignment
items.push(bulk_assignment());
if (frappe.model.can_print(doctype)) {
items.push(bulk_printing());
}
// add to desktop
items.push({
label: __('Add to Desktop'),
@ -969,6 +846,131 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
return items;
}
get_actions_menu_items() {
const doctype = this.doctype;
const actions_menu_items = [];
const bulk_operations = new BulkOperations({ doctype: this.doctype });
const is_field_editable = (field_doc) => {
return field_doc.fieldname && frappe.model.is_value_type(field_doc)
&& field_doc.fieldtype !== 'Read Only' && !field_doc.hidden && !field_doc.read_only;
};
const has_editable_fields = () => {
return frappe.meta.get_docfields(doctype).some(field_doc => is_field_editable(field_doc));
};
const has_submit_permission = () => {
return frappe.perm.has_perm('Sales Order', 0, 'submit');
};
// utility
const bulk_assignment = () => {
return {
label: __('Assign To'),
action: () => bulk_operations.assign(this.get_checked_items(true), this.refresh),
standard: true
};
};
const bulk_printing = () => {
return {
label: __('Print'),
action: () => bulk_operations.print(this.get_checked_items()),
standard: true
};
};
const bulk_delete = () => {
return {
label: __('Delete'),
action: () => {
const docnames = this.get_checked_items(true);
frappe.confirm(__('Delete {0} items permanently?', [docnames.length]),
() => bulk_operations.delete(docnames, this.refresh));
},
standard: true,
};
};
const bulk_cancel = () => {
return {
label: __('Cancel'),
action: () => {
const docnames = this.get_checked_items(true);
if (docnames.length > 0) {
frappe.confirm(__('Cancel {0} documents?', [docnames.length]),
() => bulk_operations.submit_or_cancel(docnames, 'cancel', this.refresh));
}
},
standard: true
};
};
const bulk_submit = () => {
return {
label: __('Submit'),
action: () => {
const docnames = this.get_checked_items(true);
if (docnames.length > 0) {
frappe.confirm(__('Submit {0} documents?', [docnames.length]),
() => bulk_operations.submit_or_cancel(docnames, 'submit', this.refresh));
}
},
standard: true
};
};
const bulk_edit = () => {
return {
label: __('Edit'),
action: () => {
let field_mappings = {};
frappe.meta.get_docfields(doctype).forEach(field_doc => {
if (is_field_editable(field_doc)) {
field_mappings[field_doc.label] = Object.assign({}, field_doc);
}
});
const docnames = this.get_checked_items(true);
bulk_operations.edit(docnames, field_mappings, this.refresh);
},
standard: true
};
};
// bulk assignment
actions_menu_items.push(bulk_assignment());
// bulk printing
if (frappe.model.can_print(doctype)) {
actions_menu_items.push(bulk_printing());
}
// Bulk cancel
if (frappe.model.can_cancel(doctype)) {
actions_menu_items.push(bulk_cancel());
}
// Bulk submit
if (frappe.model.is_submittable(doctype) && has_submit_permission()) {
actions_menu_items.push(bulk_submit());
}
// bulk delete
if (frappe.model.can_delete(doctype)) {
actions_menu_items.push(bulk_delete());
}
// bulk edit
if (has_editable_fields()) {
actions_menu_items.push(bulk_edit());
}
return actions_menu_items;
}
set_filters_from_route_options() {
const filters = [];
for (let field in frappe.route_options) {
@ -1021,6 +1023,6 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
}
};
$(document).on('save', function (event, doc) {
$(document).on('save', (event, doc) => {
frappe.views.ListView.trigger_list_update(doc);
});

View file

@ -43,7 +43,7 @@ frappe.socketio = {
if(data.percent==100) {
frappe.hide_progress();
} else {
frappe.show_progress(data.title || __("Progress"), data.percent, 100);
frappe.show_progress(data.title || __("Progress"), data.percent, 100, data.description);
}
}
});

View file

@ -241,11 +241,12 @@ frappe.show_progress = function(title, count, total=100, description) {
var dialog = new frappe.ui.Dialog({
title: title,
});
dialog.progress = $(`<div class="progress">
<div class="progress-bar"></div>
dialog.progress = $(`<div>
<div class="progress">
<div class="progress-bar"></div>
</div>
<p class="description text-muted small"></p>
</div>`)
.appendTo(dialog.body);
</div`).appendTo(dialog.body);
dialog.progress_bar = dialog.progress.css({"margin-top": "10px"})
.find(".progress-bar");
dialog.$wrapper.removeClass("fade");

View file

@ -4,10 +4,10 @@
<div class="col-md-7 col-sm-8 col-xs-6 page-title">
<!-- title -->
<h1>
<div class="title-image hide hidden-md hidden-lg">
</div>
<div class="ellipsis title-text"></div>
<span class="indicator hide"></span>
<div class="title-image hide hidden-md hidden-lg">
</div>
<div class="ellipsis title-text"></div>
<span class="indicator hide"></span>
</h1>
</div>
<div class="text-right col-md-5 col-sm-4 col-xs-6 page-actions">
@ -18,27 +18,28 @@
<!-- buttons -->
<div class="btn-group menu-btn-group hide">
<button type="button" class="btn btn-default btn-sm dropdown-toggle"
data-toggle="dropdown" aria-expanded="false">
<button type="button" class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
<span class="hidden-xs">
<span class="menu-btn-group-label">{%= __("Menu") %}</span>
<span class="caret"></span></span>
<span class="visible-xs"><i class="octicon octicon-triangle-down"></i></span>
</button>
<ul class="dropdown-menu" role="menu">
</ul>
<span class="caret"></span>
</span>
<span class="visible-xs">
<i class="octicon octicon-triangle-down"></i>
</span>
</button>
<ul class="dropdown-menu" role="menu"></ul>
</div>
<button class="btn btn-secondary btn-default btn-sm hide"></button>
<div class="btn-group actions-btn-group hide">
<button type="button" class="btn btn-primary btn-sm dropdown-toggle"
data-toggle="dropdown" aria-expanded="false">
<span class="hidden-xs">
{%= __("Actions") %} <span class="caret"></span>
</span>
<span class="visible-xs octicon octicon-check"></span>
</button>
<ul class="dropdown-menu" role="menu">
</ul>
<button type="button" class="btn btn-primary btn-sm dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
<span class="hidden-xs">
{%= __("Actions") %}
<span class="caret"></span>
</span>
<span class="visible-xs octicon octicon-check"></span>
</button>
<ul class="dropdown-menu" role="menu">
</ul>
</div>
<button class="btn btn-primary btn-sm hide primary-action"></button>
</div>
@ -56,4 +57,4 @@
<div class="clearfix"></div>
</div>
</div>
</div>
</div>

View file

@ -235,16 +235,30 @@ frappe.ui.Page = Class.extend({
this.icon_group.addClass("hide");
},
//--- Actions (workflow) --//
//--- Actions Menu--//
show_actions_menu: function() {
this.actions_btn_group.removeClass("hide");
},
hide_actions_menu: function() {
this.actions_btn_group.addClass("hide");
},
add_action_item: function(label, click, standard) {
return this.add_dropdown_item(label, click, standard, this.actions);
},
add_actions_menu_item: function(label, click, standard) {
return this.add_dropdown_item(label, click, standard, this.actions);
},
clear_actions_menu: function() {
this.clear_btn_group(this.actions);
},
//-- Generic --//
/*

View file

@ -548,3 +548,11 @@ def get_site_info():
# dumps -> loads to prevent datatype conflicts
return json.loads(frappe.as_json(site_info))
def parse_json(val):
"""
Parses json if string else return
"""
if isinstance(val, string_types):
return json.loads(val)
return val