@@ -329,11 +329,6 @@ | |||||
"public/js/frappe/list/list_sidebar.js", | "public/js/frappe/list/list_sidebar.js", | ||||
"public/js/frappe/list/list_sidebar.html", | "public/js/frappe/list/list_sidebar.html", | ||||
"public/js/frappe/list/list_sidebar_stat.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/list/list_view_permission_restrictions.html", | ||||
"public/js/frappe/views/gantt/gantt_view.js", | "public/js/frappe/views/gantt/gantt_view.js", | ||||
@@ -343,8 +338,6 @@ | |||||
"public/js/frappe/views/inbox/inbox_view.js", | "public/js/frappe/views/inbox/inbox_view.js", | ||||
"public/js/frappe/views/file/file_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/treeview.js", | ||||
"public/js/frappe/views/interaction.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; | |||||
}, | |||||
}); |
@@ -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); | |||||
} | |||||
} | |||||
}); | |||||
} | |||||
}); |