Merge pull request #7026 from adityahase/fast-list
feat(desk): List View Settings
This commit is contained in:
commit
2fa90a29b5
20 changed files with 341 additions and 1327 deletions
34
cypress/integration/list_view_settings.js
Normal file
34
cypress/integration/list_view_settings.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
context('List View Settings', () => {
|
||||
beforeEach(() => {
|
||||
cy.login('Administrator', 'qwe');
|
||||
cy.visit('/desk');
|
||||
});
|
||||
it('Default settings', () => {
|
||||
cy.visit('/desk#List/DocType/List');
|
||||
cy.get('.list-count').should('contain', "20 of");
|
||||
cy.get('.sidebar-stat').should('contain', "No Tags");
|
||||
});
|
||||
it('disable count and sidebar stats then verify', () => {
|
||||
cy.visit('/desk#List/DocType/List');
|
||||
cy.get('.list-count').should('contain', "20 of");
|
||||
cy.get('button').contains('Menu').click();
|
||||
cy.get('.dropdown-menu li').filter(':visible').contains('Settings').click();
|
||||
cy.get('.modal-dialog').should('contain', 'Settings');
|
||||
|
||||
cy.get('input[data-fieldname="disable_count"]').check({force: true});
|
||||
cy.get('input[data-fieldname="disable_sidebar_stats"]').check({force: true});
|
||||
cy.get('button').filter(':visible').contains('Save').click();
|
||||
|
||||
cy.reload();
|
||||
|
||||
cy.get('.list-count').should('be.empty');
|
||||
cy.get('.list-sidebar .sidebar-stat').should('not.exist');
|
||||
|
||||
cy.get('button').contains('Menu').click({force: true});
|
||||
cy.get('.dropdown-menu li').filter(':visible').contains('Settings').click();
|
||||
cy.get('.modal-dialog').should('contain', 'Settings');
|
||||
cy.get('input[data-fieldname="disable_count"]').uncheck({force: true});
|
||||
cy.get('input[data-fieldname="disable_sidebar_stats"]').uncheck({force: true});
|
||||
cy.get('button').filter(':visible').contains('Save').click();
|
||||
});
|
||||
});
|
||||
0
frappe/desk/doctype/list_view_setting/__init__.py
Normal file
0
frappe/desk/doctype/list_view_setting/__init__.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2019, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('List View Setting', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
160
frappe/desk/doctype/list_view_setting/list_view_setting.json
Normal file
160
frappe/desk/doctype/list_view_setting/list_view_setting.json
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "Prompt",
|
||||
"beta": 0,
|
||||
"creation": "2019-03-06 13:29:21.101860",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "disable_count",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Disable Count",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "disable_sidebar_stats",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Disable Sidebar Stats",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "disable_auto_refresh",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Disable Auto Refresh",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-03-06 13:40:59.533586",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "List View Setting",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
10
frappe/desk/doctype/list_view_setting/list_view_setting.py
Normal file
10
frappe/desk/doctype/list_view_setting/list_view_setting.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class ListViewSetting(Document):
|
||||
pass
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
|
||||
class TestListViewSetting(unittest.TestCase):
|
||||
pass
|
||||
26
frappe/desk/listview.py
Normal file
26
frappe/desk/listview.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import json
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_list_settings(doctype):
|
||||
try:
|
||||
return frappe.get_cached_doc("List View Setting", doctype)
|
||||
except frappe.DoesNotExistError:
|
||||
frappe.clear_messages()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_list_settings(doctype, values):
|
||||
try:
|
||||
doc = frappe.get_doc("List View Setting", doctype)
|
||||
except frappe.DoesNotExistError:
|
||||
doc = frappe.new_doc("List View Setting")
|
||||
doc.name = doctype
|
||||
frappe.clear_messages()
|
||||
doc.update(json.loads(values))
|
||||
doc.save()
|
||||
|
|
@ -330,11 +330,6 @@
|
|||
"public/js/frappe/list/list_sidebar.js",
|
||||
"public/js/frappe/list/list_sidebar.html",
|
||||
"public/js/frappe/list/list_sidebar_stat.html",
|
||||
"public/js/frappe/list/list_item_main.html",
|
||||
"public/js/frappe/list/list_item_row.html",
|
||||
"public/js/frappe/list/list_item_main_head.html",
|
||||
"public/js/frappe/list/list_item_row_head.html",
|
||||
"public/js/frappe/list/list_item_subject.html",
|
||||
"public/js/frappe/list/list_view_permission_restrictions.html",
|
||||
|
||||
"public/js/frappe/views/gantt/gantt_view.js",
|
||||
|
|
@ -344,8 +339,6 @@
|
|||
"public/js/frappe/views/inbox/inbox_view.js",
|
||||
"public/js/frappe/views/file/file_view.js",
|
||||
|
||||
"public/js/frappe/list/header_select_all_like_filter.html",
|
||||
"public/js/frappe/list/item_assigned_to_comment_count.html",
|
||||
"public/js/frappe/views/treeview.js",
|
||||
"public/js/frappe/views/interaction.js",
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
{% if (_checkbox) { %}
|
||||
<input class="list-select-all hidden-xs" type="checkbox"
|
||||
title="{%= __("Select All") %}">
|
||||
{% } %}
|
||||
<span class="liked-by-filter-button">
|
||||
<i class="fa-fw octicon octicon-heart text-extra-muted not-liked like-action list-liked-by-me"
|
||||
title="{%= __("Likes") %}"></i>
|
||||
</span>
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
<span class="list-row-modified text-muted">
|
||||
{%= comment_when(data.modified, true) %}
|
||||
</span>
|
||||
{% if (data._assign_list.length) { %}
|
||||
<span class="filterable"
|
||||
data-filter="_assign,like,%{%= data._assign_list[data._assign_list.length - 1] %}%">
|
||||
{%= frappe.avatar(data._assign_list[data._assign_list.length - 1]) %}</span>
|
||||
{% } else { %}
|
||||
<span class="avatar avatar-small avatar-empty"></span>
|
||||
{% } %}
|
||||
<span class="list-comment-count small
|
||||
{% if(!data._comment_count) { %} text-extra-muted {% } else { %} text-muted {% } %}">
|
||||
<i class="octicon octicon-comment-discussion"></i>
|
||||
{%= (data._comment_count > 99 ? "99+" : data._comment_count) || 0 %}
|
||||
</span>
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
<div class="list-item__content ellipsis
|
||||
{% if(col.type==="Subject") { %}
|
||||
list-item__content--flex-2
|
||||
{% } else { %}
|
||||
hidden-xs
|
||||
{% } %}
|
||||
{% if(col.df && ["Int", "Float", "Currency", "Percent"].indexOf(col.df.fieldtype)!==-1) { %}text-right{% } %}"
|
||||
{% if(col.type!=="Indicator" && col.title) { %}
|
||||
title="{%= col.title + ": " + value %}"
|
||||
{% } %}
|
||||
>
|
||||
{% if (col.type==="Subject") { %}
|
||||
{%= subject %}
|
||||
{% } else if (col.type==="Indicator") { %}
|
||||
{%= indicator %}
|
||||
{% } else if (col.render) { %}
|
||||
{%= col.render(data) %}
|
||||
{% } else if (col.fieldtype==="Image") { %}
|
||||
{% if(data[col.df.options]) { %}
|
||||
<img src="{%= data[col.df.options] %}" style="max-height: 30px; max-width: 100%;">
|
||||
{% } else { %}
|
||||
<div class="missing-image small"><span class="octicon octicon-circle-slash"></span></div>
|
||||
{% } %}
|
||||
{% } else if(col.fieldtype==="Select") { %}
|
||||
<span class="filterable indicator {%= frappe.utils.guess_colour(value) %} ellipsis"
|
||||
data-filter="{%= col.fieldname %},=,{%= value %}">{%= __(value) %}</span>
|
||||
{% } else if(col.fieldtype==="Link") { %}
|
||||
<a class="filterable text-muted grey ellipsis"
|
||||
data-filter="{%= col.fieldname %},=,{%= value %}">{%= value %}</a>
|
||||
{% } else { %}
|
||||
<a class="filterable text-muted ellipsis"
|
||||
data-filter="{%= col.fieldname %},=,{%= value %}">
|
||||
{% if(formatters && formatters[col.fieldname]) { %}
|
||||
{{ formatters[col.fieldname](value, col.df, data) }}
|
||||
{% } else if(col.fieldtype == "Code") { %}
|
||||
{{ value }}
|
||||
{% } else { %}
|
||||
{{ frappe.format(value, col.df, null, data) }}
|
||||
{% } %}
|
||||
</a>
|
||||
{% } %}
|
||||
</div>
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
<div class="list-item__content ellipsis text-muted
|
||||
{% if(col.type==="Subject") { %}
|
||||
list-item__content--flex-2
|
||||
{% } else { %}
|
||||
hidden-xs
|
||||
{% } %}
|
||||
{% if(col.df && ["Int", "Float", "Currency", "Percent"].indexOf(col.df.fieldtype)!==-1) { %}text-right{% } %}"
|
||||
>
|
||||
|
||||
{% if (col.type==="Subject") { %}
|
||||
{%= frappe.render_template("header_select_all_like_filter", { _checkbox: _checkbox }) %}
|
||||
{% } %}
|
||||
<span class="list-col-title ellipsis">{{ __(col.title) || __(col.label) || "" }}</span>
|
||||
|
||||
</div>
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
<div class="list-item">
|
||||
{%= main %}
|
||||
|
||||
<!-- id -->
|
||||
{% if (meta.title_field && !settings.hide_name_column) {
|
||||
var is_different = data.name !== data[meta.title_field];
|
||||
%}
|
||||
<div class="list-item__content list-item__content--id hidden-xs hidden-sm ellipsis">
|
||||
{% if (is_different) { %}
|
||||
<a class="text-muted ellipsis" href="#Form/{%= data._doctype_encoded %}/{%= data._name_encoded %}">
|
||||
{%= data.name %}</a>
|
||||
{% } %}
|
||||
</div>
|
||||
{% } %}
|
||||
|
||||
<!-- comment -->
|
||||
{% if (!data._hide_activity) { %}
|
||||
<div class="list-item__content list-item__content--activity hidden-xs">
|
||||
<!-- comments count and assigned to section -->
|
||||
{%= frappe.render_template("item_assigned_to_comment_count", { data: data }) %}
|
||||
</div>
|
||||
<div class="list-item__content list-item__content--indicator visible-xs text-right">
|
||||
{%= indicator_dot %}
|
||||
</div>
|
||||
{% } %}
|
||||
</div>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<div class="list-item list-item--head" data-list-renderer="{{list.name}}">
|
||||
<!-- title + columns -->
|
||||
{%= main %}
|
||||
|
||||
<!-- id -->
|
||||
{% if(list.meta.title_field && !list.settings.hide_name_column) { %}
|
||||
<div class="list-item__content hidden-xs hidden-sm"></div>
|
||||
{% } %}
|
||||
<!-- comment -->
|
||||
<div class="list-item__content list-item__content--activity hidden-xs text-right list-row-right"></div>
|
||||
</div>
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
{% if (_checkbox) { %}
|
||||
<input class="list-row-checkbox hidden-xs" type="checkbox" data-name="{{name}}">
|
||||
{% } %}
|
||||
{% if (!_hide_activity) { %}
|
||||
<span class="liked-by" data-liked-by=\'{{ JSON.stringify(_liked_by) }}\'>
|
||||
<i class="octicon octicon-heart
|
||||
{% if (_liked_by.indexOf(_user)===-1) { %}
|
||||
text-extra-muted not-liked
|
||||
{% }%}
|
||||
fa-fw like-action"
|
||||
data-name="{{ _name }}" data-doctype="{{ doctype }}">
|
||||
</i>
|
||||
<span class="likes-count">{{ (_liked_by.length > 99 ? "99+" : _liked_by.length) || "" }}</span>
|
||||
</span>
|
||||
{% } %}
|
||||
|
||||
{% var anchor_title = (_full_title).replace(/["]/g, "&\quot;"); %}
|
||||
<a class="grey list-id {{ css_seen }} ellipsis"
|
||||
data-name="{{ _name }}"
|
||||
href="#Form/{{ _doctype_encoded }}/{{ _name_encoded }}"
|
||||
title="{{ anchor_title }}">{{ strip_html(_title) }}</a>
|
||||
{% if (_workflow && !_without_workflow) { %}
|
||||
<span class="label label-{{ _workflow.style }} filterable"
|
||||
data-filter="{{ _workflow.fieldname }},=,{{ _workflow.value }}">
|
||||
{%= _workflow.value %}</span>
|
||||
{% } %}
|
||||
|
|
@ -1,635 +0,0 @@
|
|||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// MIT License. See license.txt
|
||||
|
||||
frappe.provide('frappe.views');
|
||||
|
||||
// Renders customized list
|
||||
// usually based on `in_list_view` property
|
||||
|
||||
frappe.views.ListRenderer = Class.extend({
|
||||
name: 'List',
|
||||
init: function (opts) {
|
||||
$.extend(this, opts);
|
||||
this.meta = frappe.get_meta(this.doctype);
|
||||
|
||||
this.init_settings();
|
||||
this.set_defaults();
|
||||
this.set_fields();
|
||||
this.set_columns();
|
||||
this.setup_cache();
|
||||
},
|
||||
set_defaults: function () {
|
||||
var me = this;
|
||||
this.page_title = __(this.doctype);
|
||||
|
||||
this.set_wrapper();
|
||||
this.setup_filterable();
|
||||
this.prepare_render_view();
|
||||
|
||||
// flag to enable/disable realtime updates in list_view
|
||||
this.no_realtime = false;
|
||||
|
||||
// set false to render view even if no results
|
||||
// e.g Calendar
|
||||
this.show_no_result = true;
|
||||
|
||||
// hide sort selector
|
||||
this.hide_sort_selector = false;
|
||||
|
||||
// default settings
|
||||
this.order_by = this.order_by || 'modified desc';
|
||||
this.filters = this.filters || [];
|
||||
this.or_filters = this.or_filters || [];
|
||||
this.page_length = this.page_length || 20;
|
||||
},
|
||||
setup_cache: function () {
|
||||
frappe.provide('frappe.views.list_renderers.' + this.doctype);
|
||||
frappe.views.list_renderers[this.doctype][this.list_view.current_view] = this;
|
||||
},
|
||||
init_settings: function () {
|
||||
this.settings = frappe.listview_settings[this.doctype] || {};
|
||||
if(!("selectable" in this.settings)) {
|
||||
this.settings.selectable = true;
|
||||
}
|
||||
this.init_user_settings();
|
||||
|
||||
this.order_by = this.user_settings.order_by || this.settings.order_by;
|
||||
this.filters = this.user_settings.filters || this.settings.filters;
|
||||
this.page_length = this.settings.page_length;
|
||||
|
||||
// default filter for submittable doctype
|
||||
if(frappe.model.is_submittable(this.doctype) && (!this.filters || !this.filters.length)) {
|
||||
this.filters = [[this.doctype, "docstatus", "!=", 2]];
|
||||
}
|
||||
},
|
||||
init_user_settings: function () {
|
||||
frappe.provide('frappe.model.user_settings.' + this.doctype + '.' + this.name);
|
||||
this.user_settings = frappe.get_user_settings(this.doctype)[this.name];
|
||||
},
|
||||
after_refresh: function() {
|
||||
// called after refresh in list_view
|
||||
},
|
||||
before_refresh: function() {
|
||||
// called before refresh in list_view
|
||||
},
|
||||
should_refresh: function() {
|
||||
return this.list_view.current_view !== this.list_view.last_view;
|
||||
},
|
||||
load_last_view: function() {
|
||||
// this function should handle loading the last view of your list_renderer,
|
||||
// If you have a last view (for e.g last kanban board in Kanban View),
|
||||
// load it using frappe.set_route and return true
|
||||
// else return false
|
||||
return false;
|
||||
},
|
||||
set_wrapper: function () {
|
||||
this.wrapper = this.list_view.wrapper && this.list_view.wrapper.find('.result-list');
|
||||
},
|
||||
set_fields: function () {
|
||||
var me = this;
|
||||
var tabDoctype = '`tab' + this.doctype + '`.';
|
||||
this.fields = [];
|
||||
this.stats = ['_user_tags'];
|
||||
|
||||
var add_field = function (fieldname) {
|
||||
if (!fieldname.includes('`tab')) {
|
||||
fieldname = tabDoctype + '`' + fieldname + '`';
|
||||
}
|
||||
if (!me.fields.includes(fieldname))
|
||||
me.fields.push(fieldname);
|
||||
}
|
||||
|
||||
var defaults = [
|
||||
'name',
|
||||
'owner',
|
||||
'docstatus',
|
||||
'_user_tags',
|
||||
'_comments',
|
||||
'modified',
|
||||
'modified_by',
|
||||
'_assign',
|
||||
'_liked_by',
|
||||
'_seen'
|
||||
];
|
||||
defaults.map(add_field);
|
||||
|
||||
// add title field
|
||||
if (this.meta.title_field) {
|
||||
this.title_field = this.meta.title_field;
|
||||
add_field(this.meta.title_field);
|
||||
}
|
||||
|
||||
if (this.meta.image_field) {
|
||||
add_field(this.meta.image_field);
|
||||
}
|
||||
|
||||
// enabled / disabled
|
||||
if (frappe.meta.has_field(this.doctype, 'enabled')) { add_field('enabled'); }
|
||||
if (frappe.meta.has_field(this.doctype, 'disabled')) { add_field('disabled'); }
|
||||
|
||||
// add workflow field (as priority)
|
||||
this.workflow_state_fieldname = frappe.workflow.get_state_fieldname(this.doctype);
|
||||
if (this.workflow_state_fieldname) {
|
||||
if (!frappe.workflow.workflows[this.doctype]['override_status']) {
|
||||
add_field(this.workflow_state_fieldname);
|
||||
}
|
||||
this.stats.push(this.workflow_state_fieldname);
|
||||
}
|
||||
|
||||
this.meta.fields.forEach(function (df, i) {
|
||||
if (df.in_list_view && frappe.perm.has_perm(me.doctype, df.permlevel, 'read')) {
|
||||
if (df.fieldtype == 'Image' && df.options) {
|
||||
add_field(df.options);
|
||||
} else {
|
||||
add_field(df.fieldname);
|
||||
}
|
||||
// currency field for symbol (multi-currency)
|
||||
if (df.fieldtype == 'Currency' && df.options) {
|
||||
if (df.options.includes(':')) {
|
||||
add_field(df.options.split(':')[1]);
|
||||
} else {
|
||||
add_field(df.options);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// additional fields
|
||||
if (this.settings.add_fields) {
|
||||
this.settings.add_fields.forEach(add_field);
|
||||
}
|
||||
// kanban column fields
|
||||
if (me.meta.__kanban_column_fields) {
|
||||
me.meta.__kanban_column_fields.map(add_field);
|
||||
}
|
||||
},
|
||||
set_columns: function () {
|
||||
var me = this;
|
||||
this.columns = [];
|
||||
var name_column = {
|
||||
colspan: this.settings.colwidths && this.settings.colwidths.subject || 6,
|
||||
type: 'Subject',
|
||||
title: 'Name'
|
||||
};
|
||||
if (this.meta.title_field) {
|
||||
name_column.title = frappe.meta.get_docfield(this.doctype, this.meta.title_field).label;
|
||||
}
|
||||
this.columns.push(name_column);
|
||||
|
||||
if (frappe.has_indicator(this.doctype)) {
|
||||
// indicator
|
||||
this.columns.push({
|
||||
colspan: this.settings.colwidths && this.settings.colwidths.indicator || 3,
|
||||
type: 'Indicator',
|
||||
title: 'Status'
|
||||
});
|
||||
}
|
||||
|
||||
// total_colspans
|
||||
this.total_colspans = this.columns.reduce(function (total, curr) {
|
||||
return total + curr.colspan;
|
||||
}, 0);
|
||||
|
||||
// overridden
|
||||
var overridden = (this.settings.add_columns || []).map(function (d) {
|
||||
return d.content;
|
||||
});
|
||||
|
||||
// custom fields in list_view
|
||||
var docfields_in_list_view =
|
||||
frappe.get_children('DocType', this.doctype, 'fields', { 'in_list_view': 1 })
|
||||
.sort(function (a, b) {
|
||||
return a.idx - b.idx
|
||||
});
|
||||
|
||||
docfields_in_list_view.forEach(function (d) {
|
||||
if (overridden.includes(d.fieldname) || d.fieldname === me.title_field) {
|
||||
return;
|
||||
}
|
||||
if (me.total_colspans < 12) {
|
||||
me.add_column(d);
|
||||
}
|
||||
});
|
||||
|
||||
// additional columns
|
||||
if (this.settings.add_columns) {
|
||||
this.settings.add_columns.forEach(function (d) {
|
||||
if (me.total_colspans < 12) {
|
||||
if (typeof d === 'string') {
|
||||
me.add_column(frappe.meta.get_docfield(me.doctype, d));
|
||||
} else {
|
||||
me.columns.push(d);
|
||||
me.total_colspans += parseInt(d.colspan);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// distribute remaining columns
|
||||
var empty_cols = flt(12 - this.total_colspans);
|
||||
while (empty_cols > 0) {
|
||||
this.columns = this.columns.map(function (col) {
|
||||
if (empty_cols > 0) {
|
||||
col.colspan = cint(col.colspan) + 1;
|
||||
empty_cols = empty_cols - 1;
|
||||
}
|
||||
return col;
|
||||
});
|
||||
}
|
||||
|
||||
// Remove duplicates
|
||||
this.columns = this.columns.uniqBy(col => col.title);
|
||||
|
||||
// Remove TextEditor field columns
|
||||
this.columns = this.columns.filter(col => col.fieldtype !== 'Text Editor');
|
||||
|
||||
// Remove color field
|
||||
this.columns = this.columns.filter(col => col.fieldtype !== 'Color');
|
||||
|
||||
// Limit number of columns to 4
|
||||
this.columns = this.columns.slice(0, 4);
|
||||
},
|
||||
add_column: function (df) {
|
||||
// field width
|
||||
var colspan = 3;
|
||||
if (in_list(['Int', 'Percent'], df.fieldtype)) {
|
||||
colspan = 2;
|
||||
} else if (in_list(['Check', 'Image'], df.fieldtype)) {
|
||||
colspan = 1;
|
||||
} else if (in_list(['name', 'subject', 'title'], df.fieldname)) {
|
||||
// subjects are longer
|
||||
colspan = 4;
|
||||
} else if (df.fieldtype == 'Text Editor' || df.fieldtype == 'Text') {
|
||||
colspan = 4;
|
||||
}
|
||||
|
||||
if (df.columns && df.columns > 0) {
|
||||
colspan = df.columns;
|
||||
} else if (this.settings.column_colspan && this.settings.column_colspan[df.fieldname]) {
|
||||
colspan = this.settings.column_colspan[df.fieldname];
|
||||
} else {
|
||||
colspan = 2;
|
||||
}
|
||||
this.total_colspans += parseInt(colspan);
|
||||
var col = {
|
||||
colspan: colspan,
|
||||
content: df.fieldname,
|
||||
type: df.fieldtype,
|
||||
df: df,
|
||||
fieldtype: df.fieldtype,
|
||||
fieldname: df.fieldname,
|
||||
title: __(df.label)
|
||||
};
|
||||
if (this.settings.column_render && this.settings.column_render[df.fieldname]) {
|
||||
col.render = this.settings.column_render[df.fieldname];
|
||||
}
|
||||
this.columns.push(col);
|
||||
},
|
||||
|
||||
setup_filterable: function () {
|
||||
var me = this;
|
||||
|
||||
this.list_view.wrapper &&
|
||||
this.list_view.wrapper.on('click', '.result-list .filterable', function (e) {
|
||||
e.stopPropagation();
|
||||
var filters = $(this).attr('data-filter').split('|');
|
||||
var added = false;
|
||||
|
||||
filters.forEach(function (f) {
|
||||
f = f.split(',');
|
||||
if (f[2] === 'Today') {
|
||||
f[2] = frappe.datetime.get_today();
|
||||
} else if (f[2] == 'User') {
|
||||
f[2] = frappe.session.user;
|
||||
}
|
||||
var new_filter = me.list_view.filter_list
|
||||
.add_filter(me.doctype, f[0], f[1], f.slice(2).join(','));
|
||||
|
||||
if (new_filter) {
|
||||
// set it to true if atleast 1 filter is added
|
||||
added = true;
|
||||
}
|
||||
});
|
||||
if (added) {
|
||||
me.list_view.refresh(true);
|
||||
}
|
||||
});
|
||||
|
||||
this.list_view.wrapper &&
|
||||
this.list_view.wrapper.on('click', '.list-item', function (e) {
|
||||
// don't open in case of checkbox, like, filterable
|
||||
if ($(e.target).hasClass('filterable')
|
||||
|| $(e.target).hasClass('octicon-heart')
|
||||
|| $(e.target).is(':checkbox')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var link = $(this).parent().find('a.list-id').get(0);
|
||||
if ( link && link.href )
|
||||
window.location.href = link.href;
|
||||
return false;
|
||||
});
|
||||
},
|
||||
|
||||
render_view: function (values) {
|
||||
var me = this;
|
||||
var $list_items = me.wrapper.find('.list-items');
|
||||
|
||||
if($list_items.length === 0) {
|
||||
$list_items = $(`
|
||||
<div class="list-items">
|
||||
</div>
|
||||
`);
|
||||
me.wrapper.append($list_items);
|
||||
}
|
||||
|
||||
values.map(value => {
|
||||
const $item = $(this.get_item_html(value));
|
||||
const $item_container = $('<div class="list-item-container">').append($item);
|
||||
|
||||
$list_items.append($item_container);
|
||||
|
||||
if (this.settings.post_render_item) {
|
||||
this.settings.post_render_item(this, $item_container, value);
|
||||
}
|
||||
|
||||
this.render_tags($item_container, value);
|
||||
});
|
||||
|
||||
this.render_count();
|
||||
},
|
||||
|
||||
render_count: function() {
|
||||
const $header_right = this.list_view.list_header.find('.list-item__content--activity');
|
||||
const current_count = this.list_view.data.length;
|
||||
|
||||
frappe.call({
|
||||
method: 'frappe.desk.reportview.get',
|
||||
args: {
|
||||
doctype: this.doctype,
|
||||
filters: this.list_view.get_filters_args(),
|
||||
fields: ['count(`tab' + this.doctype + '`.name) as total_count']
|
||||
}
|
||||
}).then(r => {
|
||||
const count = r.message.values[0][0] || current_count;
|
||||
const str = __('{0} of {1}', [current_count, count]);
|
||||
const $html = $(`<span>${str}</span>`);
|
||||
|
||||
$html.css({
|
||||
marginRight: '10px'
|
||||
})
|
||||
$header_right.addClass('text-muted');
|
||||
$header_right.html($html);
|
||||
})
|
||||
},
|
||||
|
||||
// returns html for a data item,
|
||||
// usually based on a template
|
||||
get_item_html: function (data) {
|
||||
var main = this.columns.map(column =>
|
||||
frappe.render_template('list_item_main', {
|
||||
data: data,
|
||||
col: column,
|
||||
value: data[column.fieldname],
|
||||
formatters: this.settings.formatters,
|
||||
subject: this.get_subject_html(data, true),
|
||||
indicator: this.get_indicator_html(data),
|
||||
})
|
||||
).join("");
|
||||
|
||||
return frappe.render_template('list_item_row', {
|
||||
data: data,
|
||||
main: main,
|
||||
settings: this.settings,
|
||||
meta: this.meta,
|
||||
indicator_dot: this.get_indicator_dot(data),
|
||||
})
|
||||
},
|
||||
|
||||
get_header_html: function () {
|
||||
var main = this.columns.map(column =>
|
||||
frappe.render_template('list_item_main_head', {
|
||||
col: column,
|
||||
_checkbox: ((frappe.model.can_delete(this.doctype) || this.settings.selectable)
|
||||
&& !this.no_delete)
|
||||
})
|
||||
).join("");
|
||||
|
||||
return frappe.render_template('list_item_row_head', { main: main, list: this });
|
||||
},
|
||||
|
||||
render_tags: function (element, data) {
|
||||
var me = this;
|
||||
var tag_row = $(`<div class='tag-row'>
|
||||
<div class='list-tag hidden-xs'></div>
|
||||
<div class='clearfix'></div>
|
||||
</div>`).appendTo(element);
|
||||
|
||||
if (!me.list_view.tags_shown) {
|
||||
tag_row.addClass('hide');
|
||||
}
|
||||
|
||||
// add tags
|
||||
var tag_editor = new frappe.ui.TagEditor({
|
||||
parent: tag_row.find('.list-tag'),
|
||||
frm: {
|
||||
doctype: this.doctype,
|
||||
docname: data.name
|
||||
},
|
||||
list_sidebar: me.list_view.list_sidebar,
|
||||
user_tags: data._user_tags,
|
||||
on_change: function (user_tags) {
|
||||
data._user_tags = user_tags;
|
||||
}
|
||||
});
|
||||
tag_editor.wrapper.on('click', '.tagit-label', function () {
|
||||
me.list_view.set_filter('_user_tags', $(this).text());
|
||||
});
|
||||
},
|
||||
|
||||
get_subject_html: function (data, without_workflow) {
|
||||
data._without_workflow = without_workflow;
|
||||
return frappe.render_template('list_item_subject', data);
|
||||
},
|
||||
|
||||
get_indicator_html: function (doc) {
|
||||
var indicator = frappe.get_indicator(doc, this.doctype);
|
||||
if (indicator) {
|
||||
return `<span class='indicator ${indicator[1]} filterable'
|
||||
data-filter='${indicator[2]}'>
|
||||
${__(indicator[0])}
|
||||
<span>`;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
get_indicator_dot: function (doc) {
|
||||
var indicator = frappe.get_indicator(doc, this.doctype);
|
||||
if (!indicator) {
|
||||
return '';
|
||||
}
|
||||
return `<span class='indicator ${indicator[1]}' title='${__(indicator[0])}'></span>`;
|
||||
},
|
||||
prepare_data: function (data) {
|
||||
if (data.modified) {
|
||||
this.prepare_when(data, data.modified);
|
||||
}
|
||||
|
||||
// nulls as strings
|
||||
for (var key in data) {
|
||||
if (data[key] == null) {
|
||||
data[key] = '';
|
||||
}
|
||||
}
|
||||
|
||||
data.doctype = this.doctype;
|
||||
data._liked_by = JSON.parse(data._liked_by || '[]');
|
||||
data._checkbox = (frappe.model.can_delete(this.doctype) || this.settings.selectable) && !this.no_delete
|
||||
|
||||
data._doctype_encoded = encodeURIComponent(data.doctype);
|
||||
data._name = data.name.replace(/'/g, '\'');
|
||||
data._name_encoded = encodeURIComponent(data.name);
|
||||
data._submittable = frappe.model.is_submittable(this.doctype);
|
||||
|
||||
var title_field = this.meta.title_field || 'name';
|
||||
data._title = strip_html(data[title_field] || data.name);
|
||||
|
||||
// check for duplicates
|
||||
// add suffix like (1), (2) etc
|
||||
if (data.name && this.values_map) {
|
||||
if (this.values_map[data.name]!==undefined) {
|
||||
if (this.values_map[data.name]===1) {
|
||||
// update first row!
|
||||
this.set_title_with_row_number(this.rows_map[data.name], 1);
|
||||
}
|
||||
this.values_map[data.name]++;
|
||||
this.set_title_with_row_number(data, this.values_map[data.name]);
|
||||
} else {
|
||||
this.values_map[data.name] = 1;
|
||||
this.rows_map[data.name] = data;
|
||||
}
|
||||
}
|
||||
|
||||
data._full_title = data._title;
|
||||
|
||||
data._workflow = null;
|
||||
if (this.workflow_state_fieldname) {
|
||||
data._workflow = {
|
||||
fieldname: this.workflow_state_fieldname,
|
||||
value: data[this.workflow_state_fieldname],
|
||||
style: frappe.utils.guess_style(data[this.workflow_state_fieldname])
|
||||
}
|
||||
}
|
||||
|
||||
data._user = frappe.session.user;
|
||||
|
||||
if(!data._user_tags) data._user_tags = "";
|
||||
|
||||
data._tags = data._user_tags.split(',').filter(function (v) {
|
||||
// filter falsy values
|
||||
return v;
|
||||
});
|
||||
|
||||
data.css_seen = '';
|
||||
if (data._seen) {
|
||||
var seen = JSON.parse(data._seen);
|
||||
if (seen && in_list(seen, data._user)) {
|
||||
data.css_seen = 'seen'
|
||||
}
|
||||
}
|
||||
|
||||
// whether to hide likes/comments/assignees
|
||||
data._hide_activity = 0;
|
||||
|
||||
data._assign_list = JSON.parse(data._assign || '[]');
|
||||
|
||||
// prepare data in settings
|
||||
if (this.settings.prepare_data)
|
||||
this.settings.prepare_data(data);
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
set_title_with_row_number: function (data, id) {
|
||||
data._title = data._title + ` (${__("Row")} ${id})`;
|
||||
data._full_title = data._title;
|
||||
},
|
||||
|
||||
prepare_when: function (data, date_str) {
|
||||
if (!date_str) date_str = data.modified;
|
||||
// when
|
||||
data.when = (frappe.datetime.str_to_user(date_str)).split(' ')[0];
|
||||
var diff = frappe.datetime.get_diff(frappe.datetime.get_today(), date_str.split(' ')[0]);
|
||||
if (diff === 0) {
|
||||
data.when = comment_when(date_str);
|
||||
}
|
||||
if (diff === 1) {
|
||||
data.when = __('Yesterday')
|
||||
}
|
||||
if (diff === 2) {
|
||||
data.when = __('2 days ago')
|
||||
}
|
||||
},
|
||||
|
||||
// for views which require 3rd party libs
|
||||
required_libs: null,
|
||||
|
||||
prepare_render_view: function () {
|
||||
var me = this;
|
||||
this._render_view = this.render_view;
|
||||
|
||||
var lib_exists = (typeof this.required_libs === 'string' && this.required_libs)
|
||||
|| ($.isArray(this.required_libs) && this.required_libs.length);
|
||||
|
||||
this.render_view = function (values) {
|
||||
me.values_map = {};
|
||||
me.rows_map = {};
|
||||
// prepare data before rendering view
|
||||
values = values.map(me.prepare_data.bind(this));
|
||||
// remove duplicates
|
||||
// values = values.uniqBy(value => value.name);
|
||||
|
||||
if (lib_exists) {
|
||||
me.load_lib(function () {
|
||||
me._render_view(values);
|
||||
});
|
||||
} else {
|
||||
me._render_view(values);
|
||||
}
|
||||
}.bind(this);
|
||||
},
|
||||
|
||||
load_lib: function (callback) {
|
||||
frappe.require(this.required_libs, callback);
|
||||
},
|
||||
|
||||
render_bar_graph: function (parent, data, field, label) {
|
||||
var args = {
|
||||
percent: data[field],
|
||||
label: __(label)
|
||||
}
|
||||
$(parent).append(`<span class='progress' style='width: 100 %; float: left; margin: 5px 0px;'> \
|
||||
<span class='progress- bar' title='${args.percent}% ${args.label}' \
|
||||
style='width: ${args.percent}%;'></span>\
|
||||
</span>`);
|
||||
},
|
||||
render_icon: function (parent, icon_class, label) {
|
||||
var icon_html = `<i class='${icon_class}' title='${__(label) || ''}'></i>`;
|
||||
$(parent).append(icon_html);
|
||||
},
|
||||
make_no_result: function () {
|
||||
var new_button = frappe.boot.user.can_create.includes(this.doctype)
|
||||
? (`<p><button class='btn btn-primary btn-sm'
|
||||
list_view_doc='${this.doctype}'>
|
||||
${__('Create a new {0}', [__(this.doctype)])}
|
||||
</button></p>`)
|
||||
: '';
|
||||
var no_result_message =
|
||||
`<div class='msg-box no-border'>
|
||||
<p>${__('No {0} found', [__(this.doctype)])}</p>
|
||||
${new_button}
|
||||
</div>`;
|
||||
|
||||
return no_result_message;
|
||||
},
|
||||
});
|
||||
|
|
@ -258,6 +258,9 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
|
||||
get_stats() {
|
||||
var me = this;
|
||||
if (this.list_view.list_view_settings && this.list_view.list_view_settings.disable_sidebar_stats) {
|
||||
return;
|
||||
}
|
||||
frappe.call({
|
||||
method: 'frappe.desk.reportview.get_sidebar_stats',
|
||||
type: 'GET',
|
||||
|
|
|
|||
|
|
@ -87,6 +87,11 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
}
|
||||
|
||||
this.patch_refresh_and_load_lib();
|
||||
return this.get_list_view_settings();
|
||||
}
|
||||
|
||||
get_list_view_settings() {
|
||||
return frappe.call("frappe.desk.listview.get_list_settings", {doctype: this.doctype}).then(doc => this.list_view_settings = doc.message || {});
|
||||
}
|
||||
|
||||
on_sort_change(sort_by, sort_order) {
|
||||
|
|
@ -289,7 +294,9 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
}
|
||||
|
||||
freeze() {
|
||||
this.$result.find('.list-count').html(`<span>${__('Refreshing')}...</span>`);
|
||||
if (this.list_view_settings && !this.list_view_settings.disable_count) {
|
||||
this.$result.find('.list-count').html(`<span>${__('Refreshing')}...</span>`);
|
||||
}
|
||||
}
|
||||
|
||||
get_args() {
|
||||
|
|
@ -375,10 +382,11 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
}
|
||||
|
||||
render_count() {
|
||||
this.get_count_str()
|
||||
.then(str => {
|
||||
if (!this.list_view_settings.disable_count) {
|
||||
this.get_count_str().then(str => {
|
||||
this.$result.find('.list-count').html(`<span>${str}</span>`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render_tags() {
|
||||
|
|
@ -824,6 +832,9 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
}
|
||||
|
||||
setup_realtime_updates() {
|
||||
if (this.list_view_settings.disable_auto_refresh) {
|
||||
return;
|
||||
}
|
||||
frappe.realtime.on('list_update', data => {
|
||||
if (this.filter_area.is_being_edited()) {
|
||||
return;
|
||||
|
|
@ -1026,9 +1037,33 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
});
|
||||
}
|
||||
|
||||
if (frappe.user.has_role('System Manager')) {
|
||||
items.push({
|
||||
label: __('Settings'),
|
||||
action: () => this.show_list_settings(),
|
||||
standard: true
|
||||
});
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
show_list_settings() {
|
||||
frappe.model.with_doctype("List View Setting", () => {
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: __("Settings"),
|
||||
fields: frappe.get_meta("List View Setting").fields
|
||||
});
|
||||
d.set_values(this.list_view_settings);
|
||||
d.show();
|
||||
d.set_primary_action(__('Save'), () => {
|
||||
let values = d.get_values();
|
||||
frappe.call("frappe.desk.listview.set_list_settings", {doctype: this.doctype, values: values});
|
||||
Object.assign(this.list_view_settings, values);
|
||||
d.hide();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
get_actions_menu_items() {
|
||||
const doctype = this.doctype;
|
||||
const actions_menu_items = [];
|
||||
|
|
|
|||
|
|
@ -1,539 +0,0 @@
|
|||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// MIT License. See license.txt
|
||||
|
||||
// new re-re-factored Listing object
|
||||
// now called BaseList
|
||||
//
|
||||
// opts:
|
||||
// parent
|
||||
|
||||
// method (method to call on server)
|
||||
// args (additional args to method)
|
||||
// get_args (method to return args as dict)
|
||||
|
||||
// show_filters [false]
|
||||
// doctype
|
||||
// filter_fields (if given, this list is rendered, else built from doctype)
|
||||
|
||||
// query or get_query (will be deprecated)
|
||||
// query_max
|
||||
// buttons_in_frame
|
||||
|
||||
// no_result_message ("No result")
|
||||
|
||||
// page_length (20)
|
||||
// hide_refresh (False)
|
||||
// no_toolbar
|
||||
// new_doctype
|
||||
// [function] render_row(parent, data)
|
||||
// [function] onrun
|
||||
// no_loading (no ajax indicator)
|
||||
|
||||
frappe.provide('frappe.ui');
|
||||
|
||||
frappe.ui.BaseList = Class.extend({
|
||||
init: function (opts) {
|
||||
this.opts = opts || {};
|
||||
this.set_defaults();
|
||||
if (opts) {
|
||||
this.make();
|
||||
}
|
||||
},
|
||||
set_defaults: function () {
|
||||
this.page_length = 20;
|
||||
this.start = 0;
|
||||
this.data = [];
|
||||
},
|
||||
make: function (opts) {
|
||||
if (opts) {
|
||||
this.opts = opts;
|
||||
}
|
||||
this.prepare_opts();
|
||||
|
||||
$.extend(this, this.opts);
|
||||
|
||||
// make dom
|
||||
this.wrapper = $(frappe.render_template('listing', this.opts));
|
||||
this.parent.append(this.wrapper);
|
||||
|
||||
this.set_events();
|
||||
|
||||
if (this.page) {
|
||||
this.wrapper.find('.list-toolbar-wrapper').hide();
|
||||
}
|
||||
|
||||
if (this.show_filters) {
|
||||
this.make_filters();
|
||||
}
|
||||
},
|
||||
prepare_opts: function () {
|
||||
if (this.opts.new_doctype) {
|
||||
if (!frappe.boot.user.can_create.includes(this.opts.new_doctype)) {
|
||||
this.opts.new_doctype = null;
|
||||
}
|
||||
}
|
||||
if (!this.opts.no_result_message) {
|
||||
this.opts.no_result_message = __('Nothing to show');
|
||||
}
|
||||
if (!this.opts.page_length) {
|
||||
this.opts.page_length = this.user_settings && this.user_settings.limit || 20;
|
||||
}
|
||||
this.opts._more = __('More');
|
||||
},
|
||||
add_button: function (label, click, icon) {
|
||||
if (this.page) {
|
||||
return this.page.add_menu_item(label, click, icon)
|
||||
} else {
|
||||
this.wrapper.find('.list-toolbar-wrapper').removeClass('hide');
|
||||
return $('<button class="btn btn-default"></button>')
|
||||
.appendTo(this.wrapper.find('.list-toolbar'))
|
||||
.html((icon ? ('<i class="' + icon + '"></i> ') : '') + label)
|
||||
.click(click);
|
||||
}
|
||||
},
|
||||
set_events: function () {
|
||||
var me = this;
|
||||
|
||||
// next page
|
||||
this.wrapper.find('.btn-more').click(function () {
|
||||
me.run(true);
|
||||
});
|
||||
|
||||
this.wrapper.find(".btn-group-paging").on('click', '.btn', function () {
|
||||
me.page_length = cint($(this).attr("data-value"));
|
||||
|
||||
me.wrapper.find(".btn-group-paging .btn-info").removeClass("btn-info");
|
||||
$(this).addClass("btn-info");
|
||||
|
||||
// always reset when changing list page length
|
||||
me.run();
|
||||
});
|
||||
|
||||
// select the correct page length
|
||||
if (this.opts.page_length !== 20) {
|
||||
this.wrapper.find(".btn-group-paging .btn-info").removeClass("btn-info");
|
||||
this.wrapper
|
||||
.find(".btn-group-paging .btn[data-value='" + this.opts.page_length + "']")
|
||||
.addClass('btn-info');
|
||||
}
|
||||
|
||||
// title
|
||||
if (this.title) {
|
||||
this.wrapper.find('h3').html(this.title).show();
|
||||
}
|
||||
|
||||
// new
|
||||
this.set_primary_action();
|
||||
|
||||
if (me.no_toolbar || me.hide_toolbar) {
|
||||
me.wrapper.find('.list-toolbar-wrapper').hide();
|
||||
}
|
||||
},
|
||||
|
||||
set_primary_action: function () {
|
||||
var me = this;
|
||||
if (this.new_doctype) {
|
||||
this.page.set_primary_action(
|
||||
__("New"),
|
||||
me.make_new_doc.bind(me, me.new_doctype),
|
||||
"octicon octicon-plus"
|
||||
);
|
||||
} else {
|
||||
this.page.clear_primary_action();
|
||||
}
|
||||
},
|
||||
|
||||
make_new_doc: function (doctype) {
|
||||
var me = this;
|
||||
frappe.model.with_doctype(doctype, function () {
|
||||
if (me.custom_new_doc) {
|
||||
me.custom_new_doc(doctype);
|
||||
} else {
|
||||
if (me.filter_list) {
|
||||
frappe.route_options = {};
|
||||
me.filter_list.get_filters().forEach(function (f, i) {
|
||||
if (f[2] === "=" && !frappe.model.std_fields_list.includes(f[1])) {
|
||||
frappe.route_options[f[1]] = f[3];
|
||||
}
|
||||
});
|
||||
}
|
||||
frappe.new_doc(doctype, true);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
make_filters: function () {
|
||||
this.make_standard_filters();
|
||||
|
||||
this.filter_list = new frappe.ui.FilterList({
|
||||
base_list: this,
|
||||
parent: this.wrapper.find('.list-filters').show(),
|
||||
doctype: this.doctype,
|
||||
filter_fields: this.filter_fields,
|
||||
default_filters: this.default_filters || []
|
||||
});
|
||||
// default filter for submittable doctype
|
||||
if (frappe.model.is_submittable(this.doctype)) {
|
||||
this.filter_list.add_filter(this.doctype, "docstatus", "!=", 2);
|
||||
}
|
||||
},
|
||||
|
||||
make_standard_filters: function() {
|
||||
var me = this;
|
||||
if (this.standard_filters_added) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.meta) {
|
||||
var filter_count = 1;
|
||||
if(this.is_list_view) {
|
||||
$(`<span class="octicon octicon-search text-muted small"></span>`)
|
||||
.prependTo(this.page.page_form);
|
||||
}
|
||||
this.page.add_field({
|
||||
fieldtype: 'Data',
|
||||
label: 'ID',
|
||||
condition: 'like',
|
||||
fieldname: 'name',
|
||||
onchange: () => { me.refresh(true); }
|
||||
});
|
||||
|
||||
this.meta.fields.forEach(function(df, i) {
|
||||
if(df.in_standard_filter && !frappe.model.no_value_type.includes(df.fieldtype)) {
|
||||
let options = df.options;
|
||||
let condition = '=';
|
||||
let fieldtype = df.fieldtype;
|
||||
if (['Text', 'Small Text', 'Text Editor', 'Data'].includes(fieldtype)) {
|
||||
fieldtype = 'Data';
|
||||
condition = 'like';
|
||||
}
|
||||
|
||||
if (df.fieldtype === "Select" && df.options) {
|
||||
options = df.options.split("\n");
|
||||
if(options.length > 0 && options[0] != "") {
|
||||
options.unshift("");
|
||||
options = options.join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (df.fieldtype === 'Data' && df.options) {
|
||||
// don't format email / number in filters
|
||||
options = '';
|
||||
}
|
||||
|
||||
let f = me.page.add_field({
|
||||
fieldtype: fieldtype,
|
||||
label: __(df.label),
|
||||
options: options,
|
||||
fieldname: df.fieldname,
|
||||
condition: condition,
|
||||
onchange: () => {me.refresh(true);}
|
||||
});
|
||||
filter_count ++;
|
||||
if (filter_count > 3) {
|
||||
$(f.wrapper).addClass('hidden-sm').addClass('hidden-xs');
|
||||
}
|
||||
if (filter_count > 5) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.standard_filters_added = true;
|
||||
},
|
||||
|
||||
update_standard_filters: function(filters) {
|
||||
let me = this;
|
||||
for(let key in this.page.fields_dict) {
|
||||
let field = this.page.fields_dict[key];
|
||||
let value = field.get_value();
|
||||
if (value) {
|
||||
if (field.df.condition==='like' && !value.includes('%')) {
|
||||
value = '%' + value + '%';
|
||||
}
|
||||
filters.push([
|
||||
me.doctype,
|
||||
field.df.fieldname,
|
||||
field.df.condition || '=',
|
||||
value
|
||||
]);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
clear: function () {
|
||||
this.data = [];
|
||||
this.wrapper.find('.list-select-all').prop('checked', false);
|
||||
this.wrapper.find('.result-list').empty();
|
||||
this.wrapper.find('.result').show();
|
||||
this.wrapper.find('.no-result').hide();
|
||||
this.start = 0;
|
||||
this.onreset && this.onreset();
|
||||
},
|
||||
|
||||
set_filters_from_route_options: function ({clear_filters=true} = {}) {
|
||||
var me = this;
|
||||
if(this.filter_list && clear_filters) {
|
||||
this.filter_list.clear_filters();
|
||||
}
|
||||
|
||||
for(var field in frappe.route_options) {
|
||||
var value = frappe.route_options[field];
|
||||
var doctype = null;
|
||||
|
||||
// if `Child DocType.fieldname`
|
||||
if (field.includes(".")) {
|
||||
doctype = field.split(".")[0];
|
||||
field = field.split(".")[1];
|
||||
}
|
||||
|
||||
// find the table in which the key exists
|
||||
// for example the filter could be {"item_code": "X"}
|
||||
// where item_code is in the child table.
|
||||
|
||||
// we can search all tables for mapping the doctype
|
||||
if (!doctype) {
|
||||
doctype = frappe.meta.get_doctype_for_field(me.doctype, field);
|
||||
}
|
||||
|
||||
if (doctype && me.filter_list) {
|
||||
if ($.isArray(value)) {
|
||||
me.filter_list.add_filter(doctype, field, value[0], value[1]);
|
||||
} else {
|
||||
me.filter_list.add_filter(doctype, field, "=", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
frappe.route_options = null;
|
||||
},
|
||||
|
||||
run: function(more) {
|
||||
setTimeout(() => this._run(more), 100);
|
||||
},
|
||||
|
||||
_run: function (more) {
|
||||
var me = this;
|
||||
if (!more) {
|
||||
this.start = 0;
|
||||
this.onreset && this.onreset();
|
||||
}
|
||||
|
||||
var args = this.get_call_args();
|
||||
this.save_user_settings_locally(args);
|
||||
|
||||
// user_settings are saved by db_query.py when dirty
|
||||
$.extend(args, {
|
||||
user_settings: frappe.model.user_settings[this.doctype]
|
||||
});
|
||||
|
||||
return frappe.call({
|
||||
method: this.opts.method || 'frappe.desk.query_builder.runquery',
|
||||
freeze: this.opts.freeze !== undefined ? this.opts.freeze : true,
|
||||
args: args,
|
||||
callback: function (r) {
|
||||
me.dirty = false;
|
||||
me.render_results(r);
|
||||
},
|
||||
no_spinner: this.opts.no_loading
|
||||
});
|
||||
},
|
||||
save_user_settings_locally: function (args) {
|
||||
if (this.opts.save_user_settings && this.doctype && !this.docname) {
|
||||
// save list settings locally
|
||||
var user_settings = frappe.model.user_settings[this.doctype];
|
||||
var different = false;
|
||||
|
||||
if (!user_settings) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!frappe.utils.arrays_equal(args.filters, user_settings.filters)) {
|
||||
// settings are dirty if filters change
|
||||
user_settings.filters = args.filters;
|
||||
different = true;
|
||||
}
|
||||
|
||||
if (user_settings.order_by !== args.order_by) {
|
||||
user_settings.order_by = args.order_by;
|
||||
different = true;
|
||||
}
|
||||
|
||||
if (user_settings.limit !== args.limit_page_length) {
|
||||
user_settings.limit = args.limit_page_length || 20
|
||||
different = true;
|
||||
}
|
||||
|
||||
// save fields in list settings
|
||||
if (args.save_user_settings_fields) {
|
||||
user_settings.fields = args.fields;
|
||||
}
|
||||
|
||||
if (different) {
|
||||
user_settings.updated_on = moment().toString();
|
||||
}
|
||||
}
|
||||
},
|
||||
get_call_args: function () {
|
||||
// load query
|
||||
if (!this.method) {
|
||||
var query = this.get_query && this.get_query() || this.query;
|
||||
query = this.add_limits(query);
|
||||
var args = {
|
||||
query_max: this.query_max,
|
||||
as_dict: 1
|
||||
}
|
||||
args.simple_query = query;
|
||||
} else {
|
||||
var args = {
|
||||
start: this.start,
|
||||
page_length: this.page_length
|
||||
}
|
||||
}
|
||||
|
||||
// append user-defined arguments
|
||||
if (this.args)
|
||||
$.extend(args, this.args)
|
||||
|
||||
if (this.get_args) {
|
||||
$.extend(args, this.get_args());
|
||||
}
|
||||
return args;
|
||||
},
|
||||
render_results: function (r) {
|
||||
if (this.start === 0)
|
||||
this.clear();
|
||||
|
||||
this.wrapper.find('.btn-more, .list-loading').hide();
|
||||
|
||||
var values = [];
|
||||
|
||||
if (r.message) {
|
||||
values = this.get_values_from_response(r.message);
|
||||
}
|
||||
|
||||
var show_results = true;
|
||||
if(this.show_no_result) {
|
||||
if($.isFunction(this.show_no_result)) {
|
||||
show_results = !this.show_no_result()
|
||||
} else {
|
||||
show_results = !this.show_no_result;
|
||||
}
|
||||
}
|
||||
|
||||
// render result view when
|
||||
// length > 0 OR
|
||||
// explicitly set by flag
|
||||
if (values.length || show_results) {
|
||||
this.data = this.data.concat(values);
|
||||
this.render_view(values);
|
||||
this.update_paging(values);
|
||||
} else if (this.start === 0) {
|
||||
// show no result message
|
||||
this.wrapper.find('.result').hide();
|
||||
|
||||
var msg = '';
|
||||
var no_result_message = this.no_result_message;
|
||||
if(no_result_message && $.isFunction(no_result_message)) {
|
||||
msg = no_result_message();
|
||||
} else if(typeof no_result_message === 'string') {
|
||||
msg = no_result_message;
|
||||
} else {
|
||||
msg = __('No Results')
|
||||
}
|
||||
|
||||
this.wrapper.find('.no-result').html(msg).show();
|
||||
}
|
||||
|
||||
this.wrapper.find('.list-paging-area')
|
||||
.toggle(values.length > 0|| this.start > 0);
|
||||
|
||||
// callbacks
|
||||
if (this.onrun) this.onrun();
|
||||
if (this.callback) this.callback(r);
|
||||
this.wrapper.trigger("render-complete");
|
||||
},
|
||||
|
||||
get_values_from_response: function (data) {
|
||||
// make dictionaries from keys and values
|
||||
if (data.keys && $.isArray(data.keys)) {
|
||||
return frappe.utils.dict(data.keys, data.values);
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
},
|
||||
|
||||
render_view: function (values) {
|
||||
// override this method in derived class
|
||||
},
|
||||
|
||||
update_paging: function (values) {
|
||||
if (values.length >= this.page_length) {
|
||||
this.wrapper.find('.btn-more').show();
|
||||
this.start += this.page_length;
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function () {
|
||||
this.run();
|
||||
},
|
||||
add_limits: function (query) {
|
||||
return query + ' LIMIT ' + this.start + ',' + (this.page_length + 1);
|
||||
},
|
||||
set_filter: function (fieldname, label, no_run, no_duplicate) {
|
||||
var filter = this.filter_list.get_filter(fieldname);
|
||||
if (filter) {
|
||||
var value = cstr(filter.field.get_value());
|
||||
if (value.includes(label)) {
|
||||
// already set
|
||||
return false
|
||||
|
||||
} else if (no_duplicate) {
|
||||
filter.set_values(this.doctype, fieldname, "=", label);
|
||||
} else {
|
||||
// second filter set for this field
|
||||
if (fieldname == '_user_tags' || fieldname == "_liked_by") {
|
||||
// and for tags
|
||||
this.filter_list.add_filter(this.doctype, fieldname, 'like', '%' + label + '%');
|
||||
} else {
|
||||
// or for rest using "in"
|
||||
filter.set_values(this.doctype, fieldname, 'in', value + ', ' + label);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// no filter for this item,
|
||||
// setup one
|
||||
if (['_user_tags', '_comments', '_assign', '_liked_by'].includes(fieldname)) {
|
||||
this.filter_list.add_filter(this.doctype, fieldname, 'like', '%' + label + '%');
|
||||
} else {
|
||||
this.filter_list.add_filter(this.doctype, fieldname, '=', label);
|
||||
}
|
||||
}
|
||||
if (!no_run)
|
||||
this.run();
|
||||
},
|
||||
init_user_settings: function () {
|
||||
this.user_settings = frappe.model.user_settings[this.doctype] || {};
|
||||
},
|
||||
call_for_selected_items: function (method, args) {
|
||||
var me = this;
|
||||
args.names = this.get_checked_items().map(function (item) {
|
||||
return item.name;
|
||||
});
|
||||
|
||||
frappe.call({
|
||||
method: method,
|
||||
args: args,
|
||||
freeze: true,
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
if (me.list_header) {
|
||||
me.list_header.find(".list-select-all").prop("checked", false);
|
||||
}
|
||||
me.refresh(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
53
frappe/tests/test_listview.py
Normal file
53
frappe/tests/test_listview.py
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
import frappe
|
||||
import json
|
||||
|
||||
from frappe.desk.listview import get_list_settings, set_list_settings
|
||||
|
||||
class TestListView(unittest.TestCase):
|
||||
def setUp(self):
|
||||
if frappe.db.exists("List View Setting", "DocType"):
|
||||
frappe.delete_doc("List View Setting", "DocType")
|
||||
|
||||
def test_get_list_settings_without_settings(self):
|
||||
self.assertIsNone(get_list_settings("DocType"), None)
|
||||
|
||||
def test_get_list_settings_with_default_settings(self):
|
||||
frappe.get_doc({"doctype": "List View Setting", "name": "DocType"}).insert()
|
||||
settings = get_list_settings("DocType")
|
||||
self.assertIsNotNone(settings)
|
||||
|
||||
self.assertEqual(settings.disable_auto_refresh, 0)
|
||||
self.assertEqual(settings.disable_count, 0)
|
||||
self.assertEqual(settings.disable_sidebar_stats, 0)
|
||||
|
||||
def test_get_list_settings_with_non_default_settings(self):
|
||||
frappe.get_doc({"doctype": "List View Setting", "name": "DocType", "disable_count": 1}).insert()
|
||||
settings = get_list_settings("DocType")
|
||||
self.assertIsNotNone(settings)
|
||||
|
||||
self.assertEqual(settings.disable_auto_refresh, 0)
|
||||
self.assertEqual(settings.disable_count, 1)
|
||||
self.assertEqual(settings.disable_sidebar_stats, 0)
|
||||
|
||||
def test_set_list_settings_without_settings(self):
|
||||
set_list_settings("DocType", json.dumps({}))
|
||||
settings = frappe.get_doc("List View Setting","DocType")
|
||||
|
||||
self.assertEqual(settings.disable_auto_refresh, 0)
|
||||
self.assertEqual(settings.disable_count, 0)
|
||||
self.assertEqual(settings.disable_sidebar_stats, 0)
|
||||
|
||||
def test_set_list_settings_with_existing_settings(self):
|
||||
frappe.get_doc({"doctype": "List View Setting", "name": "DocType", "disable_count": 1}).insert()
|
||||
set_list_settings("DocType", json.dumps({"disable_count": 0, "disable_auto_refresh": 1}))
|
||||
settings = frappe.get_doc("List View Setting","DocType")
|
||||
|
||||
self.assertEqual(settings.disable_auto_refresh, 1)
|
||||
self.assertEqual(settings.disable_count, 0)
|
||||
self.assertEqual(settings.disable_sidebar_stats, 0)
|
||||
|
||||
Loading…
Add table
Reference in a new issue