New ListView 💥 + DataTable (#4577)
* first cut * Code refactoring, styling * Added Sorting * Revert query_report to use slickgrid * cleanup * Edit cell working * Add regrid, remove datatable * Add clusterize * Update lib, fix get_checked_items * New ReportView * wip * Enable editing, fix styles * update lib * wip * fix refresh rows and editable cells * Refresh list_view every 3s, decouple refreshing logic * Report editing fixes * Cleanup loading fields, add column then refresh list * [wip] New List View * [working] Render results * ListView is now BaseList, add new ListView and GanttView * Create new page for each ListView * GanttView working * CalendarView working * KanbanView working * Cache list_view based on page_name * Gantt view buttons on mobile * Add ReportView * Refresh datatable on render * Setup like * [start][filters] clean up FilterList * [filters] refactor FilterList * [filters] minor fix * [filters] fix remove filter * filter utils * more utils, remove apply * rewrite as class, remove 'me' references * [filter] implement on_change to decouple parent functions * Integrate new filters with new BaseList * Setup freeze area for ListView * Set breadcrumbs on setup_page * Trigger list update from events * Setup footnote area * Fix Kanban Board filters * Add filters to standard filters, then filter_list * Remove old files * Fix ImageView * Some more fixes for BaseList.init * Fix order_by on load * Report View: remember columns * Fix for hidden filters * Fix for delete items * InboxView * Shift select checkboxes * Fix ESLint errors * More refactoring - Move ListMenu to Listview - New FileView - Ability to add custom breadcrumbs * FileManager working * Tags, set filters from route options * Custom Reports Working * List Sidebar reports * Report Name as title * Fix ESLint errors * Fix UI tests * Fix Kanban test * Format ID column * [fix] Kanban cards title * Checkbox fix * Fix Activity Page * Update rows in Report in place * Child Table columns in Report View
This commit is contained in:
parent
8895e89315
commit
7595fb75ba
80 changed files with 4844 additions and 4497 deletions
|
|
@ -120,6 +120,7 @@
|
|||
"QUnit": true,
|
||||
"JsBarcode": true,
|
||||
"L": true,
|
||||
"Chart": true
|
||||
"Chart": true,
|
||||
"DataTable": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -355,7 +355,7 @@
|
|||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Folder",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
|
|
@ -653,7 +653,7 @@
|
|||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2017-10-27 13:27:43.882914",
|
||||
"modified": "2017-12-07 17:01:54.860204",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "File",
|
||||
|
|
|
|||
|
|
@ -1,239 +0,0 @@
|
|||
frappe.provide("frappe.ui");
|
||||
|
||||
frappe.listview_settings['File'] = {
|
||||
hide_name_column: true,
|
||||
use_route: true,
|
||||
add_fields: ["is_folder", "file_name", "file_url", "folder", "is_private"],
|
||||
formatters: {
|
||||
file_size: function(value) {
|
||||
// formatter for file size
|
||||
if(value > 1048576) {
|
||||
value = flt(flt(value) / 1048576, 1) + "M";
|
||||
} else if (value > 1024) {
|
||||
value = flt(flt(value) / 1024, 1) + "K";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
},
|
||||
prepare_data: function(data) {
|
||||
// set image icons
|
||||
var icon = "";
|
||||
|
||||
if(data.is_folder) {
|
||||
icon += '<i class="fa fa-folder-close-alt fa-fw"></i> ';
|
||||
} else if(frappe.utils.is_image_file(data.file_name)) {
|
||||
icon += '<i class="fa fa-picture fa-fw"></i> ';
|
||||
} else {
|
||||
icon += '<i class="fa fa-file-alt fa-fw"></i> ';
|
||||
}
|
||||
|
||||
data._title = icon + (data.file_name ? data.file_name : data.file_url);
|
||||
|
||||
if (data.is_private) {
|
||||
data._title += ' <i class="fa fa-lock fa-fw text-warning"></i>';
|
||||
}
|
||||
},
|
||||
onload: function(doclist) {
|
||||
doclist.filter_area = doclist.wrapper.find(".show_filters");
|
||||
|
||||
doclist.breadcrumb = $('<ol class="breadcrumb for-file-list"></ol>')
|
||||
.insertBefore(doclist.filter_area);
|
||||
doclist.list_renderer.settings.setup_menu(doclist);
|
||||
doclist.list_renderer.settings.setup_dragdrop(doclist);
|
||||
|
||||
doclist.$page.on("click", ".list-row-checkbox", function(event) {
|
||||
doclist.list_renderer.settings.add_menu_item_copy(doclist);
|
||||
});
|
||||
},
|
||||
list_view_doc:function(doclist){
|
||||
$(doclist.wrapper).on("click", 'button[list_view_doc="'+doclist.doctype+'"]', function() {
|
||||
frappe.ui.get_upload_dialog({
|
||||
"args": {
|
||||
"folder": doclist.current_folder,
|
||||
"from_form": 1
|
||||
},
|
||||
callback: function() {
|
||||
doclist.refresh();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
setup_menu: function(doclist) {
|
||||
doclist.page.add_menu_item(__("New Folder"), function() {
|
||||
var d = frappe.prompt(__("Name"), function(values) {
|
||||
if((values.value.indexOf("/") > -1)){
|
||||
frappe.throw(__("Folder name should not include '/' (slash)"));
|
||||
return;
|
||||
}
|
||||
var data = {
|
||||
"file_name": values.value,
|
||||
"folder": doclist.current_folder
|
||||
};
|
||||
frappe.call({
|
||||
method: "frappe.core.doctype.file.file.create_new_folder",
|
||||
args: data,
|
||||
callback: function(r) { }
|
||||
});
|
||||
}, __('Enter folder name'), __("Create"));
|
||||
});
|
||||
|
||||
doclist.page.add_menu_item(__("Edit Folder"), function() {
|
||||
frappe.set_route("Form", "File", doclist.current_folder);
|
||||
});
|
||||
|
||||
doclist.page.add_menu_item(__("Import .zip"), function() {
|
||||
// make upload dialog
|
||||
frappe.ui.get_upload_dialog({
|
||||
args: {
|
||||
folder: doclist.current_folder,
|
||||
from_form: 1
|
||||
},
|
||||
callback: function(attachment, r) {
|
||||
frappe.call({
|
||||
method: "frappe.core.doctype.file.file.unzip_file",
|
||||
args: {
|
||||
name: r.message["name"],
|
||||
},
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
//doclist.refresh();
|
||||
} else {
|
||||
frappe.msgprint(__("Error in uploading files" + r.exc));
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
setup_dragdrop: function(doclist) {
|
||||
$(doclist.$page).on('dragenter dragover', false)
|
||||
.on('drop', function (e) {
|
||||
var dataTransfer = e.originalEvent.dataTransfer;
|
||||
if (!(dataTransfer && dataTransfer.files && dataTransfer.files.length > 0)) {
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
frappe.upload.multifile_upload(dataTransfer.files, {
|
||||
"folder": doclist.current_folder,
|
||||
"from_form": 1
|
||||
}, {
|
||||
confirm_is_private: 1
|
||||
});
|
||||
});
|
||||
},
|
||||
add_menu_item_copy: function(doclist){
|
||||
if (!doclist.copy) {
|
||||
var copy_menu = doclist.page.add_menu_item(__("Copy"), function() {
|
||||
if(doclist.$page.find(".list-row-checkbox:checked").length){
|
||||
doclist.selected_files = doclist.get_checked_items();
|
||||
doclist.old_parent = doclist.current_folder;
|
||||
doclist.list_renderer.settings.add_menu_item_paste(doclist);
|
||||
}
|
||||
else{
|
||||
frappe.throw(__("Please select file to copy"));
|
||||
}
|
||||
});
|
||||
doclist.copy = true;
|
||||
}
|
||||
},
|
||||
add_menu_item_paste:function(doclist){
|
||||
var paste_menu = doclist.page.add_menu_item(__("Paste"), function(){
|
||||
frappe.call({
|
||||
method:"frappe.core.doctype.file.file.move_file",
|
||||
args: {
|
||||
"file_list": doclist.selected_files,
|
||||
"new_parent": doclist.current_folder,
|
||||
"old_parent": doclist.old_parent
|
||||
},
|
||||
callback:function(r){
|
||||
doclist.paste = false;
|
||||
frappe.msgprint(__(r.message));
|
||||
doclist.selected_files = [];
|
||||
$(paste_menu).remove();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
before_run: function(doclist) {
|
||||
var name_filter = doclist.filter_list.get_filter("file_name");
|
||||
if(name_filter) {
|
||||
doclist.filter_area.removeClass("hide");
|
||||
doclist.breadcrumb.addClass("hide");
|
||||
} else {
|
||||
doclist.filter_area.addClass("hide");
|
||||
doclist.breadcrumb.removeClass("hide");
|
||||
}
|
||||
},
|
||||
refresh: function(doclist) {
|
||||
var name_filter = doclist.filter_list.get_filter("file_name");
|
||||
|
||||
var folder_filter = doclist.filter_list.get_filter("folder");
|
||||
if(folder_filter) {
|
||||
folder_filter.remove(true);
|
||||
}
|
||||
|
||||
if(name_filter) return;
|
||||
|
||||
var route = frappe.get_route();
|
||||
if(route[2]) {
|
||||
doclist.current_folder = route.slice(2).join("/");
|
||||
doclist.current_folder_name = route.slice(-1)[0];
|
||||
}
|
||||
|
||||
if(!doclist.current_folder || doclist.current_folder=="List") {
|
||||
doclist.current_folder = frappe.boot.home_folder;
|
||||
doclist.current_folder_name = __("Home");
|
||||
}
|
||||
|
||||
doclist.filter_list.add_filter("File", "folder", "=", doclist.current_folder, true);
|
||||
doclist.dirty = true;
|
||||
doclist.fresh = false;
|
||||
|
||||
doclist.page.set_title(doclist.current_folder_name);
|
||||
frappe.utils.set_title(doclist.current_folder_name);
|
||||
},
|
||||
set_primary_action:function(doclist){
|
||||
doclist.page.clear_primary_action();
|
||||
doclist.page.set_primary_action(__("New"), function() {
|
||||
frappe.ui.get_upload_dialog({
|
||||
"args": {
|
||||
"folder": doclist.current_folder,
|
||||
"from_form": 1
|
||||
},
|
||||
callback: function() {
|
||||
doclist.refresh();
|
||||
}
|
||||
});
|
||||
}, "octicon octicon-plus");
|
||||
},
|
||||
post_render_item: function(list, row, data) {
|
||||
if(data.is_folder) {
|
||||
$(row).find(".list-id").attr("href", "#List/File/" + data.name);
|
||||
}
|
||||
},
|
||||
set_file_route: function(name) {
|
||||
frappe.set_route(["List", "File"].concat(decodeURIComponent(name).split("/")));
|
||||
},
|
||||
post_render: function(doclist) {
|
||||
frappe.call({
|
||||
method: "frappe.core.doctype.file.file.get_breadcrumbs",
|
||||
args: {
|
||||
folder: doclist.current_folder
|
||||
},
|
||||
callback: function(r) {
|
||||
doclist.breadcrumb.empty();
|
||||
if(r.message && r.message.length) {
|
||||
$.each(r.message, function(i, folder) {
|
||||
$('<li><a href="#List/File/'+folder.name+'">'
|
||||
+ folder.file_name+'</a></li>')
|
||||
.appendTo(doclist.breadcrumb);
|
||||
});
|
||||
}
|
||||
$('<li class="active">'+ doclist.current_folder_name+'</li>')
|
||||
.appendTo(doclist.breadcrumb);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
23
frappe/core/doctype/file/test_file.js
Normal file
23
frappe/core/doctype/file/test_file.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: File", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new File
|
||||
() => frappe.tests.make('File', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -22,7 +22,7 @@ cur_frm.cscript.refresh = function(doc) {
|
|||
cur_frm.add_custom_button("Show Report", function() {
|
||||
switch(doc.report_type) {
|
||||
case "Report Builder":
|
||||
frappe.set_route("Report", doc.ref_doctype, doc.name);
|
||||
frappe.set_route('List', doc.ref_doctype, 'Report', doc.name);
|
||||
break;
|
||||
case "Query Report":
|
||||
frappe.set_route("query-report", doc.name);
|
||||
|
|
|
|||
|
|
@ -262,6 +262,7 @@ $.extend(frappe.desktop, {
|
|||
}
|
||||
|
||||
new Sortable($("#icon-grid").get(0), {
|
||||
animation: 150,
|
||||
onUpdate: function(event) {
|
||||
var new_order = [];
|
||||
$("#icon-grid .case-wrapper").each(function(i, e) {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ QUnit.test("test: Event", function (assert) {
|
|||
() => frappe.set_route('List', 'Event', 'Calendar'),
|
||||
() => frappe.timeout(2),
|
||||
() => {
|
||||
const bg_color = $(`.result-list:visible .fc-day-grid-event:contains("${subject}")`)
|
||||
const bg_color = $(`.result:visible .fc-day-grid-event:contains("${subject}")`)
|
||||
.css('background-color');
|
||||
assert.equal(bg_color, rgb, 'Event background color is set correctly');
|
||||
},
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ frappe.ui.form.on('Kanban Board', {
|
|||
field_name: function(frm) {
|
||||
var field = frappe.meta.get_field(frm.doc.reference_doctype, frm.doc.field_name);
|
||||
frm.doc.columns = [];
|
||||
field.options && field.options.split('\n').forEach(function(o, i) {
|
||||
field.options && field.options.split('\n').forEach(function(o) {
|
||||
o = o.trim();
|
||||
if(!o) return;
|
||||
var d = frm.add_child('columns');
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#page-activity .list-row {
|
||||
border: none;
|
||||
padding: 0px;
|
||||
height: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,41 +15,10 @@ frappe.pages['activity'].on_page_load = function(wrapper) {
|
|||
me.page.set_title(__("Activity"));
|
||||
|
||||
frappe.model.with_doctype("Communication", function() {
|
||||
me.page.list = new frappe.ui.BaseList({
|
||||
hide_refresh: true,
|
||||
page: me.page,
|
||||
method: 'frappe.desk.page.activity.activity.get_feed',
|
||||
parent: $("<div></div>").appendTo(me.page.main),
|
||||
render_view: function (values) {
|
||||
var me = this;
|
||||
wrapper = me.page.main.find(".result-list").get(0)
|
||||
values.map(function (value) {
|
||||
var row = $('<div class="list-row">')
|
||||
.data("data", value)
|
||||
.appendTo($(wrapper)).get(0);
|
||||
new frappe.activity.Feed(row, value);
|
||||
});
|
||||
},
|
||||
show_filters: true,
|
||||
doctype: "Communication",
|
||||
get_args: function() {
|
||||
if (frappe.route_options && frappe.route_options.show_likes) {
|
||||
delete frappe.route_options.show_likes;
|
||||
return {
|
||||
show_likes: true
|
||||
}
|
||||
} else {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
me.page.list = new frappe.views.Activity({
|
||||
doctype: 'Communication',
|
||||
parent: wrapper
|
||||
});
|
||||
|
||||
me.page.list.run();
|
||||
|
||||
me.page.set_primary_action(__("Refresh"), function() {
|
||||
me.page.list.filter_list.clear_filters();
|
||||
me.page.list.run();
|
||||
}, "octicon octicon-sync");
|
||||
});
|
||||
|
||||
frappe.activity.render_heatmap(me.page);
|
||||
|
|
@ -90,7 +59,7 @@ frappe.pages['activity'].on_page_load = function(wrapper) {
|
|||
frappe.route_options = {
|
||||
show_likes: true
|
||||
};
|
||||
me.page.list.run();
|
||||
me.page.list.refresh();
|
||||
}, 'octicon octicon-heart');
|
||||
};
|
||||
|
||||
|
|
@ -195,3 +164,47 @@ frappe.activity.render_heatmap = function(page) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
frappe.views.Activity = class Activity extends frappe.views.BaseList {
|
||||
|
||||
setup_defaults() {
|
||||
super.setup_defaults();
|
||||
|
||||
this.doctype = 'Communication';
|
||||
this.method = 'frappe.desk.page.activity.activity.get_feed';
|
||||
|
||||
}
|
||||
|
||||
setup_filter_area() {
|
||||
//
|
||||
}
|
||||
|
||||
setup_sort_selector() {
|
||||
|
||||
}
|
||||
|
||||
get_args() {
|
||||
return {
|
||||
start: this.start,
|
||||
page_length: this.page_length,
|
||||
show_likes: (frappe.route_options || {}).show_likes || 0
|
||||
};
|
||||
}
|
||||
|
||||
update_data(r) {
|
||||
let data = r.message || [];
|
||||
|
||||
if (this.start === 0) {
|
||||
this.data = data;
|
||||
} else {
|
||||
this.data = this.data.concat(data);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
this.data.map(value => {
|
||||
const row = $('<div class="list-row">').data("data", value).appendTo(this.$result).get(0);
|
||||
new frappe.activity.Feed(row, value);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -215,6 +215,8 @@ def delete_items():
|
|||
il = json.loads(frappe.form_dict.get('items'))
|
||||
doctype = frappe.form_dict.get('doctype')
|
||||
|
||||
failed = []
|
||||
|
||||
for i, d in enumerate(il):
|
||||
try:
|
||||
frappe.delete_doc(doctype, d)
|
||||
|
|
@ -223,7 +225,9 @@ def delete_items():
|
|||
dict(progress=[i+1, len(il)], title=_('Deleting {0}').format(doctype)),
|
||||
user=frappe.session.user)
|
||||
except Exception:
|
||||
pass
|
||||
failed.append(d)
|
||||
|
||||
return failed
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_sidebar_stats(stats, doctype, filters=[]):
|
||||
|
|
|
|||
|
|
@ -925,7 +925,12 @@ class Document(BaseDocument):
|
|||
|
||||
if not self.meta.get("read_only") and not self.meta.get("issingle") and \
|
||||
not self.meta.get("istable"):
|
||||
frappe.publish_realtime("list_update", {"doctype": self.doctype}, after_commit=True)
|
||||
data = {
|
||||
"doctype": self.doctype,
|
||||
"name": self.name,
|
||||
"user": frappe.session.user
|
||||
}
|
||||
frappe.publish_realtime("list_update", data, after_commit=True)
|
||||
|
||||
def db_set(self, fieldname, value=None, update_modified=True, notify=False, commit=False):
|
||||
'''Set a value in the document object, update the timestamp and update the database.
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@
|
|||
"public/css/font-awesome.css",
|
||||
"public/css/octicons/octicons.css",
|
||||
"public/css/desk.css",
|
||||
"public/css/flex.css",
|
||||
"public/css/indicator.css",
|
||||
"public/css/avatar.css",
|
||||
"public/css/navbar.css",
|
||||
|
|
@ -212,6 +213,7 @@
|
|||
"public/js/frappe/misc/help_links.js",
|
||||
"public/js/frappe/misc/address_and_contact.js",
|
||||
"public/js/frappe/misc/preview_email.js",
|
||||
"public/js/frappe/misc/file_manager.js",
|
||||
|
||||
"public/js/frappe/ui/upload.html",
|
||||
"public/js/frappe/upload.js",
|
||||
|
|
@ -299,10 +301,10 @@
|
|||
"js/list.min.js": [
|
||||
"public/js/frappe/ui/listing.html",
|
||||
|
||||
"public/js/frappe/ui/base_list.js",
|
||||
|
||||
"public/js/frappe/model/indicator.js",
|
||||
"public/js/frappe/ui/filters/filters.js",
|
||||
"public/js/frappe/ui/filters/filter.js",
|
||||
"public/js/frappe/ui/filters/filter_list.js",
|
||||
"public/js/frappe/ui/filters/field_select.js",
|
||||
"public/js/frappe/ui/filters/edit_filter.html",
|
||||
"public/js/frappe/ui/tags.js",
|
||||
"public/js/frappe/ui/tag_editor.js",
|
||||
|
|
@ -310,7 +312,9 @@
|
|||
"public/js/frappe/ui/liked_by.html",
|
||||
"public/html/print_template.html",
|
||||
|
||||
"public/js/frappe/list/base_list.js",
|
||||
"public/js/frappe/list/list_view.js",
|
||||
"public/js/frappe/list/list_factory.js",
|
||||
|
||||
"public/js/frappe/list/list_sidebar.js",
|
||||
"public/js/frappe/list/list_sidebar.html",
|
||||
|
|
@ -322,12 +326,12 @@
|
|||
"public/js/frappe/list/list_item_subject.html",
|
||||
"public/js/frappe/list/list_permission_footer.html",
|
||||
|
||||
"public/js/frappe/list/list_renderer.js",
|
||||
"public/js/frappe/views/gantt/gantt_view.js",
|
||||
"public/js/frappe/views/calendar/calendar.js",
|
||||
"public/js/frappe/views/image/image_view.js",
|
||||
"public/js/frappe/views/kanban/kanban_view.js",
|
||||
"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",
|
||||
|
|
@ -336,10 +340,6 @@
|
|||
"public/js/frappe/views/image/image_view_item_row.html",
|
||||
"public/js/frappe/views/image/photoswipe_dom.html",
|
||||
|
||||
"public/js/frappe/views/inbox/inbox_no_result.html",
|
||||
"public/js/frappe/views/inbox/inbox_view_item_row.html",
|
||||
"public/js/frappe/views/inbox/inbox_view_item_main_head.html",
|
||||
|
||||
"public/js/frappe/views/kanban/kanban_board.html",
|
||||
"public/js/frappe/views/kanban/kanban_column.html",
|
||||
"public/js/frappe/views/kanban/kanban_card.html"
|
||||
|
|
@ -347,13 +347,17 @@
|
|||
"css/report.min.css": [
|
||||
"public/css/report.css",
|
||||
"public/css/tree_grid.css",
|
||||
"public/css/frappe-datatable.css",
|
||||
|
||||
"public/js/lib/slickgrid/slick.grid.css",
|
||||
"public/js/lib/slickgrid/slick-default-theme.css",
|
||||
"public/css/slickgrid.css"
|
||||
],
|
||||
"js/report.min.js": [
|
||||
"public/js/lib/clusterize.min.js",
|
||||
"public/js/lib/frappe-datatable.js",
|
||||
"public/js/frappe/views/reports/reportview.js",
|
||||
"public/js/frappe/views/reports/report_view.js",
|
||||
"public/js/frappe/views/reports/reportview_footer.html",
|
||||
"public/js/frappe/views/reports/query_report.js",
|
||||
"public/js/frappe/views/reports/grid_report.js",
|
||||
|
|
|
|||
|
|
@ -216,6 +216,18 @@ a.no-decoration:active {
|
|||
.margin {
|
||||
margin: 15px;
|
||||
}
|
||||
.margin-top {
|
||||
margin-top: 15px;
|
||||
}
|
||||
.margin-bottom {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.margin-left {
|
||||
margin-left: 15px;
|
||||
}
|
||||
.margin-right {
|
||||
margin-right: 15px;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.text-center-xs {
|
||||
text-align: center;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,12 @@
|
|||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.unit-checkbox label {
|
||||
position: relative;
|
||||
}
|
||||
.unit-checkbox input[type=checkbox] {
|
||||
margin-left: 0;
|
||||
}
|
||||
.unit-checkbox + .checkbox {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
|
|
|
|||
|
|
@ -216,6 +216,18 @@ a.no-decoration:active {
|
|||
.margin {
|
||||
margin: 15px;
|
||||
}
|
||||
.margin-top {
|
||||
margin-top: 15px;
|
||||
}
|
||||
.margin-bottom {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.margin-left {
|
||||
margin-left: 15px;
|
||||
}
|
||||
.margin-right {
|
||||
margin-right: 15px;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.text-center-xs {
|
||||
text-align: center;
|
||||
|
|
@ -486,6 +498,9 @@ fieldset[disabled] .form-control {
|
|||
.form-control input {
|
||||
padding: 6px 10px 8px;
|
||||
}
|
||||
.input-area {
|
||||
position: relative;
|
||||
}
|
||||
.link-field.ui-front {
|
||||
z-index: inherit;
|
||||
}
|
||||
|
|
@ -587,10 +602,6 @@ li.user-progress .progress-bar {
|
|||
.intro-area {
|
||||
padding: 15px 30px;
|
||||
}
|
||||
.footnote-area {
|
||||
padding: 0px 15px;
|
||||
border-top: 1px solid #d1d8dd;
|
||||
}
|
||||
.file-upload .input-group-addon {
|
||||
color: #8D99A6;
|
||||
font-size: 12px;
|
||||
|
|
@ -972,7 +983,7 @@ li.user-progress .progress-bar {
|
|||
}
|
||||
input[type="checkbox"] {
|
||||
position: relative;
|
||||
height: 16px;
|
||||
left: -999999px;
|
||||
}
|
||||
input[type="checkbox"]:before {
|
||||
position: absolute;
|
||||
|
|
@ -990,9 +1001,7 @@ input[type="checkbox"]:before {
|
|||
-webkit-transition: 150ms color;
|
||||
-o-transition: 150ms color;
|
||||
transition: 150ms color;
|
||||
background-color: white;
|
||||
padding: 1px;
|
||||
margin: -1px;
|
||||
left: 999999px;
|
||||
}
|
||||
input[type="checkbox"]:focus:before {
|
||||
color: #8D99A6;
|
||||
|
|
|
|||
41
frappe/public/css/flex.css
Normal file
41
frappe/public/css/flex.css
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
.align-center {
|
||||
align-items: center;
|
||||
}
|
||||
.level {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.level-left,
|
||||
.level-right {
|
||||
display: flex;
|
||||
flex-basis: auto;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
}
|
||||
.level-left.is-flexible,
|
||||
.level-right.is-flexible {
|
||||
flex-grow: initial;
|
||||
flex-shrink: initial;
|
||||
}
|
||||
.level-left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.level-right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.level-item {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-basis: auto;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
58
frappe/public/css/frappe-datatable.css
Normal file
58
frappe/public/css/frappe-datatable.css
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
.data-table {
|
||||
margin-left: -1px;
|
||||
margin-top: -1px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.data-table .data-table-col .edit-cell {
|
||||
padding: 0;
|
||||
}
|
||||
.data-table .data-table-col .edit-cell input {
|
||||
font-size: inherit;
|
||||
height: 34px;
|
||||
}
|
||||
.data-table .frappe-control {
|
||||
margin: 0;
|
||||
}
|
||||
.data-table .form-group {
|
||||
margin: 0;
|
||||
}
|
||||
.data-table .form-control {
|
||||
border-radius: 0px;
|
||||
border: none;
|
||||
}
|
||||
.data-table .link-btn {
|
||||
top: 6px;
|
||||
}
|
||||
.data-table select {
|
||||
height: 34px;
|
||||
}
|
||||
.data-table .checkbox {
|
||||
margin: 7px 0 7px 8px;
|
||||
}
|
||||
.data-table [data-fieldtype="Color"] .control-input {
|
||||
overflow: hidden;
|
||||
}
|
||||
.data-table .body-scrollable::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.data-table .data-table-header {
|
||||
background-color: #F7FAFC;
|
||||
color: #8D99A6;
|
||||
}
|
||||
.data-table .data-table-row.row-update {
|
||||
animation: 500ms breathe forwards;
|
||||
}
|
||||
.data-table .data-table-row.row-highlight {
|
||||
background-color: #fffdf4;
|
||||
}
|
||||
@keyframes breathe {
|
||||
0% {
|
||||
background-color: transparent;
|
||||
}
|
||||
50% {
|
||||
background-color: #fffdf4;
|
||||
}
|
||||
100% {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +1,63 @@
|
|||
.no-result {
|
||||
padding: 150px 15px;
|
||||
color: #8D99A6;
|
||||
.result,
|
||||
.no-result,
|
||||
.freeze {
|
||||
min-height: calc(100vh - 284px);
|
||||
}
|
||||
.result-list {
|
||||
min-height: 400px;
|
||||
.freeze-row .level-left,
|
||||
.freeze-row .level-right,
|
||||
.freeze-row .list-row-col {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.freeze-row .list-row-col {
|
||||
background-color: #d1d8dd;
|
||||
border-radius: 2px;
|
||||
animation: 2s breathe infinite;
|
||||
}
|
||||
@keyframes breathe {
|
||||
0% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
.sort-selector .dropdown:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.list-filters {
|
||||
.filter-list {
|
||||
position: relative;
|
||||
}
|
||||
.list-filters .sort-selector {
|
||||
.filter-list .sort-selector {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
}
|
||||
.show_filters {
|
||||
.tag-filters-area {
|
||||
padding: 15px 15px 0px;
|
||||
border-bottom: 1px solid #d1d8dd;
|
||||
}
|
||||
.set-filters {
|
||||
.active-tag-filters {
|
||||
padding-bottom: 4px;
|
||||
padding-right: 120px;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.set-filters {
|
||||
.active-tag-filters {
|
||||
padding-right: 80px;
|
||||
}
|
||||
}
|
||||
.set-filters .btn {
|
||||
.active-tag-filters .btn {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.set-filters .btn-group {
|
||||
margin-right: 10px;
|
||||
.active-tag-filters .btn-group {
|
||||
margin-left: 10px;
|
||||
white-space: nowrap;
|
||||
font-size: 0;
|
||||
}
|
||||
.set-filters .btn-group .btn-default {
|
||||
.active-tag-filters .btn-group .btn-default {
|
||||
background-color: transparent;
|
||||
border: 1px solid #d1d8dd;
|
||||
color: #8D99A6;
|
||||
|
|
@ -51,138 +71,107 @@
|
|||
margin-top: 6px;
|
||||
margin-left: 15px;
|
||||
}
|
||||
.filter-box .filter_field {
|
||||
.filter-box .filter-field {
|
||||
padding-right: 15px;
|
||||
width: calc(64%);
|
||||
}
|
||||
.filter-box .filter_field .frappe-control {
|
||||
.filter-box .filter-field .frappe-control {
|
||||
position: relative;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
@media (min-width: 767px) {
|
||||
.filter-box .row > div[class*="col-sm-"] {
|
||||
padding-right: 0px;
|
||||
}
|
||||
.filter_field {
|
||||
.filter-field {
|
||||
width: 65% !important;
|
||||
}
|
||||
.filter_field .frappe-control {
|
||||
.filter-field .frappe-control {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
.list-row {
|
||||
padding: 9px 15px;
|
||||
.list-row-container {
|
||||
border-bottom: 1px solid #d1d8dd;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.list-row {
|
||||
padding: 12px 15px;
|
||||
height: 40px;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
-webkit-transition: color 0.2s;
|
||||
}
|
||||
.list-row .h6 {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.list-row-head {
|
||||
.list-row:hover {
|
||||
background-color: #F7FAFC;
|
||||
border-bottom: 1px solid #d1d8dd !important;
|
||||
}
|
||||
.list-row:hover,
|
||||
.grid-row:hover {
|
||||
background-color: #F7FAFC;
|
||||
}
|
||||
.no-hover:hover {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
.list-row:last-child {
|
||||
border-bottom: 0px;
|
||||
}
|
||||
.list-row .level-left {
|
||||
flex: 3;
|
||||
}
|
||||
.list-row .level-right {
|
||||
flex: 1;
|
||||
}
|
||||
.list-row-head {
|
||||
background-color: #F7FAFC;
|
||||
border-bottom: 1px solid #d1d8dd !important;
|
||||
}
|
||||
.list-row .h6 {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
.list-row-head .list-subject {
|
||||
font-weight: normal;
|
||||
}
|
||||
.list-item-col {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
height: 30px;
|
||||
padding-top: 3px;
|
||||
.list-row-head .checkbox-actions {
|
||||
display: none;
|
||||
}
|
||||
.list-paging-area {
|
||||
.list-row-col {
|
||||
flex: 1;
|
||||
margin-right: 15px;
|
||||
}
|
||||
.list-subject {
|
||||
flex: 2;
|
||||
font-weight: bold;
|
||||
justify-content: start;
|
||||
}
|
||||
.list-subject .level-item {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.list-subject.seen {
|
||||
font-weight: normal;
|
||||
}
|
||||
.list-row-activity {
|
||||
justify-content: flex-end;
|
||||
min-width: 120px;
|
||||
}
|
||||
.list-row-activity .avatar:not(.avatar-empty) {
|
||||
margin: 0;
|
||||
}
|
||||
.list-row-activity > span {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
margin-right: 0;
|
||||
}
|
||||
.list-paging-area,
|
||||
.footnote-area {
|
||||
padding: 10px 15px;
|
||||
border-top: 1px solid #d1d8dd;
|
||||
}
|
||||
.list-value {
|
||||
display: table;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.list-value .list-row-checkbox,
|
||||
.list-value .liked-by,
|
||||
.list-value .list-id,
|
||||
.list-value .list-select-all {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.list-value .list-row-checkbox,
|
||||
.list-value .list-select-all {
|
||||
margin: 0;
|
||||
margin-right: 7px;
|
||||
}
|
||||
.list-value .liked-by {
|
||||
padding-top: 2px;
|
||||
}
|
||||
.list-value .list-col-title {
|
||||
vertical-align: middle;
|
||||
overflow: auto;
|
||||
}
|
||||
.progress {
|
||||
height: 10px;
|
||||
}
|
||||
.doclist-row {
|
||||
font-size: 12px;
|
||||
}
|
||||
.likes-count {
|
||||
display: inline-block;
|
||||
width: 15px;
|
||||
margin-left: -5px;
|
||||
color: #8D99A6;
|
||||
font-size: 12px;
|
||||
display: none;
|
||||
}
|
||||
.doclist-row .docstatus .octicon {
|
||||
font-size: 12px;
|
||||
.list-liked-by-me {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
.doclist-row .progress {
|
||||
margin-top: 12px;
|
||||
input.list-check-all,
|
||||
input.list-row-checkbox {
|
||||
margin-top: 0px;
|
||||
}
|
||||
.filterable {
|
||||
cursor: pointer;
|
||||
}
|
||||
.doclist-row .label {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.list-info-row {
|
||||
float: left;
|
||||
margin-top: 1px;
|
||||
}
|
||||
.list-row-right .modified {
|
||||
margin-top: 3px;
|
||||
}
|
||||
.list-row-right .list-row-modified {
|
||||
margin-right: 9px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
.list-row-right {
|
||||
margin-top: -2px;
|
||||
margin-bottom: -4px;
|
||||
}
|
||||
.list-row-right .indicator {
|
||||
margin-left: 10px;
|
||||
margin-right: -5px;
|
||||
}
|
||||
.side-panel {
|
||||
border-bottom: 1px solid #d1d8dd;
|
||||
margin: 0px -15px;
|
||||
padding: 5px 15px;
|
||||
}
|
||||
.listview-main-section .octicon-heart {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
@ -208,38 +197,17 @@
|
|||
.like-action.octicon-heart {
|
||||
color: #ff5858;
|
||||
}
|
||||
.list-id {
|
||||
font-weight: bold;
|
||||
}
|
||||
.list-id.seen {
|
||||
font-weight: normal;
|
||||
}
|
||||
.list-col {
|
||||
height: 20px;
|
||||
}
|
||||
.list-value {
|
||||
vertical-align: middle;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.doclist-row {
|
||||
font-size: 14px;
|
||||
}
|
||||
.doclist-row [type='checkbox'] {
|
||||
display: none;
|
||||
}
|
||||
.doclist-row .list-row-right .list-row-modified {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.list-comment-count {
|
||||
display: inline-block;
|
||||
width: 37px;
|
||||
text-align: left;
|
||||
}
|
||||
.result.tags-shown .tag-row {
|
||||
display: block;
|
||||
}
|
||||
.tag-row {
|
||||
padding-left: 55px;
|
||||
margin-bottom: 0px;
|
||||
margin-top: -5px;
|
||||
display: none;
|
||||
margin-left: 50px;
|
||||
}
|
||||
.taggle_placeholder {
|
||||
top: 0;
|
||||
|
|
@ -300,6 +268,7 @@
|
|||
padding: 15px;
|
||||
border-bottom: 1px solid #EBEFF2;
|
||||
border-right: 1px solid #EBEFF2;
|
||||
max-width: 25%;
|
||||
}
|
||||
.image-view-container .image-view-item:nth-child(4n) {
|
||||
border-right: none;
|
||||
|
|
@ -329,6 +298,9 @@
|
|||
.image-view-container .image-field img {
|
||||
max-height: 100%;
|
||||
}
|
||||
.image-view-container .image-field.no-image {
|
||||
background-color: #fafbfc;
|
||||
}
|
||||
.image-view-container .placeholder-text {
|
||||
font-size: 72px;
|
||||
color: #d1d8dd;
|
||||
|
|
@ -351,6 +323,7 @@
|
|||
@media (max-width: 991px) {
|
||||
.image-view-container .image-view-item {
|
||||
flex: 0 0 33.33333333%;
|
||||
max-width: 33.33333333%;
|
||||
}
|
||||
.image-view-container .image-view-item:nth-child(3n) {
|
||||
border-right: none;
|
||||
|
|
@ -381,6 +354,7 @@
|
|||
}
|
||||
.image-view-container.three-column .image-view-item {
|
||||
flex: 0 0 33.33333333%;
|
||||
max-width: 33.33333333%;
|
||||
}
|
||||
.image-view-container.three-column .image-view-item:nth-child(3n) {
|
||||
border-right: none;
|
||||
|
|
@ -424,6 +398,10 @@
|
|||
.pswp__more-item img {
|
||||
max-height: 100%;
|
||||
}
|
||||
.list-paging-area .gantt-view-mode {
|
||||
margin-left: 15px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
.gantt .details-container .heading {
|
||||
margin-bottom: 10px;
|
||||
font-size: 12px;
|
||||
|
|
|
|||
33
frappe/public/css/regrid.css
Normal file
33
frappe/public/css/regrid.css
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
.data-table {
|
||||
font-size: 14px;
|
||||
}
|
||||
.data-table .frappe-control {
|
||||
margin: 0;
|
||||
}
|
||||
.data-table .form-group {
|
||||
margin: 0;
|
||||
}
|
||||
.data-table .form-control {
|
||||
border-radius: 0px;
|
||||
border: none;
|
||||
}
|
||||
.data-table .link-btn {
|
||||
top: 9px;
|
||||
}
|
||||
.data-table select {
|
||||
height: 36px;
|
||||
}
|
||||
.data-table .edit-cell {
|
||||
border: 2px solid #7679FC;
|
||||
}
|
||||
.data-table .checkbox {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.data-table [data-fieldtype="Color"] .control-input {
|
||||
overflow: hidden;
|
||||
}
|
||||
.data-table .data-table-col.selected .content {
|
||||
border-color: #7679FC;
|
||||
}
|
||||
|
|
@ -51,3 +51,9 @@
|
|||
.column-picker-dialog .add-btn {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.data-table .edit-popup .frappe-control {
|
||||
padding: 0;
|
||||
}
|
||||
.data-table .edit-popup .frappe-control .form-group {
|
||||
margin: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -216,6 +216,18 @@ a.no-decoration:active {
|
|||
.margin {
|
||||
margin: 15px;
|
||||
}
|
||||
.margin-top {
|
||||
margin-top: 15px;
|
||||
}
|
||||
.margin-bottom {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.margin-left {
|
||||
margin-left: 15px;
|
||||
}
|
||||
.margin-right {
|
||||
margin-right: 15px;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.text-center-xs {
|
||||
text-align: center;
|
||||
|
|
|
|||
|
|
@ -56,5 +56,14 @@ frappe.db = {
|
|||
callback && callback(r.message);
|
||||
}
|
||||
});
|
||||
},
|
||||
get_doc: function(doctype, name, filters = null) {
|
||||
return new Promise(resolve => {
|
||||
frappe.call({
|
||||
method: "frappe.client.get",
|
||||
args: { doctype, name, filters },
|
||||
callback: r => resolve(r.message)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,9 +8,12 @@ frappe.ui.form.ControlAutocomplete = frappe.ui.form.ControlData.extend({
|
|||
set_options() {
|
||||
if (this.df.options) {
|
||||
let options = this.df.options || [];
|
||||
if(typeof options === 'string') {
|
||||
if (typeof options === 'string') {
|
||||
options = options.split('\n');
|
||||
}
|
||||
if (typeof options[0] === 'string') {
|
||||
options = options.map(o => ({label: o, value: o}));
|
||||
}
|
||||
this._data = options;
|
||||
}
|
||||
},
|
||||
|
|
@ -20,13 +23,14 @@ frappe.ui.form.ControlAutocomplete = frappe.ui.form.ControlData.extend({
|
|||
minChars: 0,
|
||||
maxItems: 99,
|
||||
autoFirst: true,
|
||||
list: this.get_data()
|
||||
list: this.get_data(),
|
||||
sort: () => {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
setup_awesomplete() {
|
||||
var me = this;
|
||||
|
||||
this.awesomplete = new Awesomplete(this.input, this.get_awesomplete_settings());
|
||||
|
||||
$(this.input_area).find('.awesomplete ul').css('min-width', '100%');
|
||||
|
|
|
|||
|
|
@ -79,7 +79,6 @@ frappe.ui.form.ControlColor = frappe.ui.form.ControlData.extend({
|
|||
if(is_valid) {
|
||||
return value;
|
||||
}
|
||||
frappe.msgprint(__("{0} is not a valid hex color", [value]));
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -179,8 +179,8 @@ frappe.ui.form.AssignToDialog = Class.extend({
|
|||
});
|
||||
|
||||
frappe.ui.add_assignment = function(opts, dialog) {
|
||||
var assign_to = opts.obj.dialog.fields_dict.assign_to.get_value();
|
||||
var args = opts.obj.dialog.get_values();
|
||||
var assign_to = dialog.fields_dict.assign_to.get_value();
|
||||
var args = dialog.get_values();
|
||||
if(args && assign_to) {
|
||||
return frappe.call({
|
||||
method: opts.method,
|
||||
|
|
|
|||
|
|
@ -228,6 +228,14 @@ frappe.form.formatters = {
|
|||
},
|
||||
Email: function(value) {
|
||||
return $("<div></div>").text(value).html();
|
||||
},
|
||||
FileSize: function(value) {
|
||||
if(value > 1048576) {
|
||||
value = flt(flt(value) / 1048576, 1) + "M";
|
||||
} else if (value > 1024) {
|
||||
value = flt(flt(value) / 1024, 1) + "K";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ frappe.ui.form.LinkedWith = class LinkedWith {
|
|||
}
|
||||
|
||||
make_dialog() {
|
||||
var me = this;
|
||||
|
||||
this.dialog = new frappe.ui.Dialog({
|
||||
hide_on_page_refresh: true,
|
||||
|
|
@ -38,38 +37,29 @@ frappe.ui.form.LinkedWith = class LinkedWith {
|
|||
.then(() => this.load_doctypes())
|
||||
.then(() => this.links_not_permitted_or_missing())
|
||||
.then(() => this.get_linked_docs())
|
||||
.then(() => this.make_html())
|
||||
}
|
||||
.then(() => this.make_html());
|
||||
};
|
||||
}
|
||||
|
||||
make_html() {
|
||||
const linked_docs = this.frm.__linked_docs;
|
||||
|
||||
let html;
|
||||
let html = '';
|
||||
|
||||
if(Object.keys(linked_docs).length === 0) {
|
||||
const linked_doctypes = Object.keys(linked_docs);
|
||||
|
||||
if (linked_doctypes.length === 0) {
|
||||
html = __("Not Linked to any record");
|
||||
} else {
|
||||
html = Object.keys(linked_docs).map(dt => {
|
||||
const list_renderer = new frappe.views.ListRenderer({
|
||||
doctype: dt,
|
||||
list_view: this
|
||||
});
|
||||
return `<div class="list-item-table" style="margin-bottom: 15px">
|
||||
${this.make_doc_head(dt)}
|
||||
${linked_docs[dt]
|
||||
.map(value => {
|
||||
// prepare data
|
||||
value = list_renderer.prepare_data(value);
|
||||
value._checkbox = 0;
|
||||
value._hide_activity = 1;
|
||||
|
||||
const $item = $(list_renderer.get_item_html(value));
|
||||
const $item_container = $('<div class="list-item-container">').append($item);
|
||||
return $item_container[0].outerHTML;
|
||||
}).join("")}
|
||||
</div>`;
|
||||
});
|
||||
html = linked_doctypes.map(doctype => {
|
||||
const docs = linked_docs[doctype];
|
||||
return `
|
||||
<div class="list-item-table margin-bottom">
|
||||
${this.make_doc_head(doctype)}
|
||||
${docs.map(doc => this.make_doc_row(doc, doctype)).join('')}
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
$(this.dialog.body).html(html);
|
||||
|
|
@ -82,7 +72,7 @@ frappe.ui.form.LinkedWith = class LinkedWith {
|
|||
if (this.frm.__linked_doctypes) {
|
||||
doctypes_to_load =
|
||||
Object.keys(this.frm.__linked_doctypes)
|
||||
.filter(doctype => !already_loaded.includes(doctype));
|
||||
.filter(doctype => !already_loaded.includes(doctype));
|
||||
}
|
||||
|
||||
// load all doctypes asynchronously using with_doctype
|
||||
|
|
@ -100,19 +90,17 @@ frappe.ui.form.LinkedWith = class LinkedWith {
|
|||
}
|
||||
|
||||
links_not_permitted_or_missing() {
|
||||
var me = this;
|
||||
let links = null;
|
||||
|
||||
if (this.frm.__linked_doctypes) {
|
||||
links =
|
||||
Object.keys(this.frm.__linked_doctypes)
|
||||
.filter(frappe.model.can_get_report);
|
||||
.filter(frappe.model.can_get_report);
|
||||
}
|
||||
|
||||
let flag;
|
||||
if(!links) {
|
||||
$(this.dialog.body).html(
|
||||
`${this.frm.__linked_doctypes
|
||||
$(this.dialog.body).html(`${this.frm.__linked_doctypes
|
||||
? __("Not enough permission to see links")
|
||||
: __("Not Linked to any record")}`);
|
||||
flag = true;
|
||||
|
|
@ -126,7 +114,7 @@ frappe.ui.form.LinkedWith = class LinkedWith {
|
|||
}
|
||||
|
||||
get_linked_doctypes() {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve) => {
|
||||
if (this.frm.__linked_doctypes) {
|
||||
resolve();
|
||||
}
|
||||
|
|
@ -160,19 +148,20 @@ frappe.ui.form.LinkedWith = class LinkedWith {
|
|||
}
|
||||
|
||||
make_doc_head(heading) {
|
||||
return `<div class="list-item list-item--head">
|
||||
<div class="list-item__content">
|
||||
${heading}
|
||||
</div></div>`;
|
||||
return `
|
||||
<header class="level list-row list-row-head text-muted small">
|
||||
<div>${__(heading)}</div>
|
||||
</header>
|
||||
`;
|
||||
}
|
||||
|
||||
make_doc_row(doc, doctype) {
|
||||
return `<div class="list-item-container">
|
||||
<div class="list-item">
|
||||
<div class="list-item__content bold">
|
||||
return `<div class="list-row-container">
|
||||
<div class="level list-row small">
|
||||
<div class="level-left bold">
|
||||
<a href="#Form/${doctype}/${doc.name}">${doc.name}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
638
frappe/public/js/frappe/list/base_list.js
Normal file
638
frappe/public/js/frappe/list/base_list.js
Normal file
|
|
@ -0,0 +1,638 @@
|
|||
frappe.provide('frappe.views');
|
||||
|
||||
frappe.views.BaseList = class BaseList {
|
||||
constructor(opts) {
|
||||
Object.assign(this, opts);
|
||||
this.show();
|
||||
}
|
||||
|
||||
show() {
|
||||
this.init().then(() => this.refresh());
|
||||
}
|
||||
|
||||
init() {
|
||||
|
||||
if (this.init_promise) return this.init_promise;
|
||||
|
||||
let tasks = [
|
||||
this.setup_defaults,
|
||||
this.set_stats,
|
||||
this.setup_fields,
|
||||
// make view
|
||||
this.setup_page,
|
||||
this.setup_page_head,
|
||||
this.setup_side_bar,
|
||||
this.setup_list_wrapper,
|
||||
this.setup_filter_area,
|
||||
this.setup_sort_selector,
|
||||
this.setup_result_area,
|
||||
this.setup_no_result_area,
|
||||
this.setup_freeze_area,
|
||||
this.setup_paging_area,
|
||||
this.setup_footnote_area,
|
||||
this.setup_view,
|
||||
].map(fn => fn.bind(this));
|
||||
|
||||
this.init_promise = frappe.run_serially(tasks);
|
||||
return this.init_promise;
|
||||
}
|
||||
|
||||
setup_defaults() {
|
||||
this.page_name = frappe.get_route_str();
|
||||
this.page_title = this.page_title || __(this.doctype);
|
||||
this.meta = frappe.get_meta(this.doctype);
|
||||
this.settings = frappe.listview_settings[this.doctype] || {};
|
||||
this.user_settings = frappe.get_user_settings(this.doctype);
|
||||
|
||||
this.start = 0;
|
||||
this.page_length = 20;
|
||||
this.data = [];
|
||||
this.method = 'frappe.desk.reportview.get';
|
||||
|
||||
this.can_create = frappe.model.can_create(this.doctype);
|
||||
this.can_delete = frappe.model.can_delete(this.doctype);
|
||||
this.can_write = frappe.model.can_write(this.doctype);
|
||||
|
||||
this.filters = [];
|
||||
this.order_by = 'modified desc';
|
||||
|
||||
// Setup buttons
|
||||
this.primary_action = null;
|
||||
this.secondary_action = {
|
||||
label: __('Refresh'),
|
||||
action: () => this.refresh()
|
||||
};
|
||||
|
||||
this.menu_items = [{
|
||||
label: __('Refresh'),
|
||||
action: () => this.refresh(),
|
||||
class: 'visible-xs'
|
||||
}];
|
||||
}
|
||||
|
||||
setup_fields() {
|
||||
this.set_fields();
|
||||
this.build_fields();
|
||||
}
|
||||
|
||||
set_fields() {
|
||||
|
||||
this._fields = [];
|
||||
const add_field = f => this._add_field(f);
|
||||
|
||||
// default fields
|
||||
const std_fields = [
|
||||
'name',
|
||||
'owner',
|
||||
'docstatus',
|
||||
'_user_tags',
|
||||
'_comments',
|
||||
'modified',
|
||||
'modified_by',
|
||||
'_assign',
|
||||
'_liked_by',
|
||||
'_seen',
|
||||
'enabled',
|
||||
'disabled',
|
||||
this.meta.title_field,
|
||||
this.meta.image_field
|
||||
];
|
||||
|
||||
std_fields.map(add_field);
|
||||
|
||||
// fields in_list_view
|
||||
const fields = this.get_fields_in_list_view();
|
||||
fields.map(add_field);
|
||||
|
||||
// currency fields
|
||||
fields.filter(
|
||||
df => df.fieldtype === 'Currency' && df.options
|
||||
).map(df => {
|
||||
if (df.options.includes(':')) {
|
||||
add_field(df.options.split(':')[1]);
|
||||
} else {
|
||||
add_field(df.options);
|
||||
}
|
||||
});
|
||||
|
||||
// image fields
|
||||
fields.filter(
|
||||
df => df.fieldtype === 'Image'
|
||||
).map(df => {
|
||||
if (df.options) {
|
||||
add_field(df.options);
|
||||
} else {
|
||||
add_field(df.fieldname);
|
||||
}
|
||||
});
|
||||
|
||||
// fields in listview_settings
|
||||
(this.settings.add_fields || []).map(add_field);
|
||||
}
|
||||
|
||||
get_fields_in_list_view() {
|
||||
return this.meta.fields.filter(df => {
|
||||
return df.in_list_view
|
||||
&& frappe.perm.has_perm(this.doctype, df.permlevel, 'read')
|
||||
&& frappe.model.is_value_type(df.fieldtype);
|
||||
});
|
||||
}
|
||||
|
||||
build_fields() {
|
||||
// fill in missing doctype
|
||||
this._fields = this._fields.map(f => {
|
||||
if (typeof f === 'string') {
|
||||
f = [f, this.doctype];
|
||||
}
|
||||
return f;
|
||||
});
|
||||
//de-dup
|
||||
this._fields = this._fields.uniqBy(f => f[0] + f[1]);
|
||||
// build this.fields
|
||||
this.fields = this._fields.map(f => frappe.model.get_full_column_name(f[0], f[1]));
|
||||
}
|
||||
|
||||
_add_field(fieldname) {
|
||||
if (!fieldname) return;
|
||||
let doctype = this.doctype;
|
||||
|
||||
if (typeof fieldname === 'object') {
|
||||
// df is passed
|
||||
const df = fieldname;
|
||||
fieldname = df.fieldname;
|
||||
doctype = df.parent;
|
||||
}
|
||||
|
||||
const is_valid_field = frappe.model.std_fields_list.includes(fieldname)
|
||||
|| frappe.meta.has_field(doctype, fieldname);
|
||||
|
||||
if (!is_valid_field) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._fields.push([fieldname, doctype]);
|
||||
}
|
||||
|
||||
set_stats() {
|
||||
this.stats = ['_user_tags'];
|
||||
// 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']) {
|
||||
this._add_field(this.workflow_state_fieldname);
|
||||
}
|
||||
this.stats.push(this.workflow_state_fieldname);
|
||||
}
|
||||
}
|
||||
|
||||
setup_page() {
|
||||
this.parent.list_view = this;
|
||||
this.page = this.parent.page;
|
||||
this.$page = $(this.parent);
|
||||
this.page.page_form.removeClass('row').addClass('flex');
|
||||
}
|
||||
|
||||
setup_page_head() {
|
||||
this.page.set_title(this.page_title);
|
||||
this.set_menu_items();
|
||||
this.set_breadcrumbs();
|
||||
}
|
||||
|
||||
set_menu_items() {
|
||||
const $secondary_action = this.page.set_secondary_action(
|
||||
this.secondary_action.label,
|
||||
this.secondary_action.action,
|
||||
this.secondary_action.icon
|
||||
);
|
||||
if (!this.secondary_action.icon) {
|
||||
$secondary_action.addClass('hidden-xs');
|
||||
} else {
|
||||
$secondary_action.addClass('visible-xs');
|
||||
}
|
||||
|
||||
this.menu_items.map(item => {
|
||||
const $item = this.page.add_menu_item(item.label, item.action, item.standard);
|
||||
if (item.class) {
|
||||
$item.addClass(item.class);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
set_breadcrumbs() {
|
||||
frappe.breadcrumbs.add(this.meta.module, this.doctype);
|
||||
}
|
||||
|
||||
setup_side_bar() {
|
||||
this.list_sidebar = new frappe.views.ListSidebar({
|
||||
doctype: this.doctype,
|
||||
stats: this.stats,
|
||||
parent: this.$page.find('.layout-side-section'),
|
||||
// set_filter: this.set_filter.bind(this),
|
||||
page: this.page,
|
||||
list_view: this
|
||||
});
|
||||
}
|
||||
|
||||
setup_main_section() {
|
||||
this.setup_list_wrapper();
|
||||
this.setup_filter_area();
|
||||
this.setup_sort_selector();
|
||||
this.setup_result_area();
|
||||
this.setup_no_result_area();
|
||||
this.setup_freeze_area();
|
||||
this.setup_paging_area();
|
||||
this.setup_footnote_area();
|
||||
}
|
||||
|
||||
setup_list_wrapper() {
|
||||
this.$frappe_list = $('<div class="frappe-list">').appendTo(this.page.main);
|
||||
}
|
||||
|
||||
setup_filter_area() {
|
||||
this.filter_area = new FilterArea(this);
|
||||
|
||||
if (this.filters.length > 0) {
|
||||
return this.filter_area.set(this.filters);
|
||||
}
|
||||
}
|
||||
|
||||
setup_sort_selector() {
|
||||
this.sort_selector = new frappe.ui.SortSelector({
|
||||
parent: this.filter_area.$filter_list_wrapper,
|
||||
doctype: this.doctype,
|
||||
args: this.order_by,
|
||||
onchange: () => this.refresh(true)
|
||||
});
|
||||
}
|
||||
|
||||
setup_result_area() {
|
||||
this.$result = $(`<div class="result">`).hide();
|
||||
this.$frappe_list.append(this.$result);
|
||||
}
|
||||
|
||||
setup_no_result_area() {
|
||||
this.$no_result = $(`
|
||||
<div class="no-result text-muted flex justify-center align-center">
|
||||
${this.get_no_result_message()}
|
||||
</div>
|
||||
`).hide();
|
||||
this.$frappe_list.append(this.$no_result);
|
||||
}
|
||||
|
||||
setup_freeze_area() {
|
||||
this.$freeze = $('<div class="freeze"></div>').hide();
|
||||
this.$frappe_list.append(this.$freeze);
|
||||
}
|
||||
|
||||
get_no_result_message() {
|
||||
return __('Nothing to show');
|
||||
}
|
||||
|
||||
setup_paging_area() {
|
||||
const paging_values = [20, 100, 500];
|
||||
this.$paging_area = $(
|
||||
`<div class="list-paging-area level">
|
||||
<div class="level-left">
|
||||
<div class="btn-group">
|
||||
${paging_values.map(value => `
|
||||
<button type="button" class="btn btn-default btn-sm btn-paging"
|
||||
data-value="${value}">
|
||||
${value}
|
||||
</button>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<button class="btn btn-default btn-more btn-sm">
|
||||
${__("More")}...
|
||||
</button>
|
||||
</div>
|
||||
</div>`
|
||||
).hide();
|
||||
this.$frappe_list.append(this.$paging_area);
|
||||
|
||||
// set default paging btn active
|
||||
this.$paging_area
|
||||
.find(`.btn-paging[data-value="${this.page_length}"]`)
|
||||
.addClass('btn-info');
|
||||
|
||||
this.$paging_area.on('click', '.btn-paging, .btn-more', e => {
|
||||
const $this = $(e.currentTarget);
|
||||
|
||||
if ($this.is('.btn-paging')) {
|
||||
// set active button
|
||||
this.$paging_area.find('.btn-paging').removeClass('btn-info');
|
||||
$this.addClass('btn-info');
|
||||
|
||||
this.start = 0;
|
||||
this.page_length = $this.data().value;
|
||||
this.refresh();
|
||||
} else if ($this.is('.btn-more')) {
|
||||
this.start = this.start + this.page_length;
|
||||
this.refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setup_footnote_area() {
|
||||
this.$footnote_area = null;
|
||||
}
|
||||
|
||||
get_fields() {
|
||||
return this.fields;
|
||||
}
|
||||
|
||||
setup_view() {
|
||||
// for child classes
|
||||
}
|
||||
|
||||
get_args() {
|
||||
// filters might have a fifth param called hidden,
|
||||
// we don't want to pass that server side
|
||||
const filters = this.filter_area.get().map(filter => filter.slice(0, 4));
|
||||
return {
|
||||
doctype: this.doctype,
|
||||
fields: this.get_fields(),
|
||||
filters: filters,
|
||||
order_by: this.sort_selector.get_sql_string(),
|
||||
start: this.start,
|
||||
page_length: this.page_length
|
||||
};
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.freeze(true);
|
||||
// fetch data from server
|
||||
const args = this.get_args();
|
||||
return frappe.call({
|
||||
method: this.method,
|
||||
type: 'GET',
|
||||
args: args
|
||||
}).then(r => {
|
||||
// render
|
||||
this.freeze(false);
|
||||
|
||||
this.update_data(r);
|
||||
|
||||
this.toggle_result_area();
|
||||
this.before_render();
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
|
||||
update_data(r) {
|
||||
let data = r.message || {};
|
||||
data = frappe.utils.dict(data.keys, data.values);
|
||||
data = data.uniqBy(d => d.name);
|
||||
|
||||
if (this.start === 0) {
|
||||
this.data = data;
|
||||
} else {
|
||||
this.data = this.data.concat(data);
|
||||
}
|
||||
}
|
||||
|
||||
freeze() {
|
||||
// show a freeze message while data is loading
|
||||
}
|
||||
|
||||
before_render() {
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
// for child classes
|
||||
}
|
||||
|
||||
toggle_result_area() {
|
||||
this.$result.toggle(this.data.length > 0);
|
||||
this.$paging_area.toggle(this.data.length > 0);
|
||||
this.$no_result.toggle(this.data.length == 0);
|
||||
|
||||
const show_more = (this.start + this.page_length) <= this.data.length;
|
||||
this.$paging_area.find('.btn-more')
|
||||
.toggle(show_more);
|
||||
}
|
||||
|
||||
call_for_selected_items(method, args = {}) {
|
||||
args.names = this.get_checked_items(true);
|
||||
|
||||
frappe.call({
|
||||
method: method,
|
||||
args: args,
|
||||
freeze: true,
|
||||
callback: r => {
|
||||
if (!r.exc) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class FilterArea {
|
||||
constructor(list_view) {
|
||||
this.list_view = list_view;
|
||||
this.standard_filters_wrapper = this.list_view.page.page_form;
|
||||
this.$filter_list_wrapper = $('<div class="filter-list">').appendTo(this.list_view.$frappe_list);
|
||||
this.trigger_refresh = true;
|
||||
this.setup();
|
||||
}
|
||||
|
||||
setup() {
|
||||
this.make_standard_filters();
|
||||
this.make_filter_list();
|
||||
}
|
||||
|
||||
get() {
|
||||
let filters = this.filter_list.get_filters();
|
||||
let standard_filters = this.get_standard_filters();
|
||||
|
||||
return filters
|
||||
.concat(standard_filters)
|
||||
.uniqBy(JSON.stringify);
|
||||
}
|
||||
|
||||
set(filters) {
|
||||
// use to method to set filters without triggering refresh
|
||||
this.trigger_refresh = false;
|
||||
return this.add(filters, false)
|
||||
.then(() => {
|
||||
this.trigger_refresh = true;
|
||||
});
|
||||
}
|
||||
|
||||
add(filters, refresh = true) {
|
||||
if (!filters || Array.isArray(filters) && filters.length === 0)
|
||||
return Promise.resolve();
|
||||
|
||||
if (typeof filters[0] === 'string') {
|
||||
// passed in the format of doctype, field, condition, value
|
||||
const filter = Array.from(arguments);
|
||||
filters = [filter];
|
||||
}
|
||||
|
||||
const { non_standard_filters, promise } = this.set_standard_filter(filters);
|
||||
if (non_standard_filters.length === 0) {
|
||||
return promise;
|
||||
}
|
||||
|
||||
return promise
|
||||
.then(() => this.filter_list.add_filters(non_standard_filters))
|
||||
.then(() => {
|
||||
if (refresh) return this.list_view.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
refresh_list_view() {
|
||||
if (this.trigger_refresh) {
|
||||
this.list_view.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
set_standard_filter(filters) {
|
||||
const fields_dict = this.list_view.page.fields_dict;
|
||||
|
||||
let out = filters.reduce((out, filter) => {
|
||||
// eslint-disable-next-line
|
||||
const [dt, fieldname, condition, value] = filter;
|
||||
out.promise = out.promise || Promise.resolve();
|
||||
out.non_standard_filters = out.non_standard_filters || [];
|
||||
|
||||
if (fields_dict[fieldname] && condition === '=') {
|
||||
// standard filter
|
||||
out.promise = out.promise.then(
|
||||
() => fields_dict[fieldname].set_value(value)
|
||||
);
|
||||
} else {
|
||||
// filter out non standard filters
|
||||
out.non_standard_filters.push(filter);
|
||||
}
|
||||
return out;
|
||||
}, {});
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
remove(fieldname) {
|
||||
const fields_dict = this.list_view.page.fields_dict;
|
||||
|
||||
if (fieldname in fields_dict) {
|
||||
fields_dict[fieldname].set_value('');
|
||||
return;
|
||||
}
|
||||
this.filter_list.get_filter(fieldname).remove();
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.filter_list.clear_filters();
|
||||
|
||||
const fields_dict = this.list_view.page.fields_dict;
|
||||
for (let key in fields_dict) {
|
||||
const field = this.list_view.page.fields_dict[key];
|
||||
field.set_value('');
|
||||
}
|
||||
}
|
||||
|
||||
make_standard_filters() {
|
||||
$(
|
||||
`<div class="flex justify-center align-center">
|
||||
<span class="octicon octicon-search text-muted small"></span>
|
||||
</div>`
|
||||
)
|
||||
.css({
|
||||
height: '30px',
|
||||
width: '20px',
|
||||
marginRight: '-2px',
|
||||
marginLeft: '10px'
|
||||
})
|
||||
.prependTo(this.standard_filters_wrapper);
|
||||
|
||||
let fields = [
|
||||
{
|
||||
fieldtype: 'Data',
|
||||
label: 'ID',
|
||||
condition: 'like',
|
||||
fieldname: 'name',
|
||||
onchange: () => this.refresh_list_view()
|
||||
}
|
||||
];
|
||||
|
||||
const doctype_fields = this.list_view.meta.fields;
|
||||
|
||||
fields = fields.concat(doctype_fields.filter(
|
||||
df => df.in_standard_filter &&
|
||||
frappe.model.is_value_type(df.fieldtype)
|
||||
).map(df => {
|
||||
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");
|
||||
}
|
||||
}
|
||||
return {
|
||||
fieldtype: fieldtype,
|
||||
label: __(df.label),
|
||||
options: options,
|
||||
fieldname: df.fieldname,
|
||||
condition: condition,
|
||||
onchange: () => this.refresh_list_view()
|
||||
};
|
||||
}));
|
||||
|
||||
if (fields.length > 3) {
|
||||
fields = fields.map((df, i) => {
|
||||
if (i >= 3) {
|
||||
df.input_class = 'hidden-sm hidden-xs';
|
||||
}
|
||||
return df;
|
||||
});
|
||||
}
|
||||
|
||||
fields.map(df => this.list_view.page.add_field(df));
|
||||
}
|
||||
|
||||
get_standard_filters() {
|
||||
const filters = [];
|
||||
const fields_dict = this.list_view.page.fields_dict;
|
||||
for (let key in fields_dict) {
|
||||
let field = fields_dict[key];
|
||||
let value = field.get_value();
|
||||
if (value) {
|
||||
if (field.df.condition === 'like' && !value.includes('%')) {
|
||||
value = '%' + value + '%';
|
||||
}
|
||||
filters.push([
|
||||
this.list_view.doctype,
|
||||
field.df.fieldname,
|
||||
field.df.condition || '=',
|
||||
value
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
make_filter_list() {
|
||||
this.filter_list = new frappe.ui.FilterGroup({
|
||||
base_list: this.list_view,
|
||||
parent: this.$filter_list_wrapper,
|
||||
doctype: this.list_view.doctype,
|
||||
default_filters: [],
|
||||
on_change: () => this.refresh_list_view()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// utility function to validate view modes
|
||||
frappe.views.view_modes = ['List', 'Gantt', 'Kanban', 'Calendar', 'Image', 'Inbox', 'Report'];
|
||||
frappe.views.is_valid = view_mode => frappe.views.view_modes.includes(view_mode);
|
||||
95
frappe/public/js/frappe/list/list_factory.js
Normal file
95
frappe/public/js/frappe/list/list_factory.js
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// MIT License. See license.txt
|
||||
|
||||
frappe.provide('frappe.views.list_view');
|
||||
|
||||
cur_list = null;
|
||||
frappe.views.ListFactory = frappe.views.Factory.extend({
|
||||
make: function (route) {
|
||||
var me = this;
|
||||
var doctype = route[1];
|
||||
|
||||
frappe.model.with_doctype(doctype, function () {
|
||||
if (locals['DocType'][doctype].issingle) {
|
||||
frappe.set_re_route('Form', doctype);
|
||||
} else {
|
||||
// List / Gantt / Kanban / etc
|
||||
// File is a special view
|
||||
const view_name = doctype !== 'File' ? route[2] : 'File';
|
||||
let view_class = frappe.views[view_name + 'View'];
|
||||
if (!view_class) view_class = frappe.views.ListView;
|
||||
|
||||
if (view_class && view_class.load_last_view && view_class.load_last_view()) {
|
||||
// view can have custom routing logic
|
||||
return;
|
||||
}
|
||||
|
||||
frappe.provide('frappe.views.list_view.' + doctype);
|
||||
const page_name = frappe.get_route_str();
|
||||
|
||||
if (!frappe.views.list_view[page_name]) {
|
||||
frappe.views.list_view[page_name] = new view_class({
|
||||
doctype: doctype,
|
||||
parent: me.make_page(true, page_name)
|
||||
});
|
||||
} else {
|
||||
frappe.container.change_to(page_name);
|
||||
}
|
||||
me.set_cur_list();
|
||||
}
|
||||
});
|
||||
},
|
||||
show: function () {
|
||||
if(this.re_route_to_view()) {
|
||||
return;
|
||||
}
|
||||
this.set_module_breadcrumb();
|
||||
this._super();
|
||||
this.set_cur_list();
|
||||
cur_list && cur_list.show();
|
||||
},
|
||||
re_route_to_view: function() {
|
||||
var route = frappe.get_route();
|
||||
var doctype = route[1];
|
||||
var last_route = frappe.route_history.slice(-2)[0];
|
||||
if (route[0] === 'List' && route.length === 2 && frappe.views.list_view[doctype]) {
|
||||
if(last_route && last_route[0]==='List' && last_route[1]===doctype) {
|
||||
// last route same as this route, so going back.
|
||||
// this happens because #List/Item will redirect to #List/Item/List
|
||||
// while coming from back button, the last 2 routes will be same, so
|
||||
// we know user is coming in the reverse direction (via back button)
|
||||
|
||||
// example:
|
||||
// Step 1: #List/Item redirects to #List/Item/List
|
||||
// Step 2: User hits "back" comes back to #List/Item
|
||||
// Step 3: Now we cannot send the user back to #List/Item/List so go back one more step
|
||||
window.history.go(-1);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
set_module_breadcrumb: function () {
|
||||
if (frappe.route_history.length > 1) {
|
||||
var prev_route = frappe.route_history[frappe.route_history.length - 2];
|
||||
if (prev_route[0] === 'modules') {
|
||||
var doctype = frappe.get_route()[1],
|
||||
module = prev_route[1];
|
||||
if (frappe.module_links[module] && frappe.module_links[module].includes(doctype)) {
|
||||
// save the last page from the breadcrumb was accessed
|
||||
frappe.breadcrumbs.set_doctype_module(doctype, module);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
set_cur_list: function () {
|
||||
var route = frappe.get_route();
|
||||
var page_name = frappe.get_route_str();
|
||||
cur_list = frappe.views.list_view[page_name];
|
||||
if (cur_list && cur_list.doctype !== route[1]) {
|
||||
// changing...
|
||||
cur_list = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
<div style="padding-left: 20px;">
|
||||
<i class="octicon octicon-lock" style="float: left; margin-left: -20px;"></i>
|
||||
<div class="level">
|
||||
<i class="octicon octicon-lock level-item" style="margin-right: 5px;"></i>
|
||||
{% for(var i=0; i < condition_list.length; i++) {
|
||||
var conditions = condition_list[i]; %}
|
||||
<div style="margin-bottom: 5px;">
|
||||
<div class="level-item">
|
||||
{% if (i > 0) { %}<span style="margin-right: 10px;">{{ __("Or") }}</span>{% } %}
|
||||
{% for(key in conditions) { %}
|
||||
<span class="label label-default" style="margin-right: 10px;">
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
{{ __("Reports") }} <span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu reports-dropdown" style="max-height: 300px; overflow-y: auto;">
|
||||
<li><a href="#Report/{{ doctype }}">{{ __("Report Builder") }}</a></li>
|
||||
<li><a href="#List/{{ doctype }}/Report">{{ __("Report Builder") }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ frappe.views.ListSidebar = Class.extend({
|
|||
this.cat_tags = [];
|
||||
},
|
||||
make: function() {
|
||||
var sidebar_content = frappe.render_template("list_sidebar", {doctype: this.list_view.doctype});
|
||||
var sidebar_content = frappe.render_template("list_sidebar", {doctype: this.doctype});
|
||||
|
||||
this.sidebar = $('<div class="list-sidebar overlay-sidebar hidden-xs hidden-sm"></div>')
|
||||
.html(sidebar_content)
|
||||
|
|
@ -96,7 +96,8 @@ frappe.views.ListSidebar = Class.extend({
|
|||
$.each(reports, function(name, r) {
|
||||
if(!r.ref_doctype || r.ref_doctype==me.doctype) {
|
||||
var report_type = r.report_type==='Report Builder'
|
||||
? 'Report/' + r.ref_doctype : 'query-report';
|
||||
? `List/${r.ref_doctype}/Report` : 'query-report';
|
||||
|
||||
var route = r.route || report_type + '/' + (r.title || r.name);
|
||||
|
||||
if(added.indexOf(route)===-1) {
|
||||
|
|
@ -113,11 +114,11 @@ frappe.views.ListSidebar = Class.extend({
|
|||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// from reference doctype
|
||||
if(this.list_view.list_renderer.settings.reports) {
|
||||
add_reports(this.list_view.list_renderer.settings.reports)
|
||||
if(this.list_view.settings.reports) {
|
||||
add_reports(this.list_view.settings.reports);
|
||||
}
|
||||
|
||||
// from specially tagged reports
|
||||
|
|
@ -175,7 +176,7 @@ frappe.views.ListSidebar = Class.extend({
|
|||
fieldname: 'custom_column',
|
||||
label: __('Custom Column'),
|
||||
default: 0,
|
||||
onchange: function(e) {
|
||||
onchange: function() {
|
||||
var checked = d.get_value('custom_column');
|
||||
if(checked) {
|
||||
$(d.body).find('.frappe-control[data-fieldname="field_name"]').hide();
|
||||
|
|
@ -202,19 +203,19 @@ frappe.views.ListSidebar = Class.extend({
|
|||
|
||||
var custom_column = values.custom_column !== undefined ?
|
||||
values.custom_column : 1;
|
||||
|
||||
var field_name;
|
||||
if(custom_column) {
|
||||
var field_name = 'kanban_column';
|
||||
field_name = 'kanban_column';
|
||||
} else {
|
||||
var field_name =
|
||||
field_name =
|
||||
select_fields
|
||||
.find(df => df.label === values.field_name)
|
||||
.fieldname;
|
||||
}
|
||||
|
||||
me.add_custom_column_field(custom_column)
|
||||
.then(function(custom_column) {
|
||||
return me.make_kanban_board(values.board_name, field_name)
|
||||
.then(function() {
|
||||
return me.make_kanban_board(values.board_name, field_name);
|
||||
})
|
||||
.then(function() {
|
||||
d.hide();
|
||||
|
|
@ -321,7 +322,7 @@ frappe.views.ListSidebar = Class.extend({
|
|||
|
||||
if(has_common(frappe.user_roles, ["System Manager", "Administrator"])) {
|
||||
$(`<li class="new-email-account"><a>${__("New Email Account")}</a></li>`)
|
||||
.appendTo($dropdown)
|
||||
.appendTo($dropdown);
|
||||
}
|
||||
|
||||
let accounts = frappe.boot.email_accounts;
|
||||
|
|
@ -334,7 +335,7 @@ frappe.views.ListSidebar = Class.extend({
|
|||
}
|
||||
$(`<li><a href="#${route}">${account.email_id}</a></li>`).appendTo($dropdown);
|
||||
if(account.email_id === "Sent Mail")
|
||||
divider = false
|
||||
divider = false;
|
||||
});
|
||||
|
||||
$dropdown.find('.new-email-account').click(function() {
|
||||
|
|
@ -354,7 +355,7 @@ frappe.views.ListSidebar = Class.extend({
|
|||
// if account is holding one user free plan or
|
||||
// if account's expiry date within range of 30 days from today's date
|
||||
|
||||
let upgrade_date = frappe.datetime.add_days(get_today(), 30);
|
||||
let upgrade_date = frappe.datetime.add_days(frappe.datetime.get_today(), 30);
|
||||
if (frappe.boot.limits.users === 1 || upgrade_date >= frappe.boot.limits.expiry) {
|
||||
let upgrade_box = $(`<div class="border" style="
|
||||
padding: 0px 10px;
|
||||
|
|
@ -386,12 +387,12 @@ frappe.views.ListSidebar = Class.extend({
|
|||
args: {
|
||||
stats: me.stats,
|
||||
doctype: me.doctype,
|
||||
filters:me.default_filters
|
||||
filters: me.default_filters || []
|
||||
},
|
||||
callback: function(r) {
|
||||
me.defined_category = r.message;
|
||||
if (r.message.defined_cat ){
|
||||
me.defined_category = r.message.defined_cat
|
||||
me.defined_category = r.message.defined_cat;
|
||||
me.cats = {};
|
||||
//structure the tag categories
|
||||
for (var i in me.defined_category){
|
||||
|
|
@ -400,10 +401,10 @@ frappe.views.ListSidebar = Class.extend({
|
|||
}else{
|
||||
me.cats[me.defined_category[i].category].push(me.defined_category[i].tag);
|
||||
}
|
||||
me.cat_tags[i]=me.defined_category[i].tag
|
||||
me.cat_tags[i]=me.defined_category[i].tag;
|
||||
}
|
||||
me.tempstats =r.message.stats
|
||||
var len = me.cats.length;
|
||||
me.tempstats =r.message.stats;
|
||||
|
||||
$.each(me.cats, function (i, v) {
|
||||
me.render_stat(i, (me.tempstats || {})["_user_tags"],v);
|
||||
});
|
||||
|
|
@ -414,19 +415,18 @@ frappe.views.ListSidebar = Class.extend({
|
|||
//render normal stats
|
||||
me.render_stat("_user_tags", (r.message.stats|| {})["_user_tags"]);
|
||||
}
|
||||
me.list_view.set_sidebar_height();
|
||||
}
|
||||
});
|
||||
},
|
||||
render_stat: function(field, stat, tags) {
|
||||
var me = this;
|
||||
var sum = 0;
|
||||
var stats = []
|
||||
var stats = [];
|
||||
var label = frappe.meta.docfield_map[this.doctype][field] ?
|
||||
frappe.meta.docfield_map[this.doctype][field].label : field;
|
||||
|
||||
stat = (stat || []).sort(function(a, b) { return b[1] - a[1] });
|
||||
$.each(stat, function(i,v) { sum = sum + v[1]; })
|
||||
stat = (stat || []).sort(function(a, b) { return b[1] - a[1]; });
|
||||
$.each(stat, function(i,v) { sum = sum + v[1]; });
|
||||
|
||||
if(tags) {
|
||||
for (var t in tags) {
|
||||
|
|
@ -454,12 +454,12 @@ frappe.views.ListSidebar = Class.extend({
|
|||
sum: sum,
|
||||
label: field==='_user_tags' ? (tags ? __(label) : __("Tags")) : __(label),
|
||||
};
|
||||
var sidebar_stat = $(frappe.render_template("list_sidebar_stat", context))
|
||||
$(frappe.render_template("list_sidebar_stat", context))
|
||||
.on("click", ".stat-link", function() {
|
||||
var fieldname = $(this).attr('data-field');
|
||||
var label = $(this).attr('data-label');
|
||||
if (label == "No Tags") {
|
||||
me.list_view.filter_list.add_filter(me.list_view.doctype, fieldname, 'not like', '%,%')
|
||||
me.list_view.filter_list.add_filter(me.list_view.doctype, fieldname, 'not like', '%,%');
|
||||
me.list_view.run();
|
||||
} else {
|
||||
me.set_filter(fieldname, label);
|
||||
|
|
@ -467,7 +467,7 @@ frappe.views.ListSidebar = Class.extend({
|
|||
})
|
||||
.insertBefore(this.sidebar.find(".close-sidebar-button"));
|
||||
},
|
||||
set_fieldtype: function(df, fieldtype) {
|
||||
set_fieldtype: function(df) {
|
||||
|
||||
// scrub
|
||||
if(df.fieldname=="docstatus") {
|
||||
|
|
@ -476,11 +476,11 @@ frappe.views.ListSidebar = Class.extend({
|
|||
{value:0, label:"Draft"},
|
||||
{value:1, label:"Submitted"},
|
||||
{value:2, label:"Cancelled"},
|
||||
]
|
||||
];
|
||||
} else if(df.fieldtype=='Check') {
|
||||
df.fieldtype='Select';
|
||||
df.options=[{value:0,label:'No'},
|
||||
{value:1,label:'Yes'}]
|
||||
{value:1,label:'Yes'}];
|
||||
} else if(['Text','Small Text','Text Editor','Code','Tag','Comments',
|
||||
'Dynamic Link','Read Only','Assign'].indexOf(df.fieldtype)!=-1) {
|
||||
df.fieldtype = 'Data';
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
56
frappe/public/js/frappe/misc/file_manager.js
Normal file
56
frappe/public/js/frappe/misc/file_manager.js
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
frappe.provide('frappe.file_manager');
|
||||
|
||||
frappe.file_manager = function() {
|
||||
let files_to_move = [];
|
||||
let old_folder = null;
|
||||
let new_folder = null;
|
||||
|
||||
function cut(files, old_folder_) {
|
||||
files_to_move = files;
|
||||
old_folder = old_folder_;
|
||||
}
|
||||
|
||||
function paste(new_folder_) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (files_to_move.length === 0 || !old_folder) {
|
||||
reset();
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
new_folder = new_folder_;
|
||||
|
||||
frappe.call({
|
||||
method:"frappe.core.doctype.file.file.move_file",
|
||||
args: {
|
||||
file_list: files_to_move,
|
||||
new_parent: new_folder,
|
||||
old_parent: old_folder
|
||||
},
|
||||
callback: r => {
|
||||
reset();
|
||||
resolve(r);
|
||||
}
|
||||
}).fail(reject);
|
||||
});
|
||||
}
|
||||
|
||||
function reset() {
|
||||
files_to_move = [];
|
||||
old_folder = null;
|
||||
new_folder = null;
|
||||
}
|
||||
|
||||
return {
|
||||
cut,
|
||||
paste,
|
||||
get can_paste() {
|
||||
return Boolean(files_to_move.length > 0 && old_folder);
|
||||
},
|
||||
get old_folder() {
|
||||
return old_folder;
|
||||
},
|
||||
get files_to_move() {
|
||||
return files_to_move;
|
||||
}
|
||||
};
|
||||
}();
|
||||
|
|
@ -195,13 +195,11 @@ frappe.utils = {
|
|||
},
|
||||
set_footnote: function(footnote_area, wrapper, txt) {
|
||||
if(!footnote_area) {
|
||||
footnote_area = $('<div class="text-muted footnote-area">')
|
||||
footnote_area = $('<div class="text-muted footnote-area level">')
|
||||
.appendTo(wrapper);
|
||||
}
|
||||
|
||||
if(txt) {
|
||||
if(!txt.includes('<p>'))
|
||||
txt = '<p>' + txt + '</p>';
|
||||
footnote_area.html(txt);
|
||||
} else {
|
||||
footnote_area.remove();
|
||||
|
|
@ -591,7 +589,47 @@ frappe.utils = {
|
|||
catch (err) {
|
||||
return false;
|
||||
}
|
||||
}()
|
||||
}(),
|
||||
throttle: function (func, wait, options) {
|
||||
var context, args, result;
|
||||
var timeout = null;
|
||||
var previous = 0;
|
||||
if (!options) options = {};
|
||||
|
||||
let later = function () {
|
||||
previous = options.leading === false ? 0 : Date.now();
|
||||
timeout = null;
|
||||
result = func.apply(context, args);
|
||||
if (!timeout) context = args = null;
|
||||
};
|
||||
|
||||
return function () {
|
||||
var now = Date.now();
|
||||
if (!previous && options.leading === false) previous = now;
|
||||
let remaining = wait - (now - previous);
|
||||
context = this;
|
||||
args = arguments;
|
||||
if (remaining <= 0 || remaining > wait) {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
}
|
||||
previous = now;
|
||||
result = func.apply(context, args);
|
||||
if (!timeout) context = args = null;
|
||||
} else if (!timeout && options.trailing !== false) {
|
||||
timeout = setTimeout(later, remaining);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
},
|
||||
get_form_link: function(doctype, name, html = false) {
|
||||
const route = ['#Form', doctype, name].join('/');
|
||||
if (html) {
|
||||
return `<a href="${route}">${name}</a>`;
|
||||
}
|
||||
return route;
|
||||
}
|
||||
};
|
||||
|
||||
// String.prototype.includes polyfill
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ $.extend(frappe.model, {
|
|||
{fieldname:'docstatus', fieldtype:'Int', label:__('Document Status')},
|
||||
],
|
||||
|
||||
numeric_fieldtypes: ["Int", "Float", "Currency", "Percent"],
|
||||
|
||||
std_fields_table: [
|
||||
{fieldname:'parent', fieldtype:'Data', label:__('Parent')},
|
||||
],
|
||||
|
|
@ -39,8 +41,9 @@ $.extend(frappe.model, {
|
|||
// setup refresh if the document is updated somewhere else
|
||||
frappe.realtime.on("doc_update", function(data) {
|
||||
// set list dirty
|
||||
frappe.views.set_list_as_dirty(data.doctype);
|
||||
frappe.views.ListView.trigger_list_update(data);
|
||||
var doc = locals[data.doctype] && locals[data.doctype][data.name];
|
||||
|
||||
if(doc) {
|
||||
// current document is dirty, show message if its not me
|
||||
if(frappe.get_route()[0]==="Form" && cur_frm.doc.doctype===doc.doctype && cur_frm.doc.name===doc.name) {
|
||||
|
|
@ -61,7 +64,7 @@ $.extend(frappe.model, {
|
|||
});
|
||||
|
||||
frappe.realtime.on("list_update", function(data) {
|
||||
frappe.views.set_list_as_dirty(data.doctype);
|
||||
frappe.views.ListView.trigger_list_update(data);
|
||||
});
|
||||
|
||||
},
|
||||
|
|
@ -74,6 +77,10 @@ $.extend(frappe.model, {
|
|||
return frappe.model.no_value_type.indexOf(fieldtype)===-1;
|
||||
},
|
||||
|
||||
is_non_std_field: function(fieldname) {
|
||||
return !frappe.model.std_fields_list.includes(fieldname);
|
||||
},
|
||||
|
||||
get_std_field: function(fieldname) {
|
||||
var docfield = $.map([].concat(frappe.model.std_fields).concat(frappe.model.std_fields_table),
|
||||
function(d) {
|
||||
|
|
@ -576,6 +583,19 @@ $.extend(frappe.model, {
|
|||
}
|
||||
return all;
|
||||
},
|
||||
|
||||
get_full_column_name: function(fieldname, doctype) {
|
||||
if (fieldname.includes('`tab')) return fieldname;
|
||||
return '`tab' + doctype + '`.`' + fieldname + '`';
|
||||
},
|
||||
|
||||
is_numeric_field: function(fieldtype) {
|
||||
if (!fieldtype) return;
|
||||
if (typeof fieldtype === 'object') {
|
||||
fieldtype = fieldtype.fieldtype;
|
||||
}
|
||||
return frappe.model.numeric_fieldtypes.includes(fieldtype);
|
||||
}
|
||||
});
|
||||
|
||||
// legacy
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ $.extend(frappe.model.user_settings, {
|
|||
var user_settings = frappe.model.user_settings[doctype] || {};
|
||||
|
||||
if ($.isPlainObject(value)) {
|
||||
user_settings[key] = user_settings[key] || {};
|
||||
$.extend(user_settings[key], value);
|
||||
} else {
|
||||
user_settings[key] = value;
|
||||
|
|
|
|||
|
|
@ -1,532 +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");
|
||||
}
|
||||
}
|
||||
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('.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',
|
||||
type: "GET",
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<div class="filter-box">
|
||||
<div class="list_filter row">
|
||||
<div class="fieldname_select_area col-sm-4 form-group ui-front"></div>
|
||||
<div class="fieldname-select-area col-sm-4 form-group ui-front"></div>
|
||||
<div class="col-sm-2 form-group">
|
||||
<select class="condition form-control">
|
||||
<option value="=">{%= __("Equals") %}</option>
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
</select>
|
||||
</div>
|
||||
<div class="col-sm-6 col-xs-12">
|
||||
<div class="filter_field pull-left" style="width: calc(100% - 70px)"></div>
|
||||
<div class="filter-field pull-left" style="width: calc(100% - 70px)"></div>
|
||||
<div class="filter-actions pull-left">
|
||||
<a class="set-filter-and-run btn btn-sm btn-primary pull-left">
|
||||
<i class=" fa fa-check visible-xs"></i>
|
||||
|
|
|
|||
155
frappe/public/js/frappe/ui/filters/field_select.js
Normal file
155
frappe/public/js/frappe/ui/filters/field_select.js
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
// <select> widget with all fields of a doctype as options
|
||||
frappe.ui.FieldSelect = Class.extend({
|
||||
// opts parent, doctype, filter_fields, with_blank, select
|
||||
init(opts) {
|
||||
var me = this;
|
||||
$.extend(this, opts);
|
||||
this.fields_by_name = {};
|
||||
this.options = [];
|
||||
this.$input = $('<input class="form-control">')
|
||||
.appendTo(this.parent)
|
||||
.on("click", function () { $(this).select(); });
|
||||
this.select_input = this.$input.get(0);
|
||||
this.awesomplete = new Awesomplete(this.select_input, {
|
||||
minChars: 0,
|
||||
maxItems: 99,
|
||||
autoFirst: true,
|
||||
list: me.options,
|
||||
item(item) {
|
||||
return $(repl('<li class="filter-field-select"><p>%(label)s</p></li>', item))
|
||||
.data("item.autocomplete", item)
|
||||
.get(0);
|
||||
}
|
||||
});
|
||||
this.$input.on("awesomplete-select", function(e) {
|
||||
var o = e.originalEvent;
|
||||
var value = o.text.value;
|
||||
var item = me.awesomplete.get_item(value);
|
||||
me.selected_doctype = item.doctype;
|
||||
me.selected_fieldname = item.fieldname;
|
||||
if(me.select) me.select(item.doctype, item.fieldname);
|
||||
});
|
||||
this.$input.on("awesomplete-selectcomplete", function(e) {
|
||||
var o = e.originalEvent;
|
||||
var value = o.text.value;
|
||||
var item = me.awesomplete.get_item(value);
|
||||
me.$input.val(item.label);
|
||||
});
|
||||
|
||||
if(this.filter_fields) {
|
||||
for(var i in this.filter_fields)
|
||||
this.add_field_option(this.filter_fields[i]);
|
||||
} else {
|
||||
this.build_options();
|
||||
}
|
||||
this.set_value(this.doctype, "name");
|
||||
},
|
||||
get_value() {
|
||||
return this.selected_doctype ? this.selected_doctype + "." + this.selected_fieldname : null;
|
||||
},
|
||||
val(value) {
|
||||
if(value===undefined) {
|
||||
return this.get_value();
|
||||
} else {
|
||||
this.set_value(value);
|
||||
}
|
||||
},
|
||||
clear() {
|
||||
this.selected_doctype = null;
|
||||
this.selected_fieldname = null;
|
||||
this.$input.val("");
|
||||
},
|
||||
set_value(doctype, fieldname) {
|
||||
var me = this;
|
||||
this.clear();
|
||||
if(!doctype) return;
|
||||
|
||||
// old style
|
||||
if(doctype.indexOf(".")!==-1) {
|
||||
var parts = doctype.split(".");
|
||||
doctype = parts[0];
|
||||
fieldname = parts[1];
|
||||
}
|
||||
|
||||
$.each(this.options, function(i, v) {
|
||||
if(v.doctype===doctype && v.fieldname===fieldname) {
|
||||
me.selected_doctype = doctype;
|
||||
me.selected_fieldname = fieldname;
|
||||
me.$input.val(v.label);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
build_options() {
|
||||
var me = this;
|
||||
me.table_fields = [];
|
||||
var std_filters = $.map(frappe.model.std_fields, function(d) {
|
||||
var opts = {parent: me.doctype};
|
||||
if(d.fieldname=="name") opts.options = me.doctype;
|
||||
return $.extend(copy_dict(d), opts);
|
||||
});
|
||||
|
||||
// add parenttype column
|
||||
var doctype_obj = locals['DocType'][me.doctype];
|
||||
if(doctype_obj && cint(doctype_obj.istable)) {
|
||||
std_filters = std_filters.concat([{
|
||||
fieldname: 'parent',
|
||||
fieldtype: 'Data',
|
||||
label: 'Parent',
|
||||
parent: me.doctype,
|
||||
}]);
|
||||
}
|
||||
|
||||
// blank
|
||||
if(this.with_blank) {
|
||||
this.options.push({
|
||||
label:"",
|
||||
value:"",
|
||||
});
|
||||
}
|
||||
|
||||
// main table
|
||||
var main_table_fields = std_filters.concat(frappe.meta.docfield_list[me.doctype]);
|
||||
$.each(frappe.utils.sort(main_table_fields, "label", "string"), function(i, df) {
|
||||
// show fields where user has read access and if report hide flag is not set
|
||||
if(frappe.perm.has_perm(me.doctype, df.permlevel, "read") && !df.report_hide)
|
||||
me.add_field_option(df);
|
||||
});
|
||||
|
||||
// child tables
|
||||
$.each(me.table_fields, function(i, table_df) {
|
||||
if(table_df.options) {
|
||||
var child_table_fields = [].concat(frappe.meta.docfield_list[table_df.options]);
|
||||
$.each(frappe.utils.sort(child_table_fields, "label", "string"), function(i, df) {
|
||||
// show fields where user has read access and if report hide flag is not set
|
||||
if(frappe.perm.has_perm(me.doctype, df.permlevel, "read") && !df.report_hide)
|
||||
me.add_field_option(df);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
add_field_option(df) {
|
||||
var me = this;
|
||||
var label, table;
|
||||
if(me.doctype && df.parent==me.doctype) {
|
||||
label = __(df.label);
|
||||
table = me.doctype;
|
||||
if(df.fieldtype=='Table') me.table_fields.push(df);
|
||||
} else {
|
||||
label = __(df.label) + ' (' + __(df.parent) + ')';
|
||||
table = df.parent;
|
||||
}
|
||||
if(frappe.model.no_value_type.indexOf(df.fieldtype) == -1 &&
|
||||
!(me.fields_by_name[df.parent] && me.fields_by_name[df.parent][df.fieldname])) {
|
||||
this.options.push({
|
||||
label: label,
|
||||
value: table + "." + df.fieldname,
|
||||
fieldname: df.fieldname,
|
||||
doctype: df.parent
|
||||
});
|
||||
if(!me.fields_by_name[df.parent]) me.fields_by_name[df.parent] = {};
|
||||
me.fields_by_name[df.parent][df.fieldname] = df;
|
||||
}
|
||||
},
|
||||
});
|
||||
335
frappe/public/js/frappe/ui/filters/filter.js
Normal file
335
frappe/public/js/frappe/ui/filters/filter.js
Normal file
|
|
@ -0,0 +1,335 @@
|
|||
frappe.ui.Filter = class {
|
||||
constructor(opts) {
|
||||
$.extend(this, opts);
|
||||
|
||||
this.utils = frappe.ui.filter_utils;
|
||||
this.make();
|
||||
this.make_select();
|
||||
this.set_events();
|
||||
this.setup();
|
||||
}
|
||||
|
||||
make() {
|
||||
this.filter_edit_area = $(frappe.render_template("edit_filter", {}))
|
||||
.appendTo(this.parent.find('.filter-edit-area'));
|
||||
}
|
||||
|
||||
make_select() {
|
||||
this.fieldselect = new frappe.ui.FieldSelect({
|
||||
parent: this.filter_edit_area.find('.fieldname-select-area'),
|
||||
doctype: this.doctype,
|
||||
filter_fields: this.filter_fields,
|
||||
select: (doctype, fieldname) => {
|
||||
this.set_field(doctype, fieldname);
|
||||
}
|
||||
});
|
||||
|
||||
if(this.fieldname) {
|
||||
this.fieldselect.set_value(this.doctype, this.fieldname);
|
||||
}
|
||||
}
|
||||
|
||||
set_events() {
|
||||
this.filter_edit_area.find("a.remove-filter").on("click", () => {
|
||||
this.remove();
|
||||
});
|
||||
|
||||
this.filter_edit_area.find(".set-filter-and-run").on("click", () => {
|
||||
this.filter_edit_area.removeClass("new-filter");
|
||||
this.on_change();
|
||||
});
|
||||
|
||||
this.filter_edit_area.find('.condition').change(() => {
|
||||
if(!this.field) return;
|
||||
|
||||
let condition = this.get_condition();
|
||||
let fieldtype = null;
|
||||
|
||||
if(["in", "like", "not in", "not like"].includes(condition)) {
|
||||
fieldtype = 'Data';
|
||||
this.add_condition_help(condition);
|
||||
}
|
||||
this.set_field(this.field.df.parent, this.field.df.fieldname, fieldtype, condition);
|
||||
});
|
||||
}
|
||||
|
||||
setup() {
|
||||
const fieldname = this.fieldname || 'name';
|
||||
// set the field
|
||||
return this.set_values(this.doctype, fieldname, this.condition, this.value);
|
||||
}
|
||||
|
||||
setup_state(is_new) {
|
||||
let promise = Promise.resolve();
|
||||
if (is_new) {
|
||||
this.filter_edit_area.addClass("new-filter");
|
||||
} else {
|
||||
promise = this.update_filter_tag();
|
||||
}
|
||||
|
||||
if(this.hidden) {
|
||||
promise.then(() => this.$filter_tag.hide());
|
||||
}
|
||||
}
|
||||
|
||||
freeze() {
|
||||
this.update_filter_tag();
|
||||
}
|
||||
|
||||
update_filter_tag() {
|
||||
return this._filter_value_set.then(() => {
|
||||
!this.$filter_tag ? this.make_tag() : this.set_filter_button_text();
|
||||
this.filter_edit_area.hide();
|
||||
});
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.filter_edit_area.remove();
|
||||
this.$filter_tag && this.$filter_tag.remove();
|
||||
this.field = null;
|
||||
this.on_change(true);
|
||||
}
|
||||
|
||||
set_values(doctype, fieldname, condition, value) {
|
||||
// presents given (could be via tags!)
|
||||
this.set_field(doctype, fieldname);
|
||||
|
||||
if(this.field.df.original_type==='Check') {
|
||||
value = (value==1) ? 'Yes' : 'No';
|
||||
}
|
||||
if(condition) this.set_condition(condition, true);
|
||||
|
||||
// set value can be asynchronous, so update_filter_tag should happen after field is set
|
||||
this._filter_value_set = Promise.resolve();
|
||||
if(value) {
|
||||
this._filter_value_set = this.field.set_value(value);
|
||||
}
|
||||
return this._filter_value_set;
|
||||
}
|
||||
|
||||
set_field(doctype, fieldname, fieldtype, condition) {
|
||||
// set in fieldname (again)
|
||||
let cur = {};
|
||||
if(this.field) for(let k in this.field.df) cur[k] = this.field.df[k];
|
||||
|
||||
let original_docfield = this.fieldselect.fields_by_name[doctype][fieldname];
|
||||
if(!original_docfield) {
|
||||
frappe.msgprint(__("Field {0} is not selectable.", [fieldname]));
|
||||
return;
|
||||
}
|
||||
|
||||
let df = copy_dict(original_docfield);
|
||||
|
||||
// filter field shouldn't be read only or hidden
|
||||
df.read_only = 0; df.hidden = 0;
|
||||
|
||||
let c = condition ? condition : this.utils.get_default_condition(df);
|
||||
this.set_condition(c);
|
||||
|
||||
this.utils.set_fieldtype(df, fieldtype, this.get_condition());
|
||||
|
||||
// called when condition is changed,
|
||||
// don't change if all is well
|
||||
if(this.field && cur.fieldname == fieldname && df.fieldtype == cur.fieldtype &&
|
||||
df.parent == cur.parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// clear field area and make field
|
||||
this.fieldselect.selected_doctype = doctype;
|
||||
this.fieldselect.selected_fieldname = fieldname;
|
||||
|
||||
this.make_field(df, cur.fieldtype);
|
||||
}
|
||||
|
||||
make_field(df, old_fieldtype) {
|
||||
let old_text = this.field ? this.field.get_value() : null;
|
||||
|
||||
let field_area = this.filter_edit_area.find('.filter-field').empty().get(0);
|
||||
let f = frappe.ui.form.make_control({
|
||||
df: df,
|
||||
parent: field_area,
|
||||
only_input: true,
|
||||
});
|
||||
f.refresh();
|
||||
|
||||
this.field = f;
|
||||
if(old_text && f.fieldtype===old_fieldtype) {
|
||||
this.field.set_value(old_text);
|
||||
}
|
||||
|
||||
// run on enter
|
||||
$(this.field.wrapper).find(':input').keydown(e => {
|
||||
if(e.which==13) {
|
||||
this.on_change();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get_value() {
|
||||
return [
|
||||
this.fieldselect.selected_doctype,
|
||||
this.field.df.fieldname,
|
||||
this.get_condition(),
|
||||
this.get_selected_value(),
|
||||
this.hidden
|
||||
];
|
||||
}
|
||||
|
||||
get_selected_value() {
|
||||
return this.utils.get_selected_value(this.field, this.get_condition());
|
||||
}
|
||||
|
||||
get_condition() {
|
||||
return this.filter_edit_area.find('.condition').val();
|
||||
}
|
||||
|
||||
set_condition(condition, trigger_change=false) {
|
||||
let $condition_field = this.filter_edit_area.find('.condition');
|
||||
$condition_field.val(condition);
|
||||
if(trigger_change) $condition_field.change();
|
||||
}
|
||||
|
||||
make_tag() {
|
||||
this.$filter_tag = this.get_filter_tag_element()
|
||||
.insertAfter(this.parent.find(".active-tag-filters .add-filter"));
|
||||
this.set_filter_button_text();
|
||||
this.bind_tag();
|
||||
}
|
||||
|
||||
bind_tag() {
|
||||
this.$filter_tag.find(".remove-filter").on("click", this.remove.bind(this));
|
||||
|
||||
let filter_button = this.$filter_tag.find(".toggle-filter");
|
||||
filter_button.on("click", () => {
|
||||
filter_button.closest('.tag-filters-area').find('.filter-edit-area').show();
|
||||
this.filter_edit_area.toggle();
|
||||
});
|
||||
}
|
||||
|
||||
set_filter_button_text() {
|
||||
this.$filter_tag.find(".toggle-filter").html(this.get_filter_button_text());
|
||||
}
|
||||
|
||||
get_filter_button_text() {
|
||||
let value = this.utils.get_formatted_value(this.field, this.get_selected_value());
|
||||
return `${__(this.field.df.label)} ${__(this.get_condition())} ${__(value)}`;
|
||||
}
|
||||
|
||||
get_filter_tag_element() {
|
||||
return $(`<div class="filter-tag btn-group">
|
||||
<button class="btn btn-default btn-xs toggle-filter"
|
||||
title="${ __("Edit Filter") }">
|
||||
</button>
|
||||
<button class="btn btn-default btn-xs remove-filter"
|
||||
title="${ __("Remove Filter") }">
|
||||
<i class="fa fa-remove text-muted"></i>
|
||||
</button>
|
||||
</div>`);
|
||||
}
|
||||
|
||||
add_condition_help(condition) {
|
||||
let $desc = this.field.desc_area;
|
||||
if(!$desc) {
|
||||
$desc = $('<div class="text-muted small">').appendTo(this.field.wrapper);
|
||||
}
|
||||
// set description
|
||||
$desc.html((in_list(["in", "not in"], condition)==="in"
|
||||
? __("values separated by commas")
|
||||
: __("use % as wildcard"))+'</div>');
|
||||
}
|
||||
};
|
||||
|
||||
frappe.ui.filter_utils = {
|
||||
get_formatted_value(field, value) {
|
||||
if(field.df.fieldname==="docstatus") {
|
||||
value = {0:"Draft", 1:"Submitted", 2:"Cancelled"}[value] || value;
|
||||
} else if(field.df.original_type==="Check") {
|
||||
value = {0:"No", 1:"Yes"}[cint(value)];
|
||||
}
|
||||
return frappe.format(value, field.df, {only_value: 1});
|
||||
},
|
||||
|
||||
get_selected_value(field, condition) {
|
||||
let val = field.get_value();
|
||||
|
||||
if(typeof val==='string') {
|
||||
val = strip(val);
|
||||
}
|
||||
|
||||
if(field.df.original_type == 'Check') {
|
||||
val = (val=='Yes' ? 1 :0);
|
||||
}
|
||||
|
||||
if(condition.indexOf('like', 'not like')!==-1) {
|
||||
// automatically append wildcards
|
||||
if(val) {
|
||||
if(val.slice(0,1) !== "%") {
|
||||
val = "%" + val;
|
||||
}
|
||||
if(val.slice(-1) !== "%") {
|
||||
val = val + "%";
|
||||
}
|
||||
}
|
||||
} else if(in_list(["in", "not in"], condition)) {
|
||||
if(val) {
|
||||
val = $.map(val.split(","), function(v) { return strip(v); });
|
||||
}
|
||||
} if(val === '%') {
|
||||
val = "";
|
||||
}
|
||||
|
||||
return val;
|
||||
},
|
||||
|
||||
get_default_condition(df) {
|
||||
if (df.fieldtype == 'Data') {
|
||||
return 'like';
|
||||
} else if (df.fieldtype == 'Date' || df.fieldtype == 'Datetime'){
|
||||
return 'Between';
|
||||
} else {
|
||||
return '=';
|
||||
}
|
||||
},
|
||||
|
||||
set_fieldtype(df, fieldtype, condition) {
|
||||
// reset
|
||||
if(df.original_type)
|
||||
df.fieldtype = df.original_type;
|
||||
else
|
||||
df.original_type = df.fieldtype;
|
||||
|
||||
df.description = ''; df.reqd = 0;
|
||||
df.ignore_link_validation = true;
|
||||
|
||||
// given
|
||||
if(fieldtype) {
|
||||
df.fieldtype = fieldtype;
|
||||
return;
|
||||
}
|
||||
|
||||
// scrub
|
||||
if(df.fieldname=="docstatus") {
|
||||
df.fieldtype="Select",
|
||||
df.options=[
|
||||
{value:0, label:__("Draft")},
|
||||
{value:1, label:__("Submitted")},
|
||||
{value:2, label:__("Cancelled")}
|
||||
];
|
||||
} else if(df.fieldtype=='Check') {
|
||||
df.fieldtype='Select';
|
||||
df.options='No\nYes';
|
||||
} else if(['Text','Small Text','Text Editor','Code','Tag','Comments',
|
||||
'Dynamic Link','Read Only','Assign'].indexOf(df.fieldtype)!=-1) {
|
||||
df.fieldtype = 'Data';
|
||||
} else if(df.fieldtype=='Link' && ['=', '!='].indexOf(condition)==-1) {
|
||||
df.fieldtype = 'Data';
|
||||
}
|
||||
if(df.fieldtype==="Data" && (df.options || "").toLowerCase()==="email") {
|
||||
df.options = null;
|
||||
}
|
||||
if(condition == "Between" && (df.fieldtype == 'Date' || df.fieldtype == 'Datetime')){
|
||||
df.fieldtype = 'DateRange';
|
||||
}
|
||||
}
|
||||
};
|
||||
141
frappe/public/js/frappe/ui/filters/filter_list.js
Normal file
141
frappe/public/js/frappe/ui/filters/filter_list.js
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
frappe.ui.FilterGroup = class {
|
||||
constructor(opts) {
|
||||
$.extend(this, opts);
|
||||
this.wrapper = this.parent;
|
||||
this.filters = [];
|
||||
this.make();
|
||||
}
|
||||
|
||||
make() {
|
||||
this.wrapper.append(this.get_container_template());
|
||||
this.set_events();
|
||||
}
|
||||
|
||||
set_events() {
|
||||
this.wrapper.find('.add-filter').on('click', () => {
|
||||
this.add_filter(this.doctype, 'name');
|
||||
});
|
||||
}
|
||||
|
||||
add_filters(filters) {
|
||||
let promises = [];
|
||||
|
||||
for (const filter of filters) {
|
||||
promises.push(this.add_filter(...filter));
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
add_filter(doctype, fieldname, condition, value, hidden) {
|
||||
if (!fieldname) return Promise.resolve();
|
||||
// adds a new filter, returns true if filter has been added
|
||||
|
||||
// {}: Add in page filter by fieldname if exists ('=' => 'like')
|
||||
|
||||
if(!this.validate_args(doctype, fieldname)) return false;
|
||||
|
||||
const is_new_filter = arguments.length < 2;
|
||||
if (is_new_filter && this.wrapper.find(".new-filter:visible").length) {
|
||||
// only allow 1 new filter at a time!
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
let args = [doctype, fieldname, condition, value, hidden];
|
||||
const promise = this.push_new_filter(args, is_new_filter);
|
||||
return (promise && promise.then) ? promise : Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
validate_args(doctype, fieldname) {
|
||||
if(doctype && fieldname
|
||||
&& !frappe.meta.has_field(doctype, fieldname)
|
||||
&& !frappe.model.std_fields_list.includes(fieldname)) {
|
||||
|
||||
frappe.throw(__(`Invalid filter: "${[fieldname.bold()]}"`));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
push_new_filter(args, is_new_filter=false) {
|
||||
// args: [doctype, fieldname, condition, value]
|
||||
if(this.filter_exists(args)) return;
|
||||
|
||||
// {}: Clear page filter fieldname field
|
||||
|
||||
let filter = this._push_new_filter(...args);
|
||||
|
||||
if (filter && filter.value) {
|
||||
filter.setup_state(is_new_filter);
|
||||
return filter._filter_value_set; // internal promise
|
||||
}
|
||||
}
|
||||
|
||||
_push_new_filter(doctype, fieldname, condition, value, hidden = false) {
|
||||
let args = {
|
||||
parent: this.wrapper,
|
||||
doctype: doctype,
|
||||
fieldname: fieldname,
|
||||
condition: condition,
|
||||
value: value,
|
||||
hidden: hidden,
|
||||
on_change: (update) => {
|
||||
if(update) this.update_filters();
|
||||
this.on_change();
|
||||
}
|
||||
};
|
||||
let filter = new frappe.ui.Filter(args);
|
||||
this.filters.push(filter);
|
||||
return filter;
|
||||
}
|
||||
|
||||
filter_exists(filter_value) {
|
||||
// filter_value of form: [doctype, fieldname, condition, value]
|
||||
let exists = false;
|
||||
this.filters.filter(f => f.field).map(f => {
|
||||
let f_value = f.get_value();
|
||||
let value = filter_value[3];
|
||||
let equal = frappe.utils.arrays_equal;
|
||||
|
||||
if(equal(f_value, filter_value) || (Array.isArray(value) && equal(value, f_value[3]))) {
|
||||
exists = true;
|
||||
}
|
||||
});
|
||||
return exists;
|
||||
}
|
||||
|
||||
get_filters() {
|
||||
return this.filters.filter(f => f.field).map(f => {
|
||||
f.freeze();
|
||||
return f.get_value();
|
||||
});
|
||||
// {}: this.list.update_standard_filters(values);
|
||||
}
|
||||
|
||||
update_filters() {
|
||||
this.filters = this.filters.filter(f => f.field); // remove hidden filters
|
||||
}
|
||||
|
||||
clear_filters() {
|
||||
this.filters.map(f => { f.remove(true); });
|
||||
// {}: Clear page filters, .date-range-picker (called list run())
|
||||
this.filters = [];
|
||||
}
|
||||
|
||||
get_filter(fieldname) {
|
||||
return this.filters.filter(f => {
|
||||
return (f.field && f.field.df.fieldname==fieldname);
|
||||
})[0];
|
||||
}
|
||||
|
||||
get_container_template() {
|
||||
return $(`<div class="tag-filters-area">
|
||||
<div class="active-tag-filters">
|
||||
<button class="btn btn-default btn-xs add-filter text-muted">
|
||||
${__("Add Filter")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-edit-area"></div>`);
|
||||
}
|
||||
};
|
||||
|
|
@ -1,679 +0,0 @@
|
|||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// MIT License. See license.txt
|
||||
|
||||
frappe.ui.FilterList = Class.extend({
|
||||
init: function(opts) {
|
||||
$.extend(this, opts);
|
||||
this.filters = [];
|
||||
this.wrapper = this.parent;
|
||||
this.stats = [];
|
||||
this.make();
|
||||
this.set_events();
|
||||
},
|
||||
make: function() {
|
||||
this.wrapper.find('.show_filters, .filter_area').remove();
|
||||
this.wrapper.append(`
|
||||
<div class="show_filters">
|
||||
<div class="set-filters">
|
||||
<button
|
||||
style="margin-right: 10px;"
|
||||
class="btn btn-default btn-xs new-filter text-muted">
|
||||
${__("Add Filter")}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter_area"></div>`);
|
||||
},
|
||||
set_events: function() {
|
||||
var me = this;
|
||||
// show filters
|
||||
this.wrapper.find('.new-filter').bind('click', function() {
|
||||
me.add_filter();
|
||||
});
|
||||
|
||||
this.wrapper.find('.clear-filters').bind('click', function() {
|
||||
me.clear_filters();
|
||||
$('.date-range-picker').val('')
|
||||
me.base_list.run();
|
||||
$(this).addClass("hide");
|
||||
});
|
||||
},
|
||||
|
||||
show_filters: function() {
|
||||
this.wrapper.find('.show_filters').toggle();
|
||||
if(!this.filters.length) {
|
||||
this.add_filter(this.doctype, 'name');
|
||||
this.filters[0].wrapper.find(".filter_field input").focus();
|
||||
}
|
||||
},
|
||||
|
||||
clear_filters: function() {
|
||||
$.each(this.filters, function(i, f) { f.remove(true); });
|
||||
if(this.base_list.page.fields_dict) {
|
||||
$.each(this.base_list.page.fields_dict, (key, value) => {
|
||||
value.set_input('');
|
||||
});
|
||||
}
|
||||
this.filters = [];
|
||||
},
|
||||
|
||||
add_filter: function(doctype, fieldname, condition, value, hidden) {
|
||||
// adds a new filter, returns true if filter has been added
|
||||
|
||||
// allow equal to be used as like
|
||||
let base_filter = this.base_list.page.fields_dict[fieldname];
|
||||
if (base_filter
|
||||
&& (base_filter.df.condition==condition
|
||||
|| (condition==='=' && base_filter.df.condition==='like'))) {
|
||||
// if filter exists in base_list, then exit
|
||||
this.base_list.page.fields_dict[fieldname].set_input(value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if(doctype && fieldname
|
||||
&& !frappe.meta.has_field(doctype, fieldname)
|
||||
&& !in_list(frappe.model.std_fields_list, fieldname)) {
|
||||
frappe.msgprint({
|
||||
message: __('Filter {0} missing', [fieldname.bold()]),
|
||||
title: 'Invalid Filter',
|
||||
indicator: 'red'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
this.wrapper.find('.show_filters').toggle(true);
|
||||
var is_new_filter = arguments.length===0;
|
||||
|
||||
if (is_new_filter && this.wrapper.find(".is-new-filter:visible").length) {
|
||||
// only allow 1 new filter at a time!
|
||||
return false;
|
||||
}
|
||||
|
||||
var filter = this.push_new_filter(doctype, fieldname, condition, value);
|
||||
if (!filter) return;
|
||||
|
||||
if(this.wrapper.find('.clear-filters').hasClass("hide")) {
|
||||
this.wrapper.find('.clear-filters').removeClass("hide");
|
||||
}
|
||||
|
||||
if (filter && is_new_filter) {
|
||||
filter.wrapper.addClass("is-new-filter");
|
||||
} else {
|
||||
filter.freeze();
|
||||
}
|
||||
|
||||
if (hidden) {
|
||||
filter.$btn_group.addClass("hide");
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
push_new_filter: function(doctype, fieldname, condition, value) {
|
||||
if(this.filter_exists(doctype, fieldname, condition, value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if standard filter exists, then clear it.
|
||||
if(this.base_list.page.fields_dict[fieldname]) {
|
||||
this.base_list.page.fields_dict[fieldname].set_input('');
|
||||
}
|
||||
|
||||
var filter = new frappe.ui.Filter({
|
||||
flist: this,
|
||||
_doctype: doctype,
|
||||
fieldname: fieldname,
|
||||
condition: condition,
|
||||
value: value
|
||||
});
|
||||
|
||||
this.filters.push(filter);
|
||||
|
||||
return filter;
|
||||
},
|
||||
|
||||
remove: function(filter) {
|
||||
// remove `filter` from flist
|
||||
for (var i in this.filters) {
|
||||
if (this.filters[i] === filter) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i!==undefined) {
|
||||
// remove index
|
||||
this.filters.splice(i, 1);
|
||||
}
|
||||
},
|
||||
|
||||
filter_exists: function(doctype, fieldname, condition, value) {
|
||||
var flag = false;
|
||||
for(var i in this.filters) {
|
||||
if(this.filters[i].field) {
|
||||
var f = this.filters[i].get_value();
|
||||
|
||||
if(f[0]==doctype && f[1]==fieldname && f[2]==condition && f[3]==value) {
|
||||
flag = true;
|
||||
} else if($.isArray(value) && frappe.utils.arrays_equal(value, f[3])) {
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
},
|
||||
|
||||
get_filters: function() {
|
||||
// get filter values as dict
|
||||
var values = [];
|
||||
$.each(this.filters, function(i, filter) {
|
||||
if(filter.field) {
|
||||
filter.freeze();
|
||||
values.push(filter.get_value());
|
||||
}
|
||||
});
|
||||
this.base_list.update_standard_filters(values);
|
||||
|
||||
return values;
|
||||
},
|
||||
|
||||
// remove hidden filters
|
||||
update_filters: function() {
|
||||
var fl = [];
|
||||
$.each(this.filters, function(i, f) {
|
||||
if(f.field) fl.push(f);
|
||||
})
|
||||
this.filters = fl;
|
||||
if(this.filters.length === 0) {
|
||||
this.wrapper.find('.clear-filters').addClass("hide");
|
||||
}
|
||||
},
|
||||
|
||||
get_filter: function(fieldname) {
|
||||
for(var i in this.filters) {
|
||||
if(this.filters[i].field && this.filters[i].field.df.fieldname==fieldname)
|
||||
return this.filters[i];
|
||||
}
|
||||
},
|
||||
|
||||
get_formatted_value: function(field, val){
|
||||
var value = val;
|
||||
|
||||
if(field.df.fieldname==="docstatus") {
|
||||
value = {0:"Draft", 1:"Submitted", 2:"Cancelled"}[value] || value;
|
||||
} else if(field.df.original_type==="Check") {
|
||||
value = {0:"No", 1:"Yes"}[cint(value)];
|
||||
}
|
||||
|
||||
value = frappe.format(value, field.df, {only_value: 1});
|
||||
return value;
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.Filter = Class.extend({
|
||||
init: function(opts) {
|
||||
$.extend(this, opts);
|
||||
|
||||
this.doctype = this.flist.doctype;
|
||||
this.make();
|
||||
this.make_select();
|
||||
this.set_events();
|
||||
},
|
||||
make: function() {
|
||||
this.wrapper = $(frappe.render_template("edit_filter", {}))
|
||||
.appendTo(this.flist.wrapper.find('.filter_area'));
|
||||
},
|
||||
make_select: function() {
|
||||
var me = this;
|
||||
this.fieldselect = new frappe.ui.FieldSelect({
|
||||
parent: this.wrapper.find('.fieldname_select_area'),
|
||||
doctype: this.doctype,
|
||||
filter_fields: this.filter_fields,
|
||||
select: function(doctype, fieldname) {
|
||||
me.set_field(doctype, fieldname);
|
||||
}
|
||||
});
|
||||
if(this.fieldname) {
|
||||
this.fieldselect.set_value(this._doctype || this.doctype, this.fieldname);
|
||||
}
|
||||
},
|
||||
set_events: function() {
|
||||
var me = this;
|
||||
|
||||
this.wrapper.find("a.remove-filter").on("click", function() {
|
||||
me.remove();
|
||||
});
|
||||
|
||||
this.wrapper.find(".set-filter-and-run").on("click", function() {
|
||||
me.wrapper.removeClass("is-new-filter");
|
||||
me.flist.base_list.run();
|
||||
me.apply();
|
||||
});
|
||||
|
||||
// add help for "in" codition
|
||||
me.wrapper.find('.condition').change(function() {
|
||||
if(!me.field) return;
|
||||
var condition = $(this).val();
|
||||
if(in_list(["in", "like", "not in", "not like"], condition)) {
|
||||
me.set_field(me.field.df.parent, me.field.df.fieldname, 'Data', condition);
|
||||
if(!me.field.desc_area) {
|
||||
me.field.desc_area = $('<div class="text-muted small">').appendTo(me.field.wrapper);
|
||||
}
|
||||
// set description
|
||||
me.field.desc_area.html((in_list(["in", "not in"], condition)==="in"
|
||||
? __("values separated by commas")
|
||||
: __("use % as wildcard"))+'</div>');
|
||||
} else {
|
||||
//if condition selected after refresh
|
||||
me.set_field(me.field.df.parent, me.field.df.fieldname, null, condition);
|
||||
}
|
||||
});
|
||||
|
||||
// set the field
|
||||
if(me.fieldname) {
|
||||
// pre-sets given (could be via tags!)
|
||||
return this.set_values(me._doctype, me.fieldname, me.condition, me.value);
|
||||
} else {
|
||||
me.set_field(me.doctype, 'name');
|
||||
}
|
||||
},
|
||||
|
||||
apply: function() {
|
||||
var f = this.get_value();
|
||||
|
||||
this.flist.remove(this);
|
||||
this.flist.push_new_filter(f[0], f[1], f[2], f[3]);
|
||||
this.wrapper.remove();
|
||||
this.flist.update_filters();
|
||||
},
|
||||
|
||||
remove: function(dont_run) {
|
||||
this.wrapper.remove();
|
||||
this.$btn_group && this.$btn_group.remove();
|
||||
this.field = null;
|
||||
this.flist.update_filters();
|
||||
|
||||
if(!dont_run) {
|
||||
this.flist.base_list.refresh(true);
|
||||
}
|
||||
},
|
||||
|
||||
set_values: function(doctype, fieldname, condition, value) {
|
||||
// presents given (could be via tags!)
|
||||
this.set_field(doctype, fieldname);
|
||||
|
||||
// change 0,1 to Yes, No for check field type
|
||||
if(this.field.df.original_type==='Check') {
|
||||
if(value==0) value = 'No';
|
||||
else if(value==1) value = 'Yes';
|
||||
}
|
||||
|
||||
if(condition) {
|
||||
this.wrapper.find('.condition').val(condition).change();
|
||||
}
|
||||
if(value!=null) {
|
||||
return this.field.set_value(value);
|
||||
}
|
||||
},
|
||||
|
||||
set_field: function(doctype, fieldname, fieldtype, condition) {
|
||||
var me = this;
|
||||
|
||||
// set in fieldname (again)
|
||||
var cur = me.field ? {
|
||||
fieldname: me.field.df.fieldname,
|
||||
fieldtype: me.field.df.fieldtype,
|
||||
parent: me.field.df.parent,
|
||||
} : {};
|
||||
|
||||
var original_docfield = me.fieldselect.fields_by_name[doctype][fieldname];
|
||||
if(!original_docfield) {
|
||||
frappe.msgprint(__("Field {0} is not selectable.", [fieldname]));
|
||||
return;
|
||||
}
|
||||
|
||||
var df = copy_dict(me.fieldselect.fields_by_name[doctype][fieldname]);
|
||||
|
||||
// filter field shouldn't be read only or hidden
|
||||
df.read_only = 0;
|
||||
df.hidden = 0;
|
||||
|
||||
if(!condition) this.set_default_condition(df, fieldtype);
|
||||
this.set_fieldtype(df, fieldtype);
|
||||
|
||||
// called when condition is changed,
|
||||
// don't change if all is well
|
||||
if(me.field && cur.fieldname == fieldname && df.fieldtype == cur.fieldtype &&
|
||||
df.parent == cur.parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// clear field area and make field
|
||||
me.fieldselect.selected_doctype = doctype;
|
||||
me.fieldselect.selected_fieldname = fieldname;
|
||||
|
||||
// save old text
|
||||
var old_text = null;
|
||||
if(me.field) {
|
||||
old_text = me.field.get_value();
|
||||
}
|
||||
|
||||
var field_area = me.wrapper.find('.filter_field').empty().get(0);
|
||||
var f = frappe.ui.form.make_control({
|
||||
df: df,
|
||||
parent: field_area,
|
||||
only_input: true,
|
||||
})
|
||||
f.refresh();
|
||||
|
||||
me.field = f;
|
||||
if(old_text && me.field.df.fieldtype===cur.fieldtype) {
|
||||
me.field.set_value(old_text);
|
||||
}
|
||||
|
||||
// run on enter
|
||||
$(me.field.wrapper).find(':input').keydown(function(ev) {
|
||||
if(ev.which==13) {
|
||||
me.flist.base_list.run();
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
set_fieldtype: function(df, fieldtype) {
|
||||
// reset
|
||||
if(df.original_type)
|
||||
df.fieldtype = df.original_type;
|
||||
else
|
||||
df.original_type = df.fieldtype;
|
||||
|
||||
df.description = ''; df.reqd = 0;
|
||||
df.ignore_link_validation = true;
|
||||
|
||||
// given
|
||||
if(fieldtype) {
|
||||
df.fieldtype = fieldtype;
|
||||
return;
|
||||
}
|
||||
|
||||
// scrub
|
||||
if(df.fieldname=="docstatus") {
|
||||
df.fieldtype="Select",
|
||||
df.options=[
|
||||
{value:0, label:__("Draft")},
|
||||
{value:1, label:__("Submitted")},
|
||||
{value:2, label:__("Cancelled")}
|
||||
]
|
||||
} else if(df.fieldtype=='Check') {
|
||||
df.fieldtype='Select';
|
||||
df.options='No\nYes';
|
||||
} else if(['Text','Small Text','Text Editor','Code','Tag','Comments',
|
||||
'Dynamic Link','Read Only','Assign'].indexOf(df.fieldtype)!=-1) {
|
||||
df.fieldtype = 'Data';
|
||||
} else if(df.fieldtype=='Link' && ['=', '!='].indexOf(this.wrapper.find('.condition').val())==-1) {
|
||||
df.fieldtype = 'Data';
|
||||
}
|
||||
if(df.fieldtype==="Data" && (df.options || "").toLowerCase()==="email") {
|
||||
df.options = null;
|
||||
}
|
||||
if(this.wrapper.find('.condition').val()== "Between" && (df.fieldtype == 'Date' || df.fieldtype == 'Datetime')){
|
||||
df.fieldtype = 'DateRange';
|
||||
}
|
||||
},
|
||||
|
||||
set_default_condition: function(df, fieldtype) {
|
||||
if(!fieldtype) {
|
||||
// set as "like" for data fields
|
||||
if (df.fieldtype == 'Data') {
|
||||
this.wrapper.find('.condition').val('like');
|
||||
} else if (df.fieldtype == 'Date' || df.fieldtype == 'Datetime'){
|
||||
this.wrapper.find('.condition').val('Between');
|
||||
}else{
|
||||
this.wrapper.find('.condition').val('=');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
get_value: function() {
|
||||
return [this.fieldselect.selected_doctype,
|
||||
this.field.df.fieldname, this.get_condition(), this.get_selected_value()];
|
||||
},
|
||||
|
||||
get_selected_value: function() {
|
||||
var val = this.field.get_value();
|
||||
|
||||
if(typeof val==='string') {
|
||||
val = strip(val);
|
||||
}
|
||||
|
||||
if(this.field.df.original_type == 'Check') {
|
||||
val = (val=='Yes' ? 1 :0);
|
||||
}
|
||||
|
||||
if(this.get_condition().indexOf('like', 'not like')!==-1) {
|
||||
// automatically append wildcards
|
||||
if(val) {
|
||||
if(val.slice(0,1) !== "%") {
|
||||
val = "%" + val;
|
||||
}
|
||||
if(val.slice(-1) !== "%") {
|
||||
val = val + "%";
|
||||
}
|
||||
}
|
||||
} else if(in_list(["in", "not in"], this.get_condition())) {
|
||||
if(val) {
|
||||
val = $.map(val.split(","), function(v) { return strip(v); });
|
||||
}
|
||||
} if(val === '%') {
|
||||
val = "";
|
||||
}
|
||||
|
||||
return val;
|
||||
},
|
||||
|
||||
get_condition: function() {
|
||||
return this.wrapper.find('.condition').val();
|
||||
},
|
||||
|
||||
freeze: function() {
|
||||
if(this.$btn_group) {
|
||||
// already made, just hide the condition setter
|
||||
this.set_filter_button_text();
|
||||
this.wrapper.toggle(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var me = this;
|
||||
|
||||
// add a button for new filter if missing
|
||||
this.$btn_group = $(`<div class="btn-group">
|
||||
<button class="btn btn-default btn-xs toggle-filter"
|
||||
title="${ __("Edit Filter") }">
|
||||
</button>
|
||||
<button class="btn btn-default btn-xs remove-filter"
|
||||
title="${ __("Remove Filter") }">
|
||||
<i class="fa fa-remove text-muted"></i>
|
||||
</button></div>`)
|
||||
.insertAfter(this.flist.wrapper.find(".set-filters .new-filter"));
|
||||
|
||||
this.set_filter_button_text();
|
||||
|
||||
this.$btn_group.find(".remove-filter").on("click", function() {
|
||||
me.remove();
|
||||
});
|
||||
|
||||
this.$btn_group.find(".toggle-filter").on("click", function() {
|
||||
$(this).closest('.show_filters').find('.filter_area').show()
|
||||
me.wrapper.toggle();
|
||||
})
|
||||
this.wrapper.toggle(false);
|
||||
},
|
||||
|
||||
set_filter_button_text: function() {
|
||||
var value = this.get_selected_value();
|
||||
value = this.flist.get_formatted_value(this.field, value);
|
||||
|
||||
// for translations
|
||||
// __("like"), __("not like"), __("in")
|
||||
|
||||
this.$btn_group.find(".toggle-filter")
|
||||
.html(repl('%(label)s %(condition)s "%(value)s"', {
|
||||
label: __(this.field.df.label),
|
||||
condition: __(this.get_condition()),
|
||||
value: __(value),
|
||||
}));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// <select> widget with all fields of a doctype as options
|
||||
frappe.ui.FieldSelect = Class.extend({
|
||||
// opts parent, doctype, filter_fields, with_blank, select
|
||||
init: function(opts) {
|
||||
var me = this;
|
||||
$.extend(this, opts);
|
||||
this.fields_by_name = {};
|
||||
this.options = [];
|
||||
this.$select = $('<input class="form-control">')
|
||||
.appendTo(this.parent)
|
||||
.on("click", function () { $(this).select(); });
|
||||
this.select_input = this.$select.get(0);
|
||||
this.awesomplete = new Awesomplete(this.select_input, {
|
||||
minChars: 0,
|
||||
maxItems: 99,
|
||||
autoFirst: true,
|
||||
list: me.options,
|
||||
item: function(item, input) {
|
||||
return $(repl('<li class="filter-field-select"><p>%(label)s</p></li>', item))
|
||||
.data("item.autocomplete", item)
|
||||
.get(0);
|
||||
}
|
||||
});
|
||||
this.$select.on("awesomplete-select", function(e) {
|
||||
var o = e.originalEvent;
|
||||
var value = o.text.value;
|
||||
var item = me.awesomplete.get_item(value);
|
||||
me.selected_doctype = item.doctype;
|
||||
me.selected_fieldname = item.fieldname;
|
||||
if(me.select) me.select(item.doctype, item.fieldname);
|
||||
});
|
||||
this.$select.on("awesomplete-selectcomplete", function(e) {
|
||||
var o = e.originalEvent;
|
||||
var value = o.text.value;
|
||||
var item = me.awesomplete.get_item(value);
|
||||
me.$select.val(item.label);
|
||||
});
|
||||
|
||||
if(this.filter_fields) {
|
||||
for(var i in this.filter_fields)
|
||||
this.add_field_option(this.filter_fields[i])
|
||||
} else {
|
||||
this.build_options();
|
||||
}
|
||||
this.set_value(this.doctype, "name");
|
||||
window.last_filter = this;
|
||||
},
|
||||
get_value: function() {
|
||||
return this.selected_doctype ? this.selected_doctype + "." + this.selected_fieldname : null;
|
||||
},
|
||||
val: function(value) {
|
||||
if(value===undefined) {
|
||||
return this.get_value();
|
||||
} else {
|
||||
this.set_value(value);
|
||||
}
|
||||
},
|
||||
clear: function() {
|
||||
this.selected_doctype = null;
|
||||
this.selected_fieldname = null;
|
||||
this.$select.val("");
|
||||
},
|
||||
set_value: function(doctype, fieldname) {
|
||||
var me = this;
|
||||
this.clear();
|
||||
if(!doctype) return;
|
||||
|
||||
// old style
|
||||
if(doctype.indexOf(".")!==-1) {
|
||||
var parts = doctype.split(".");
|
||||
doctype = parts[0];
|
||||
fieldname = parts[1];
|
||||
}
|
||||
|
||||
$.each(this.options, function(i, v) {
|
||||
if(v.doctype===doctype && v.fieldname===fieldname) {
|
||||
me.selected_doctype = doctype;
|
||||
me.selected_fieldname = fieldname;
|
||||
me.$select.val(v.label);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
build_options: function() {
|
||||
var me = this;
|
||||
me.table_fields = [];
|
||||
var std_filters = $.map(frappe.model.std_fields, function(d) {
|
||||
var opts = {parent: me.doctype}
|
||||
if(d.fieldname=="name") opts.options = me.doctype;
|
||||
return $.extend(copy_dict(d), opts);
|
||||
});
|
||||
|
||||
// add parenttype column
|
||||
var doctype_obj = locals['DocType'][me.doctype];
|
||||
if(doctype_obj && cint(doctype_obj.istable)) {
|
||||
std_filters = std_filters.concat([{
|
||||
fieldname: 'parent',
|
||||
fieldtype: 'Data',
|
||||
label: 'Parent',
|
||||
parent: me.doctype,
|
||||
}]);
|
||||
}
|
||||
|
||||
// blank
|
||||
if(this.with_blank) {
|
||||
this.options.push({
|
||||
label:"",
|
||||
value:"",
|
||||
})
|
||||
}
|
||||
|
||||
// main table
|
||||
var main_table_fields = std_filters.concat(frappe.meta.docfield_list[me.doctype]);
|
||||
$.each(frappe.utils.sort(main_table_fields, "label", "string"), function(i, df) {
|
||||
// show fields where user has read access and if report hide flag is not set
|
||||
if(frappe.perm.has_perm(me.doctype, df.permlevel, "read") && !df.report_hide)
|
||||
me.add_field_option(df);
|
||||
});
|
||||
|
||||
// child tables
|
||||
$.each(me.table_fields, function(i, table_df) {
|
||||
if(table_df.options) {
|
||||
var child_table_fields = [].concat(frappe.meta.docfield_list[table_df.options]);
|
||||
$.each(frappe.utils.sort(child_table_fields, "label", "string"), function(i, df) {
|
||||
// show fields where user has read access and if report hide flag is not set
|
||||
if(frappe.perm.has_perm(me.doctype, df.permlevel, "read") && !df.report_hide)
|
||||
me.add_field_option(df);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
add_field_option: function(df) {
|
||||
var me = this;
|
||||
if(me.doctype && df.parent==me.doctype) {
|
||||
var label = __(df.label);
|
||||
var table = me.doctype;
|
||||
if(df.fieldtype=='Table') me.table_fields.push(df);
|
||||
} else {
|
||||
var label = __(df.label) + ' (' + __(df.parent) + ')';
|
||||
var table = df.parent;
|
||||
}
|
||||
if(frappe.model.no_value_type.indexOf(df.fieldtype) == -1 &&
|
||||
!(me.fields_by_name[df.parent] && me.fields_by_name[df.parent][df.fieldname])) {
|
||||
this.options.push({
|
||||
label: label,
|
||||
value: table + "." + df.fieldname,
|
||||
fieldname: df.fieldname,
|
||||
doctype: df.parent
|
||||
});
|
||||
if(!me.fields_by_name[df.parent]) me.fields_by_name[df.parent] = {};
|
||||
me.fields_by_name[df.parent][df.fieldname] = df;
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
@ -97,7 +97,10 @@ frappe.ui.setup_like_popover = function($parent, selector) {
|
|||
animation: true,
|
||||
placement: "right",
|
||||
content: function() {
|
||||
var liked_by = JSON.parse($wrapper.attr('data-liked-by') || "[]");
|
||||
var liked_by = $wrapper.attr('data-liked-by');
|
||||
liked_by = liked_by ? decodeURI(liked_by) : '[]';
|
||||
liked_by = JSON.parse(liked_by);
|
||||
|
||||
var user = frappe.session.user;
|
||||
// hack
|
||||
if ($wrapper.find(".not-liked").length) {
|
||||
|
|
|
|||
|
|
@ -184,5 +184,9 @@ frappe.ui.SortSelector = Class.extend({
|
|||
return this.labels[fieldname]
|
||||
|| frappe.meta.get_label(this.doctype, fieldname);
|
||||
}
|
||||
},
|
||||
get_sql_string: function() {
|
||||
// build string like `tabTask`.`subject` desc
|
||||
return '`tab' + this.doctype + '`.`' + this.sort_by + '` ' + this.sort_order;
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -377,7 +377,7 @@ frappe.upload = {
|
|||
frappe.throw(__("File size exceeded the maximum allowed size of {0} MB", [max_file_size / 1048576]));
|
||||
}
|
||||
},
|
||||
multifile_upload:function(fileobjs, args, opts) {
|
||||
multifile_upload:function(fileobjs, args, opts={}) {
|
||||
//loop through filenames and checkboxes then append to list
|
||||
var fields = [];
|
||||
for (var i =0,j = fileobjs.length;i<j;i++) {
|
||||
|
|
|
|||
|
|
@ -17,17 +17,23 @@ frappe.breadcrumbs = {
|
|||
},
|
||||
|
||||
add: function(module, doctype, type) {
|
||||
frappe.breadcrumbs.all[frappe.breadcrumbs.current_page()] = {module:module, doctype:doctype, type:type};
|
||||
let obj;
|
||||
if (typeof module === 'object') {
|
||||
obj = module;
|
||||
} else {
|
||||
obj = {
|
||||
module:module,
|
||||
doctype:doctype,
|
||||
type:type
|
||||
}
|
||||
}
|
||||
|
||||
frappe.breadcrumbs.all[frappe.breadcrumbs.current_page()] = obj;
|
||||
frappe.breadcrumbs.update();
|
||||
},
|
||||
|
||||
current_page: function() {
|
||||
var route = frappe.get_route();
|
||||
// for List/DocType/{?} return List/DocType
|
||||
if (route[0] === 'List') {
|
||||
route = route.slice(0, 2);
|
||||
}
|
||||
return route.join("/");
|
||||
return frappe.get_route_str();
|
||||
},
|
||||
|
||||
update: function() {
|
||||
|
|
@ -38,11 +44,19 @@ frappe.breadcrumbs = {
|
|||
}
|
||||
|
||||
var $breadcrumbs = $("#navbar-breadcrumbs").empty();
|
||||
|
||||
if(!breadcrumbs) {
|
||||
$("body").addClass("no-breadcrumbs");
|
||||
return;
|
||||
}
|
||||
|
||||
if (breadcrumbs.type === 'Custom') {
|
||||
const html = `<li><a href="${breadcrumbs.route}">${breadcrumbs.label}</a></li>`;
|
||||
$breadcrumbs.append(html);
|
||||
$("body").removeClass("no-breadcrumbs");
|
||||
return;
|
||||
}
|
||||
|
||||
// get preferred module for breadcrumbs, based on sent via module
|
||||
var from_module = frappe.breadcrumbs.get_doctype_module(breadcrumbs.doctype);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,82 +4,63 @@
|
|||
frappe.provide("frappe.views.calendar");
|
||||
frappe.provide("frappe.views.calendars");
|
||||
|
||||
frappe.views.CalendarView = frappe.views.ListRenderer.extend({
|
||||
name: 'Calendar',
|
||||
render_view: function() {
|
||||
var me = this;
|
||||
this.get_calendar_options()
|
||||
frappe.views.CalendarView = class CalendarView extends frappe.views.ListView {
|
||||
static load_last_view() {
|
||||
const route = frappe.get_route();
|
||||
if (route.length === 3) {
|
||||
const doctype = route[1];
|
||||
const user_settings = frappe.get_user_settings(doctype)['Calendar'] || {};
|
||||
route.push(user_settings.last_calendar_view || 'Default');
|
||||
frappe.set_route(route);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
setup_defaults() {
|
||||
super.setup_defaults();
|
||||
this.page_title = this.page_title + ' ' + __('Calendar');
|
||||
this.calendar_settings = frappe.views.calendar[this.doctype] || {};
|
||||
this.calendar_name = frappe.get_route()[3];
|
||||
}
|
||||
|
||||
before_render() {
|
||||
super.before_render();
|
||||
this.save_view_user_settings({
|
||||
last_calendar: this.calendar_name
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.calendar) {
|
||||
this.calendar.refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
this.load_lib
|
||||
.then(() => this.get_calendar_options())
|
||||
.then(options => {
|
||||
this.calendar = new frappe.views.Calendar(options);
|
||||
});
|
||||
},
|
||||
load_last_view: function() {
|
||||
const route = frappe.get_route();
|
||||
|
||||
if (!route[3]) {
|
||||
// routed to Calendar view, check last calendar_view
|
||||
let calendar_view = this.user_settings.last_calendar_view;
|
||||
|
||||
if (calendar_view) {
|
||||
frappe.set_route('List', this.doctype, 'Calendar', calendar_view);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
set_defaults: function() {
|
||||
this._super();
|
||||
this.page_title = this.page_title + ' ' + __('Calendar');
|
||||
this.no_realtime = true;
|
||||
this.show_no_result = false;
|
||||
this.hide_sort_selector = true;
|
||||
},
|
||||
get_header_html: function() {
|
||||
return null;
|
||||
},
|
||||
should_refresh: function() {
|
||||
var should_refresh = this._super();
|
||||
if(!should_refresh) {
|
||||
this.last_calendar_view = this.current_calendar_view || '';
|
||||
this.current_calendar_view = this.get_calendar_view();
|
||||
|
||||
if (this.current_calendar_view !== 'Default') {
|
||||
this.page_title = __(this.current_calendar_view);
|
||||
} else {
|
||||
this.page_title = this.doctype + ' ' + __('Calendar');
|
||||
}
|
||||
|
||||
should_refresh = this.current_calendar_view !== this.last_calendar_view;
|
||||
}
|
||||
return should_refresh;
|
||||
},
|
||||
get_calendar_view: function() {
|
||||
return frappe.get_route()[3];
|
||||
},
|
||||
get_calendar_options: function() {
|
||||
const calendar_view = frappe.get_route()[3] || 'Default';
|
||||
|
||||
// save in user_settings
|
||||
frappe.model.user_settings.save(this.doctype, 'Calendar', {
|
||||
last_calendar_view: calendar_view
|
||||
});
|
||||
}
|
||||
|
||||
get_calendar_options() {
|
||||
const options = {
|
||||
doctype: this.doctype,
|
||||
parent: this.wrapper,
|
||||
page: this.list_view.page,
|
||||
list_view: this.list_view
|
||||
}
|
||||
parent: this.$result,
|
||||
page: this.page,
|
||||
list_view: this
|
||||
};
|
||||
const calendar_name = this.calendar_name;
|
||||
|
||||
return new Promise(resolve => {
|
||||
if (calendar_view === 'Default') {
|
||||
if (calendar_name === 'Default') {
|
||||
Object.assign(options, frappe.views.calendar[this.doctype]);
|
||||
resolve(options);
|
||||
} else {
|
||||
|
||||
frappe.model.with_doc('Calendar View', calendar_view, () => {
|
||||
const doc = frappe.get_doc('Calendar View', calendar_view);
|
||||
frappe.model.with_doc('Calendar View', calendar_name, () => {
|
||||
const doc = frappe.get_doc('Calendar View', calendar_name);
|
||||
Object.assign(options, {
|
||||
field_map: {
|
||||
id: "name",
|
||||
|
|
@ -88,18 +69,20 @@ frappe.views.CalendarView = frappe.views.ListRenderer.extend({
|
|||
title: doc.subject_field
|
||||
}
|
||||
});
|
||||
|
||||
resolve(options);
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
required_libs: [
|
||||
'assets/frappe/js/lib/fullcalendar/fullcalendar.min.css',
|
||||
'assets/frappe/js/lib/fullcalendar/fullcalendar.min.js',
|
||||
'assets/frappe/js/lib/fullcalendar/locale-all.js'
|
||||
]
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
get required_libs() {
|
||||
return [
|
||||
'assets/frappe/js/lib/fullcalendar/fullcalendar.min.css',
|
||||
'assets/frappe/js/lib/fullcalendar/fullcalendar.min.js',
|
||||
'assets/frappe/js/lib/fullcalendar/locale-all.js'
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
frappe.views.Calendar = Class.extend({
|
||||
init: function(options) {
|
||||
|
|
@ -123,11 +106,10 @@ frappe.views.Calendar = Class.extend({
|
|||
|
||||
$(this.parent).on("show", function() {
|
||||
me.$cal.fullCalendar("refetchEvents");
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
make: function() {
|
||||
var me = this;
|
||||
this.$wrapper = this.parent;
|
||||
this.$cal = $("<div>").appendTo(this.$wrapper);
|
||||
this.footnote_area = frappe.utils.set_footnote(this.footnote_area, this.$wrapper,
|
||||
|
|
@ -146,9 +128,9 @@ frappe.views.Calendar = Class.extend({
|
|||
this.$wrapper.find(".fc-button-group").addClass("btn-group");
|
||||
|
||||
this.$wrapper.find('.fc-prev-button span')
|
||||
.attr('class', '').addClass('fa fa-chevron-left')
|
||||
.attr('class', '').addClass('fa fa-chevron-left');
|
||||
this.$wrapper.find('.fc-next-button span')
|
||||
.attr('class', '').addClass('fa fa-chevron-right')
|
||||
.attr('class', '').addClass('fa fa-chevron-right');
|
||||
|
||||
var btn_group = this.$wrapper.find(".fc-button-group");
|
||||
btn_group.find(".fc-state-active").addClass("active");
|
||||
|
|
@ -197,22 +179,22 @@ frappe.views.Calendar = Class.extend({
|
|||
events = me.prepare_events(events);
|
||||
callback(events);
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
eventRender: function(event, element) {
|
||||
element.attr('title', event.tooltip);
|
||||
},
|
||||
eventClick: function(event, jsEvent, view) {
|
||||
eventClick: function(event) {
|
||||
// edit event description or delete
|
||||
var doctype = event.doctype || me.doctype;
|
||||
if(frappe.model.can_read(doctype)) {
|
||||
frappe.set_route("Form", doctype, event.name);
|
||||
}
|
||||
},
|
||||
eventDrop: function(event, delta, revertFunc, jsEvent, ui, view) {
|
||||
eventDrop: function(event, delta, revertFunc) {
|
||||
me.update_event(event, revertFunc);
|
||||
},
|
||||
eventResize: function(event, delta, revertFunc, jsEvent, ui, view) {
|
||||
eventResize: function(event, delta, revertFunc) {
|
||||
me.update_event(event, revertFunc);
|
||||
},
|
||||
select: function(startDate, endDate, jsEvent, view) {
|
||||
|
|
@ -269,7 +251,7 @@ frappe.views.Calendar = Class.extend({
|
|||
doctype: this.doctype,
|
||||
start: this.get_system_datetime(start),
|
||||
end: this.get_system_datetime(end),
|
||||
filters: this.list_view.filter_list.get_filters(),
|
||||
filters: this.list_view.filter_area.get(),
|
||||
field_map: this.field_map
|
||||
};
|
||||
return args;
|
||||
|
|
@ -384,4 +366,4 @@ frappe.views.Calendar = Class.extend({
|
|||
event.end = event.end ? $.fullCalendar.moment(event.end).add(1, "day").stripTime() : null;
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -418,8 +418,7 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
if(!form_values) return;
|
||||
|
||||
var selected_attachments =
|
||||
$.map($(me.dialog.wrapper)
|
||||
.find("[data-file-name]:checked"), function (element) {
|
||||
$.map($(me.dialog.wrapper).find("[data-file-name]:checked"), function (element) {
|
||||
return $(element).attr("data-file-name");
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -11,12 +11,9 @@ frappe.views.Factory = Class.extend({
|
|||
show: function() {
|
||||
var page_name = frappe.get_route_str(),
|
||||
me = this;
|
||||
if(page_name.substr(0, 4) === 'List') {
|
||||
page_name = frappe.get_route().slice(0, 2).join('/');
|
||||
}
|
||||
|
||||
if(frappe.pages[page_name] && !page_name.includes("Form/")) {
|
||||
frappe.container.change_to(frappe.pages[page_name]);
|
||||
frappe.container.change_to(page_name);
|
||||
if(me.on_show) {
|
||||
me.on_show();
|
||||
}
|
||||
|
|
|
|||
266
frappe/public/js/frappe/views/file/file_view.js
Normal file
266
frappe/public/js/frappe/views/file/file_view.js
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
frappe.provide('frappe.views');
|
||||
|
||||
frappe.views.FileView = class FileView extends frappe.views.ListView {
|
||||
static load_last_view() {
|
||||
const route = frappe.get_route();
|
||||
if (route.length === 2) {
|
||||
const view_user_settings = frappe.get_user_settings('File', 'File');
|
||||
frappe.set_route('List', 'File', view_user_settings.last_folder || frappe.boot.home_folder);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
setup_view() {
|
||||
this.setup_events();
|
||||
}
|
||||
|
||||
set_breadcrumbs() {
|
||||
const route = frappe.get_route();
|
||||
route.splice(-1);
|
||||
const last_folder = route[route.length - 1];
|
||||
if (last_folder === 'File') return;
|
||||
|
||||
const last_folder_route = '#' + route.join('/');
|
||||
frappe.breadcrumbs.add({
|
||||
type: 'Custom',
|
||||
label: last_folder,
|
||||
route: last_folder_route
|
||||
});
|
||||
}
|
||||
|
||||
setup_defaults() {
|
||||
super.setup_defaults();
|
||||
this.page_title = __('File Manager');
|
||||
|
||||
const route = frappe.get_route();
|
||||
this.current_folder = route.slice(2).join('/');
|
||||
this.filters = [['File', 'folder', '=', this.current_folder, true]];
|
||||
this.order_by = this.view_user_settings.order_by || 'file_name asc';
|
||||
|
||||
this.menu_items = this.menu_items.concat(this.file_menu_items());
|
||||
}
|
||||
|
||||
file_menu_items() {
|
||||
const items = [
|
||||
{
|
||||
label: __('Cut'),
|
||||
action: () => {
|
||||
frappe.file_manager.cut(this.get_checked_items(), this.current_folder);
|
||||
},
|
||||
class: 'cut-menu-button hide'
|
||||
},
|
||||
{
|
||||
label: __('Paste'),
|
||||
action: () => {
|
||||
frappe.file_manager.paste(this.current_folder);
|
||||
},
|
||||
class: 'paste-menu-button hide'
|
||||
},
|
||||
{
|
||||
label: __('New Folder'),
|
||||
action: () => {
|
||||
frappe.prompt(__('Name'), (values) => {
|
||||
if((values.value.indexOf("/") > -1)) {
|
||||
frappe.throw(__("Folder name should not include '/' (slash)"));
|
||||
}
|
||||
const data = {
|
||||
file_name: values.value,
|
||||
folder: this.current_folder
|
||||
};
|
||||
frappe.call({
|
||||
method: "frappe.core.doctype.file.file.create_new_folder",
|
||||
args: data
|
||||
});
|
||||
}, __('Enter folder name'), __('Create'));
|
||||
}
|
||||
},
|
||||
{
|
||||
label: __('Import Zip'),
|
||||
action: () => {
|
||||
// make upload dialog
|
||||
frappe.ui.get_upload_dialog({
|
||||
args: {
|
||||
folder: this.current_folder,
|
||||
from_form: 1
|
||||
},
|
||||
callback: (attachment, r) => {
|
||||
frappe.call({
|
||||
method: 'frappe.core.doctype.file.file.unzip_file',
|
||||
args: {
|
||||
name: r.message.name,
|
||||
},
|
||||
callback: function (r) {
|
||||
if(r.exc) {
|
||||
frappe.msgprint(__('Error in uploading files' + r.exc));
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
set_fields() {
|
||||
this._fields = this.meta.fields
|
||||
.filter(df => frappe.model.is_value_type(df.fieldtype) && !df.hidden)
|
||||
.map(df => df.fieldname)
|
||||
.concat(['name', 'modified']);
|
||||
}
|
||||
|
||||
update_data(data) {
|
||||
super.update_data(data);
|
||||
|
||||
this.data = this.data.map(d => {
|
||||
let icon_class = '';
|
||||
if (d.is_folder) {
|
||||
icon_class = "octicon octicon-file-directory";
|
||||
} else if (frappe.utils.is_image_file(d.file_name)) {
|
||||
icon_class = "octicon octicon-file-media";
|
||||
} else {
|
||||
icon_class = 'octicon octicon-file-text';
|
||||
}
|
||||
|
||||
let title = d.file_name || d.file_url;
|
||||
title = title.slice(0, 60);
|
||||
|
||||
d._title = `
|
||||
<i class="${icon_class} text-muted" style="width: 16px;"></i>
|
||||
<span>${title}</span>
|
||||
${d.is_private ? '<i class="fa fa-lock fa-fw text-warning"></i>' : ''}
|
||||
`;
|
||||
return d;
|
||||
});
|
||||
|
||||
// Bring folders to the top
|
||||
const { sort_by } = this.sort_selector;
|
||||
if (sort_by === 'file_name') {
|
||||
this.data.sort((a, b) => {
|
||||
if (a.is_folder && !b.is_folder) {
|
||||
return -1;
|
||||
}
|
||||
if (!a.is_folder &&b.is_folder) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
before_render() {
|
||||
super.before_render();
|
||||
this.save_view_user_settings({
|
||||
last_folder: this.current_folder
|
||||
});
|
||||
}
|
||||
|
||||
get_header_html() {
|
||||
let subject_html = `
|
||||
<div class="list-row-col list-subject level">
|
||||
<input class="level-item list-check-all hidden-xs" type="checkbox" title="${__("Select All")}">
|
||||
<span class="level-item">${__('File Name')}</span>
|
||||
</div>
|
||||
<div class="list-row-col ellipsis hidden-xs text-right">
|
||||
<span>${__('File Size')}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return this.get_header_html_skeleton(subject_html, '<span class="list-count"></span>');
|
||||
}
|
||||
|
||||
get_left_html(file) {
|
||||
const file_size = frappe.form.formatters.FileSize(file.file_size);
|
||||
const route_url = file.is_folder ? '#List/File/' + file.name : this.get_form_link(file);
|
||||
|
||||
return `
|
||||
<div class="list-row-col ellipsis list-subject level">
|
||||
<input class="level-item list-row-checkbox hidden-xs" type="checkbox" data-name="${file.name}">
|
||||
<span class="level-item ellipsis" title="${file.file_name}">
|
||||
<a class="ellipsis" href="${route_url}" title="${file.file_name}">
|
||||
${file._title}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="list-row-col ellipsis hidden-xs text-muted text-right">
|
||||
<span>${file_size}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
get_right_html(file) {
|
||||
return `
|
||||
<div class="level-item list-row-activity">
|
||||
${comment_when(file.modified)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
make_new_doc() {
|
||||
frappe.ui.get_upload_dialog({
|
||||
"args": {
|
||||
"folder": this.current_folder,
|
||||
"from_form": 1
|
||||
},
|
||||
callback:() => this.refresh()
|
||||
});
|
||||
}
|
||||
|
||||
setup_events() {
|
||||
super.setup_events();
|
||||
this.setup_drag_drop();
|
||||
}
|
||||
|
||||
setup_drag_drop() {
|
||||
this.$result.on('dragenter dragover', false)
|
||||
.on('drop', e => {
|
||||
var dataTransfer = e.originalEvent.dataTransfer;
|
||||
if (!(dataTransfer && dataTransfer.files && dataTransfer.files.length > 0)) {
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
frappe.upload.make({
|
||||
files: dataTransfer.files,
|
||||
"args": {
|
||||
"folder": this.current_folder,
|
||||
"from_form": 1
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
toggle_result_area() {
|
||||
super.toggle_result_area();
|
||||
this.toggle_cut_paste_buttons();
|
||||
}
|
||||
|
||||
on_row_checked() {
|
||||
super.on_row_checked();
|
||||
this.toggle_cut_paste_buttons();
|
||||
}
|
||||
|
||||
toggle_cut_paste_buttons() {
|
||||
// paste btn
|
||||
const $paste_btn = this.page.menu_btn_group.find('.paste-menu-button');
|
||||
const hide = !frappe.file_manager.can_paste ||
|
||||
frappe.file_manager.old_folder === this.current_folder;
|
||||
|
||||
if (hide) {
|
||||
$paste_btn.addClass('hide');
|
||||
} else {
|
||||
$paste_btn.removeClass('hide');
|
||||
}
|
||||
|
||||
// cut btn
|
||||
const $cut_btn = this.page.menu_btn_group.find('.cut-menu-button');
|
||||
if (this.$checks && this.$checks.length > 0) {
|
||||
$cut_btn.removeClass('hide');
|
||||
} else {
|
||||
$cut_btn.addClass('hide');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -1,73 +1,117 @@
|
|||
frappe.provide('frappe.views');
|
||||
|
||||
frappe.views.GanttView = frappe.views.ListRenderer.extend({
|
||||
name: 'Gantt',
|
||||
prepare: function(values) {
|
||||
this.items = values;
|
||||
this.prepare_tasks();
|
||||
this.prepare_dom();
|
||||
},
|
||||
frappe.views.GanttView = class GanttView extends frappe.views.ListView {
|
||||
|
||||
render_view: function(values) {
|
||||
var me = this;
|
||||
this.prepare(values);
|
||||
this.render_gantt();
|
||||
},
|
||||
|
||||
set_defaults: function() {
|
||||
this._super();
|
||||
this.no_realtime = true;
|
||||
setup_defaults() {
|
||||
super.setup_defaults();
|
||||
this.page_title = this.page_title + ' ' + __('Gantt');
|
||||
},
|
||||
this.calendar_settings = frappe.views.calendar[this.doctype] || {};
|
||||
this.order_by = this.view_user_settings.order_by || this.calendar_settings.field_map.start + ' asc';
|
||||
}
|
||||
|
||||
init_settings: function() {
|
||||
this._super();
|
||||
this.field_map = frappe.views.calendar[this.doctype].field_map;
|
||||
this.order_by = this.order_by || this.field_map.start + ' asc';
|
||||
},
|
||||
setup_view() {
|
||||
this.$result
|
||||
.css('overflow', 'auto')
|
||||
.append('<svg class="gantt-container" width="20" height="20"></svg>');
|
||||
}
|
||||
|
||||
prepare_dom: function() {
|
||||
this.wrapper.css('overflow', 'auto')
|
||||
.append('<svg class="gantt-container" width="20" height="20"></svg>')
|
||||
},
|
||||
update_data(data) {
|
||||
super.update_data(data);
|
||||
this.prepare_tasks();
|
||||
}
|
||||
|
||||
render_gantt: function(tasks) {
|
||||
prepare_tasks() {
|
||||
var me = this;
|
||||
this.gantt_view_mode = this.user_settings.gantt_view_mode || 'Day';
|
||||
var field_map = frappe.views.calendar[this.doctype].field_map;
|
||||
var meta = this.meta;
|
||||
var field_map = this.calendar_settings.field_map;
|
||||
|
||||
this.tasks = this.data.map(function (item) {
|
||||
// set progress
|
||||
var progress = 0;
|
||||
if (field_map.progress && $.isFunction(field_map.progress)) {
|
||||
progress = field_map.progress(item);
|
||||
} else if (field_map.progress) {
|
||||
progress = item[field_map.progress];
|
||||
}
|
||||
|
||||
// title
|
||||
var label;
|
||||
if (meta.title_field) {
|
||||
label = $.format("{0} ({1})", [item[meta.title_field], item.name]);
|
||||
} else {
|
||||
label = item[field_map.title];
|
||||
}
|
||||
|
||||
var r = {
|
||||
start: item[field_map.start],
|
||||
end: item[field_map.end],
|
||||
name: label,
|
||||
id: item[field_map.id || 'name'],
|
||||
doctype: me.doctype,
|
||||
progress: progress,
|
||||
dependencies: item.depends_on_tasks || ""
|
||||
};
|
||||
|
||||
if (item.color && frappe.ui.color.validate_hex(item.color)) {
|
||||
r['custom_class'] = 'color-' + item.color.substr(1);
|
||||
}
|
||||
|
||||
if (item.is_milestone) {
|
||||
r['custom_class'] = 'bar-milestone';
|
||||
}
|
||||
|
||||
return r;
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
this.load_lib.then(() => {
|
||||
this.render_gantt();
|
||||
});
|
||||
}
|
||||
|
||||
render_gantt() {
|
||||
const me = this;
|
||||
const gantt_view_mode = this.view_user_settings.gantt_view_mode || 'Day';
|
||||
const field_map = this.calendar_settings.field_map;
|
||||
const date_format = 'YYYY-MM-DD';
|
||||
|
||||
this.gantt = new Gantt(".gantt-container", this.tasks, {
|
||||
view_mode: this.gantt_view_mode,
|
||||
view_mode: gantt_view_mode,
|
||||
date_format: "YYYY-MM-DD",
|
||||
on_click: function (task) {
|
||||
frappe.set_route('Form', task.doctype, task.id);
|
||||
},
|
||||
on_date_change: function(task, start, end) {
|
||||
if(!me.can_write()) return;
|
||||
me.update_gantt_task(task, start, end);
|
||||
on_date_change: function (task, start, end) {
|
||||
if (!me.can_write) return;
|
||||
frappe.db.set_value(task.doctype, task.id, {
|
||||
[field_map.start]: start.format(date_format),
|
||||
[field_map.end]: end.format(date_format)
|
||||
});
|
||||
},
|
||||
on_progress_change: function(task, progress) {
|
||||
if(!me.can_write()) return;
|
||||
on_progress_change: function (task, progress) {
|
||||
if (!me.can_write) return;
|
||||
var progress_fieldname = 'progress';
|
||||
|
||||
if($.isFunction(field_map.progress)) {
|
||||
if ($.isFunction(field_map.progress)) {
|
||||
progress_fieldname = null;
|
||||
} else if(field_map.progress) {
|
||||
} else if (field_map.progress) {
|
||||
progress_fieldname = field_map.progress;
|
||||
}
|
||||
|
||||
if(progress_fieldname) {
|
||||
frappe.db.set_value(task.doctype, task.id,
|
||||
progress_fieldname, parseInt(progress));
|
||||
if (progress_fieldname) {
|
||||
frappe.db.set_value(task.doctype, task.id, {
|
||||
[progress_fieldname]: parseInt(progress)
|
||||
});
|
||||
}
|
||||
},
|
||||
on_view_change: function(mode) {
|
||||
on_view_change: function (mode) {
|
||||
// save view mode
|
||||
frappe.model.user_settings.save(me.doctype, 'Gantt', {
|
||||
me.save_view_user_settings({
|
||||
gantt_view_mode: mode
|
||||
});
|
||||
},
|
||||
custom_popup_html: function(task) {
|
||||
custom_popup_html: function (task) {
|
||||
var item = me.get_item(task.id);
|
||||
|
||||
var html =
|
||||
|
|
@ -76,48 +120,51 @@ frappe.views.GanttView = frappe.views.ListRenderer.extend({
|
|||
|
||||
// custom html in doctype settings
|
||||
var custom = me.settings.gantt_custom_popup_html;
|
||||
if(custom && $.isFunction(custom)) {
|
||||
if (custom && $.isFunction(custom)) {
|
||||
var ganttobj = task;
|
||||
html = custom(ganttobj, item);
|
||||
}
|
||||
return '<div class="details-container">' + html + '</div>';
|
||||
}
|
||||
});
|
||||
this.render_dropdown();
|
||||
this.setup_view_mode_buttons();
|
||||
this.set_colors();
|
||||
},
|
||||
|
||||
render_dropdown: function() {
|
||||
var me = this;
|
||||
var view_modes = this.gantt.config.view_modes || [];
|
||||
var dropdown = "<div class='dropdown pull-right'>" +
|
||||
"<a class='text-muted dropdown-toggle' data-toggle='dropdown'>" +
|
||||
"<span class='dropdown-text'>"+__(this.gantt_view_mode)+"</span><i class='caret'></i></a>" +
|
||||
"<ul class='dropdown-menu'></ul>" +
|
||||
"</div>";
|
||||
}
|
||||
|
||||
setup_view_mode_buttons() {
|
||||
// view modes (for translation) __("Day"), __("Week"), __("Month"),
|
||||
//__("Half Day"), __("Quarter Day")
|
||||
|
||||
var dropdown_list = "";
|
||||
view_modes.forEach(function(view_mode) {
|
||||
dropdown_list += "<li>" +
|
||||
"<a class='option' data-value='" + view_mode + "'>" +
|
||||
__(view_mode) + "</a></li>";
|
||||
});
|
||||
var $dropdown = $(dropdown)
|
||||
$dropdown.find(".dropdown-menu").append(dropdown_list);
|
||||
me.list_view.$page
|
||||
.find(`[data-list-renderer='Gantt'] > .list-row-right`)
|
||||
.css("margin-right", "15px").html($dropdown)
|
||||
$dropdown.on("click", ".option", function() {
|
||||
var mode = $(this).data('value');
|
||||
me.gantt.change_view_mode(mode);
|
||||
$dropdown.find(".dropdown-text").text(mode);
|
||||
});
|
||||
},
|
||||
let $btn_group = this.$paging_area.find('.gantt-view-mode');
|
||||
if ($btn_group.length > 0) return;
|
||||
|
||||
set_colors: function() {
|
||||
const view_modes = this.gantt.config.view_modes || [];
|
||||
const active_class = view_mode => this.gantt.view_is(view_mode) ? 'btn-info' : '';
|
||||
const html =
|
||||
`<div class="btn-group gantt-view-mode">
|
||||
${view_modes.map(value => `<button type="button"
|
||||
class="btn btn-default btn-sm btn-view-mode ${active_class(value)}"
|
||||
data-value="${value}">
|
||||
${__(value)}
|
||||
</button>`).join('')}
|
||||
</div>`;
|
||||
|
||||
this.$paging_area.find('.level-left').append(html);
|
||||
|
||||
// change view mode asynchronously
|
||||
const change_view_mode = (value) => setTimeout(() => this.gantt.change_view_mode(value), 0);
|
||||
|
||||
this.$paging_area.on('click', '.btn-view-mode', e => {
|
||||
const $btn = $(e.currentTarget);
|
||||
this.$paging_area.find('.btn-view-mode').removeClass('btn-info');
|
||||
$btn.addClass('btn-info');
|
||||
|
||||
const value = $btn.data().value;
|
||||
change_view_mode(value);
|
||||
});
|
||||
}
|
||||
|
||||
set_colors() {
|
||||
const classes = this.tasks
|
||||
.map(t => t.custom_class)
|
||||
.filter(c => c && c.startsWith('color-'));
|
||||
|
|
@ -137,102 +184,19 @@ frappe.views.GanttView = frappe.views.ListRenderer.extend({
|
|||
}).join("");
|
||||
|
||||
style = `<style>${style}</style>`;
|
||||
this.$result.prepend(style);
|
||||
}
|
||||
|
||||
this.wrapper.prepend(style);
|
||||
},
|
||||
|
||||
prepare_tasks: function() {
|
||||
var me = this;
|
||||
var meta = frappe.get_meta(this.doctype);
|
||||
var field_map = frappe.views.calendar[this.doctype].field_map;
|
||||
this.tasks = this.items.map(function(item) {
|
||||
// set progress
|
||||
var progress = 0;
|
||||
if(field_map.progress && $.isFunction(field_map.progress)) {
|
||||
progress = field_map.progress(item);
|
||||
} else if(field_map.progress) {
|
||||
progress = item[field_map.progress]
|
||||
}
|
||||
|
||||
// title
|
||||
if(meta.title_field) {
|
||||
var label = $.format("{0} ({1})", [item[meta.title_field], item.name]);
|
||||
} else {
|
||||
var label = item[field_map.title];
|
||||
}
|
||||
|
||||
var r = {
|
||||
start: item[field_map.start],
|
||||
end: item[field_map.end],
|
||||
name: label,
|
||||
id: item[field_map.id || 'name'],
|
||||
doctype: me.doctype,
|
||||
progress: progress,
|
||||
dependencies: item.depends_on_tasks || ""
|
||||
};
|
||||
|
||||
if(item.color && frappe.ui.color.validate_hex(item.color)) {
|
||||
r['custom_class'] = 'color-' + item.color.substr(1);
|
||||
}
|
||||
|
||||
if(item.is_milestone) {
|
||||
r['custom_class'] = 'bar-milestone';
|
||||
}
|
||||
|
||||
return r;
|
||||
});
|
||||
},
|
||||
get_item: function(name) {
|
||||
return this.items.find(function(item) {
|
||||
get_item(name) {
|
||||
return this.data.find(function (item) {
|
||||
return item.name === name;
|
||||
});
|
||||
},
|
||||
update_gantt_task: function(task, start, end) {
|
||||
var me = this;
|
||||
if(me.gantt.updating_task) {
|
||||
setTimeout(me.update_gantt_task.bind(me, task, start, end), 200)
|
||||
return;
|
||||
}
|
||||
me.gantt.updating_task = true;
|
||||
}
|
||||
|
||||
var field_map = frappe.views.calendar[this.doctype].field_map;
|
||||
frappe.call({
|
||||
method: 'frappe.desk.gantt.update_task',
|
||||
args: {
|
||||
args: {
|
||||
doctype: task.doctype,
|
||||
name: task.id,
|
||||
start: start.format('YYYY-MM-DD'),
|
||||
end: end.format('YYYY-MM-DD')
|
||||
},
|
||||
field_map: field_map
|
||||
},
|
||||
callback: function() {
|
||||
me.gantt.updating_task = false;
|
||||
frappe.show_alert({message:__("Saved"), indicator: 'green'}, 1);
|
||||
}
|
||||
});
|
||||
},
|
||||
get_header_html: function() {
|
||||
return frappe.render_template('list_item_row_head', { main: '', list: this });
|
||||
},
|
||||
refresh: function(values) {
|
||||
this.prepare(values);
|
||||
this.render();
|
||||
},
|
||||
can_write: function() {
|
||||
if(frappe.model.can_write(this.doctype)) {
|
||||
return true;
|
||||
} else {
|
||||
// reset gantt state
|
||||
this.gantt.change_view_mode(this.gantt_view_mode);
|
||||
frappe.show_alert({message: __("Not permitted"), indicator: 'red'}, 1);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
set_columns: function() {},
|
||||
required_libs: [
|
||||
"assets/frappe/js/lib/snap.svg-min.js",
|
||||
"assets/frappe/js/lib/frappe-gantt/frappe-gantt.js"
|
||||
]
|
||||
});
|
||||
get required_libs() {
|
||||
return [
|
||||
"assets/frappe/js/lib/snap.svg-min.js",
|
||||
"assets/frappe/js/lib/frappe-gantt/frappe-gantt.js"
|
||||
];
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,11 +3,27 @@
|
|||
*/
|
||||
frappe.provide("frappe.views");
|
||||
|
||||
frappe.views.ImageView = frappe.views.ListRenderer.extend({
|
||||
name: 'Image',
|
||||
render_view: function (values) {
|
||||
this.items = values;
|
||||
frappe.views.ImageView = class ImageView extends frappe.views.ListView {
|
||||
|
||||
setup_defaults() {
|
||||
super.setup_defaults();
|
||||
this.page_title = this.page_title + ' ' + __('Images');
|
||||
}
|
||||
|
||||
set_fields() {
|
||||
this._fields = [
|
||||
'name',
|
||||
this.meta.title_field,
|
||||
this.meta.image_field
|
||||
];
|
||||
}
|
||||
|
||||
update_data(data) {
|
||||
super.update_data(data);
|
||||
this.items = this.data.map(this.prepare_data.bind(this));
|
||||
}
|
||||
|
||||
render() {
|
||||
this.get_attached_images()
|
||||
.then(() => {
|
||||
this.render_image_view();
|
||||
|
|
@ -18,105 +34,138 @@ frappe.views.ImageView = frappe.views.ListRenderer.extend({
|
|||
this.gallery.prepare_pswp_items(this.items, this.images_map);
|
||||
}
|
||||
});
|
||||
},
|
||||
set_defaults: function() {
|
||||
this._super();
|
||||
this.page_title = this.page_title + ' ' + __('Images');
|
||||
},
|
||||
prepare_data: function(data) {
|
||||
data = this._super(data);
|
||||
}
|
||||
|
||||
prepare_data(data) {
|
||||
// absolute url if cordova, else relative
|
||||
data._image_url = this.get_image_url(data);
|
||||
return data;
|
||||
},
|
||||
render_image_view: function () {
|
||||
var html = this.items.map(this.render_item.bind(this)).join("");
|
||||
}
|
||||
|
||||
this.container = this.wrapper.find('.image-view-container');
|
||||
if (this.container.length === 0) {
|
||||
this.container = $('<div>')
|
||||
.addClass('image-view-container')
|
||||
.appendTo(this.wrapper);
|
||||
}
|
||||
render_image_view() {
|
||||
var html = this.items.map(this.item_html.bind(this)).join("");
|
||||
|
||||
this.container.append(html);
|
||||
},
|
||||
render_item: function (item) {
|
||||
var indicator = this.get_indicator_html(item);
|
||||
return frappe.render_template("image_view_item_row", {
|
||||
data: item,
|
||||
indicator: indicator,
|
||||
subject: this.get_subject_html(item, true),
|
||||
additional_columns: this.additional_columns,
|
||||
color: frappe.get_palette(item.item_name)
|
||||
});
|
||||
},
|
||||
get_image_url: function (item) {
|
||||
this.$result.html(`
|
||||
${this.get_header_html()}
|
||||
<div class="image-view-container small">
|
||||
${html}
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
item_html(item) {
|
||||
item._name = encodeURI(item.name);
|
||||
const encoded_name = item._name;
|
||||
const title = strip_html(item[this.meta.title_field || 'name']);
|
||||
const _class = !item._image_url ? 'no-image' : '';
|
||||
const _html = item._image_url ?
|
||||
`<img data-name="${encoded_name}" src="${ item._image_url }" alt="${ title }">` :
|
||||
`<span class="placeholder-text">
|
||||
${ frappe.get_abbr(title) }
|
||||
</span>`;
|
||||
|
||||
return `
|
||||
<div class="image-view-item">
|
||||
<div class="image-view-header">
|
||||
<div class="list-row-col list-subject ellipsis level">
|
||||
${this.get_subject_html(item)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="image-view-body">
|
||||
<a data-name="${encoded_name}"
|
||||
title="${encoded_name}"
|
||||
href="${this.get_form_link(item)}"
|
||||
>
|
||||
<div class="image-field ${_class}"
|
||||
data-name="${encoded_name}"
|
||||
>
|
||||
${_html}
|
||||
<button class="btn btn-default zoom-view" data-name="${encoded_name}">
|
||||
<i class="fa fa-search-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
get_image_url(data) {
|
||||
var url;
|
||||
url = item.image ? item.image : item[this.meta.image_field];
|
||||
url = data.image ? data.image : data[this.meta.image_field];
|
||||
|
||||
// absolute url for mobile
|
||||
if (window.cordova && !frappe.utils.is_url(url)) {
|
||||
url = frappe.base_url + url;
|
||||
}
|
||||
if (url) {
|
||||
return url
|
||||
return url;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
get_attached_images: function () {
|
||||
}
|
||||
|
||||
get_attached_images() {
|
||||
return frappe.call({
|
||||
method: 'frappe.core.doctype.file.file.get_attached_images',
|
||||
args: { doctype: this.doctype, names: this.items.map(i => i.name) }
|
||||
args: {
|
||||
doctype: this.doctype,
|
||||
names: this.items.map(i => i.name)
|
||||
}
|
||||
}).then(r => {
|
||||
this.images_map = Object.assign(this.images_map || {}, r.message);
|
||||
});
|
||||
},
|
||||
get_header_html: function () {
|
||||
var main = frappe.render_template('list_item_main_head', {
|
||||
col: { type: "Subject" },
|
||||
_checkbox: ((frappe.model.can_delete(this.doctype) || this.settings.selectable)
|
||||
&& !this.no_delete)
|
||||
});
|
||||
return frappe.render_template('list_item_row_head', { main: main, list: this });
|
||||
},
|
||||
setup_gallery: function() {
|
||||
}
|
||||
|
||||
get_header_html() {
|
||||
return this.get_header_html_skeleton(`
|
||||
<div class="list-row-col list-subject level ">
|
||||
<input class="level-item list-check-all hidden-xs" type="checkbox" title="Select All">
|
||||
<span class="level-item list-liked-by-me">
|
||||
<i class="octicon octicon-heart text-extra-muted" title="Likes"></i>
|
||||
</span>
|
||||
<span class="level-item"></span>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
setup_gallery() {
|
||||
var me = this;
|
||||
this.gallery = new frappe.views.GalleryView({
|
||||
doctype: this.doctype,
|
||||
items: this.items,
|
||||
wrapper: this.container,
|
||||
wrapper: this.$result,
|
||||
images_map: this.images_map
|
||||
});
|
||||
this.container.on('click', '.btn.zoom-view', function(e) {
|
||||
this.$result.on('click', '.btn.zoom-view', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var name = $(this).data().name;
|
||||
name = decodeURIComponent(name);
|
||||
me.gallery.show(name);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
frappe.views.GalleryView = Class.extend({
|
||||
init: function(opts) {
|
||||
init: function (opts) {
|
||||
$.extend(this, opts);
|
||||
var me = this;
|
||||
|
||||
this.lib_ready = this.load_lib();
|
||||
this.lib_ready.then(function() {
|
||||
this.lib_ready.then(function () {
|
||||
me.prepare();
|
||||
});
|
||||
},
|
||||
prepare: function() {
|
||||
prepare: function () {
|
||||
// keep only one pswp dom element
|
||||
this.pswp_root = $('body > .pswp');
|
||||
if(this.pswp_root.length === 0) {
|
||||
if (this.pswp_root.length === 0) {
|
||||
var pswp = frappe.render_template('photoswipe_dom');
|
||||
this.pswp_root = $(pswp).appendTo('body');
|
||||
}
|
||||
},
|
||||
prepare_pswp_items: function(_items, _images_map) {
|
||||
prepare_pswp_items: function (_items, _images_map) {
|
||||
var me = this;
|
||||
|
||||
if (_items) {
|
||||
|
|
@ -126,18 +175,18 @@ frappe.views.GalleryView = Class.extend({
|
|||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
const items = this.items.map(function(i) {
|
||||
const query = 'img[data-name="'+i.name+'"]';
|
||||
const items = this.items.map(function (i) {
|
||||
const query = 'img[data-name="' + i._name + '"]';
|
||||
let el = me.wrapper.find(query).get(0);
|
||||
|
||||
let width, height;
|
||||
if(el) {
|
||||
if (el) {
|
||||
width = el.naturalWidth;
|
||||
height = el.naturalHeight;
|
||||
}
|
||||
|
||||
if(!el) {
|
||||
el = me.wrapper.find('.image-field[data-name="'+i.name+'"]').get(0);
|
||||
if (!el) {
|
||||
el = me.wrapper.find('.image-field[data-name="' + i._name + '"]').get(0);
|
||||
width = el.getBoundingClientRect().width;
|
||||
height = el.getBoundingClientRect().height;
|
||||
}
|
||||
|
|
@ -149,26 +198,26 @@ frappe.views.GalleryView = Class.extend({
|
|||
w: width,
|
||||
h: height,
|
||||
el: el
|
||||
}
|
||||
};
|
||||
});
|
||||
this.pswp_items = items;
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
show: function(docname) {
|
||||
show: function (docname) {
|
||||
this.lib_ready
|
||||
.then(() => this.prepare_pswp_items())
|
||||
.then(() => this._show(docname));
|
||||
},
|
||||
_show: function(docname) {
|
||||
_show: function (docname) {
|
||||
const me = this;
|
||||
const items = this.pswp_items;
|
||||
const item_index = items.findIndex(item => item.name === docname);
|
||||
|
||||
var options = {
|
||||
index: item_index,
|
||||
getThumbBoundsFn: function(index) {
|
||||
const query = 'img[data-name="' + items[index].name + '"]';
|
||||
getThumbBoundsFn: function (index) {
|
||||
const query = 'img[data-name="' + items[index]._name + '"]';
|
||||
let thumbnail = me.wrapper.find(query).get(0);
|
||||
|
||||
if (!thumbnail) {
|
||||
|
|
@ -178,12 +227,16 @@ frappe.views.GalleryView = Class.extend({
|
|||
var pageYScroll = window.pageYOffset || document.documentElement.scrollTop,
|
||||
rect = thumbnail.getBoundingClientRect();
|
||||
|
||||
return {x:rect.left, y:rect.top + pageYScroll, w:rect.width};
|
||||
return {
|
||||
x: rect.left,
|
||||
y: rect.top + pageYScroll,
|
||||
w: rect.width
|
||||
};
|
||||
},
|
||||
history: false,
|
||||
shareEl: false,
|
||||
showHideOpacity: true
|
||||
}
|
||||
};
|
||||
|
||||
// init
|
||||
this.pswp = new PhotoSwipe(
|
||||
|
|
@ -195,12 +248,12 @@ frappe.views.GalleryView = Class.extend({
|
|||
this.browse_images();
|
||||
this.pswp.init();
|
||||
},
|
||||
browse_images: function() {
|
||||
browse_images: function () {
|
||||
const $more_items = this.pswp_root.find('.pswp__more-items');
|
||||
const images_map = this.images_map;
|
||||
let last_hide_timeout = null;
|
||||
|
||||
this.pswp.listen('afterChange', function() {
|
||||
this.pswp.listen('afterChange', function () {
|
||||
const images = images_map[this.currItem.name];
|
||||
if (!images || images.length === 1) {
|
||||
$more_items.html('');
|
||||
|
|
@ -214,7 +267,9 @@ frappe.views.GalleryView = Class.extend({
|
|||
|
||||
this.pswp.listen('beforeChange', hide_more_items);
|
||||
this.pswp.listen('initialZoomOut', hide_more_items);
|
||||
this.pswp.listen('destroy', $(document).off('mousemove', hide_more_items_after_2s));
|
||||
this.pswp.listen('destroy', () => {
|
||||
$(document).off('mousemove', hide_more_items_after_2s);
|
||||
});
|
||||
|
||||
// Replace current image on click
|
||||
$more_items.on('click', '.pswp__more-item', (e) => {
|
||||
|
|
@ -255,7 +310,7 @@ frappe.views.GalleryView = Class.extend({
|
|||
</div>`;
|
||||
}
|
||||
},
|
||||
load_lib: function() {
|
||||
load_lib: function () {
|
||||
return new Promise(resolve => {
|
||||
var asset_dir = 'assets/frappe/js/lib/photoswipe/';
|
||||
frappe.require([
|
||||
|
|
|
|||
|
|
@ -32,13 +32,4 @@
|
|||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="image-view-footer hide">
|
||||
<div class="row">
|
||||
<div class="col-xs-4">{%= indicator %}</div>
|
||||
<div class="col-xs-8 text-right">
|
||||
<!-- comments count and assigned to section -->
|
||||
{%= frappe.render_template("item_assigned_to_comment_count", { data: data }) %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
<div class="msg-box no-border">
|
||||
{% if(!frappe.model.can_create(doctype) && doctype == "Email Account") { %}
|
||||
<p>{{ __("No Email Accounts Assigned") }}</p>
|
||||
{% } else { %}
|
||||
<p>{{ msg }}</p>
|
||||
<p>
|
||||
<button class="btn btn-primary btn-sm btn-no-result" list_view_doc="{{ doctype }}">
|
||||
{{ label }}
|
||||
</button>
|
||||
</p>
|
||||
{% } %}
|
||||
</div>
|
||||
|
|
@ -1,168 +1,204 @@
|
|||
/**
|
||||
* frappe.views.EmailInboxView
|
||||
* frappe.views.InboxView
|
||||
*/
|
||||
|
||||
frappe.provide("frappe.views");
|
||||
|
||||
frappe.views.InboxView = frappe.views.ListRenderer.extend({
|
||||
name: 'Inbox',
|
||||
render_view: function(values) {
|
||||
var me = this;
|
||||
frappe.views.InboxView = class InboxView extends frappe.views.ListView {
|
||||
static load_last_view() {
|
||||
const route = frappe.get_route();
|
||||
if (!route[3] && frappe.boot.email_accounts.length) {
|
||||
let email_account;
|
||||
if (frappe.boot.email_accounts[0].email_id == "All Accounts") {
|
||||
email_account = "All Accounts";
|
||||
} else {
|
||||
email_account = frappe.boot.email_accounts[0].email_account;
|
||||
}
|
||||
frappe.set_route("List", "Communication", "Inbox", email_account);
|
||||
return true;
|
||||
} else if (!route[3] || (route[3] !== "All Accounts" && !is_valid(route[3]))) {
|
||||
frappe.msgprint(__('Invalid Email Account'));
|
||||
window.history.back();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
this.emails = values;
|
||||
function is_valid(email_account) {
|
||||
return frappe.boot.email_accounts.find(d => d.email_account === email_account);
|
||||
}
|
||||
}
|
||||
show() {
|
||||
super.show();
|
||||
// save email account in user_settings
|
||||
frappe.model.user_settings.save("Communication", 'Inbox', {
|
||||
this.save_view_user_settings({
|
||||
last_email_account: this.current_email_account
|
||||
});
|
||||
}
|
||||
|
||||
this.render_inbox_view();
|
||||
},
|
||||
render_inbox_view: function() {
|
||||
var html = ""
|
||||
|
||||
var email_account = this.get_current_email_account()
|
||||
if(email_account)
|
||||
html = this.emails.map(this.render_email_row.bind(this)).join("");
|
||||
else
|
||||
html = this.make_no_result()
|
||||
|
||||
this.container = $('<div>')
|
||||
.addClass('inbox-container')
|
||||
.appendTo(this.wrapper);
|
||||
this.container.append(html);
|
||||
},
|
||||
render_email_row: function(email) {
|
||||
if(!email.css_seen && email.seen)
|
||||
email.css_seen = "seen"
|
||||
|
||||
return frappe.render_template("inbox_view_item_row", {
|
||||
data: email,
|
||||
is_sent_emails: this.is_sent_emails,
|
||||
});
|
||||
},
|
||||
set_defaults: function() {
|
||||
this._super();
|
||||
this.page_title = __("Email Inbox");
|
||||
},
|
||||
|
||||
init_settings: function() {
|
||||
this._super();
|
||||
setup_defaults() {
|
||||
super.setup_defaults();
|
||||
this.email_account = frappe.get_route()[3];
|
||||
this.page_title = this.email_account;
|
||||
this.filters = this.get_inbox_filters();
|
||||
},
|
||||
should_refresh: function() {
|
||||
var to_refresh = this._super();
|
||||
if(!to_refresh) {
|
||||
this.last_email_account = this.current_email_account || '';
|
||||
this.current_email_account = this.get_current_email_account();
|
||||
this.is_sent_emails = this.current_email_account === "Sent"? true: false
|
||||
}
|
||||
|
||||
to_refresh = this.current_email_account !== this.last_email_account;
|
||||
}
|
||||
get is_sent_emails() {
|
||||
const f = this.filter_area.get()
|
||||
.find(filter => filter[1] === 'sent_or_received');
|
||||
return f && f[3] === 'Sent';
|
||||
}
|
||||
|
||||
if(to_refresh){
|
||||
this.list_view.page.main.find(".list-headers").empty();
|
||||
}
|
||||
return to_refresh;
|
||||
},
|
||||
get_inbox_filters: function() {
|
||||
var email_account = this.get_current_email_account();
|
||||
render() {
|
||||
this.emails = this.data;
|
||||
this.render_inbox_view();
|
||||
}
|
||||
|
||||
render_inbox_view() {
|
||||
let html = this.emails.map(this.render_email_row.bind(this)).join("");
|
||||
|
||||
this.$result.html(`
|
||||
${this.get_header_html()}
|
||||
${html}
|
||||
`);
|
||||
}
|
||||
|
||||
get_header_html() {
|
||||
return this.get_header_html_skeleton(`
|
||||
<div class="list-row-col list-subject level">
|
||||
<input class="level-item list-check-all hidden-xs" type="checkbox" title="Select All">
|
||||
<span class="level-item">${__('Subject')}</span>
|
||||
</div>
|
||||
<div class="list-row-col hidden-xs">
|
||||
<span>${this.is_sent_emails ? __("To") : __("From")}</span>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
render_email_row(email) {
|
||||
if (!email.css_seen && email.seen)
|
||||
email.css_seen = "seen";
|
||||
|
||||
const columns_html = `
|
||||
<div class="list-row-col list-subject level">
|
||||
<input class="level-item list-row-checkbox hidden-xs" type="checkbox" data-name="${email.name}">
|
||||
<span class="level-item">
|
||||
<a class="${ email.seen ? 'seen' : ''} ellipsis" href="${this.get_form_link(email)}">
|
||||
${email.subject}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="list-row-col hidden-xs">
|
||||
<span>${this.is_sent_emails ? email.recipients : email.sender }</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return this.get_list_row_html_skeleton(columns_html, this.get_meta_html(email));
|
||||
}
|
||||
|
||||
get_meta_html(email) {
|
||||
const attachment = email.has_attachment ?
|
||||
`<span class="fa fa-paperclip fa-large" title="${__('Has Attachments')}"></span>` : '';
|
||||
|
||||
const form_link = frappe.utils.get_form_link(email.reference_doctype, email.reference_name);
|
||||
const link = email.reference_doctype && email.reference_doctype !== this.doctype ?
|
||||
`<a class="text-muted grey" href="${form_link}"
|
||||
title="${__('Linked with {0}', [email.reference_doctype])}">
|
||||
<i class="fa fa-link fa-large"></i>
|
||||
</a>` : '';
|
||||
|
||||
const modified = comment_when(email.modified, true);
|
||||
|
||||
return `
|
||||
<div class="level-item hidden-xs list-row-activity">
|
||||
${link}
|
||||
${attachment}
|
||||
${modified}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
get_inbox_filters() {
|
||||
var email_account = this.email_account;
|
||||
var default_filters = [
|
||||
["Communication", "communication_type", "=", "Communication", true],
|
||||
["Communication", "communication_medium", "=", "Email", true],
|
||||
|
||||
]
|
||||
var filters = []
|
||||
];
|
||||
var filters = [];
|
||||
if (email_account === "Sent") {
|
||||
filters = default_filters.concat([
|
||||
["Communication", "sent_or_received", "=", "Sent", true],
|
||||
["Communication", "email_status", "not in", "Spam,Trash", true],
|
||||
])
|
||||
}
|
||||
else if (in_list(["Spam", "Trash"], email_account)) {
|
||||
]);
|
||||
} else if (in_list(["Spam", "Trash"], email_account)) {
|
||||
filters = default_filters.concat([
|
||||
["Communication", "email_status", "=", email_account, true],
|
||||
["Communication", "email_account", "in", frappe.boot.all_accounts, true]
|
||||
])
|
||||
}
|
||||
else {
|
||||
var op = "="
|
||||
]);
|
||||
} else {
|
||||
var op = "=";
|
||||
if (email_account == "All Accounts") {
|
||||
op = "in";
|
||||
email_account = frappe.boot.all_accounts
|
||||
email_account = frappe.boot.all_accounts;
|
||||
}
|
||||
|
||||
filters = default_filters.concat([
|
||||
["Communication", "sent_or_received", "=", "Received", true],
|
||||
["Communication", "email_account", op, email_account, true],
|
||||
["Communication", "email_status", "not in", "Spam,Trash", true],
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
return filters
|
||||
},
|
||||
get_header_html: function() {
|
||||
var header = ""
|
||||
if(this.current_email_account) {
|
||||
header = frappe.render_template('inbox_view_item_main_head', {
|
||||
_checkbox: ((frappe.model.can_delete(this.doctype) || this.settings.selectable)
|
||||
&& !this.no_delete),
|
||||
is_sent_emails: this.is_sent_emails
|
||||
});
|
||||
}
|
||||
return filters;
|
||||
}
|
||||
|
||||
return header;
|
||||
},
|
||||
get_current_email_account: function() {
|
||||
var route = frappe.get_route();
|
||||
if(!route[3] && frappe.boot.email_accounts.length) {
|
||||
var email_account;
|
||||
if(frappe.boot.email_accounts[0].email_id == "All Accounts") {
|
||||
email_account = "All Accounts"
|
||||
} else {
|
||||
email_account = frappe.boot.email_accounts[0].email_account
|
||||
}
|
||||
frappe.set_route("List", "Communication", "Inbox", email_account);
|
||||
} else if(route[3] && route[3] != "All Accounts" &&
|
||||
!frappe.boot.email_accounts.find(b => b.email_account === route[3])) {
|
||||
// frappe.throw(__(`Email Account <b>${route[3] || ''}</b> not found`));
|
||||
return ''
|
||||
}
|
||||
return route[3];
|
||||
},
|
||||
make_no_result: function () {
|
||||
var no_result_message = ""
|
||||
var email_account = this.get_current_email_account();
|
||||
get_no_result_message() {
|
||||
var email_account = this.email_account;
|
||||
var args;
|
||||
if (in_list(["Spam", "Trash"], email_account)) {
|
||||
return __("No {0} mail", [email_account])
|
||||
} else if(!email_account && !frappe.boot.email_accounts.length) {
|
||||
return __("No {0} mail", [email_account]);
|
||||
} else if (!email_account && !frappe.boot.email_accounts.length) {
|
||||
// email account is not configured
|
||||
this.no_result_doctype = "Email Account"
|
||||
args = {
|
||||
doctype: "Email Account",
|
||||
msg: __("No Email Account"),
|
||||
label: __("New Email Account"),
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// no sent mail
|
||||
this.no_result_doctype = "Communication";
|
||||
args = {
|
||||
doctype: "Communication",
|
||||
msg: __("No Emails"),
|
||||
label: __("Compose Email")
|
||||
}
|
||||
};
|
||||
}
|
||||
var no_result_message = frappe.render_template("inbox_no_result", args)
|
||||
return no_result_message;
|
||||
},
|
||||
make_new_doc: function() {
|
||||
if (this.no_result_doctype == "Communication") {
|
||||
|
||||
const html = frappe.model.can_create(args.doctype) ?
|
||||
`<p>${args.msg}</p>
|
||||
<p>
|
||||
<button class="btn btn-primary btn-sm btn-new-doc">
|
||||
${args.label}
|
||||
</button>
|
||||
</p>
|
||||
` :
|
||||
`<p>${ __("No Email Accounts Assigned") }</p>`;
|
||||
|
||||
return `
|
||||
<div class="msg-box no-border">
|
||||
${html}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
make_new_doc() {
|
||||
if (!this.email_account && !frappe.boot.email_accounts.length) {
|
||||
frappe.route_options = {
|
||||
'email_id': frappe.session.user_email
|
||||
};
|
||||
frappe.new_doc('Email Account');
|
||||
} else {
|
||||
new frappe.views.CommunicationComposer({
|
||||
doc: {}
|
||||
})
|
||||
} else {
|
||||
frappe.route_options = { 'email_id': frappe.session.user_email }
|
||||
frappe.new_doc(this.no_result_doctype)
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
<div class="list-row list-row-head" data-list-renderer="Inbox">
|
||||
<div class="row doclist-row">
|
||||
<div class="col-sm-10 list-row-left">
|
||||
<!-- title + columns -->
|
||||
<div class="row">
|
||||
<div class="col-sm-8 col-xs-12 list-col ellipsis h6 text-muted">
|
||||
<div class="list-value">
|
||||
{% if (_checkbox) { %}
|
||||
<input class="list-select-all" type="checkbox"
|
||||
title="{%= __("Select All") %}">
|
||||
{% } %}
|
||||
<span class="list-col-title">{%= __("Subject") %}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4 hidden-xs list-col ellipsis h6 text-muted">
|
||||
<div class="list-value">
|
||||
<span class="list-col-title">{%= __(is_sent_emails ? "To": "From") %}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-2 hidden-xs list-row-right"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
<div class="list-row">
|
||||
<div class="row doclist-row {% if (data._checkbox) { %} has-checkbox {% } %}">
|
||||
<div class="col-sm-10 col-xs-10 list-row-left">
|
||||
<div class="row">
|
||||
<div class="col-sm-8 list-col ellipsis h6 text-muted">
|
||||
<span class="list-value">
|
||||
{% if (data._checkbox) { %}
|
||||
<input class="list-row-checkbox" type="checkbox" data-name="{{data.name}}">
|
||||
{% } %}
|
||||
<a class="grey list-id {{ data.css_seen }} inbox-value" href="#Form/{%= data._doctype_encoded %}/{%= data._name_encoded %}">
|
||||
{%= data.subject %}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-sm-4 hidden-xs list-col ellipsis h6 text-muted">
|
||||
<span class="filterable text-muted" data-filter="sender,=,{%= data.sender %} inbox-value">
|
||||
{%= is_sent_emails? data.recipients: data.sender %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-2 col-xs-2 text-right list-row-right" style="padding-left:0px">
|
||||
<div class="visible-xs">
|
||||
<span class="text-muted inbox-attachment inbox-value">
|
||||
{% if(data.has_attachment) { %}
|
||||
<i class="fa fa-paperclip fa-large"></i>
|
||||
{% } %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="hidden-xs">
|
||||
<span class="text-muted inbox-attachment inbox-value">
|
||||
{% if(data.reference_doctype && data.reference_name) { %}
|
||||
<a class="text-muted grey" href="#Form/{%= data.reference_doctype %}/{%= data.reference_name %}">
|
||||
<i class="fa fa-link fa-large"></i>
|
||||
</a>
|
||||
{% } %}
|
||||
</span>
|
||||
<span class="text-muted inbox-attachment inbox-value">
|
||||
{% if(data.has_attachment) { %}
|
||||
<i class="fa fa-paperclip fa-large"></i>
|
||||
{% } %}
|
||||
</span>
|
||||
<span class="list-row-modified text-muted inbox-value">
|
||||
{%= comment_when(data.modified, true) %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -33,11 +33,6 @@ frappe.provide("frappe.views");
|
|||
});
|
||||
var columns = prepare_columns(board.columns);
|
||||
|
||||
// save kanban board name in user_settings
|
||||
frappe.model.user_settings.save(opts.doctype, 'Kanban', {
|
||||
last_kanban_board: opts.board_name
|
||||
});
|
||||
|
||||
updater.set({
|
||||
doctype: opts.doctype,
|
||||
board: board,
|
||||
|
|
@ -57,10 +52,8 @@ frappe.provide("frappe.views");
|
|||
},
|
||||
update_cards: function (updater, cards) {
|
||||
var state = this;
|
||||
var _cards =
|
||||
cards.map(card => {
|
||||
return prepare_card(card, state);
|
||||
})
|
||||
var _cards = cards
|
||||
.map(card => prepare_card(card, state))
|
||||
.concat(this.cards)
|
||||
.uniqBy(card => card.name);
|
||||
|
||||
|
|
@ -90,18 +83,18 @@ frappe.provide("frappe.views");
|
|||
var board = this.board;
|
||||
fetch_customization(doctype)
|
||||
.then(function (doc) {
|
||||
return modify_column_field_in_c11n(doc, board, col.title, action)
|
||||
return modify_column_field_in_c11n(doc, board, col.title, action);
|
||||
})
|
||||
.then(save_customization)
|
||||
.then(function (r) {
|
||||
return update_kanban_board(board.name, col.title, action)
|
||||
.then(function () {
|
||||
return update_kanban_board(board.name, col.title, action);
|
||||
}).then(function (r) {
|
||||
var cols = r.message;
|
||||
updater.set({
|
||||
columns: prepare_columns(cols)
|
||||
});
|
||||
}, function (err) {
|
||||
console.error(err);
|
||||
console.error(err); // eslint-disable-line
|
||||
});
|
||||
},
|
||||
set_filter_state: function (updater) {
|
||||
|
|
@ -115,14 +108,14 @@ frappe.provide("frappe.views");
|
|||
save_filters: function (updater) {
|
||||
if(saving_filters) return;
|
||||
saving_filters = true;
|
||||
var filters = JSON.stringify(this.cur_list.filter_list.get_filters());
|
||||
var filters = JSON.stringify(this.cur_list.filter_area.get());
|
||||
frappe.call({
|
||||
method: method_prefix + 'save_filters',
|
||||
args: {
|
||||
board_name: this.board.name,
|
||||
filters: filters
|
||||
}
|
||||
}).then(function(r) {
|
||||
}).then(function() {
|
||||
saving_filters = false;
|
||||
updater.set({ filters_modified: false });
|
||||
frappe.show_alert({
|
||||
|
|
@ -135,7 +128,6 @@ frappe.provide("frappe.views");
|
|||
var doc = frappe.model.get_new_doc(this.doctype);
|
||||
var field = this.card_meta.title_field;
|
||||
var quick_entry = this.card_meta.quick_entry;
|
||||
var board = this.board;
|
||||
var state = this;
|
||||
|
||||
var doc_fields = {};
|
||||
|
|
@ -198,7 +190,6 @@ frappe.provide("frappe.views");
|
|||
order: order
|
||||
},
|
||||
callback: (r) => {
|
||||
var state = this;
|
||||
var board = r.message[0];
|
||||
var updated_cards = r.message[1];
|
||||
var cards = update_cards_column(updated_cards);
|
||||
|
|
@ -208,8 +199,7 @@ frappe.provide("frappe.views");
|
|||
columns: columns
|
||||
});
|
||||
}
|
||||
})
|
||||
.fail(function(e) {
|
||||
}).fail(function() {
|
||||
// revert original order
|
||||
updater.set({
|
||||
cards: _cards,
|
||||
|
|
@ -246,7 +236,7 @@ frappe.provide("frappe.views");
|
|||
updater.set({
|
||||
columns: columns
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -262,12 +252,12 @@ frappe.provide("frappe.views");
|
|||
// update cards internally
|
||||
opts.cards = cards;
|
||||
|
||||
if(self.wrapper.find('.kanban').length > 0) {
|
||||
if(self.wrapper.find('.kanban').length > 0 && self.cur_list.start !== 0) {
|
||||
fluxify.doAction('update_cards', cards);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function init() {
|
||||
fluxify.doAction('init', opts);
|
||||
|
|
@ -286,7 +276,7 @@ frappe.provide("frappe.views");
|
|||
self.$kanban_board.appendTo(self.wrapper);
|
||||
}
|
||||
|
||||
self.$filter_area = self.cur_list.$page.find('.set-filters');
|
||||
self.$filter_area = self.cur_list.$page.find('.active-tag-filters');
|
||||
bind_events();
|
||||
setup_sortable();
|
||||
}
|
||||
|
|
@ -312,7 +302,7 @@ frappe.provide("frappe.views");
|
|||
dataIdAttr: 'data-column-value',
|
||||
filter: '.add-new-column',
|
||||
handle: '.kanban-column-title',
|
||||
onEnd: function(evt) {
|
||||
onEnd: function() {
|
||||
var order = sortable.toArray();
|
||||
order = order.slice(1);
|
||||
fluxify.doAction('update_column_order', order);
|
||||
|
|
@ -322,7 +312,6 @@ frappe.provide("frappe.views");
|
|||
|
||||
function bind_add_column() {
|
||||
|
||||
var wrapper = self.$kanban_board;
|
||||
var $add_new_column = self.$kanban_board.find(".add-new-column"),
|
||||
$compose_column = $add_new_column.find(".compose-column"),
|
||||
$compose_column_form = $add_new_column.find(".compose-column-form").hide();
|
||||
|
|
@ -342,7 +331,7 @@ frappe.provide("frappe.views");
|
|||
var title = $compose_column_form.serializeArray()[0].value;
|
||||
var col = {
|
||||
title: title.trim()
|
||||
}
|
||||
};
|
||||
fluxify.doAction('add_column', col);
|
||||
$compose_column_form.find('input').val('');
|
||||
$compose_column.show();
|
||||
|
|
@ -352,7 +341,7 @@ frappe.provide("frappe.views");
|
|||
});
|
||||
|
||||
// on form blur
|
||||
$compose_column_form.find('input').on("blur", function (e) {
|
||||
$compose_column_form.find('input').on("blur", function () {
|
||||
$(this).val('');
|
||||
$compose_column.show();
|
||||
$compose_column_form.hide();
|
||||
|
|
@ -362,7 +351,7 @@ frappe.provide("frappe.views");
|
|||
function bind_save_filter() {
|
||||
var set_filter_state = function () {
|
||||
fluxify.doAction('set_filter_state');
|
||||
}
|
||||
};
|
||||
|
||||
if(isBound(self.$kanban_board, 'after-refresh', set_filter_state)) return;
|
||||
|
||||
|
|
@ -375,8 +364,8 @@ frappe.provide("frappe.views");
|
|||
function setup_restore_columns() {
|
||||
var cur_list = store.getState().cur_list;
|
||||
var columns = store.getState().columns;
|
||||
var list_row_right =
|
||||
cur_list.$page.find(`[data-list-renderer='Kanban'] .list-row-right`)
|
||||
var list_row_right = cur_list.$page
|
||||
.find(`[data-list-renderer='Kanban'] .list-row-right`)
|
||||
.css('margin-right', '15px');
|
||||
list_row_right.empty();
|
||||
|
||||
|
|
@ -398,16 +387,16 @@ frappe.provide("frappe.views");
|
|||
"<a class='text-muted dropdown-toggle' data-toggle='dropdown'>" +
|
||||
"<span class='dropdown-text'>" + __('Archived Columns') + "</span><i class='caret'></i></a>" +
|
||||
"<ul class='dropdown-menu'>" + options + "</ul>" +
|
||||
"</div>")
|
||||
"</div>");
|
||||
|
||||
list_row_right.html($dropdown);
|
||||
|
||||
$dropdown.find(".dropdown-menu").on("click", "button.restore-column", function (e) {
|
||||
$dropdown.find(".dropdown-menu").on("click", "button.restore-column", function () {
|
||||
var column_title = $(this).data().column;
|
||||
var col = {
|
||||
title: column_title,
|
||||
status: 'Archived'
|
||||
}
|
||||
};
|
||||
fluxify.doAction('restore_column', col);
|
||||
});
|
||||
}
|
||||
|
|
@ -427,7 +416,7 @@ frappe.provide("frappe.views");
|
|||
init();
|
||||
|
||||
return self;
|
||||
}
|
||||
};
|
||||
|
||||
frappe.views.KanbanBoardColumn = function (column, wrapper) {
|
||||
var self = {};
|
||||
|
|
@ -455,7 +444,6 @@ frappe.provide("frappe.views");
|
|||
function make_cards() {
|
||||
self.$kanban_cards.empty();
|
||||
var cards = store.getState().cards;
|
||||
var board = store.getState().board;
|
||||
filtered_cards = get_cards_for_column(cards, column);
|
||||
var filtered_cards_names = filtered_cards.map(card => card.name);
|
||||
|
||||
|
|
@ -480,20 +468,20 @@ frappe.provide("frappe.views");
|
|||
}
|
||||
|
||||
function setup_sortable() {
|
||||
var sortable = Sortable.create(self.$kanban_cards.get(0), {
|
||||
Sortable.create(self.$kanban_cards.get(0), {
|
||||
group: "cards",
|
||||
animation: 150,
|
||||
dataIdAttr: 'data-name',
|
||||
onStart: function (evt) {
|
||||
onStart: function () {
|
||||
wrapper.find('.kanban-card.add-card').fadeOut(200, function () {
|
||||
wrapper.find('.kanban-cards').height('100vh');
|
||||
});
|
||||
},
|
||||
onEnd: function (evt) {
|
||||
onEnd: function () {
|
||||
wrapper.find('.kanban-card.add-card').fadeIn(100);
|
||||
wrapper.find('.kanban-cards').height('auto');
|
||||
// update order
|
||||
var order = {}
|
||||
var order = {};
|
||||
wrapper.find('.kanban-column[data-column-value]')
|
||||
.each(function() {
|
||||
var col_name = $(this).data().columnValue;
|
||||
|
|
@ -505,7 +493,7 @@ frappe.provide("frappe.views");
|
|||
});
|
||||
fluxify.doAction('update_order', order);
|
||||
},
|
||||
onAdd: function (evt) {
|
||||
onAdd: function () {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -543,7 +531,7 @@ frappe.provide("frappe.views");
|
|||
});
|
||||
|
||||
// on textarea blur
|
||||
$textarea.on("blur", function (e) {
|
||||
$textarea.on("blur", function () {
|
||||
$(this).val('');
|
||||
$btn_add.show();
|
||||
$new_card_area.hide();
|
||||
|
|
@ -552,7 +540,7 @@ frappe.provide("frappe.views");
|
|||
|
||||
function bind_options() {
|
||||
self.$kanban_column.find(".column-options .dropdown-menu")
|
||||
.on("click", "[data-action]", function (e) {
|
||||
.on("click", "[data-action]", function () {
|
||||
var $btn = $(this);
|
||||
var action = $btn.data().action;
|
||||
|
||||
|
|
@ -564,11 +552,11 @@ frappe.provide("frappe.views");
|
|||
}
|
||||
});
|
||||
get_column_indicators(function(indicators) {
|
||||
var html = '<li class="button-group">'
|
||||
var html = '<li class="button-group">';
|
||||
html += indicators.reduce(function(prev, curr) {
|
||||
return prev + '<div \
|
||||
data-action="indicator" data-indicator="'+curr+'"\
|
||||
class="btn btn-default btn-xs indicator ' + curr + '"></div>'
|
||||
class="btn btn-default btn-xs indicator ' + curr + '"></div>';
|
||||
}, "");
|
||||
html += '</li>';
|
||||
self.$kanban_column.find(".column-options .dropdown-menu")
|
||||
|
|
@ -577,7 +565,7 @@ frappe.provide("frappe.views");
|
|||
}
|
||||
|
||||
init();
|
||||
}
|
||||
};
|
||||
|
||||
frappe.views.KanbanBoardCard = function (card, wrapper) {
|
||||
var self = {};
|
||||
|
|
@ -630,56 +618,6 @@ frappe.provide("frappe.views");
|
|||
});
|
||||
}
|
||||
|
||||
function setup_edit_card() {
|
||||
if (self.edit_dialog) {
|
||||
refresh_dialog();
|
||||
self.edit_dialog.show();
|
||||
return;
|
||||
}
|
||||
|
||||
var card_meta = store.getState().card_meta;
|
||||
get_doc().then(function () {
|
||||
// prepare dialog fields
|
||||
var fields = [];
|
||||
if (card_meta.description_field) {
|
||||
fields.push({
|
||||
fieldtype: "Small Text", label: __("Description"),
|
||||
fieldname: card_meta.description_field.fieldname
|
||||
});
|
||||
}
|
||||
|
||||
fields.push({ fieldtype: "Section Break" });
|
||||
fields.push({
|
||||
fieldtype: "Read Only", label: "Assigned to",
|
||||
fieldname: "assignees"
|
||||
});
|
||||
fields.push({ fieldtype: "Column Break" });
|
||||
|
||||
if (card_meta.due_date_field) {
|
||||
fields.push(card_meta.due_date_field);
|
||||
}
|
||||
|
||||
var d = make_edit_dialog(card.title, fields);
|
||||
|
||||
refresh_dialog();
|
||||
make_timeline();
|
||||
edit_card_title();
|
||||
|
||||
d.set_primary_action(__('Save'), function () {
|
||||
if (d.working) return;
|
||||
var doc = d.get_values(true);
|
||||
$.extend(doc, { name: card.name, doctype: card.doctype });
|
||||
d.working = true;
|
||||
fluxify.doAction('update_doc', doc, card)
|
||||
.then(function (r) {
|
||||
d.working = false;
|
||||
d.hide();
|
||||
});
|
||||
});
|
||||
d.show();
|
||||
});
|
||||
}
|
||||
|
||||
function refresh_dialog() {
|
||||
set_dialog_fields();
|
||||
make_assignees();
|
||||
|
|
@ -694,36 +632,6 @@ frappe.provide("frappe.views");
|
|||
});
|
||||
}
|
||||
|
||||
function get_doc() {
|
||||
return new Promise(function (resolve, reject) {
|
||||
frappe.model.with_doc(card.doctype, card.name, function () {
|
||||
frappe.call({
|
||||
method: 'frappe.client.get',
|
||||
args: {
|
||||
doctype: card.doctype,
|
||||
name: card.name
|
||||
},
|
||||
callback: function (r) {
|
||||
var doc = r.message;
|
||||
if (!doc) {
|
||||
reject(__("{0} {1} does not exist", [card.doctype, card.name]));
|
||||
}
|
||||
card.doc = doc;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function make_edit_dialog(title, fields) {
|
||||
self.edit_dialog = new frappe.ui.Dialog({
|
||||
title: title,
|
||||
fields: fields
|
||||
});
|
||||
return self.edit_dialog;
|
||||
}
|
||||
|
||||
function make_assignees() {
|
||||
var d = self.edit_dialog;
|
||||
var html = get_assignees_html() + '<a class="add-assignment avatar avatar-small avatar-empty">\
|
||||
|
|
@ -751,7 +659,7 @@ frappe.provide("frappe.views");
|
|||
method: 'frappe.desk.form.assign_to.add',
|
||||
doctype: card.doctype,
|
||||
docname: card.name,
|
||||
callback: function(r) {
|
||||
callback: function() {
|
||||
var user = self.assign_to_dialog.get_values().assign_to;
|
||||
card.assigned_list.push(user);
|
||||
fluxify.doAction('update_card', card);
|
||||
|
|
@ -762,100 +670,8 @@ frappe.provide("frappe.views");
|
|||
self.assign_to_dialog.show();
|
||||
}
|
||||
|
||||
function make_timeline() {
|
||||
var d = self.edit_dialog;
|
||||
// timeline wrapper
|
||||
d.$wrapper.find('.modal-body').append('<div class="form-comments" style="padding:7px">');
|
||||
|
||||
// edit in full page button
|
||||
$('<div class="text-muted small" style="padding-left: 10px; padding-top: 15px;">\
|
||||
<a class="edit-full">'+ __('Edit in full page') + '</a></div>')
|
||||
.appendTo(d.$wrapper.find('.modal-body'))
|
||||
.on('click', function () {
|
||||
frappe.set_route("Form", card.doctype, card.name);
|
||||
});
|
||||
var tl = new frappe.ui.form.Timeline({
|
||||
parent: d.$wrapper.find(".form-comments"),
|
||||
frm: {
|
||||
doctype: card.doctype,
|
||||
docname: card.name,
|
||||
get_docinfo: function () {
|
||||
return frappe.model.get_docinfo(card.doctype, card.name)
|
||||
},
|
||||
doc: card.doc,
|
||||
sidebar: {
|
||||
refresh_comments: function () { }
|
||||
},
|
||||
trigger: function () { }
|
||||
}
|
||||
});
|
||||
tl.wrapper.addClass('in-dialog');
|
||||
tl.wrapper.find('.timeline-new-email').remove();
|
||||
// update comment count
|
||||
var tl_refresh = tl.refresh.bind(tl);
|
||||
tl.refresh = function () {
|
||||
tl_refresh();
|
||||
var communications = tl.get_communications();
|
||||
var comment_count = communications.filter(function (c) {
|
||||
return c.comment_type === 'Comment';
|
||||
}).length;
|
||||
if (comment_count !== card.comment_count) {
|
||||
card.comment_count = comment_count;
|
||||
fluxify.doAction('update_card', card);
|
||||
}
|
||||
}
|
||||
tl.refresh();
|
||||
}
|
||||
|
||||
function edit_card_title() {
|
||||
var $card_title = self.edit_dialog.header.find('.modal-title');
|
||||
var $title_wrapper = $card_title.parent();
|
||||
|
||||
$title_wrapper.addClass('edit-card-title').empty();
|
||||
|
||||
var template = repl('<div class="h4">\
|
||||
<span>%(card_title)s</span>\
|
||||
<input type="text">\
|
||||
</div>', { card_title: card.title });
|
||||
|
||||
$title_wrapper.html(template);
|
||||
|
||||
var $input = $title_wrapper.find('input').hide();
|
||||
var $span = $title_wrapper.find('span');
|
||||
|
||||
$span.on('click', function() {
|
||||
$input.show();
|
||||
$span.hide();
|
||||
$input.val(card.title);
|
||||
$input.focus();
|
||||
});
|
||||
|
||||
$input.on('blur', function() {
|
||||
$input.hide();
|
||||
$span.show();
|
||||
});
|
||||
|
||||
$input.keydown(function(e) {
|
||||
if (e.which === 13) {
|
||||
e.preventDefault();
|
||||
var new_title = $input.val();
|
||||
if (card.title === new_title) {
|
||||
return;
|
||||
}
|
||||
get_doc().then(function () {
|
||||
var tf = store.getState().card_meta.title_field.fieldname;
|
||||
var doc = card.doc;
|
||||
doc[tf] = new_title;
|
||||
fluxify.doAction('update_doc', doc, card);
|
||||
$span.html(new_title);
|
||||
$input.trigger('blur');
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
init();
|
||||
}
|
||||
};
|
||||
|
||||
// Helpers
|
||||
function get_board(board_name) {
|
||||
|
|
@ -874,7 +690,7 @@ frappe.provide("frappe.views");
|
|||
}
|
||||
return prepare_board(board);
|
||||
}, function(e) {
|
||||
console.log(e)
|
||||
console.log(e); // eslint-disable-line
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -897,7 +713,11 @@ frappe.provide("frappe.views");
|
|||
}
|
||||
|
||||
meta.fields.forEach(function (df) {
|
||||
if (in_list(['Data', 'Text', 'Small Text', 'Text Editor'], df.fieldtype) && !title_field) {
|
||||
const is_valid_field =
|
||||
in_list(['Data', 'Text', 'Small Text', 'Text Editor'], df.fieldtype)
|
||||
&& !df.hidden;
|
||||
|
||||
if (is_valid_field && !title_field) {
|
||||
// can be mapped to textarea
|
||||
title_field = df;
|
||||
}
|
||||
|
|
@ -926,7 +746,7 @@ frappe.provide("frappe.views");
|
|||
title_field: title_field,
|
||||
description_field: description_field,
|
||||
due_date_field: due_date_field,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function get_date_field(fields) {
|
||||
|
|
@ -993,7 +813,7 @@ frappe.provide("frappe.views");
|
|||
}
|
||||
|
||||
function fetch_customization(doctype) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
return new Promise(function (resolve) {
|
||||
frappe.model.with_doc("Customize Form", "Customize Form", function () {
|
||||
var doc = frappe.get_doc("Customize Form");
|
||||
doc.doc_type = doctype;
|
||||
|
|
@ -1023,7 +843,7 @@ frappe.provide("frappe.views");
|
|||
args: {
|
||||
doc: doc
|
||||
},
|
||||
callback: function (r) {
|
||||
callback: function () {
|
||||
frappe.model.clear_doc(doc.doctype, doc.name);
|
||||
frappe.show_alert({ message: __("Saved"), indicator: 'green' }, 1);
|
||||
}
|
||||
|
|
@ -1049,24 +869,27 @@ frappe.provide("frappe.views");
|
|||
}
|
||||
|
||||
function is_filters_modified(board, cur_list) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
return new Promise(function(resolve) {
|
||||
setTimeout(function() {
|
||||
// sometimes the filter_list is not initiated, so early return
|
||||
if(!cur_list.filter_list) resolve(false);
|
||||
try {
|
||||
var list_filters = JSON.stringify(cur_list.filter_area.get());
|
||||
resolve(list_filters !== board.filters);
|
||||
} catch(e) {
|
||||
// sometimes the filter_list is not initiated
|
||||
resolve(false);
|
||||
}
|
||||
|
||||
var list_filters = JSON.stringify(cur_list.filter_list.get_filters());
|
||||
resolve(list_filters !== board.filters);
|
||||
}, 2000);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function is_active_column(col) {
|
||||
return col.status !== 'Archived'
|
||||
return col.status !== 'Archived';
|
||||
}
|
||||
|
||||
function get_cards_for_column(cards, column) {
|
||||
return cards.filter(function (card) {
|
||||
return card.column === column.title
|
||||
return card.column === column.title;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1099,7 +922,7 @@ frappe.provide("frappe.views");
|
|||
});
|
||||
if(!indicators) {
|
||||
//
|
||||
indicators = ['green', 'blue', 'orange', 'grey']
|
||||
indicators = ['green', 'blue', 'orange', 'grey'];
|
||||
}
|
||||
callback(indicators);
|
||||
});
|
||||
|
|
@ -1118,7 +941,7 @@ frappe.provide("frappe.views");
|
|||
}
|
||||
|
||||
function remove_img_tags(html) {
|
||||
const $temp = $(`<div>${html}</div>`)
|
||||
const $temp = $(`<div>${html}</div>`);
|
||||
$temp.find('img').remove();
|
||||
return $temp.html();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,77 +1,63 @@
|
|||
frappe.provide('frappe.views');
|
||||
|
||||
frappe.views.KanbanView = frappe.views.ListRenderer.extend({
|
||||
name: 'Kanban',
|
||||
render_view: function(values) {
|
||||
var board_name = this.get_board_name();
|
||||
frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
|
||||
static load_last_view() {
|
||||
const route = frappe.get_route();
|
||||
if (route.length === 3) {
|
||||
const doctype = route[1];
|
||||
const user_settings = frappe.get_user_settings(doctype)['Kanban'] || {};
|
||||
if (!user_settings.last_kanban_board) {
|
||||
frappe.msgprint({
|
||||
title: __('Error'),
|
||||
indicator: 'red',
|
||||
message: __('Missing parameter Kanban Board Name')
|
||||
});
|
||||
frappe.set_route('List', doctype, 'List');
|
||||
return true;
|
||||
}
|
||||
route.push(user_settings.last_kanban_board);
|
||||
frappe.set_route(route);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
setup_defaults() {
|
||||
super.setup_defaults();
|
||||
this.board_name = frappe.get_route()[3];
|
||||
this.page_title = this.board_name;
|
||||
}
|
||||
|
||||
show() {
|
||||
super.show();
|
||||
this.save_view_user_settings({
|
||||
last_kanban_board: this.board_name
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const board_name = this.board_name;
|
||||
if(this.kanban && board_name === this.kanban.board_name) {
|
||||
this.kanban.update(values);
|
||||
this.kanban.update(this.data);
|
||||
this.kanban.$kanban_board.trigger('after-refresh');
|
||||
return;
|
||||
}
|
||||
|
||||
this.kanban = new frappe.views.KanbanBoard({
|
||||
doctype: this.doctype,
|
||||
board_name: board_name,
|
||||
cards: values,
|
||||
wrapper: this.wrapper,
|
||||
cur_list: this.list_view,
|
||||
user_settings: this.user_settings
|
||||
cards: this.data,
|
||||
wrapper: this.$result,
|
||||
cur_list: this,
|
||||
user_settings: this.view_user_settings
|
||||
});
|
||||
},
|
||||
after_refresh: function() {
|
||||
this.wrapper.find('.kanban').trigger('after-refresh');
|
||||
frappe.kanban_filters[this.get_board_name()] =
|
||||
this.list_view.filter_list.get_filters();
|
||||
},
|
||||
should_refresh: function() {
|
||||
var to_refresh = this._super();
|
||||
if(!to_refresh) {
|
||||
this.last_kanban_board = this.current_kanban_board || '';
|
||||
this.current_kanban_board = this.get_board_name();
|
||||
this.page_title = __(this.get_board_name());
|
||||
this.kanban.$kanban_board.trigger('after-refresh');
|
||||
}
|
||||
|
||||
to_refresh = this.current_kanban_board !== this.last_kanban_board;
|
||||
}
|
||||
return to_refresh;
|
||||
},
|
||||
init_settings: function() {
|
||||
this._super();
|
||||
this.filters = this.get_kanban_filters();
|
||||
},
|
||||
get_kanban_filters: function() {
|
||||
frappe.provide('frappe.kanban_filters');
|
||||
|
||||
var board_name = this.get_board_name();
|
||||
if (!frappe.kanban_filters[board_name]) {
|
||||
var kb = this.meta.__kanban_boards.find(
|
||||
board => board.name === board_name
|
||||
);
|
||||
frappe.kanban_filters[board_name] = JSON.parse(kb && kb.filters || '[]');
|
||||
}
|
||||
if(typeof frappe.kanban_filters[board_name] === 'string') {
|
||||
frappe.kanban_filters[board_name] =
|
||||
JSON.parse(
|
||||
frappe.kanban_filters[board_name] || '[]'
|
||||
)
|
||||
}
|
||||
var filters = frappe.kanban_filters[board_name];
|
||||
return filters;
|
||||
},
|
||||
set_defaults: function() {
|
||||
this._super();
|
||||
this.no_realtime = true;
|
||||
this.show_no_result = false;
|
||||
this.page_title = __(this.get_board_name());
|
||||
},
|
||||
get_board_name: function() {
|
||||
var route = frappe.get_route();
|
||||
return route[3];
|
||||
},
|
||||
get_header_html: function() {
|
||||
return frappe.render_template('list_item_row_head', { main: '', list: this });
|
||||
},
|
||||
required_libs: [
|
||||
'assets/frappe/js/lib/fluxify.min.js',
|
||||
'assets/frappe/js/frappe/views/kanban/kanban_board.js'
|
||||
]
|
||||
});
|
||||
get required_libs() {
|
||||
return [
|
||||
'assets/frappe/js/lib/fluxify.min.js',
|
||||
'assets/frappe/js/frappe/views/kanban/kanban_board.js'
|
||||
];
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
{% for col in columns %}
|
||||
{% if col.name && col._id !== "_check" %}
|
||||
<th style="min-width: {{ col.minWidth }}px"
|
||||
{% if col.docfield && in_list(["Float", "Currency", "Int"], col.docfield.fieldtype) %}
|
||||
{% if col.docfield && frappe.model.is_numeric_field(col.docfield) %}
|
||||
class="text-right"
|
||||
{% endif %}>{{ __(col.name) }}</th>
|
||||
{% endif %}
|
||||
|
|
@ -24,11 +24,13 @@
|
|||
{% for col in columns %}
|
||||
{% if col.name && col._id !== "_check" %}
|
||||
|
||||
{% var value = col.fieldname ? row[col.fieldname] : row[col.field]; %}
|
||||
{% var value = col.fieldname ? row[col.fieldname] : row[col.field]; %}
|
||||
|
||||
<td>{{ col.formatter
|
||||
<td>
|
||||
{{ col.formatter
|
||||
? col.formatter(row._index, col._index, value, col, row, true)
|
||||
: value }}</td>
|
||||
: (col.docfield ? frappe.format(value, col.docfield) : value) }}
|
||||
</td>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
|
|
|
|||
716
frappe/public/js/frappe/views/reports/report_view.js
Normal file
716
frappe/public/js/frappe/views/reports/report_view.js
Normal file
|
|
@ -0,0 +1,716 @@
|
|||
/**
|
||||
* frappe.views.ReportView
|
||||
*/
|
||||
frappe.provide('frappe.views');
|
||||
|
||||
frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
||||
setup_defaults() {
|
||||
super.setup_defaults();
|
||||
this.page_title = __('Report:') + ' ' + this.page_title;
|
||||
this.menu_items = this.report_menu_items();
|
||||
|
||||
const route = frappe.get_route();
|
||||
if (route.length === 4) {
|
||||
this.report_name = route[3];
|
||||
}
|
||||
|
||||
this.add_totals_row = this.view_user_settings.add_totals_row || 0;
|
||||
|
||||
if (this.report_name) {
|
||||
return this.get_report_doc()
|
||||
.then(doc => {
|
||||
this.report_doc = doc;
|
||||
this.report_doc.json = JSON.parse(this.report_doc.json);
|
||||
|
||||
this.filters = this.report_doc.json.filters;
|
||||
this.order_by = this.report_doc.json.order_by;
|
||||
this.add_totals_row = this.report_doc.json.add_totals_row;
|
||||
this.page_title = this.report_name;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setup_view() {
|
||||
this.setup_columns();
|
||||
}
|
||||
|
||||
before_render() {
|
||||
this.save_report_settings();
|
||||
}
|
||||
|
||||
save_report_settings() {
|
||||
frappe.model.user_settings.save(this.doctype, 'last_view', this.view_name);
|
||||
|
||||
if (!this.report_name) {
|
||||
this.save_view_user_settings({
|
||||
fields: this._fields,
|
||||
filters: this.filter_area.get(),
|
||||
order_by: this.sort_selector.get_sql_string(),
|
||||
add_totals_row: this.add_totals_row
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
update_data(r) {
|
||||
let data = r.message || {};
|
||||
data = frappe.utils.dict(data.keys, data.values);
|
||||
|
||||
if (this.start === 0) {
|
||||
this.data = data;
|
||||
} else {
|
||||
this.data = this.data.concat(data);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.datatable) {
|
||||
this.datatable.refresh(this.get_data(this.data));
|
||||
return;
|
||||
}
|
||||
this.setup_datatable(this.data);
|
||||
}
|
||||
|
||||
on_update(data) {
|
||||
if (this.doctype === data.doctype && data.name) {
|
||||
// flash row when doc is updated by some other user
|
||||
const flash_row = data.user !== frappe.session.user;
|
||||
if (this.data.find(d => d.name === data.name)) {
|
||||
// update existing
|
||||
frappe.db.get_doc(data.doctype, data.name)
|
||||
.then(doc => this.update_row(doc, flash_row));
|
||||
} else {
|
||||
// refresh
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update_row(doc, flash_row) {
|
||||
// update this.data
|
||||
const data = this.data.find(d => d.name === doc.name);
|
||||
const rowIndex = this.data.findIndex(d => d.name === doc.name);
|
||||
if (!data) return;
|
||||
|
||||
for (let fieldname in data) {
|
||||
data[fieldname] = doc[fieldname];
|
||||
}
|
||||
|
||||
const new_row = this.build_row(data);
|
||||
this.datatable.refreshRow(new_row, rowIndex);
|
||||
|
||||
// indicate row update
|
||||
if (flash_row) {
|
||||
const $row = this.$result.find(`.data-table-row[data-row-index="${rowIndex}"]`);
|
||||
$row.addClass('row-update');
|
||||
setTimeout(() => $row.removeClass('row-update'), 500);
|
||||
}
|
||||
}
|
||||
|
||||
setup_datatable(values) {
|
||||
this.datatable = new DataTable(this.$result[0], {
|
||||
data: this.get_data(values),
|
||||
enableClusterize: true,
|
||||
addCheckbox: this.can_delete,
|
||||
takeAvailableSpace: true,
|
||||
editing: this.get_editing_object.bind(this),
|
||||
events: {
|
||||
onRemoveColumn: (column) => {
|
||||
this.remove_column_from_datatable(column);
|
||||
},
|
||||
onSwitchColumn: (column1, column2) => {
|
||||
this.switch_column(column1, column2);
|
||||
}
|
||||
},
|
||||
headerDropdown: [{
|
||||
label: __('Add Column'),
|
||||
action: (datatabe_col) => {
|
||||
let columns_in_picker = [];
|
||||
const columns = this.get_columns_for_picker();
|
||||
|
||||
columns_in_picker = columns[this.doctype]
|
||||
.filter(df => !this.is_column_added(df))
|
||||
.map(df => ({
|
||||
label: __(df.label),
|
||||
value: df.fieldname
|
||||
}));
|
||||
|
||||
delete columns[this.doctype];
|
||||
|
||||
for (let cdt in columns) {
|
||||
columns[cdt]
|
||||
.filter(df => !this.is_column_added(df))
|
||||
.map(df => ({
|
||||
label: __(df.label) + ` (${cdt})`,
|
||||
value: df.fieldname + ',' + cdt
|
||||
}))
|
||||
.forEach(df => columns_in_picker.push(df));
|
||||
}
|
||||
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Add Column'),
|
||||
fields: [
|
||||
{
|
||||
label: __('Select Column'),
|
||||
fieldname: 'column',
|
||||
fieldtype: 'Autocomplete',
|
||||
options: columns_in_picker
|
||||
},
|
||||
{
|
||||
label: __('Insert Column Before {0}', [datatabe_col.docfield.label.bold()]),
|
||||
fieldname: 'insert_before',
|
||||
fieldtype: 'Check'
|
||||
}
|
||||
],
|
||||
primary_action: ({ column, insert_before }) => {
|
||||
|
||||
let doctype = this.doctype;
|
||||
if (column.includes(',')) {
|
||||
[column, doctype] = column.split(',');
|
||||
}
|
||||
|
||||
let index = datatabe_col.colIndex;
|
||||
if (insert_before) {
|
||||
index = index - 1;
|
||||
}
|
||||
|
||||
this.add_column_to_datatable(column, doctype, index);
|
||||
d.hide();
|
||||
}
|
||||
});
|
||||
|
||||
d.show();
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
get_editing_object(colIndex, rowIndex, value, parent) {
|
||||
const control = this.render_editing_input(colIndex, value, parent);
|
||||
if (!control) return false;
|
||||
|
||||
return {
|
||||
initValue: (value) => {
|
||||
control.set_focus();
|
||||
return control.set_value(value);
|
||||
},
|
||||
setValue: (value) => {
|
||||
const cell = this.datatable.getCell(colIndex, rowIndex);
|
||||
let fieldname = this.datatable.getColumn(colIndex).docfield.fieldname;
|
||||
let docname = cell.name;
|
||||
|
||||
control.set_value(value);
|
||||
return this.set_control_value(docname, fieldname, value);
|
||||
},
|
||||
getValue: () => {
|
||||
return control.get_value();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
set_control_value(docname, fieldname, value) {
|
||||
this.last_updated_doc = docname;
|
||||
return new Promise((resolve, reject) => {
|
||||
frappe.db.set_value(this.doctype, docname, {[fieldname]: value})
|
||||
.then(r => {
|
||||
if (r.message) {
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
})
|
||||
.fail(reject);
|
||||
});
|
||||
}
|
||||
|
||||
render_editing_input(colIndex, value, parent) {
|
||||
const col = this.datatable.getColumn(colIndex);
|
||||
|
||||
// make control
|
||||
const control = frappe.ui.form.make_control({
|
||||
df: col.docfield,
|
||||
parent: parent,
|
||||
render_input: true
|
||||
});
|
||||
control.set_value(value);
|
||||
control.toggle_label(false);
|
||||
control.toggle_description(false);
|
||||
|
||||
return control;
|
||||
}
|
||||
|
||||
is_editable(df, data) {
|
||||
if (!df || data.docstatus !== 0) return false;
|
||||
const is_standard_field = frappe.model.std_fields_list.includes(df.fieldname);
|
||||
const can_edit = !(
|
||||
is_standard_field
|
||||
|| df.read_only
|
||||
|| df.hidden
|
||||
|| !frappe.model.can_write(this.doctype)
|
||||
);
|
||||
return can_edit;
|
||||
}
|
||||
|
||||
get_data(values) {
|
||||
return {
|
||||
columns: this.columns,
|
||||
rows: this.build_rows(values)
|
||||
};
|
||||
}
|
||||
|
||||
set_fields() {
|
||||
if (this.report_name) {
|
||||
this._fields = this.report_doc.json._fields;
|
||||
return;
|
||||
}
|
||||
|
||||
// get from user_settings
|
||||
else if (this.view_user_settings.fields) {
|
||||
this._fields = this.view_user_settings.fields;
|
||||
return;
|
||||
}
|
||||
|
||||
// get fields from meta
|
||||
this._fields = [];
|
||||
const add_field = f => this._add_field(f);
|
||||
|
||||
// default fields
|
||||
[
|
||||
'name', 'docstatus',
|
||||
this.meta.title_field,
|
||||
this.meta.image_field
|
||||
].map(add_field);
|
||||
|
||||
// fields in_list_view or in_standard_filter
|
||||
const fields = this.meta.fields.filter(df => {
|
||||
return (df.in_list_view || df.in_standard_filter)
|
||||
&& frappe.perm.has_perm(this.doctype, df.permlevel, 'read')
|
||||
&& frappe.model.is_value_type(df.fieldtype)
|
||||
&& !df.report_hide;
|
||||
});
|
||||
|
||||
fields.map(add_field);
|
||||
|
||||
// currency fields
|
||||
fields.filter(
|
||||
df => df.fieldtype === 'Currency' && df.options
|
||||
).map(df => {
|
||||
if (df.options.includes(':')) {
|
||||
add_field(df.options.split(':')[1]);
|
||||
} else {
|
||||
add_field(df.options);
|
||||
}
|
||||
});
|
||||
|
||||
// fields in listview_settings
|
||||
(this.settings.add_fields || []).map(add_field);
|
||||
}
|
||||
|
||||
build_fields() {
|
||||
this._fields.push(['docstatus', this.doctype]);
|
||||
super.build_fields();
|
||||
}
|
||||
|
||||
add_column_to_datatable(fieldname, doctype, col_index) {
|
||||
const field = [fieldname, doctype];
|
||||
this._fields.splice(col_index, 0, field);
|
||||
|
||||
this.build_fields();
|
||||
this.setup_columns();
|
||||
|
||||
this.datatable.destroy();
|
||||
this.datatable = null;
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
remove_column_from_datatable(column) {
|
||||
const index = this._fields.findIndex(f => column.field === f[0]);
|
||||
if (index === -1) return;
|
||||
const field = this._fields[index];
|
||||
if (field[0] === 'name') {
|
||||
frappe.throw(__('Cannot remove ID field'));
|
||||
}
|
||||
this._fields.splice(index, 1);
|
||||
this.build_fields();
|
||||
this.setup_columns();
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
switch_column(col1, col2) {
|
||||
const index1 = this._fields.findIndex(f => col1.field === f[0]);
|
||||
const index2 = this._fields.findIndex(f => col2.field === f[0]);
|
||||
const _fields = this._fields.slice();
|
||||
|
||||
let temp = _fields[index1];
|
||||
_fields[index1] = _fields[index2];
|
||||
_fields[index2] = temp;
|
||||
|
||||
this._fields = _fields;
|
||||
this.build_fields();
|
||||
this.setup_columns();
|
||||
this.save_report_settings();
|
||||
}
|
||||
|
||||
get_columns_for_picker() {
|
||||
let out = {};
|
||||
let doctype_fields = frappe.meta.get_docfields(this.doctype).filter(df =>
|
||||
!in_list(frappe.model.no_value_type, df.fieldtype) &&
|
||||
!df.report_hide && df.fieldname !== 'naming_series' &&
|
||||
!df.hidden
|
||||
);
|
||||
|
||||
doctype_fields = [{
|
||||
label: __('ID'),
|
||||
fieldname: 'name',
|
||||
fieldtype: 'Data'
|
||||
}].concat(doctype_fields);
|
||||
|
||||
out[this.doctype] = doctype_fields;
|
||||
|
||||
const table_fields = frappe.meta.get_table_fields(this.doctype)
|
||||
.filter(df => !df.hidden);
|
||||
|
||||
table_fields.forEach(df => {
|
||||
const cdt = df.options;
|
||||
const child_table_fields =
|
||||
frappe.meta.get_docfields(cdt)
|
||||
.filter(df => df.in_list_view);
|
||||
|
||||
out[cdt] = child_table_fields;
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
get_dialog_fields() {
|
||||
const dialog_fields = [];
|
||||
const columns = this.get_columns_for_picker();
|
||||
|
||||
dialog_fields.push({
|
||||
label: __(this.doctype),
|
||||
fieldname: this.doctype,
|
||||
fieldtype: 'MultiCheck',
|
||||
columns: 2,
|
||||
options: columns[this.doctype]
|
||||
.map(df => ({
|
||||
label: __(df.label),
|
||||
value: df.fieldname,
|
||||
checked: this._fields.find(f => f[0] === df.fieldname)
|
||||
}))
|
||||
});
|
||||
|
||||
delete columns[this.doctype];
|
||||
|
||||
const table_fields = frappe.meta.get_table_fields(this.doctype)
|
||||
.filter(df => !df.hidden);
|
||||
|
||||
table_fields.forEach(df => {
|
||||
const cdt = df.options;
|
||||
|
||||
dialog_fields.push({
|
||||
label: __(df.label) + ` (${__(cdt)})`,
|
||||
fieldname: df.options,
|
||||
fieldtype: 'MultiCheck',
|
||||
columns: 2,
|
||||
options: columns[cdt]
|
||||
.map(df => ({
|
||||
label: __(df.label),
|
||||
value: df.fieldname,
|
||||
checked: this._fields.find(f => f[0] === df.fieldname && f[1] === cdt)
|
||||
}))
|
||||
});
|
||||
});
|
||||
|
||||
return dialog_fields;
|
||||
}
|
||||
|
||||
is_column_added(df) {
|
||||
return Boolean(
|
||||
this._fields.find(f => f[0] === df.fieldname && df.parent === f[1])
|
||||
);
|
||||
}
|
||||
|
||||
setup_columns() {
|
||||
const hide_columns = ['docstatus'];
|
||||
const fields = this._fields.filter(f => !hide_columns.includes(f[0]));
|
||||
this.columns = fields.map(f => this.build_column(f));
|
||||
}
|
||||
|
||||
build_column(c) {
|
||||
let [fieldname, doctype] = c;
|
||||
let docfield = frappe.meta.docfield_map[doctype || this.doctype][fieldname];
|
||||
|
||||
if (!docfield) {
|
||||
docfield = frappe.model.get_std_field(fieldname);
|
||||
|
||||
if (docfield) {
|
||||
docfield.parent = this.doctype;
|
||||
if (fieldname == "name") {
|
||||
docfield.options = this.doctype;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!docfield) return;
|
||||
|
||||
const title = __(docfield ? docfield.label : toTitle(fieldname));
|
||||
const editable = frappe.model.is_non_std_field(fieldname) && !docfield.read_only;
|
||||
|
||||
return {
|
||||
id: fieldname,
|
||||
field: fieldname,
|
||||
docfield: docfield,
|
||||
name: title,
|
||||
content: title, // required by datatable
|
||||
width: (docfield ? cint(docfield.width) : 120) || 120,
|
||||
editable: editable
|
||||
};
|
||||
}
|
||||
|
||||
build_rows(data) {
|
||||
const out = data.map(d => this.build_row(d));
|
||||
|
||||
if (this.add_totals_row) {
|
||||
const totals_row = data.reduce((totals_row, d) => {
|
||||
this.columns.forEach((col, i) => {
|
||||
totals_row[i] = totals_row[i] || {
|
||||
name: 'Totals Row',
|
||||
content: ''
|
||||
};
|
||||
|
||||
if (col.field in d && frappe.model.is_numeric_field(col.docfield)) {
|
||||
|
||||
if (!totals_row[i].format) {
|
||||
totals_row[i].format = value => frappe.format(value, col.docfield, { always_show_decimals: true });
|
||||
}
|
||||
|
||||
totals_row[i].content = totals_row[i].content || 0;
|
||||
totals_row[i].content += parseInt(d[col.field], 10);
|
||||
}
|
||||
});
|
||||
|
||||
return totals_row;
|
||||
}, []);
|
||||
|
||||
totals_row[0].content = __('Totals').bold();
|
||||
|
||||
out.push(totals_row);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
build_row(d) {
|
||||
return this.columns.map(col => {
|
||||
if (col.field in d) {
|
||||
const value = d[col.field];
|
||||
return {
|
||||
name: d.name,
|
||||
content: value,
|
||||
editable: this.is_editable(col.docfield, d),
|
||||
format: value => {
|
||||
if (col.field === 'name') {
|
||||
return frappe.utils.get_form_link(this.doctype, value, true);
|
||||
}
|
||||
return frappe.format(value, col.docfield, { always_show_decimals: true });
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: ''
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
get_checked_items(only_docnames) {
|
||||
const indexes = this.datatable.rowmanager.getCheckedRows();
|
||||
const items = indexes.filter(i => i != undefined)
|
||||
.map(i => this.data[i]);
|
||||
|
||||
if (only_docnames) {
|
||||
return items.map(d => d.name);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
save_report(save_type) {
|
||||
const _save_report = (name) => {
|
||||
// callback
|
||||
return frappe.call({
|
||||
method: 'frappe.desk.reportview.save_report',
|
||||
args: {
|
||||
name: name,
|
||||
doctype: this.doctype,
|
||||
json: JSON.stringify({
|
||||
filters: this.filter_area.get(),
|
||||
_fields: this._fields,
|
||||
order_by: this.sort_selector.get_sql_string(),
|
||||
add_totals_row: this.add_totals_row
|
||||
})
|
||||
},
|
||||
callback:(r) => {
|
||||
if(r.exc) {
|
||||
frappe.msgprint(__("Report was not saved (there were errors)"));
|
||||
return;
|
||||
}
|
||||
if(r.message != this.report_name) {
|
||||
frappe.set_route('List', this.doctype, 'Report', r.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
if(this.report_name && save_type == "save") {
|
||||
_save_report(this.report_name);
|
||||
} else {
|
||||
frappe.prompt({fieldname: 'name', label: __('New Report name'), reqd: 1, fieldtype: 'Data'}, (data) => {
|
||||
_save_report(data.name);
|
||||
}, __('Save As'));
|
||||
}
|
||||
}
|
||||
|
||||
get_report_doc() {
|
||||
return new Promise(resolve => {
|
||||
frappe.model.with_doc('Report', this.report_name, () => {
|
||||
resolve(frappe.get_doc('Report', this.report_name));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
report_menu_items() {
|
||||
let items = [
|
||||
{
|
||||
label: __('Show Totals'),
|
||||
action: () => {
|
||||
this.add_totals_row = !this.add_totals_row;
|
||||
this.save_view_user_settings({ add_totals_row: this.add_totals_row });
|
||||
this.datatable.refresh(this.get_data(this.data));
|
||||
}
|
||||
},
|
||||
{
|
||||
label: __('Print'),
|
||||
action: () => {
|
||||
frappe.ui.get_print_settings(false, (print_settings) => {
|
||||
var title = __(this.doctype);
|
||||
frappe.render_grid({
|
||||
title: title,
|
||||
print_settings: print_settings,
|
||||
columns: this.columns,
|
||||
data: this.data
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
label: __('Pick Columns'),
|
||||
action: () => {
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Pick Columns'),
|
||||
fields: this.get_dialog_fields(),
|
||||
primary_action: (values) => {
|
||||
// doctype fields
|
||||
let fields = values[this.doctype].map(f => [f, this.doctype]);
|
||||
delete values[this.doctype];
|
||||
|
||||
// child table fields
|
||||
for (let cdt in values) {
|
||||
fields = fields.concat(values[cdt].map(f => [f, cdt]));
|
||||
}
|
||||
|
||||
// this._fields = this._fields.concat(fields);
|
||||
this._fields = fields;
|
||||
|
||||
this.build_fields();
|
||||
this.setup_columns();
|
||||
|
||||
this.datatable.destroy();
|
||||
this.datatable = null;
|
||||
this.refresh();
|
||||
|
||||
d.hide();
|
||||
}
|
||||
});
|
||||
|
||||
d.show();
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
if (frappe.model.can_export(this.doctype)) {
|
||||
items.push({
|
||||
label: __('Export'),
|
||||
action: () => {
|
||||
const args = this.get_args();
|
||||
const selected_items = this.get_checked_items(true);
|
||||
|
||||
frappe.prompt({
|
||||
fieldtype:"Select", label: __("Select File Type"), fieldname:"file_format_type",
|
||||
options:"Excel\nCSV", default:"Excel", reqd: 1
|
||||
},
|
||||
(data) => {
|
||||
args.cmd = 'frappe.desk.reportview.export_query';
|
||||
args.file_format_type = data.file_format_type;
|
||||
|
||||
if(this.add_totals_row) {
|
||||
args.add_totals_row = 1;
|
||||
}
|
||||
|
||||
if(selected_items.length > 0) {
|
||||
args.selected_items = selected_items;
|
||||
}
|
||||
open_url_post(frappe.request.url, args);
|
||||
},
|
||||
__("Export Report: {0}",[__(this.doctype)]), __("Download"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
items.push({
|
||||
label: __("Setup Auto Email"),
|
||||
action: () => {
|
||||
if(this.report_name) {
|
||||
frappe.set_route('List', 'Auto Email Report', {'report' : this.report_name});
|
||||
} else {
|
||||
frappe.msgprint(__('Please save the report first'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// save buttons
|
||||
if(frappe.user.is_report_manager()) {
|
||||
items = items.concat([
|
||||
{ label: __('Save'), action: () => this.save_report('save') },
|
||||
{ label: __('Save As'), action: () => this.save_report('save_as') }
|
||||
]);
|
||||
}
|
||||
|
||||
// user permissions
|
||||
if(this.report_name && frappe.model.can_set_user_permissions("Report")) {
|
||||
items.push({
|
||||
label: __("User Permissions"),
|
||||
action: () => {
|
||||
const args = {
|
||||
doctype: "Report",
|
||||
name: this.report_name
|
||||
};
|
||||
frappe.set_route('List', 'User Permission', args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// add to desktop
|
||||
items.push({
|
||||
label: __('Add to Desktop'),
|
||||
action: () => {
|
||||
frappe.add_to_desktop(
|
||||
this.report_name || __('{0} Report', [this.doctype]),
|
||||
this.doctype, this.report_name
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return items.map(i => Object.assign(i, { standard: true }));
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -54,889 +54,10 @@ frappe.views.ReportViewPage = Class.extend({
|
|||
var module = locals.DocType[this.doctype].module;
|
||||
frappe.breadcrumbs.add(module, this.doctype);
|
||||
|
||||
this.parent.reportview = new frappe.views.ReportView({
|
||||
this.parent.reportview = new frappe.views.ReportView2({
|
||||
doctype: this.doctype,
|
||||
docname: this.docname,
|
||||
parent: this.parent
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
frappe.views.ReportView = frappe.ui.BaseList.extend({
|
||||
init: function(opts) {
|
||||
var me = this;
|
||||
$.extend(this, opts);
|
||||
this.can_delete = frappe.model.can_delete(me.doctype);
|
||||
this.tab_name = '`tab'+this.doctype+'`';
|
||||
this.setup();
|
||||
},
|
||||
|
||||
setup: function() {
|
||||
var me = this;
|
||||
|
||||
this.add_totals_row = 0;
|
||||
this.page = this.parent.page;
|
||||
this.meta = frappe.get_meta(this.doctype);
|
||||
this._body = $('<div>').appendTo(this.page.main);
|
||||
this.page_title = __('Report')+ ': ' + (this.docname ?
|
||||
__(this.doctype) + ' - ' + __(this.docname) : __(this.doctype));
|
||||
this.page.set_title(this.page_title);
|
||||
this.init_user_settings();
|
||||
this.make({
|
||||
page: this.parent.page,
|
||||
method: 'frappe.desk.reportview.get',
|
||||
save_user_settings: true,
|
||||
get_args: this.get_args,
|
||||
parent: this._body,
|
||||
start: 0,
|
||||
show_filters: true,
|
||||
allow_delete: true,
|
||||
});
|
||||
|
||||
this.make_new_and_refresh();
|
||||
this.make_delete();
|
||||
this.make_column_picker();
|
||||
this.make_sorter();
|
||||
this.make_totals_row_button();
|
||||
this.setup_print();
|
||||
this.make_export();
|
||||
this.setup_auto_email();
|
||||
this.set_init_columns();
|
||||
this.make_save();
|
||||
this.make_user_permissions();
|
||||
this.set_tag_and_status_filter();
|
||||
this.setup_listview_settings();
|
||||
|
||||
// add to desktop
|
||||
this.page.add_menu_item(__("Add to Desktop"), function() {
|
||||
frappe.add_to_desktop(me.docname || __('{0} Report', [me.doctype]), me.doctype, me.docname);
|
||||
}, true);
|
||||
|
||||
},
|
||||
|
||||
make_new_and_refresh: function() {
|
||||
var me = this;
|
||||
this.page.set_primary_action(__("Refresh"), function() {
|
||||
me.run();
|
||||
});
|
||||
|
||||
this.page.add_menu_item(__("New {0}", [this.doctype]), function() {
|
||||
me.make_new_doc(me.doctype);
|
||||
}, true);
|
||||
|
||||
},
|
||||
|
||||
setup_auto_email: function() {
|
||||
var me = this;
|
||||
this.page.add_menu_item(__("Setup Auto Email"), function() {
|
||||
if(me.docname) {
|
||||
frappe.set_route('List', 'Auto Email Report', {'report' : me.docname});
|
||||
} else {
|
||||
frappe.msgprint({message:__('Please save the report first'), indicator: 'red'});
|
||||
}
|
||||
}, true);
|
||||
},
|
||||
|
||||
set_init_columns: function() {
|
||||
// pre-select mandatory columns
|
||||
var me = this;
|
||||
var columns = [];
|
||||
if(this.user_settings.fields && !this.docname) {
|
||||
this.user_settings.fields.forEach(function(field) {
|
||||
var coldef = me.get_column_info_from_field(field);
|
||||
if(!in_list(['_seen', '_comments', '_user_tags', '_assign', '_liked_by', 'docstatus'], coldef[0])) {
|
||||
columns.push(coldef);
|
||||
}
|
||||
});
|
||||
}
|
||||
if(!columns.length) {
|
||||
var columns = [['name', this.doctype],];
|
||||
$.each(frappe.meta.docfield_list[this.doctype], function(i, df) {
|
||||
if((df.in_standard_filter || df.in_list_view) && df.fieldname!='naming_series'
|
||||
&& !in_list(frappe.model.no_value_type, df.fieldtype)
|
||||
&& !df.report_hide) {
|
||||
columns.push([df.fieldname, df.parent]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.set_columns(columns);
|
||||
|
||||
this.page.footer.on('click', '.show-all-data', function() {
|
||||
me.show_all_data = $(this).prop('checked');
|
||||
me.run();
|
||||
})
|
||||
},
|
||||
|
||||
set_columns: function(columns) {
|
||||
this.columns = columns;
|
||||
this.column_info = this.get_columns();
|
||||
this.refresh_footer();
|
||||
},
|
||||
|
||||
refresh_footer: function() {
|
||||
var can_write = frappe.model.can_write(this.doctype);
|
||||
var has_child_column = this.has_child_column();
|
||||
|
||||
this.page.footer.empty();
|
||||
|
||||
if(can_write || has_child_column) {
|
||||
$(frappe.render_template('reportview_footer', {
|
||||
has_child_column: has_child_column,
|
||||
can_write: can_write,
|
||||
show_all_data: this.show_all_data
|
||||
})).appendTo(this.page.footer);
|
||||
this.page.footer.removeClass('hide');
|
||||
} else {
|
||||
this.page.footer.addClass('hide');
|
||||
}
|
||||
},
|
||||
|
||||
// preset columns and filters from saved info
|
||||
set_columns_and_filters: function(opts) {
|
||||
var me = this;
|
||||
this.filter_list.clear_filters();
|
||||
if(opts.columns) {
|
||||
this.set_columns(opts.columns);
|
||||
}
|
||||
if(opts.filters) {
|
||||
$.each(opts.filters, function(i, f) {
|
||||
// f = [doctype, fieldname, condition, value]
|
||||
var df = frappe.meta.get_docfield(f[0], f[1]);
|
||||
if (df && df.fieldtype == "Check") {
|
||||
var value = f[3] ? "Yes" : "No";
|
||||
} else {
|
||||
var value = f[3];
|
||||
}
|
||||
me.filter_list.add_filter(f[0], f[1], f[2], value);
|
||||
});
|
||||
}
|
||||
|
||||
if(opts.add_total_row) {
|
||||
this.add_total_row = opts.add_total_row
|
||||
}
|
||||
|
||||
// first sort
|
||||
if(opts.sort_by) this.sort_by_select.val(opts.sort_by);
|
||||
if(opts.sort_order) this.sort_order_select.val(opts.sort_order);
|
||||
|
||||
// second sort
|
||||
if(opts.sort_by_next) this.sort_by_next_select.val(opts.sort_by_next);
|
||||
if(opts.sort_order_next) this.sort_order_next_select.val(opts.sort_order_next);
|
||||
|
||||
this.add_totals_row = cint(opts.add_totals_row);
|
||||
},
|
||||
|
||||
set_route_filters: function() {
|
||||
var me = this;
|
||||
if(frappe.route_options) {
|
||||
this.set_filters_from_route_options({clear_filters: this.docname ? false : true});
|
||||
return true;
|
||||
} else if(this.user_settings
|
||||
&& this.user_settings.filters
|
||||
&& !this.docname
|
||||
&& (this.user_settings.updated_on != this.user_settings_updated_on)) {
|
||||
// list settings (previous settings)
|
||||
this.filter_list.clear_filters();
|
||||
$.each(this.user_settings.filters, function(i, f) {
|
||||
me.filter_list.add_filter(f[0], f[1], f[2], f[3]);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
this.user_settings_updated_on = this.user_settings.updated_on;
|
||||
},
|
||||
|
||||
setup_print: function() {
|
||||
var me = this;
|
||||
this.page.add_menu_item(__("Print"), function() {
|
||||
frappe.ui.get_print_settings(false, function(print_settings) {
|
||||
var title = __(me.docname || me.doctype);
|
||||
frappe.render_grid({grid:me.grid, title:title, print_settings:print_settings});
|
||||
})
|
||||
|
||||
}, true);
|
||||
},
|
||||
|
||||
// build args for query
|
||||
get_args: function() {
|
||||
let me = this;
|
||||
let filters = this.filter_list? this.filter_list.get_filters(): [];
|
||||
|
||||
return {
|
||||
doctype: this.doctype,
|
||||
fields: $.map(this.columns || [], function(v) { return me.get_full_column_name(v); }),
|
||||
order_by: this.get_order_by(),
|
||||
add_total_row: this.add_total_row,
|
||||
filters: filters,
|
||||
save_user_settings_fields: 1,
|
||||
with_childnames: 1,
|
||||
file_format_type: this.file_format_type
|
||||
}
|
||||
},
|
||||
|
||||
get_order_by: function() {
|
||||
var order_by = [];
|
||||
|
||||
// first
|
||||
var sort_by_select = this.get_selected_table_and_column(this.sort_by_select);
|
||||
if (sort_by_select) {
|
||||
order_by.push(sort_by_select + " " + this.sort_order_select.val());
|
||||
}
|
||||
|
||||
// second
|
||||
if(this.sort_by_next_select && this.sort_by_next_select.val()) {
|
||||
order_by.push(this.get_selected_table_and_column(this.sort_by_next_select)
|
||||
+ ' ' + this.sort_order_next_select.val());
|
||||
}
|
||||
|
||||
return order_by.join(", ");
|
||||
},
|
||||
|
||||
get_selected_table_and_column: function(select) {
|
||||
if(!select) {
|
||||
return
|
||||
}
|
||||
|
||||
return select.selected_doctype ?
|
||||
this.get_full_column_name([select.selected_fieldname, select.selected_doctype]) : "";
|
||||
},
|
||||
|
||||
// get table_name.column_name
|
||||
get_full_column_name: function(v) {
|
||||
if(!v) return;
|
||||
return (v[1] ? ('`tab' + v[1] + '`') : this.tab_name) + '.`' + v[0] + '`';
|
||||
},
|
||||
|
||||
get_column_info_from_field: function(t) {
|
||||
if(t.indexOf('.')===-1) {
|
||||
return [strip(t, '`'), this.doctype];
|
||||
} else {
|
||||
var parts = t.split('.');
|
||||
return [strip(parts[1], '`'), strip(parts[0], '`').substr(3)];
|
||||
}
|
||||
},
|
||||
|
||||
// build columns for slickgrid
|
||||
build_columns: function() {
|
||||
var me = this;
|
||||
return $.map(this.columns, function(c) {
|
||||
var docfield = frappe.meta.docfield_map[c[1] || me.doctype][c[0]];
|
||||
if(!docfield) {
|
||||
var docfield = frappe.model.get_std_field(c[0]);
|
||||
if(docfield) {
|
||||
docfield.parent = me.doctype;
|
||||
if(c[0]=="name") {
|
||||
docfield.options = me.doctype;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!docfield) return;
|
||||
|
||||
let coldef = {
|
||||
id: c[0],
|
||||
field: c[0],
|
||||
docfield: docfield,
|
||||
name: __(docfield ? docfield.label : toTitle(c[0])),
|
||||
width: (docfield ? cint(docfield.width) : 120) || 120,
|
||||
formatter: function(row, cell, value, columnDef, dataContext, for_print) {
|
||||
var docfield = columnDef.docfield;
|
||||
docfield.fieldtype = {
|
||||
"_user_tags": "Tag",
|
||||
"_comments": "Comment",
|
||||
"_assign": "Assign",
|
||||
"_liked_by": "LikedBy",
|
||||
}[docfield.fieldname] || docfield.fieldtype;
|
||||
|
||||
if(docfield.fieldtype==="Link" && docfield.fieldname!=="name") {
|
||||
|
||||
// make a copy of docfield for reportview
|
||||
// as it needs to add a link_onclick property
|
||||
if(!columnDef.report_docfield) {
|
||||
columnDef.report_docfield = copy_dict(docfield);
|
||||
}
|
||||
docfield = columnDef.report_docfield;
|
||||
|
||||
docfield.link_onclick =
|
||||
repl('frappe.container.page.reportview.filter_or_open("%(parent)s", "%(fieldname)s", "%(value)s")',
|
||||
{parent: docfield.parent, fieldname:docfield.fieldname, value:value});
|
||||
}
|
||||
return frappe.format(value, docfield, {for_print: for_print, always_show_decimals: true}, dataContext);
|
||||
}
|
||||
}
|
||||
return coldef;
|
||||
});
|
||||
},
|
||||
|
||||
filter_or_open: function(parent, fieldname, value) {
|
||||
// set filter on click, if filter is set, open the document
|
||||
var filter_set = false;
|
||||
this.filter_list.get_filters().forEach(function(f) {
|
||||
if(f[1]===fieldname) {
|
||||
filter_set = true;
|
||||
}
|
||||
});
|
||||
|
||||
if(!filter_set) {
|
||||
this.set_filter(fieldname, value, false, false, parent);
|
||||
} else {
|
||||
var df = frappe.meta.get_docfield(parent, fieldname);
|
||||
if(df.fieldtype==='Link') {
|
||||
frappe.set_route('Form', df.options, value);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// render data
|
||||
render_view: function() {
|
||||
var me = this;
|
||||
var data = this.get_unique_data(this.column_info);
|
||||
|
||||
this.set_totals_row(data);
|
||||
|
||||
// add sr in data
|
||||
$.each(data, function(i, v) {
|
||||
// add index
|
||||
v._idx = i+1;
|
||||
v.id = v._idx;
|
||||
});
|
||||
|
||||
var options = {
|
||||
enableCellNavigation: true,
|
||||
enableColumnReorder: false,
|
||||
};
|
||||
|
||||
if(this.slickgrid_options) {
|
||||
$.extend(options, this.slickgrid_options);
|
||||
}
|
||||
|
||||
this.dataView = new Slick.Data.DataView();
|
||||
this.set_data(data);
|
||||
|
||||
var grid_wrapper = this.wrapper.find('.result-list').addClass("slick-wrapper");
|
||||
|
||||
// set height if not auto
|
||||
if(!options.autoHeight)
|
||||
grid_wrapper.css('height', '500px');
|
||||
|
||||
this.grid = new Slick.Grid(grid_wrapper
|
||||
.get(0), this.dataView,
|
||||
this.column_info, options);
|
||||
|
||||
if (!frappe.dom.is_touchscreen()) {
|
||||
this.grid.setSelectionModel(new Slick.CellSelectionModel());
|
||||
this.grid.registerPlugin(new Slick.CellExternalCopyManager({
|
||||
dataItemColumnValueExtractor: function(item, columnDef, value) {
|
||||
return item[columnDef.field];
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
frappe.slickgrid_tools.add_property_setter_on_resize(this.grid);
|
||||
if(this.start!=0 && !options.autoHeight) {
|
||||
this.grid.scrollRowIntoView(data.length-1);
|
||||
}
|
||||
|
||||
this.grid.onDblClick.subscribe(function(e, args) {
|
||||
var row = me.dataView.getItem(args.row);
|
||||
var cell = me.grid.getColumns()[args.cell];
|
||||
me.edit_cell(row, cell.docfield);
|
||||
});
|
||||
|
||||
this.dataView.onRowsChanged.subscribe(function (e, args) {
|
||||
me.grid.invalidateRows(args.rows);
|
||||
me.grid.render();
|
||||
});
|
||||
|
||||
this.grid.onHeaderClick.subscribe(function(e, args) {
|
||||
if(e.target.className === "slick-resizable-handle")
|
||||
return;
|
||||
|
||||
|
||||
var df = args.column.docfield,
|
||||
sort_by = df.parent + "." + df.fieldname;
|
||||
|
||||
if(sort_by===me.sort_by_select.val()) {
|
||||
me.sort_order_select.val(me.sort_order_select.val()==="asc" ? "desc" : "asc");
|
||||
} else {
|
||||
me.sort_by_select.val(df.parent + "." + df.fieldname);
|
||||
me.sort_order_select.val("asc");
|
||||
}
|
||||
|
||||
me.run();
|
||||
});
|
||||
},
|
||||
|
||||
has_child_column: function() {
|
||||
var me = this;
|
||||
return this.column_info.some(function(c) {
|
||||
return c.docfield && c.docfield.parent !== me.doctype;
|
||||
});
|
||||
},
|
||||
|
||||
get_unique_data: function(columns) {
|
||||
// if child columns are selected, show parent data only once
|
||||
let has_child_column = this.has_child_column();
|
||||
|
||||
var data = [], prev_row = null;
|
||||
this.data.forEach((d) => {
|
||||
if (this.show_all_data || !has_child_column) {
|
||||
data.push(d);
|
||||
} else if (prev_row && d.name == prev_row.name) {
|
||||
var new_row = {};
|
||||
columns.forEach((c) => {
|
||||
if(!c.docfield || c.docfield.parent!==this.doctype) {
|
||||
var val = d[c.field];
|
||||
// add child table row name for update
|
||||
if(c.docfield && c.docfield.parent!==this.doctype) {
|
||||
new_row[c.docfield.parent+":name"] = d[c.docfield.parent+":name"];
|
||||
}
|
||||
} else {
|
||||
var val = '';
|
||||
new_row.__is_repeat = true;
|
||||
}
|
||||
new_row[c.field] = val;
|
||||
});
|
||||
data.push(new_row);
|
||||
} else {
|
||||
data.push(d);
|
||||
}
|
||||
prev_row = d;
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
edit_cell: function(row, docfield) {
|
||||
if(!docfield || docfield.fieldname !== "idx"
|
||||
&& frappe.model.std_fields_list.indexOf(docfield.fieldname)!==-1) {
|
||||
return;
|
||||
} else if(frappe.boot.user.can_write.indexOf(this.doctype)===-1) {
|
||||
frappe.throw({message:__("No permission to edit"), title:__('Not Permitted')});
|
||||
}
|
||||
var me = this;
|
||||
var d = new frappe.ui.Dialog({
|
||||
title: __("Edit") + " " + __(docfield.label),
|
||||
fields: [docfield],
|
||||
primary_action_label: __("Update"),
|
||||
primary_action: function() {
|
||||
me.update_value(docfield, d, row);
|
||||
}
|
||||
});
|
||||
d.set_input(docfield.fieldname, row[docfield.fieldname]);
|
||||
|
||||
// Show dialog if field is editable and not hidden
|
||||
if (d.fields_list[0].disp_status != "Write") d.hide();
|
||||
else d.show();
|
||||
},
|
||||
|
||||
update_value: function(docfield, dialog, row) {
|
||||
// update value on the serverside
|
||||
var me = this;
|
||||
var args = {
|
||||
doctype: docfield.parent,
|
||||
name: row[docfield.parent===me.doctype ? "name" : docfield.parent+":name"],
|
||||
fieldname: docfield.fieldname,
|
||||
value: dialog.get_value(docfield.fieldname)
|
||||
};
|
||||
|
||||
if (!args.name) {
|
||||
frappe.throw(__("ID field is required to edit values using Report. Please select the ID field using the Column Picker"));
|
||||
}
|
||||
|
||||
frappe.call({
|
||||
method: "frappe.client.set_value",
|
||||
args: args,
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
dialog.hide();
|
||||
var doc = r.message;
|
||||
$.each(me.dataView.getItems(), function(i, item) {
|
||||
if (item.name === doc.name) {
|
||||
var new_item = $.extend({}, item);
|
||||
$.each(frappe.model.get_all_docs(doc), function(i, d) {
|
||||
// find the document of the current updated record
|
||||
// from locals (which is synced in the response)
|
||||
var name = item[d.doctype + ":name"];
|
||||
if(!name) name = item.name;
|
||||
|
||||
if(name===d.name) {
|
||||
for(var k in d) {
|
||||
var v = d[k];
|
||||
if(frappe.model.std_fields_list.indexOf(k)===-1
|
||||
&& item[k]!==undefined) {
|
||||
new_item[k] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
me.dataView.updateItem(item.id, new_item);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
set_data: function(data) {
|
||||
this.dataView.beginUpdate();
|
||||
this.dataView.setItems(data);
|
||||
this.dataView.endUpdate();
|
||||
},
|
||||
|
||||
get_columns: function() {
|
||||
var std_columns = [{id:'_idx', field:'_idx', name: 'Sr.', width: 40, maxWidth: 40}];
|
||||
if(this.can_delete) {
|
||||
std_columns = std_columns.concat([{
|
||||
id:'_check', field:'_check', name: "", width: 30, maxWidth: 30,
|
||||
formatter: function(row, cell, value, columnDef, dataContext) {
|
||||
return repl("<input type='checkbox' \
|
||||
data-row='%(row)s' %(checked)s>", {
|
||||
row: row,
|
||||
checked: (dataContext.selected ? "checked=\"checked\"" : "")
|
||||
});
|
||||
}
|
||||
}]);
|
||||
}
|
||||
return std_columns.concat(this.build_columns());
|
||||
},
|
||||
|
||||
// setup column picker
|
||||
make_column_picker: function() {
|
||||
var me = this;
|
||||
this.column_picker = new frappe.ui.ColumnPicker(this);
|
||||
this.page.add_inner_button(__('Pick Columns'), function() {
|
||||
me.column_picker.show(me.columns);
|
||||
});
|
||||
},
|
||||
|
||||
make_totals_row_button: function() {
|
||||
var me = this;
|
||||
|
||||
this.page.add_inner_button(__('Show Totals'), function() {
|
||||
me.add_totals_row = !!!me.add_totals_row;
|
||||
me.render_view();
|
||||
});
|
||||
},
|
||||
|
||||
set_totals_row: function(data) {
|
||||
if(this.add_totals_row) {
|
||||
var totals_row = {_totals_row: 1};
|
||||
if(data.length) {
|
||||
data.forEach(function(row, ri) {
|
||||
$.each(row, function(key, value) {
|
||||
if($.isNumeric(value)) {
|
||||
totals_row[key] = (totals_row[key] || 0) + value;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
data.push(totals_row);
|
||||
}
|
||||
},
|
||||
|
||||
set_tag_and_status_filter: function() {
|
||||
var me = this;
|
||||
this.wrapper.find('.result-list').on("click", ".label-info", function() {
|
||||
if($(this).attr("data-label")) {
|
||||
me.set_filter("_user_tags", $(this).attr("data-label"));
|
||||
}
|
||||
});
|
||||
this.wrapper.find('.result-list').on("click", "[data-workflow-state]", function() {
|
||||
if($(this).attr("data-workflow-state")) {
|
||||
me.set_filter(me.state_fieldname,
|
||||
$(this).attr("data-workflow-state"));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// setup sorter
|
||||
make_sorter: function() {
|
||||
var me = this;
|
||||
this.sort_dialog = new frappe.ui.Dialog({title:__('Sorting Preferences')});
|
||||
$(this.sort_dialog.body).html('<p class="help">'+__('Sort By')+'</p>\
|
||||
<div class="sort-column"></div>\
|
||||
<div><select class="sort-order form-control" style="margin-top: 10px; width: 60%;">\
|
||||
<option value="asc">'+__('Ascending')+'</option>\
|
||||
<option value="desc">'+__('Descending')+'</option>\
|
||||
</select></div>\
|
||||
<hr><p class="help">'+__('Then By (optional)')+'</p>\
|
||||
<div class="sort-column-1"></div>\
|
||||
<div><select class="sort-order-1 form-control" style="margin-top: 10px; width: 60%;">\
|
||||
<option value="asc">'+__('Ascending')+'</option>\
|
||||
<option value="desc">'+__('Descending')+'</option>\
|
||||
</select></div><hr>\
|
||||
<div><button class="btn btn-primary">'+__('Update')+'</div>');
|
||||
|
||||
// first
|
||||
this.sort_by_select = new frappe.ui.FieldSelect({
|
||||
parent: $(this.sort_dialog.body).find('.sort-column'),
|
||||
doctype: this.doctype
|
||||
});
|
||||
this.sort_by_select.$select.css('width', '60%');
|
||||
this.sort_order_select = $(this.sort_dialog.body).find('.sort-order');
|
||||
|
||||
// second
|
||||
this.sort_by_next_select = new frappe.ui.FieldSelect({
|
||||
parent: $(this.sort_dialog.body).find('.sort-column-1'),
|
||||
doctype: this.doctype,
|
||||
with_blank: true
|
||||
});
|
||||
this.sort_by_next_select.$select.css('width', '60%');
|
||||
this.sort_order_next_select = $(this.sort_dialog.body).find('.sort-order-1');
|
||||
|
||||
// initial values
|
||||
this.sort_by_select.set_value(this.doctype, 'modified');
|
||||
this.sort_order_select.val('desc');
|
||||
|
||||
this.sort_by_next_select.clear();
|
||||
this.sort_order_next_select.val('desc');
|
||||
|
||||
// button actions
|
||||
this.page.add_inner_button(__('Sort Order'), function() {
|
||||
me.sort_dialog.show();
|
||||
});
|
||||
|
||||
$(this.sort_dialog.body).find('.btn-primary').click(function() {
|
||||
me.sort_dialog.hide();
|
||||
me.run();
|
||||
});
|
||||
},
|
||||
|
||||
// setup export
|
||||
make_export: function() {
|
||||
var me = this;
|
||||
if(!frappe.model.can_export(this.doctype)) {
|
||||
return;
|
||||
}
|
||||
var export_btn = this.page.add_menu_item(__('Export'), function() {
|
||||
var args = me.get_args();
|
||||
var selected_items = me.get_checked_items()
|
||||
frappe.prompt({fieldtype:"Select", label: __("Select File Type"), fieldname:"file_format_type",
|
||||
options:"Excel\nCSV", default:"Excel", reqd: 1},
|
||||
function(data) {
|
||||
args.cmd = 'frappe.desk.reportview.export_query';
|
||||
args.file_format_type = data.file_format_type;
|
||||
|
||||
if(me.add_totals_row) {
|
||||
args.add_totals_row = 1;
|
||||
}
|
||||
|
||||
if(selected_items.length >= 1) {
|
||||
args.selected_items = $.map(selected_items, function(d) { return d.name; });
|
||||
}
|
||||
open_url_post(frappe.request.url, args);
|
||||
|
||||
}, __("Export Report: {0}",[__(me.doctype)]), __("Download"));
|
||||
|
||||
}, true);
|
||||
},
|
||||
|
||||
|
||||
// save
|
||||
make_save: function() {
|
||||
var me = this;
|
||||
if(frappe.user.is_report_manager()) {
|
||||
this.page.add_menu_item(__('Save'), function() { me.save_report('save') }, true);
|
||||
this.page.add_menu_item(__('Save As'), function() { me.save_report('save_as') }, true);
|
||||
}
|
||||
},
|
||||
|
||||
save_report: function(save_type) {
|
||||
var me = this;
|
||||
|
||||
var _save_report = function(name) {
|
||||
// callback
|
||||
return frappe.call({
|
||||
method: 'frappe.desk.reportview.save_report',
|
||||
args: {
|
||||
name: name,
|
||||
doctype: me.doctype,
|
||||
json: JSON.stringify({
|
||||
filters: me.filter_list.get_filters(),
|
||||
columns: me.columns,
|
||||
sort_by: me.sort_by_select.val(),
|
||||
sort_order: me.sort_order_select.val(),
|
||||
sort_by_next: me.sort_by_next_select.val(),
|
||||
sort_order_next: me.sort_order_next_select.val(),
|
||||
add_totals_row: me.add_totals_row
|
||||
})
|
||||
},
|
||||
callback: function(r) {
|
||||
if(r.exc) {
|
||||
frappe.msgprint(__("Report was not saved (there were errors)"));
|
||||
return;
|
||||
}
|
||||
if(r.message != me.docname)
|
||||
frappe.set_route('Report', me.doctype, r.message);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
if(me.docname && save_type == "save") {
|
||||
_save_report(me.docname);
|
||||
} else {
|
||||
frappe.prompt({fieldname: 'name', label: __('New Report name'), reqd: 1, fieldtype: 'Data'}, function(data) {
|
||||
_save_report(data.name);
|
||||
}, __('Save As'));
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
make_delete: function() {
|
||||
var me = this;
|
||||
if(this.can_delete) {
|
||||
$(this.parent).on("click", "input[type='checkbox'][data-row]", function() {
|
||||
me.data[$(this).attr("data-row")].selected
|
||||
= this.checked ? true : false;
|
||||
});
|
||||
|
||||
this.page.add_menu_item(__("Delete"), function() {
|
||||
var delete_list = $.map(me.get_checked_items(), function(d) { return d.name; });
|
||||
if(!delete_list.length)
|
||||
return;
|
||||
if(frappe.confirm(__("This is PERMANENT action and you cannot undo. Continue?"),
|
||||
function() {
|
||||
return frappe.call({
|
||||
method: 'frappe.desk.reportview.delete_items',
|
||||
args: {
|
||||
items: delete_list,
|
||||
doctype: me.doctype
|
||||
},
|
||||
callback: function() {
|
||||
me.refresh();
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
}, true);
|
||||
}
|
||||
},
|
||||
|
||||
make_user_permissions: function() {
|
||||
var me = this;
|
||||
if(this.docname && frappe.model.can_set_user_permissions("Report")) {
|
||||
this.page.add_menu_item(__("User Permissions"), function() {
|
||||
frappe.route_options = {
|
||||
doctype: "Report",
|
||||
name: me.docname
|
||||
};
|
||||
frappe.set_route('List', 'User Permission');
|
||||
}, true);
|
||||
}
|
||||
},
|
||||
|
||||
setup_listview_settings: function() {
|
||||
if(frappe.listview_settings[this.doctype] && frappe.listview_settings[this.doctype].onload) {
|
||||
frappe.listview_settings[this.doctype].onload(this);
|
||||
}
|
||||
},
|
||||
|
||||
get_checked_items: function() {
|
||||
var me = this;
|
||||
var selected_records = []
|
||||
|
||||
$.each(me.data, function(i, d) {
|
||||
if(d.selected && d.name) {
|
||||
selected_records.push(d);
|
||||
}
|
||||
});
|
||||
|
||||
return selected_records
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.ColumnPicker = Class.extend({
|
||||
init: function(list) {
|
||||
this.list = list;
|
||||
this.doctype = list.doctype;
|
||||
},
|
||||
clear: function() {
|
||||
this.columns = [];
|
||||
$(this.dialog.body).html('<div class="text-muted">'+__("Drag to sort columns")+'</div>\
|
||||
<div class="column-list"></div>\
|
||||
<div><button class="btn btn-default btn-sm btn-add">'+__("Add Column")+'</button></div>');
|
||||
|
||||
},
|
||||
show: function(columns) {
|
||||
var me = this;
|
||||
if(!this.dialog) {
|
||||
this.dialog = new frappe.ui.Dialog({
|
||||
title: __("Pick Columns"),
|
||||
width: '400',
|
||||
primary_action_label: __("Update"),
|
||||
primary_action: function() {
|
||||
me.update_column_selection();
|
||||
}
|
||||
});
|
||||
this.dialog.$wrapper.addClass("column-picker-dialog");
|
||||
}
|
||||
|
||||
this.clear();
|
||||
|
||||
this.column_list = $(this.dialog.body).find('.column-list');
|
||||
|
||||
// show existing
|
||||
$.each(columns, function(i, c) {
|
||||
me.add_column(c);
|
||||
});
|
||||
|
||||
new Sortable(this.column_list.get(0), {
|
||||
//handle: '.sortable-handle',
|
||||
filter: 'input',
|
||||
draggable: '.column-list-item',
|
||||
chosenClass: 'sortable-chosen',
|
||||
dragClass: 'sortable-chosen',
|
||||
onUpdate: function(event) {
|
||||
me.columns = [];
|
||||
$.each($(me.dialog.body).find('.column-list .column-list-item'),
|
||||
function(i, ele) {
|
||||
me.columns.push($(ele).data("fieldselect"))
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// add column
|
||||
$(this.dialog.body).find('.btn-add').click(function() {
|
||||
me.add_column(['name']);
|
||||
});
|
||||
|
||||
this.dialog.show();
|
||||
},
|
||||
add_column: function(c) {
|
||||
if(!c) return;
|
||||
var me = this;
|
||||
|
||||
var w = $('<div class="column-list-item"><div class="row">\
|
||||
<div class="col-xs-1">\
|
||||
<i class="fa fa-sort text-muted"></i></div>\
|
||||
<div class="col-xs-10"></div>\
|
||||
<div class="col-xs-1"><a class="close">×</a></div>\
|
||||
</div></div>')
|
||||
.appendTo(this.column_list);
|
||||
|
||||
var fieldselect = new frappe.ui.FieldSelect({parent:w.find('.col-xs-10'), doctype:this.doctype});
|
||||
fieldselect.val((c[1] || this.doctype) + "." + c[0]);
|
||||
|
||||
w.data("fieldselect", fieldselect);
|
||||
|
||||
w.find('.close').data("fieldselect", fieldselect)
|
||||
.click(function() {
|
||||
delete me.columns[me.columns.indexOf($(this).data('fieldselect'))];
|
||||
$(this).parents('.column-list-item').remove();
|
||||
});
|
||||
|
||||
this.columns.push(fieldselect);
|
||||
},
|
||||
update_column_selection: function() {
|
||||
this.dialog.hide();
|
||||
// selected columns as list of [column_name, table_name]
|
||||
var columns = $.map(this.columns, function(v) {
|
||||
return (v && v.selected_fieldname && v.selected_doctype)
|
||||
? [[v.selected_fieldname, v.selected_doctype]]
|
||||
: null;
|
||||
});
|
||||
|
||||
this.list.set_columns(columns);
|
||||
this.list.run();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
16
frappe/public/js/lib/clusterize.min.js
vendored
Normal file
16
frappe/public/js/lib/clusterize.min.js
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/*! Clusterize.js - v0.17.6 - 2017-03-05
|
||||
* http://NeXTs.github.com/Clusterize.js/
|
||||
* Copyright (c) 2015 Denis Lukov; Licensed GPLv3 */
|
||||
|
||||
;(function(q,n){"undefined"!=typeof module?module.exports=n():"function"==typeof define&&"object"==typeof define.amd?define(n):this[q]=n()})("Clusterize",function(){function q(b,a,c){return a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent("on"+b,c)}function n(b,a,c){return a.removeEventListener?a.removeEventListener(b,c,!1):a.detachEvent("on"+b,c)}function r(b){return"[object Array]"===Object.prototype.toString.call(b)}function m(b,a){return window.getComputedStyle?window.getComputedStyle(a)[b]:
|
||||
a.currentStyle[b]}var l=function(){for(var b=3,a=document.createElement("b"),c=a.all||[];a.innerHTML="\x3c!--[if gt IE "+ ++b+"]><i><![endif]--\x3e",c[0];);return 4<b?b:document.documentMode}(),x=navigator.platform.toLowerCase().indexOf("mac")+1,p=function(b){if(!(this instanceof p))return new p(b);var a=this,c={rows_in_block:50,blocks_in_cluster:4,tag:null,show_no_data_row:!0,no_data_class:"clusterize-no-data",no_data_text:"No data",keep_parity:!0,callbacks:{}};a.options={};for(var d="rows_in_block blocks_in_cluster show_no_data_row no_data_class no_data_text keep_parity tag callbacks".split(" "),
|
||||
f=0,h;h=d[f];f++)a.options[h]="undefined"!=typeof b[h]&&null!=b[h]?b[h]:c[h];c=["scroll","content"];for(f=0;d=c[f];f++)if(a[d+"_elem"]=b[d+"Id"]?document.getElementById(b[d+"Id"]):b[d+"Elem"],!a[d+"_elem"])throw Error("Error! Could not find "+d+" element");a.content_elem.hasAttribute("tabindex")||a.content_elem.setAttribute("tabindex",0);var e=r(b.rows)?b.rows:a.fetchMarkup(),g={};b=a.scroll_elem.scrollTop;a.insertToDOM(e,g);a.scroll_elem.scrollTop=b;var k=!1,m=0,l=!1,t=function(){x&&(l||(a.content_elem.style.pointerEvents=
|
||||
"none"),l=!0,clearTimeout(m),m=setTimeout(function(){a.content_elem.style.pointerEvents="auto";l=!1},50));k!=(k=a.getClusterNum())&&a.insertToDOM(e,g);a.options.callbacks.scrollingProgress&&a.options.callbacks.scrollingProgress(a.getScrollProgress())},u=0,v=function(){clearTimeout(u);u=setTimeout(a.refresh,100)};q("scroll",a.scroll_elem,t);q("resize",window,v);a.destroy=function(b){n("scroll",a.scroll_elem,t);n("resize",window,v);a.html((b?a.generateEmptyRow():e).join(""))};a.refresh=function(b){(a.getRowsHeight(e)||
|
||||
b)&&a.update(e)};a.update=function(b){e=r(b)?b:[];b=a.scroll_elem.scrollTop;e.length*a.options.item_height<b&&(k=a.scroll_elem.scrollTop=0);a.insertToDOM(e,g);a.scroll_elem.scrollTop=b};a.clear=function(){a.update([])};a.getRowsAmount=function(){return e.length};a.getScrollProgress=function(){return this.options.scroll_top/(e.length*this.options.item_height)*100||0};var w=function(b,c){var d=r(c)?c:[];d.length&&(e="append"==b?e.concat(d):d.concat(e),a.insertToDOM(e,g))};a.append=function(a){w("append",
|
||||
a)};a.prepend=function(a){w("prepend",a)}};p.prototype={constructor:p,fetchMarkup:function(){for(var b=[],a=this.getChildNodes(this.content_elem);a.length;)b.push(a.shift().outerHTML);return b},exploreEnvironment:function(b,a){var c=this.options;c.content_tag=this.content_elem.tagName.toLowerCase();b.length&&(l&&9>=l&&!c.tag&&(c.tag=b[0].match(/<([^>\s/]*)/)[1].toLowerCase()),1>=this.content_elem.children.length&&(a.data=this.html(b[0]+b[0]+b[0])),c.tag||(c.tag=this.content_elem.children[0].tagName.toLowerCase()),
|
||||
this.getRowsHeight(b))},getRowsHeight:function(b){var a=this.options,c=a.item_height;a.cluster_height=0;if(b.length){b=this.content_elem.children;var d=b[Math.floor(b.length/2)];a.item_height=d.offsetHeight;"tr"==a.tag&&"collapse"!=m("borderCollapse",this.content_elem)&&(a.item_height+=parseInt(m("borderSpacing",this.content_elem),10)||0);"tr"!=a.tag&&(b=parseInt(m("marginTop",d),10)||0,d=parseInt(m("marginBottom",d),10)||0,a.item_height+=Math.max(b,d));a.block_height=a.item_height*a.rows_in_block;
|
||||
a.rows_in_cluster=a.blocks_in_cluster*a.rows_in_block;a.cluster_height=a.blocks_in_cluster*a.block_height;return c!=a.item_height}},getClusterNum:function(){this.options.scroll_top=this.scroll_elem.scrollTop;return Math.floor(this.options.scroll_top/(this.options.cluster_height-this.options.block_height))||0},generateEmptyRow:function(){var b=this.options;if(!b.tag||!b.show_no_data_row)return[];var a=document.createElement(b.tag),c=document.createTextNode(b.no_data_text),d;a.className=b.no_data_class;
|
||||
"tr"==b.tag&&(d=document.createElement("td"),d.colSpan=100,d.appendChild(c));a.appendChild(d||c);return[a.outerHTML]},generate:function(b,a){var c=this.options,d=b.length;if(d<c.rows_in_block)return{top_offset:0,bottom_offset:0,rows_above:0,rows:d?b:this.generateEmptyRow()};var f=Math.max((c.rows_in_cluster-c.rows_in_block)*a,0),h=f+c.rows_in_cluster,e=Math.max(f*c.item_height,0),c=Math.max((d-h)*c.item_height,0),d=[],g=f;for(1>e&&g++;f<h;f++)b[f]&&d.push(b[f]);return{top_offset:e,bottom_offset:c,
|
||||
rows_above:g,rows:d}},renderExtraTag:function(b,a){var c=document.createElement(this.options.tag);c.className=["clusterize-extra-row","clusterize-"+b].join(" ");a&&(c.style.height=a+"px");return c.outerHTML},insertToDOM:function(b,a){this.options.cluster_height||this.exploreEnvironment(b,a);var c=this.generate(b,this.getClusterNum()),d=c.rows.join(""),f=this.checkChanges("data",d,a),h=this.checkChanges("top",c.top_offset,a),e=this.checkChanges("bottom",c.bottom_offset,a),g=this.options.callbacks,
|
||||
k=[];f||h?(c.top_offset&&(this.options.keep_parity&&k.push(this.renderExtraTag("keep-parity")),k.push(this.renderExtraTag("top-space",c.top_offset))),k.push(d),c.bottom_offset&&k.push(this.renderExtraTag("bottom-space",c.bottom_offset)),g.clusterWillChange&&g.clusterWillChange(),this.html(k.join("")),"ol"==this.options.content_tag&&this.content_elem.setAttribute("start",c.rows_above),g.clusterChanged&&g.clusterChanged()):e&&(this.content_elem.lastChild.style.height=c.bottom_offset+"px")},html:function(b){var a=
|
||||
this.content_elem;if(l&&9>=l&&"tr"==this.options.tag){var c=document.createElement("div");for(c.innerHTML="<table><tbody>"+b+"</tbody></table>";b=a.lastChild;)a.removeChild(b);for(c=this.getChildNodes(c.firstChild.firstChild);c.length;)a.appendChild(c.shift())}else a.innerHTML=b},getChildNodes:function(b){b=b.children;for(var a=[],c=0,d=b.length;c<d;c++)a.push(b[c]);return a},checkChanges:function(b,a,c){var d=a!=c[b];c[b]=a;return d}};return p});
|
||||
1
frappe/public/js/lib/frappe-datatable.js
Normal file
1
frappe/public/js/lib/frappe-datatable.js
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -93,8 +93,6 @@ frappe.render_grid = function(opts) {
|
|||
} else if(opts.grid) {
|
||||
opts.data = opts.grid.getData().getItems();
|
||||
}
|
||||
} else {
|
||||
opts.columns = [];
|
||||
}
|
||||
|
||||
// show landscape view if columns more than 10
|
||||
|
|
|
|||
|
|
@ -237,6 +237,17 @@ a.no-decoration& {
|
|||
margin: 15px;
|
||||
}
|
||||
|
||||
.margin-(@position) {
|
||||
.margin-@{position} {
|
||||
margin-@{position}: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.margin-(top);
|
||||
.margin-(bottom);
|
||||
.margin-(left);
|
||||
.margin-(right);
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.text-center-xs {
|
||||
text-align: center;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,14 @@
|
|||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
|
||||
label {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
& + .checkbox {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
|
|
|
|||
|
|
@ -304,6 +304,10 @@ textarea.form-control {
|
|||
}
|
||||
}
|
||||
|
||||
.input-area {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.link-field.ui-front {
|
||||
z-index: inherit;
|
||||
}
|
||||
|
|
@ -434,11 +438,6 @@ li.user-progress {
|
|||
padding: 15px 30px;
|
||||
}
|
||||
|
||||
.footnote-area {
|
||||
padding: 0px 15px;
|
||||
border-top: 1px solid @border-color;
|
||||
}
|
||||
|
||||
.file-upload {
|
||||
.input-group-addon {
|
||||
color: @text-muted;
|
||||
|
|
@ -897,7 +896,7 @@ li.user-progress {
|
|||
// custom font awesome checkbox
|
||||
input[type="checkbox"] {
|
||||
position: relative;
|
||||
height: 16px;
|
||||
left: -999999px;
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
|
|
@ -913,9 +912,7 @@ input[type="checkbox"] {
|
|||
font-size: 14px;
|
||||
color: @text-extra-muted;
|
||||
.transition(150ms color);
|
||||
background-color: white;
|
||||
padding: 1px;
|
||||
margin: -1px;
|
||||
left: 999999px;
|
||||
}
|
||||
|
||||
&:focus:before {
|
||||
|
|
|
|||
47
frappe/public/less/flex.less
Normal file
47
frappe/public/less/flex.less
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.level {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.level-left, .level-right {
|
||||
display: flex;
|
||||
flex-basis: auto;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
|
||||
&.is-flexible {
|
||||
flex-grow: initial;
|
||||
flex-shrink: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.level-left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.level-right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.level-item {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-basis: auto;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
71
frappe/public/less/frappe-datatable.less
Normal file
71
frappe/public/less/frappe-datatable.less
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
@import "variables.less";
|
||||
|
||||
.data-table {
|
||||
margin-left: -1px;
|
||||
margin-top: -1px;
|
||||
font-size: @text-medium;
|
||||
|
||||
.data-table-col .edit-cell {
|
||||
padding: 0;
|
||||
|
||||
input {
|
||||
font-size: inherit;
|
||||
height: 34px;
|
||||
}
|
||||
}
|
||||
|
||||
.frappe-control {
|
||||
margin: 0;
|
||||
}
|
||||
.form-group {
|
||||
margin: 0;
|
||||
}
|
||||
.form-control {
|
||||
border-radius: 0px;
|
||||
border: none;
|
||||
}
|
||||
.link-btn {
|
||||
top: 6px;
|
||||
}
|
||||
select {
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
margin: 7px 0 7px 8px;
|
||||
}
|
||||
[data-fieldtype="Color"] .control-input {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.body-scrollable {
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.data-table-header {
|
||||
background-color: @panel-bg;
|
||||
color: @text-muted;
|
||||
}
|
||||
|
||||
.data-table-row.row-update {
|
||||
animation: 500ms breathe forwards;
|
||||
}
|
||||
|
||||
.data-table-row.row-highlight {
|
||||
background-color: @extra-light-yellow;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes breathe {
|
||||
0% {
|
||||
background-color: transparent;
|
||||
}
|
||||
50% {
|
||||
background-color: @extra-light-yellow;
|
||||
}
|
||||
100% {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,34 @@
|
|||
@import "variables.less";
|
||||
|
||||
.no-result {
|
||||
padding: 150px 15px;
|
||||
color: @text-muted;
|
||||
.result, .no-result, .freeze {
|
||||
min-height: ~"calc(100vh - 284px)";
|
||||
}
|
||||
|
||||
.result-list {
|
||||
min-height: 400px;
|
||||
.freeze-row {
|
||||
.level-left, .level-right, .list-row-col {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.list-row-col {
|
||||
background-color: @border-color;
|
||||
border-radius: 2px;
|
||||
animation: 2s breathe infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes breathe {
|
||||
0% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
.sort-selector {
|
||||
|
|
@ -15,7 +37,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.list-filters {
|
||||
.filter-list{
|
||||
position: relative;
|
||||
|
||||
.sort-selector {
|
||||
|
|
@ -25,12 +47,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.show_filters {
|
||||
.tag-filters-area {
|
||||
padding: 15px 15px 0px;
|
||||
border-bottom: 1px solid @border-color;
|
||||
}
|
||||
|
||||
.set-filters {
|
||||
.active-tag-filters {
|
||||
padding-bottom: 4px;
|
||||
padding-right: 120px;
|
||||
|
||||
|
|
@ -39,17 +61,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
.set-filters .btn {
|
||||
.active-tag-filters .btn {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.set-filters .btn-group {
|
||||
margin-right: 10px;
|
||||
.active-tag-filters .btn-group {
|
||||
margin-left: 10px;
|
||||
white-space: nowrap;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
.set-filters .btn-group .btn-default {
|
||||
.active-tag-filters .btn-group .btn-default {
|
||||
background-color: transparent;
|
||||
border: 1px solid @border-color;
|
||||
color: @text-muted;
|
||||
|
|
@ -58,7 +80,6 @@
|
|||
|
||||
.filter-box {
|
||||
border-bottom: 1px solid @border-color;
|
||||
// margin: 0px -15px;
|
||||
padding: 10px 15px 3px;
|
||||
|
||||
.remove-filter {
|
||||
|
|
@ -66,7 +87,7 @@
|
|||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.filter_field {
|
||||
.filter-field {
|
||||
padding-right: 15px;
|
||||
width: calc(100% - 36px);
|
||||
|
||||
|
|
@ -77,12 +98,12 @@
|
|||
}
|
||||
|
||||
// for sm and above
|
||||
@media (min-width: 768px) {
|
||||
@media (min-width: @screen-xs) {
|
||||
.filter-box .row > div[class*="col-sm-"] {
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.filter_field {
|
||||
.filter-field {
|
||||
width: 65% !important;
|
||||
|
||||
.frappe-control {
|
||||
|
|
@ -91,144 +112,108 @@
|
|||
}
|
||||
}
|
||||
|
||||
.list-row {
|
||||
padding: 9px 15px;
|
||||
.list-row-container {
|
||||
border-bottom: 1px solid @border-color;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.list-row {
|
||||
padding: 12px 15px;
|
||||
height: 40px;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
-webkit-transition: color 0.2s;
|
||||
}
|
||||
|
||||
.list-row .h6 {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
&:hover {
|
||||
background-color: @panel-bg;
|
||||
}
|
||||
&:last-child {
|
||||
border-bottom: 0px;
|
||||
}
|
||||
|
||||
.level-left {
|
||||
flex: 3;
|
||||
}
|
||||
.level-right {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.list-row-head {
|
||||
background-color: @panel-bg;
|
||||
border-bottom: 1px solid @border-color !important;
|
||||
|
||||
.list-subject {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.checkbox-actions {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.list-row:hover, .grid-row:hover {
|
||||
background-color: @panel-bg;
|
||||
.list-row-col {
|
||||
flex: 1;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.no-hover:hover {
|
||||
background-color: transparent !important;
|
||||
.list-subject {
|
||||
flex: 2;
|
||||
font-weight: bold;
|
||||
justify-content: start;
|
||||
|
||||
.level-item {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&.seen {
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.list-row:last-child {
|
||||
border-bottom: 0px;
|
||||
.list-row-activity {
|
||||
justify-content: flex-end;
|
||||
min-width: 120px;
|
||||
|
||||
.avatar:not(.avatar-empty) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&> span {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.list-row-head {
|
||||
background-color: @panel-bg;
|
||||
border-bottom: 1px solid @border-color !important;
|
||||
}
|
||||
|
||||
.list-row .h6 {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.list-item-col {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
height: 30px;
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
.list-paging-area {
|
||||
.list-paging-area, .footnote-area {
|
||||
padding: 10px 15px;
|
||||
border-top: 1px solid @border-color;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.list-value {
|
||||
display: table;
|
||||
vertical-align: middle;
|
||||
|
||||
.list-row-checkbox, .liked-by, .list-id, .list-select-all {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.list-row-checkbox, .list-select-all {
|
||||
margin: 0;
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
.liked-by {
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.list-col-title {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.progress {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.doclist-row {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.likes-count {
|
||||
display: inline-block;
|
||||
width: 15px;
|
||||
margin-left: -5px;
|
||||
color: @text-muted;
|
||||
font-size: 12px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.doclist-row .docstatus .octicon {
|
||||
font-size: 12px;
|
||||
.list-liked-by-me {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
.doclist-row .progress {
|
||||
margin-top: 12px;
|
||||
input.list-check-all, input.list-row-checkbox {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.filterable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
.doclist-row .label {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.list-info-row {
|
||||
float: left;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.list-row-right .modified {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.list-row-right .list-row-modified {
|
||||
margin-right: 9px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.list-row-right {
|
||||
margin-top: -2px;
|
||||
margin-bottom: -4px;
|
||||
}
|
||||
|
||||
.list-row-right .indicator {
|
||||
margin-left: 10px;
|
||||
margin-right: -5px;
|
||||
}
|
||||
|
||||
.side-panel {
|
||||
border-bottom: 1px solid @border-color;
|
||||
margin: 0px -15px;
|
||||
padding: 5px 15px;
|
||||
}
|
||||
|
||||
.listview-main-section {
|
||||
.octicon-heart {
|
||||
cursor: pointer;
|
||||
|
|
@ -256,36 +241,6 @@
|
|||
color: @heart-color;
|
||||
}
|
||||
|
||||
.list-id {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.list-id.seen {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.list-col {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.list-value {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@media(max-width: 767px) {
|
||||
.doclist-row {
|
||||
font-size: @text-regular;
|
||||
}
|
||||
|
||||
.doclist-row [type='checkbox'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.doclist-row .list-row-right .list-row-modified {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.list-comment-count {
|
||||
display: inline-block;
|
||||
width: 37px;
|
||||
|
|
@ -294,10 +249,15 @@
|
|||
|
||||
// tags
|
||||
|
||||
.result.tags-shown {
|
||||
.tag-row {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.tag-row {
|
||||
padding-left: 55px;
|
||||
margin-bottom: 0px;
|
||||
margin-top: -5px;
|
||||
display: none;
|
||||
margin-left: 50px;
|
||||
}
|
||||
|
||||
.taggle_placeholder {
|
||||
|
|
@ -374,6 +334,7 @@
|
|||
padding: 15px;
|
||||
border-bottom: 1px solid @light-border-color;
|
||||
border-right: 1px solid @light-border-color;
|
||||
max-width: 100%/4;
|
||||
}
|
||||
|
||||
.image-view-item:nth-child(4n) {
|
||||
|
|
@ -410,6 +371,10 @@
|
|||
img {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
&.no-image {
|
||||
background-color: @light-bg;
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder-text {
|
||||
|
|
@ -457,6 +422,7 @@
|
|||
.image-view-container.three-column {
|
||||
.image-view-item {
|
||||
flex: 0 0 100%/3;
|
||||
max-width: 100%/3;
|
||||
}
|
||||
|
||||
.image-view-item:nth-child(3n) {
|
||||
|
|
@ -510,6 +476,11 @@
|
|||
}
|
||||
|
||||
// gantt
|
||||
.list-paging-area .gantt-view-mode {
|
||||
margin-left: 15px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.gantt {
|
||||
.details-container {
|
||||
.heading {
|
||||
|
|
|
|||
|
|
@ -67,3 +67,16 @@
|
|||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// datatable
|
||||
.data-table {
|
||||
.edit-popup {
|
||||
.frappe-control {
|
||||
padding: 0;
|
||||
|
||||
.form-group {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,34 +22,31 @@ QUnit.test("Calendar View Tests", function(assert) {
|
|||
{event_type: 'Private'}
|
||||
]),
|
||||
|
||||
() => frappe.timeout(1),
|
||||
|
||||
() => frappe.tests.make("Event", [
|
||||
{subject: random_text + ':Pub'},
|
||||
{starts_on: today},
|
||||
{event_type: 'Public'}
|
||||
]),
|
||||
|
||||
() => frappe.timeout(1),
|
||||
|
||||
// Goto Calendar view
|
||||
() => frappe.set_route(["List", "Event", "Calendar"]),
|
||||
() => {
|
||||
// clear filter
|
||||
$('[data-fieldname="event_type"]').val('').trigger('change');
|
||||
},
|
||||
|
||||
// clear filter
|
||||
() => cur_list.filter_area.remove('event_type'),
|
||||
() => frappe.timeout(2),
|
||||
// Check if event is created
|
||||
() => {
|
||||
// Check if the event exists and if its title matches with the one created
|
||||
assert.ok(event_title_text().includes(random_text + ':Pri'),
|
||||
"Event title verified");
|
||||
// Check if time of event created is correct
|
||||
|
||||
// assert.ok(visible_time().includes("4:20"),
|
||||
// "Event start time verified");
|
||||
},
|
||||
|
||||
// check filter
|
||||
() => {
|
||||
$('[data-fieldname="event_type"]').val('Public').trigger('change');
|
||||
},
|
||||
() => cur_list.filter_area.add('Event', 'event_type', '=', 'Public'),
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
// private event should be hidden
|
||||
|
|
|
|||
|
|
@ -4,17 +4,21 @@ QUnit.test("Test: Creation [Kanban view]", function(assert) {
|
|||
assert.expect(2);
|
||||
let done = assert.async();
|
||||
|
||||
const board_name = 'Kanban test';
|
||||
|
||||
frappe.run_serially([
|
||||
() => frappe.set_route("List", "ToDo", "List"),
|
||||
// wait for cur_list to initialize
|
||||
() => cur_list.init(),
|
||||
// click kanban in side bar
|
||||
() => frappe.click_link('Kanban'),
|
||||
() => frappe.click_link('New Kanban Board'),
|
||||
() => frappe.tests.click_link('Kanban'),
|
||||
() => frappe.tests.click_link('New Kanban Board'),
|
||||
() => frappe.timeout(0.5),
|
||||
// create new kanban
|
||||
() => {
|
||||
assert.equal(cur_dialog.title, 'New Kanban Board',
|
||||
"Dialog for new kanban opened.");
|
||||
cur_dialog.set_value('board_name', 'Kanban test');
|
||||
cur_dialog.set_value('board_name', board_name);
|
||||
cur_dialog.set_value('field_name', 'Priority');
|
||||
},
|
||||
() => frappe.timeout(0.5),
|
||||
|
|
@ -23,7 +27,7 @@ QUnit.test("Test: Creation [Kanban view]", function(assert) {
|
|||
() => frappe.set_route("List", "Kanban Board", "List"),
|
||||
() => frappe.timeout(0.5),
|
||||
// check in kanban list if new kanban is created
|
||||
() => assert.equal(cur_list.data[0].name, 'Kanban test',
|
||||
() => assert.equal(cur_list.data[0].name, board_name,
|
||||
"Added kanban is visible in kanban list."),
|
||||
() => done()
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ QUnit.test("Test: Filters [Kanban view]", function(assert) {
|
|||
() => {
|
||||
assert.deepEqual(["List", "ToDo", "Kanban", "Kanban test"], frappe.get_route(),
|
||||
"Kanban view opened successfully.");
|
||||
// set filter values
|
||||
return frappe.set_control('priority', 'Low');
|
||||
},
|
||||
// set filter values
|
||||
() => cur_list.filter_area.add('ToDo', 'priority', '=', 'Low'),
|
||||
() => frappe.timeout(1),
|
||||
() => cur_list.page.btn_secondary.click(),
|
||||
() => frappe.timeout(1),
|
||||
|
|
|
|||
|
|
@ -1,25 +1,28 @@
|
|||
QUnit.module('views');
|
||||
|
||||
QUnit.test("Test: Kanban view", function(assert) {
|
||||
assert.expect(3);
|
||||
assert.expect(4);
|
||||
let done = assert.async();
|
||||
let total_elements;
|
||||
|
||||
frappe.run_serially([
|
||||
() => frappe.set_route("List", "ToDo", "List"),
|
||||
// calculate number of element in list
|
||||
() => frappe.timeout(1),
|
||||
() => total_elements = cur_list.data.length,
|
||||
() => frappe.set_route("List", "ToDo", "Kanban", "Kanban test"),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.timeout(2),
|
||||
() => {
|
||||
assert.equal('Kanban', cur_list.current_view,
|
||||
assert.equal('Kanban', cur_list.view_name,
|
||||
"Current view is kanban.");
|
||||
assert.equal("Kanban test", cur_list.list_renderer.page_title,
|
||||
assert.equal("Kanban test", cur_list.page_title,
|
||||
"Kanban view opened successfully.");
|
||||
// check if all elements are visible in kanban view
|
||||
assert.equal(total_elements, cur_list.data.length,
|
||||
"All elements are visible in kanban view.");
|
||||
const $high_priority_cards =
|
||||
$('.kanban-column[data-column-value="High"] .kanban-card-wrapper');
|
||||
const $low_priority_cards =
|
||||
$('.kanban-column[data-column-value="Low"] .kanban-card-wrapper');
|
||||
|
||||
assert.equal($high_priority_cards.length, 1);
|
||||
assert.equal($low_priority_cards.length, 1);
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ QUnit.test("Test paging in list view", function(assert) {
|
|||
() => frappe.click_button('More'),
|
||||
() => frappe.timeout(2),
|
||||
() => assert.equal(cur_list.data.length, 40, 'show more items'),
|
||||
() => frappe.click_button('100', '.btn-group-paging'),
|
||||
() => frappe.tests.click_button('100'),
|
||||
() => frappe.timeout(2),
|
||||
() => assert.ok(cur_list.data.length > 40, 'show 100 items'),
|
||||
() => frappe.click_button('20', '.btn-group-paging'),
|
||||
() => frappe.tests.click_button('20'),
|
||||
() => frappe.timeout(2),
|
||||
() => assert.equal(cur_list.data.length, 20, 'show 20 items again'),
|
||||
() => done()
|
||||
|
|
|
|||
|
|
@ -8,24 +8,23 @@ QUnit.test("Test List Count", function(assert) {
|
|||
() => frappe.set_route('List', 'DocType'),
|
||||
() => frappe.timeout(0.5),
|
||||
() => {
|
||||
let count = $('.list-row-right').text().split(' ')[0];
|
||||
let count = $('.list-count').text().split(' ')[0];
|
||||
assert.equal(cur_list.data.length, count, "Correct Count");
|
||||
},
|
||||
|
||||
() => frappe.timeout(1),
|
||||
() => cur_list.filter_list.add_filter('Doctype', 'module', '=', 'Desk'),
|
||||
() => cur_list.filter_area.add('Doctype', 'module', '=', 'Desk'),
|
||||
() => frappe.click_button('Refresh'),
|
||||
() => {
|
||||
let count = $('.list-row-right').text().split(' ')[0];
|
||||
let count = $('.list-count').text().split(' ')[0];
|
||||
assert.equal(cur_list.data.length, count, "Correct Count");
|
||||
},
|
||||
|
||||
() => cur_list.filter_list.clear_filters(),
|
||||
() => cur_list.filter_area.clear(),
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
cur_list.filter_list.push_new_filter('DocField', 'fieldname', 'like', 'owner');
|
||||
frappe.click_button('Apply');
|
||||
let count = $('.list-row-right').text().split(' ')[0];
|
||||
cur_list.filter_area.add('DocField', 'fieldname', 'like', 'owner');
|
||||
let count = $('.list-count').text().split(' ')[0];
|
||||
assert.equal(cur_list.data.length, count, "Correct Count");
|
||||
},
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue