@@ -48,6 +48,7 @@ def get_bootinfo(): | |||||
bootinfo.letter_heads = get_letter_heads() | bootinfo.letter_heads = get_letter_heads() | ||||
bootinfo.active_domains = frappe.get_active_domains() | bootinfo.active_domains = frappe.get_active_domains() | ||||
bootinfo.all_domains = [d.get("name") for d in frappe.get_all("Domain")] | bootinfo.all_domains = [d.get("name") for d in frappe.get_all("Domain")] | ||||
add_routes(bootinfo) | |||||
bootinfo.module_app = frappe.local.module_app | bootinfo.module_app = frappe.local.module_app | ||||
bootinfo.single_types = [d.name for d in frappe.get_all('DocType', {'issingle': 1})] | bootinfo.single_types = [d.name for d in frappe.get_all('DocType', {'issingle': 1})] | ||||
@@ -309,6 +310,16 @@ def get_additional_filters_from_hooks(): | |||||
return filter_config | return filter_config | ||||
def add_routes(bootinfo): | |||||
# add routes for readable doctypes | |||||
doctype_routes = frappe.get_all('DocType', ['name', 'route'], dict(istable = 0)) | |||||
bootinfo.routes = {d.route:dict(doctype=d.name) for d in doctype_routes if d.route} | |||||
# add routes for layouts | |||||
doctype_layout_routes = frappe.get_all('DocType Layout', ['name', 'route', 'document_type'], | |||||
dict(document_type = ('in', bootinfo.user.can_read))) | |||||
bootinfo.routes.update({d.route:dict(doctype=d.document_type, doctype_layout=d.name) for d in doctype_layout_routes if d.route}) | |||||
def get_desk_settings(): | def get_desk_settings(): | ||||
role_list = frappe.get_all('Role', fields=['*'], filters=dict( | role_list = frappe.get_all('Role', fields=['*'], filters=dict( | ||||
name=['in', frappe.get_roles()] | name=['in', frappe.get_roles()] | ||||
@@ -16,6 +16,7 @@ | |||||
"is_tree", | "is_tree", | ||||
"editable_grid", | "editable_grid", | ||||
"quick_entry", | "quick_entry", | ||||
"route", | |||||
"cb01", | "cb01", | ||||
"track_changes", | "track_changes", | ||||
"track_seen", | "track_seen", | ||||
@@ -71,7 +72,6 @@ | |||||
"has_web_view", | "has_web_view", | ||||
"allow_guest_to_view", | "allow_guest_to_view", | ||||
"index_web_pages_for_search", | "index_web_pages_for_search", | ||||
"route", | |||||
"is_published_field", | "is_published_field", | ||||
"advanced", | "advanced", | ||||
"engine" | "engine" | ||||
@@ -132,7 +132,7 @@ | |||||
"label": "Editable Grid" | "label": "Editable Grid" | ||||
}, | }, | ||||
{ | { | ||||
"default": "1", | |||||
"default": "0", | |||||
"depends_on": "eval:!doc.istable && !doc.issingle", | "depends_on": "eval:!doc.istable && !doc.issingle", | ||||
"description": "Open a dialog with mandatory fields to create a new record quickly", | "description": "Open a dialog with mandatory fields to create a new record quickly", | ||||
"fieldname": "quick_entry", | "fieldname": "quick_entry", | ||||
@@ -427,7 +427,7 @@ | |||||
"label": "Allow Guest to View" | "label": "Allow Guest to View" | ||||
}, | }, | ||||
{ | { | ||||
"depends_on": "has_web_view", | |||||
"depends_on": "eval:!doc.istable", | |||||
"fieldname": "route", | "fieldname": "route", | ||||
"fieldtype": "Data", | "fieldtype": "Data", | ||||
"label": "Route" | "label": "Route" | ||||
@@ -609,7 +609,7 @@ | |||||
"link_fieldname": "reference_doctype" | "link_fieldname": "reference_doctype" | ||||
} | } | ||||
], | ], | ||||
"modified": "2020-09-24 13:13:58.227153", | |||||
"modified": "2020-12-07 11:48:57.395126", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "DocType", | "name": "DocType", | ||||
@@ -637,6 +637,7 @@ | |||||
"write": 1 | "write": 1 | ||||
} | } | ||||
], | ], | ||||
"route": "doctype", | |||||
"search_fields": "module", | "search_fields": "module", | ||||
"show_name_in_global_search": 1, | "show_name_in_global_search": 1, | ||||
"sort_field": "modified", | "sort_field": "modified", | ||||
@@ -26,6 +26,7 @@ from frappe.database.schema import validate_column_name, validate_column_length | |||||
from frappe.model.docfield import supports_translation | from frappe.model.docfield import supports_translation | ||||
from frappe.modules.import_file import get_file_path | from frappe.modules.import_file import get_file_path | ||||
from frappe.model.meta import Meta | from frappe.model.meta import Meta | ||||
from frappe.desk.utils import get_doctype_route | |||||
class InvalidFieldNameError(frappe.ValidationError): pass | class InvalidFieldNameError(frappe.ValidationError): pass | ||||
@@ -63,15 +64,7 @@ class DocType(Document): | |||||
self.validate_name() | self.validate_name() | ||||
if self.issingle: | |||||
self.allow_import = 0 | |||||
self.is_submittable = 0 | |||||
self.istable = 0 | |||||
elif self.istable: | |||||
self.allow_import = 0 | |||||
self.permissions = [] | |||||
self.set_defaults_for_single_and_table() | |||||
self.scrub_field_names() | self.scrub_field_names() | ||||
self.set_default_in_list_view() | self.set_default_in_list_view() | ||||
self.set_default_translatable() | self.set_default_translatable() | ||||
@@ -79,10 +72,7 @@ class DocType(Document): | |||||
self.validate_document_type() | self.validate_document_type() | ||||
validate_fields(self) | validate_fields(self) | ||||
if self.istable: | |||||
# no permission records for child table | |||||
self.permissions = [] | |||||
else: | |||||
if not self.istable: | |||||
validate_permissions(self) | validate_permissions(self) | ||||
self.make_amendable() | self.make_amendable() | ||||
@@ -93,8 +83,6 @@ class DocType(Document): | |||||
if not self.is_new(): | if not self.is_new(): | ||||
self.before_update = frappe.get_doc('DocType', self.name) | self.before_update = frappe.get_doc('DocType', self.name) | ||||
if not self.is_new(): | |||||
self.setup_fields_to_fetch() | self.setup_fields_to_fetch() | ||||
check_email_append_to(self) | check_email_append_to(self) | ||||
@@ -102,14 +90,20 @@ class DocType(Document): | |||||
if self.default_print_format and not self.custom: | if self.default_print_format and not self.custom: | ||||
frappe.throw(_('Standard DocType cannot have default print format, use Customize Form')) | frappe.throw(_('Standard DocType cannot have default print format, use Customize Form')) | ||||
if frappe.conf.get('developer_mode'): | |||||
self.owner = 'Administrator' | |||||
self.modified_by = 'Administrator' | |||||
def after_insert(self): | def after_insert(self): | ||||
# clear user cache so that on the next reload this doctype is included in boot | # clear user cache so that on the next reload this doctype is included in boot | ||||
clear_user_cache(frappe.session.user) | clear_user_cache(frappe.session.user) | ||||
def set_defaults_for_single_and_table(self): | |||||
if self.issingle: | |||||
self.allow_import = 0 | |||||
self.is_submittable = 0 | |||||
self.istable = 0 | |||||
elif self.istable: | |||||
self.allow_import = 0 | |||||
self.permissions = [] | |||||
def set_default_in_list_view(self): | def set_default_in_list_view(self): | ||||
'''Set default in-list-view for first 4 mandatory fields''' | '''Set default in-list-view for first 4 mandatory fields''' | ||||
if not [d.fieldname for d in self.fields if d.in_list_view]: | if not [d.fieldname for d in self.fields if d.in_list_view]: | ||||
@@ -134,6 +128,10 @@ class DocType(Document): | |||||
if not frappe.conf.get("developer_mode") and not self.custom: | if not frappe.conf.get("developer_mode") and not self.custom: | ||||
frappe.throw(_("Not in Developer Mode! Set in site_config.json or make 'Custom' DocType."), CannotCreateStandardDoctypeError) | frappe.throw(_("Not in Developer Mode! Set in site_config.json or make 'Custom' DocType."), CannotCreateStandardDoctypeError) | ||||
if frappe.conf.get('developer_mode'): | |||||
self.owner = 'Administrator' | |||||
self.modified_by = 'Administrator' | |||||
def setup_fields_to_fetch(self): | def setup_fields_to_fetch(self): | ||||
'''Setup query to update values for newly set fetch values''' | '''Setup query to update values for newly set fetch values''' | ||||
try: | try: | ||||
@@ -192,6 +190,12 @@ class DocType(Document): | |||||
def validate_website(self): | def validate_website(self): | ||||
"""Ensure that website generator has field 'route'""" | """Ensure that website generator has field 'route'""" | ||||
if not self.istable and not self.route: | |||||
self.route = get_doctype_route(self.name) | |||||
if self.route: | |||||
self.route = self.route.strip('/') | |||||
if self.has_web_view: | if self.has_web_view: | ||||
# route field must be present | # route field must be present | ||||
if not 'route' in [d.fieldname for d in self.fields]: | if not 'route' in [d.fieldname for d in self.fields]: | ||||
@@ -0,0 +1,7 @@ | |||||
import frappe | |||||
from frappe.desk.utils import get_doctype_route | |||||
def execute(): | |||||
for doctype in frappe.get_all('DocType', ['name', 'route'], dict(istable=0)): | |||||
if not doctype.route: | |||||
frappe.db.set_value('DocType', doctype.name, 'route', get_doctype_route(doctype.name), update_modified = False) |
@@ -8,6 +8,7 @@ | |||||
"engine": "InnoDB", | "engine": "InnoDB", | ||||
"field_order": [ | "field_order": [ | ||||
"document_type", | "document_type", | ||||
"route", | |||||
"fields", | "fields", | ||||
"client_script" | "client_script" | ||||
], | ], | ||||
@@ -31,11 +32,17 @@ | |||||
"fieldname": "client_script", | "fieldname": "client_script", | ||||
"fieldtype": "Code", | "fieldtype": "Code", | ||||
"label": "Client Script" | "label": "Client Script" | ||||
}, | |||||
{ | |||||
"fieldname": "route", | |||||
"fieldtype": "Data", | |||||
"label": "Route", | |||||
"reqd": 1 | |||||
} | } | ||||
], | ], | ||||
"index_web_pages_for_search": 1, | "index_web_pages_for_search": 1, | ||||
"links": [], | "links": [], | ||||
"modified": "2020-11-17 15:49:49.669291", | |||||
"modified": "2020-12-04 12:29:12.838656", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Custom", | "module": "Custom", | ||||
"name": "DocType Layout", | "name": "DocType Layout", | ||||
@@ -7,6 +7,10 @@ from __future__ import unicode_literals | |||||
import frappe | import frappe | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
from frappe.desk.utils import get_route | |||||
class DocTypeLayout(Document): | class DocTypeLayout(Document): | ||||
def validate(self): | def validate(self): | ||||
if not self.route: | |||||
self.route = get_route(self.name) | |||||
frappe.cache().delete_value('doctype_name_map') | frappe.cache().delete_value('doctype_name_map') |
@@ -0,0 +1,13 @@ | |||||
import frappe | |||||
def execute(): | |||||
for web_form_name in frappe.db.get_all('Web Form', pluck='name'): | |||||
web_form = frappe.get_doc('Web Form', web_form_name) | |||||
doctype_layout = frappe.get_doc(dict( | |||||
doctype = 'DocType Layout', | |||||
document_type = web_form.doc_type, | |||||
name = web_form.title, | |||||
route = web_form.route, | |||||
fields = [dict(fieldname = d.fieldname, label=d.label) for d in web_form.web_form_fields if d.fieldname] | |||||
)).insert() | |||||
print(doctype_layout.name) |
@@ -12,10 +12,10 @@ def get_doctype_name(name): | |||||
def get_name_map(): | def get_name_map(): | ||||
name_map = {} | name_map = {} | ||||
for d in frappe.get_all('DocType'): | for d in frappe.get_all('DocType'): | ||||
name_map[d.name.lower().replace(' ', '-')] = frappe._dict(doctype = d.name) | |||||
name_map[get_doctype_route(d.name)] = frappe._dict(doctype = d.name) | |||||
for d in frappe.get_all('DocType Layout', fields = ['name', 'document_type']): | for d in frappe.get_all('DocType Layout', fields = ['name', 'document_type']): | ||||
name_map[d.name.lower().replace(' ', '-')] = frappe._dict(doctype = d.document_type, doctype_layout = d.name) | |||||
name_map[get_doctype_route(d.name)] = frappe._dict(doctype = d.document_type, doctype_layout = d.name) | |||||
return name_map | return name_map | ||||
@@ -25,4 +25,7 @@ def get_doctype_name(name): | |||||
# return the layout object | # return the layout object | ||||
frappe.response.docs.append(frappe.get_doc('DocType Layout', data.name_map.get('doctype_layout')).as_dict()) | frappe.response.docs.append(frappe.get_doc('DocType Layout', data.name_map.get('doctype_layout')).as_dict()) | ||||
return data | |||||
return data | |||||
def get_doctype_route(name): | |||||
return name.lower().replace(' ', '-') |
@@ -168,7 +168,7 @@ frappe.Application = Class.extend({ | |||||
localStorage.removeItem("session_last_route"); | localStorage.removeItem("session_last_route"); | ||||
} else { | } else { | ||||
// route to home page | // route to home page | ||||
frappe.route(); | |||||
frappe.router.route(); | |||||
} | } | ||||
}, | }, | ||||
@@ -161,7 +161,7 @@ frappe.ui.form.Dashboard = class FormDashboard { | |||||
refresh() { | refresh() { | ||||
this.reset(); | this.reset(); | ||||
if (this.frm.doc.__islocal) { | |||||
if (this.frm.doc.__islocal || !frappe.boot.desk_settings.form_dashboard) { | |||||
return; | return; | ||||
} | } | ||||
@@ -1,4 +1,5 @@ | |||||
frappe.provide('frappe.ui.form'); | frappe.provide('frappe.ui.form'); | ||||
frappe.provide('frappe.model.docinfo'); | |||||
import './quick_entry'; | import './quick_entry'; | ||||
import './toolbar'; | import './toolbar'; | ||||
@@ -1668,7 +1669,7 @@ frappe.ui.form.Form = class FrappeForm { | |||||
}); | }); | ||||
driver.defineSteps(steps); | driver.defineSteps(steps); | ||||
frappe.route.on('change', () => driver.reset()); | |||||
frappe.router.on('change', () => driver.reset()); | |||||
driver.start(); | driver.start(); | ||||
} | } | ||||
@@ -121,7 +121,7 @@ frappe.form.formatters = { | |||||
{onclick: docfield.link_onclick.replace(/"/g, '"'), value:value}); | {onclick: docfield.link_onclick.replace(/"/g, '"'), value:value}); | ||||
} else if(docfield && doctype) { | } else if(docfield && doctype) { | ||||
return `<a | return `<a | ||||
href="/app/form/${encodeURIComponent(frappe.router.slug(doctype))}/${encodeURIComponent(original_value)}" | |||||
href="/app/${encodeURIComponent(frappe.router.slug(doctype))}/${encodeURIComponent(original_value)}" | |||||
data-doctype="${doctype}" | data-doctype="${doctype}" | ||||
data-name="${original_value}"> | data-name="${original_value}"> | ||||
${__(options && options.label || value)}</a>` | ${__(options && options.label || value)}</a>` | ||||
@@ -199,32 +199,39 @@ frappe.ui.form.Toolbar = Class.extend({ | |||||
this.page.clear_icons(); | this.page.clear_icons(); | ||||
this.page.clear_menu(); | this.page.clear_menu(); | ||||
if (frappe.session.user === 'Guest') return; | |||||
var me = this; | |||||
var p = this.frm.perm[0]; | |||||
var docstatus = cint(this.frm.doc.docstatus); | |||||
var is_submittable = frappe.model.is_submittable(this.frm.doc.doctype) | |||||
var issingle = this.frm.meta.issingle; | |||||
var print_settings = frappe.model.get_doc(":Print Settings", "Print Settings") | |||||
var allow_print_for_draft = cint(print_settings.allow_print_for_draft); | |||||
var allow_print_for_cancelled = cint(print_settings.allow_print_for_cancelled); | |||||
if (frappe.boot.desk_settings.form_sidebar) { | |||||
this.make_navigation(); | |||||
this.make_menu_items(); | |||||
} | |||||
}, | |||||
make_navigation() { | |||||
// Navigate | // Navigate | ||||
if (!this.frm.is_new() && !issingle) { | |||||
this.page.add_action_icon("left", function() { | |||||
me.frm.navigate_records(1); | |||||
}, 'prev-doc', __("Previous Document")); | |||||
this.page.add_action_icon("right", function() { | |||||
me.frm.navigate_records(0); | |||||
}, 'next-doc', __("Next Document")); | |||||
} | |||||
if (!this.frm.is_new() && !this.frm.meta.issingle) { | |||||
this.page.add_action_icon("left", () => { | |||||
this.frm.navigate_records(1); | |||||
}, 'prev-doc'); | |||||
this.page.add_action_icon("right", ()=> { | |||||
this.frm.navigate_records(0); | |||||
}, 'next-doc'); | |||||
} | |||||
}, | |||||
make_menu_items() { | |||||
const me = this; | |||||
const p = this.frm.perm[0]; | |||||
const docstatus = cint(this.frm.doc.docstatus); | |||||
const is_submittable = frappe.model.is_submittable(this.frm.doc.doctype) | |||||
const print_settings = frappe.model.get_doc(":Print Settings", "Print Settings") | |||||
const allow_print_for_draft = cint(print_settings.allow_print_for_draft); | |||||
const allow_print_for_cancelled = cint(print_settings.allow_print_for_cancelled); | |||||
if (!is_submittable || docstatus == 1 || | if (!is_submittable || docstatus == 1 || | ||||
(allow_print_for_cancelled && docstatus == 2)|| | (allow_print_for_cancelled && docstatus == 2)|| | ||||
(allow_print_for_draft && docstatus == 0)) { | (allow_print_for_draft && docstatus == 0)) { | ||||
if (frappe.model.can_print(null, me.frm) && !issingle) { | |||||
if (frappe.model.can_print(null, me.frm) && !this.frm.meta.issingle) { | |||||
this.page.add_menu_item(__("Print"), function() { | this.page.add_menu_item(__("Print"), function() { | ||||
me.frm.print_doc(); | me.frm.print_doc(); | ||||
}, true); | }, true); | ||||
@@ -286,30 +293,7 @@ frappe.ui.form.Toolbar = Class.extend({ | |||||
}); | }); | ||||
} | } | ||||
if (frappe.user_roles.includes("System Manager")) { | |||||
let is_doctype_form = me.frm.doctype === 'DocType'; | |||||
let doctype = is_doctype_form ? me.frm.docname : me.frm.doctype; | |||||
let is_doctype_custom = is_doctype_form ? me.frm.doc.custom : false; | |||||
if (doctype != 'DocType' && !is_doctype_custom && me.frm.meta.issingle === 0) { | |||||
this.page.add_menu_item(__("Customize"), function() { | |||||
if (me.frm.meta && me.frm.meta.custom) { | |||||
frappe.set_route('Form', 'DocType', doctype); | |||||
} else { | |||||
frappe.set_route('Form', 'Customize Form', { | |||||
doc_type: doctype | |||||
}); | |||||
} | |||||
}, true); | |||||
} | |||||
if (frappe.boot.developer_mode===1 && !is_doctype_form) { | |||||
// edit doctype | |||||
this.page.add_menu_item(__("Edit DocType"), function() { | |||||
frappe.set_route('Form', 'DocType', me.frm.doctype); | |||||
}, true); | |||||
} | |||||
} | |||||
this.make_customize_buttons(); | |||||
// Auto Repeat | // Auto Repeat | ||||
if(this.can_repeat()) { | if(this.can_repeat()) { | ||||
@@ -328,6 +312,35 @@ frappe.ui.form.Toolbar = Class.extend({ | |||||
}); | }); | ||||
} | } | ||||
}, | }, | ||||
make_customize_buttons() { | |||||
if (frappe.user_roles.includes("System Manager")) { | |||||
let is_doctype_form = this.frm.doctype === 'DocType'; | |||||
let doctype = is_doctype_form ? this.frm.docname : this.frm.doctype; | |||||
let is_doctype_custom = is_doctype_form ? this.frm.doc.custom : false; | |||||
if (doctype != 'DocType' && !is_doctype_custom && this.frm.meta.issingle === 0) { | |||||
this.page.add_menu_item(__("Customize"), () => { | |||||
if (this.frm.meta && this.frm.meta.custom) { | |||||
frappe.set_route('Form', 'DocType', doctype); | |||||
} else { | |||||
frappe.set_route('Form', 'Customize Form', { | |||||
doc_type: doctype | |||||
}); | |||||
} | |||||
}, true); | |||||
} | |||||
if (frappe.boot.developer_mode===1 && !is_doctype_form) { | |||||
// edit doctype | |||||
this.page.add_menu_item(__("Edit DocType"), () => { | |||||
frappe.set_route('Form', 'DocType', this.frm.doctype); | |||||
}, true); | |||||
} | |||||
} | |||||
}, | |||||
can_repeat: function() { | can_repeat: function() { | ||||
return this.frm.meta.allow_auto_repeat | return this.frm.meta.allow_auto_repeat | ||||
&& !this.frm.is_new() | && !this.frm.is_new() | ||||
@@ -182,15 +182,17 @@ frappe.views.BaseList = class BaseList { | |||||
'Dashboard': 'dashboard' | 'Dashboard': 'dashboard' | ||||
} | } | ||||
this.views_menu = this.page.add_custom_button_group(__(`{0} View`, [this.view_name]), icon_map[this.view_name] || 'list'); | |||||
this.views_list = new frappe.views.Views({ | |||||
doctype: this.doctype, | |||||
parent: this.views_menu, | |||||
page: this.page, | |||||
list_view: this, | |||||
sidebar: this.list_sidebar, | |||||
icon_map: icon_map | |||||
}); | |||||
if (frappe.boot.desk_settings.view_switcher) { | |||||
this.views_menu = this.page.add_custom_button_group(__(`{0} View`, [this.view_name]), icon_map[this.view_name] || 'list'); | |||||
this.views_list = new frappe.views.Views({ | |||||
doctype: this.doctype, | |||||
parent: this.views_menu, | |||||
page: this.page, | |||||
list_view: this, | |||||
sidebar: this.list_sidebar, | |||||
icon_map: icon_map | |||||
}); | |||||
} | |||||
} | } | ||||
set_default_secondary_action() { | set_default_secondary_action() { | ||||
@@ -871,7 +871,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
? encodeURIComponent(doc.name) | ? encodeURIComponent(doc.name) | ||||
: doc.name; | : doc.name; | ||||
return "/app/form/" + frappe.router.slug(frappe.router.doctype_layout || this.doctype) + "/" + docname; | |||||
return `/app/${frappe.router.slug(frappe.router.doctype_layout || this.doctype)}/${docname}`; | |||||
} | } | ||||
get_seen_class(doc) { | get_seen_class(doc) { | ||||
@@ -905,6 +905,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
let escaped_subject = frappe.utils.escape_html(subject); | let escaped_subject = frappe.utils.escape_html(subject); | ||||
const seen = this.get_seen_class(doc); | const seen = this.get_seen_class(doc); | ||||
console.log(this.get_form_link(doc)); | |||||
let subject_html = ` | let subject_html = ` | ||||
<input class="level-item list-row-checkbox hidden-xs" type="checkbox" | <input class="level-item list-row-checkbox hidden-xs" type="checkbox" | ||||
@@ -26,7 +26,7 @@ $(window).on('hashchange', function() { | |||||
window.addEventListener('popstate', () => { | window.addEventListener('popstate', () => { | ||||
// forward-back button, just re-render based on current route | // forward-back button, just re-render based on current route | ||||
frappe.route(); | |||||
frappe.router.route(); | |||||
}); | }); | ||||
// routing v2, capture all clicks so that the target is managed with push-state | // routing v2, capture all clicks so that the target is managed with push-state | ||||
@@ -53,18 +53,27 @@ $('body').on('click', 'a', function(e) { | |||||
} | } | ||||
// target has "/app, this is a v2 style route. | // target has "/app, this is a v2 style route. | ||||
if (e.currentTarget.pathname && | |||||
(e.currentTarget.pathname.startsWith('/app') || e.currentTarget.pathname.startsWith('app'))) { | |||||
if (e.currentTarget.pathname && frappe.router.is_app_route()) { | |||||
return override(e, e.currentTarget.pathname); | return override(e, e.currentTarget.pathname); | ||||
} | } | ||||
}); | }); | ||||
frappe.router = { | frappe.router = { | ||||
current_route: null, | current_route: null, | ||||
doctype_names: {}, | |||||
factory_views: ['form', 'list', 'report', 'tree', 'print'], | |||||
factory_views: ['form', 'list', 'report', 'tree', 'print', 'dashboard'], | |||||
list_views: ['list', 'kanban', 'report', 'calendar', 'tree', 'gantt', 'dashboard'], | |||||
layout_mapped: {}, | layout_mapped: {}, | ||||
is_app_route() { | |||||
// desk paths must begin with /app or doctype route | |||||
let path = window.location.pathname; | |||||
if (path.substr(0, 1) === '/') path = path.substr(1); | |||||
path = path.split('/'); | |||||
if (path[0]) { | |||||
return path[0]==='app'; | |||||
} | |||||
}, | |||||
route() { | route() { | ||||
// resolve the route from the URL or hash | // resolve the route from the URL or hash | ||||
// translate it so the objects are well defined | // translate it so the objects are well defined | ||||
@@ -72,64 +81,81 @@ frappe.router = { | |||||
if (!frappe.app) return; | if (!frappe.app) return; | ||||
let sub_path = frappe.router.get_sub_path(); | |||||
if (frappe.router.re_route(sub_path)) return; | |||||
frappe.router.translate_doctype_name().then(() => { | |||||
frappe.router.set_history(sub_path); | |||||
let sub_path = this.get_sub_path(); | |||||
if (this.re_route(sub_path)) return; | |||||
if (frappe.router.current_route[0]) { | |||||
frappe.router.render_page(); | |||||
} else { | |||||
// Show home | |||||
frappe.views.pageview.show(''); | |||||
} | |||||
this.current_route = this.parse(); | |||||
this.set_history(sub_path); | |||||
this.render(); | |||||
this.set_title(); | |||||
this.trigger('change'); | |||||
}, | |||||
frappe.router.set_title(); | |||||
frappe.route.trigger('change'); | |||||
}); | |||||
parse(route) { | |||||
route = this.get_sub_path_string(route).split('/'); | |||||
route = $.map(route, this.decode_component); | |||||
this.set_route_options_from_url(route); | |||||
return this.convert_to_standard_route(route); | |||||
}, | }, | ||||
translate_doctype_name() { | |||||
return new Promise((resolve) => { | |||||
const route = frappe.router.current_route = frappe.router.parse(); | |||||
const factory = route[0].toLowerCase(); | |||||
const set_name = () => { | |||||
const d = frappe.router.doctype_names[route[1]]; | |||||
route[1] = d.doctype; | |||||
frappe.router.doctype_layout = d.doctype_layout; | |||||
resolve(); | |||||
}; | |||||
if (frappe.router.factory_views.includes(factory)) { | |||||
// translate the doctype to its original name | |||||
if (frappe.router.doctype_names[route[1]]) { | |||||
set_name(); | |||||
convert_to_standard_route(route) { | |||||
// /app/user = ["List", "User"] | |||||
// /app/user/views/report = ["List", "User", "Report"] | |||||
// /app/user/views/tree = ["Tree", "User"] | |||||
// /app/user/user-001 = ["Form", "User", "user-001"] | |||||
// /app/user/user-001 = ["Form", "User", "user-001"] | |||||
let standard_route = route; | |||||
let doctype_route = frappe.boot.routes[route[0]]; | |||||
if (doctype_route) { | |||||
// doctype route | |||||
if (route[1]) { | |||||
if (route[2] && route[1]==='views') { | |||||
if (route[2].toLowerCase()==='tree') { | |||||
standard_route = ['Tree', doctype_route.doctype]; | |||||
} else { | |||||
standard_route = ['List', doctype_route.doctype, route[2]]; | |||||
} | |||||
} else { | } else { | ||||
frappe.xcall('frappe.desk.utils.get_doctype_name', {name: route[1]}).then((data) => { | |||||
frappe.router.doctype_names[route[1]] = data.name_map; | |||||
set_name(); | |||||
}); | |||||
standard_route = ['Form', doctype_route.doctype, route[1]]; | |||||
} | } | ||||
} else if (frappe.model.is_single(doctype_route.doctype)) { | |||||
standard_route = ['Form', doctype_route.doctype, doctype_route.doctype]; | |||||
} else { | } else { | ||||
resolve(); | |||||
standard_route = ['List', doctype_route.doctype, 'List']; | |||||
} | } | ||||
}); | |||||
if (doctype_route.doctype_layout) { | |||||
// set the layout | |||||
this.doctype_layout = doctype_route.doctype_layout; | |||||
} | |||||
} | |||||
return standard_route; | |||||
}, | }, | ||||
set_history(sub_path) { | set_history(sub_path) { | ||||
frappe.route_history.push(frappe.router.current_route); | |||||
frappe.route_history.push(this.current_route); | |||||
frappe.route_titles[sub_path] = frappe._original_title || document.title; | frappe.route_titles[sub_path] = frappe._original_title || document.title; | ||||
frappe.ui.hide_open_dialog(); | frappe.ui.hide_open_dialog(); | ||||
}, | }, | ||||
render() { | |||||
if (this.current_route[0]) { | |||||
this.render_page(); | |||||
} else { | |||||
// Show home | |||||
frappe.views.pageview.show(''); | |||||
} | |||||
}, | |||||
render_page() { | render_page() { | ||||
// create the page generator (factory) object and call `show` | // create the page generator (factory) object and call `show` | ||||
// if there is no generator, render the `Page` object | // if there is no generator, render the `Page` object | ||||
// first the router needs to know if its a "page", "doctype", "workspace" | // first the router needs to know if its a "page", "doctype", "workspace" | ||||
const route = frappe.router.current_route; | |||||
const route = this.current_route; | |||||
const factory = frappe.utils.to_title_case(route[0]); | const factory = frappe.utils.to_title_case(route[0]); | ||||
if (factory === 'Workspace') { | if (factory === 'Workspace') { | ||||
frappe.views.pageview.show(''); | frappe.views.pageview.show(''); | ||||
@@ -161,7 +187,7 @@ frappe.router = { | |||||
// it doesn't allow us to go back to the one prior to "New DocType 1" | // it doesn't allow us to go back to the one prior to "New DocType 1" | ||||
// Hence if this check is true, instead of changing location hash, | // Hence if this check is true, instead of changing location hash, | ||||
// we just do a back to go to the doc previous to the "New DocType 1" | // we just do a back to go to the doc previous to the "New DocType 1" | ||||
var re_route_val = frappe.router.get_sub_path(frappe.re_route[sub_path]); | |||||
var re_route_val = this.get_sub_path(frappe.re_route[sub_path]); | |||||
if (decodeURIComponent(re_route_val) === decodeURIComponent(sub_path)) { | if (decodeURIComponent(re_route_val) === decodeURIComponent(sub_path)) { | ||||
window.history.back(); | window.history.back(); | ||||
return true; | return true; | ||||
@@ -186,32 +212,14 @@ frappe.router = { | |||||
// set the route (push state) with given arguments | // set the route (push state) with given arguments | ||||
// example 1: frappe.set_route('a', 'b', 'c'); | // example 1: frappe.set_route('a', 'b', 'c'); | ||||
// example 2: frappe.set_route(['a', 'b', 'c']); | // example 2: frappe.set_route(['a', 'b', 'c']); | ||||
// example 3: frappe.set_route('a/b/c'); | |||||
// example 3: frappe.set_route('a/b/c'); | |||||
let route = arguments; | |||||
return new Promise(resolve => { | return new Promise(resolve => { | ||||
var route = arguments; | |||||
if (route.length===1 && $.isArray(route[0])) { | |||||
// called as frappe.set_route(['a', 'b', 'c']); | |||||
route = route[0]; | |||||
} | |||||
if (route.length===1 && route[0].includes('/')) { | |||||
// called as frappe.set_route('a/b/c') | |||||
route = $.map(route[0].split('/'), frappe.router.decode_component); | |||||
} | |||||
if (route && route[0] == '') { | |||||
route.shift(); | |||||
} | |||||
if (route && ['desk', 'app'].includes(route[0])) { | |||||
// we only need subpath, remove "app" (or "desk") | |||||
route.shift(); | |||||
} | |||||
frappe.router.slug_parts(route); | |||||
const sub_path = frappe.router.make_url_from_list(route); | |||||
frappe.router.push_state(sub_path); | |||||
route = this.get_route_from_arguments(route); | |||||
route = this.convert_from_standard_route(route); | |||||
const sub_path = this.make_url(route); | |||||
this.push_state(sub_path); | |||||
setTimeout(() => { | setTimeout(() => { | ||||
frappe.after_ajax && frappe.after_ajax(() => { | frappe.after_ajax && frappe.after_ajax(() => { | ||||
@@ -221,19 +229,63 @@ frappe.router = { | |||||
}); | }); | ||||
}, | }, | ||||
get_route_from_arguments(route) { | |||||
if (route.length===1 && $.isArray(route[0])) { | |||||
// called as frappe.set_route(['a', 'b', 'c']); | |||||
route = route[0]; | |||||
} | |||||
if (route.length===1 && route[0].includes('/')) { | |||||
// called as frappe.set_route('a/b/c') | |||||
route = $.map(route[0].split('/'), this.decode_component); | |||||
} | |||||
if (route && route[0] == '') { | |||||
route.shift(); | |||||
} | |||||
if (route && ['desk', 'app'].includes(route[0])) { | |||||
// we only need subpath, remove "app" (or "desk") | |||||
route.shift(); | |||||
} | |||||
return route; | |||||
}, | |||||
convert_from_standard_route(route) { | |||||
// ["List", "Sales Order"] => /sales-order | |||||
// ["Form", "Sales Order", "SO-0001"] => /sales-order/SO-0001 | |||||
// ["Tree", "Account"] = /account/view/tree | |||||
const view = route[0].toLowerCase(); | |||||
if (view === 'list') { | |||||
if (route[2] && route[2] !== 'list') { | |||||
return [this.slug(route[1]), 'view', view] | |||||
} else { | |||||
return [this.slug(route[1])] | |||||
} | |||||
} else if (view === 'form') { | |||||
return [this.slug(route[1]), route[2]] | |||||
} else if (view === 'tree') { | |||||
return [this.slug(route[1]), 'view', 'tree']; | |||||
} | |||||
return route; | |||||
}, | |||||
slug_parts(route) { | slug_parts(route) { | ||||
// slug doctype | // slug doctype | ||||
// if app is part of the route, then first 2 elements are "" and "app" | // if app is part of the route, then first 2 elements are "" and "app" | ||||
if (route[0] && frappe.router.factory_views.includes(route[0].toLowerCase())) { | |||||
if (route[0] && this.factory_views.includes(route[0].toLowerCase())) { | |||||
route[0] = route[0].toLowerCase(); | route[0] = route[0].toLowerCase(); | ||||
route[1] = frappe.router.slug(route[1]); | |||||
route[1] = this.slug(route[1]); | |||||
} | } | ||||
return route; | return route; | ||||
}, | }, | ||||
make_url_from_list(params) { | |||||
return $.map(params, function(a) { | |||||
make_url(params) { | |||||
return '/app/' + $.map(params, function(a) { | |||||
if ($.isPlainObject(a)) { | if ($.isPlainObject(a)) { | ||||
frappe.route_options = a; | frappe.route_options = a; | ||||
return null; | return null; | ||||
@@ -248,10 +300,8 @@ frappe.router = { | |||||
}).join('/'); | }).join('/'); | ||||
}, | }, | ||||
push_state(sub_path) { | |||||
push_state(url) { | |||||
// change the URL and call the router | // change the URL and call the router | ||||
const url = `/app/${sub_path}`; | |||||
if (window.location.pathname !== url) { | if (window.location.pathname !== url) { | ||||
// cleanup any remenants of v1 routing | // cleanup any remenants of v1 routing | ||||
window.location.hash = ''; | window.location.hash = ''; | ||||
@@ -260,19 +310,10 @@ frappe.router = { | |||||
history.pushState(null, null, url); | history.pushState(null, null, url); | ||||
// now process the route | // now process the route | ||||
frappe.router.route(); | |||||
this.route(); | |||||
} | } | ||||
}, | }, | ||||
parse(route) { | |||||
route = frappe.router.get_sub_path_string(route).split('/'); | |||||
route = $.map(route, frappe.router.decode_component); | |||||
frappe.router.set_route_options_from_url(route); | |||||
return route; | |||||
}, | |||||
get_sub_path_string(route) { | get_sub_path_string(route) { | ||||
// return clean sub_path from hash or url | // return clean sub_path from hash or url | ||||
// supports both v1 and v2 routing | // supports both v1 and v2 routing | ||||
@@ -280,7 +321,7 @@ frappe.router = { | |||||
route = window.location.hash || window.location.pathname; | route = window.location.hash || window.location.pathname; | ||||
} | } | ||||
return frappe.router.strip_prefix(route); | |||||
return this.strip_prefix(route); | |||||
}, | }, | ||||
strip_prefix(route) { | strip_prefix(route) { | ||||
@@ -293,8 +334,8 @@ frappe.router = { | |||||
}, | }, | ||||
get_sub_path(route) { | get_sub_path(route) { | ||||
var sub_path = frappe.router.get_sub_path_string(route); | |||||
route = $.map(sub_path.split('/'), frappe.router.decode_component).join('/'); | |||||
var sub_path = this.get_sub_path_string(route); | |||||
route = $.map(sub_path.split('/'), this.decode_component).join('/'); | |||||
return route; | return route; | ||||
}, | }, | ||||
@@ -334,10 +375,9 @@ frappe.router = { | |||||
}; | }; | ||||
// global functions for backward compatibility | // global functions for backward compatibility | ||||
frappe.route = frappe.router.route; | |||||
frappe.get_route = () => frappe.router.current_route; | frappe.get_route = () => frappe.router.current_route; | ||||
frappe.get_route_str = () => frappe.router.current_route.join('/'); | frappe.get_route_str = () => frappe.router.current_route.join('/'); | ||||
frappe.set_route = frappe.router.set_route; | |||||
frappe.set_route = function() { frappe.router.set_route.apply(frappe.router, arguments) }; | |||||
frappe.get_prev_route = function() { | frappe.get_prev_route = function() { | ||||
if (frappe.route_history && frappe.route_history.length > 1) { | if (frappe.route_history && frappe.route_history.length > 1) { | ||||
@@ -357,4 +397,4 @@ frappe.has_route_options = function() { | |||||
return Boolean(Object.keys(frappe.route_options || {}).length); | return Boolean(Object.keys(frappe.route_options || {}).length); | ||||
}; | }; | ||||
frappe.utils.make_event_emitter(frappe.route); | |||||
frappe.utils.make_event_emitter(frappe.router); |
@@ -1,4 +1,3 @@ | |||||
frappe.provide('frappe.route'); | |||||
frappe.route_history_queue = []; | frappe.route_history_queue = []; | ||||
const routes_to_skip = ['Form', 'social', 'setup-wizard', 'recorder']; | const routes_to_skip = ['Form', 'social', 'setup-wizard', 'recorder']; | ||||
@@ -16,7 +15,7 @@ const save_routes = frappe.utils.debounce(() => { | |||||
}, 10000); | }, 10000); | ||||
frappe.route.on('change', () => { | |||||
frappe.router.on('change', () => { | |||||
const route = frappe.get_route(); | const route = frappe.get_route(); | ||||
if (is_route_useful(route)) { | if (is_route_useful(route)) { | ||||
frappe.route_history_queue.push({ | frappe.route_history_queue.push({ | ||||
@@ -59,8 +59,8 @@ | |||||
</span> | </span> | ||||
</a> | </a> | ||||
</li> | </li> | ||||
<li class="vertical-bar hidden d-none d-sm-block"></li> | |||||
<li class="nav-item dropdown dropdown-help dropdown-mobile hidden d-none d-lg-block"> | |||||
<li class="vertical-bar d-none d-sm-block"></li> | |||||
<li class="nav-item dropdown dropdown-help dropdown-mobile d-none d-lg-block"> | |||||
<a class="nav-link" data-toggle="dropdown" href="#" onclick="return false;"> | <a class="nav-link" data-toggle="dropdown" href="#" onclick="return false;"> | ||||
{{ __("Help") }} | {{ __("Help") }} | ||||
<span> | <span> | ||||
@@ -402,7 +402,7 @@ frappe.search.SearchDialog = class { | |||||
frappe.set_route(result.route); | frappe.set_route(result.route); | ||||
// hashchange didn't fire! | // hashchange didn't fire! | ||||
if (window.location.hash == previous_hash) { | if (window.location.hash == previous_hash) { | ||||
frappe.route(); | |||||
frappe.router.route(); | |||||
} | } | ||||
} | } | ||||
}); | }); | ||||
@@ -816,7 +816,7 @@ Object.assign(frappe.utils, { | |||||
display_text = display_text || name; | display_text = display_text || name; | ||||
doctype = encodeURIComponent(doctype); | doctype = encodeURIComponent(doctype); | ||||
name = encodeURIComponent(name); | name = encodeURIComponent(name); | ||||
const route = ['/app/Form', doctype, name].join('/'); | |||||
const route = `/app/${frappe.router.slug(doctype)}/${name}`; | |||||
if (html) { | if (html) { | ||||
return `<a href="${route}">${display_text}</a>`; | return `<a href="${route}">${display_text}</a>`; | ||||
} | } | ||||
@@ -1127,7 +1127,7 @@ Object.assign(frappe.utils, { | |||||
let doctype_slug = frappe.router.slug(item.doctype); | let doctype_slug = frappe.router.slug(item.doctype); | ||||
if (frappe.model.is_single(item.doctype)) { | if (frappe.model.is_single(item.doctype)) { | ||||
route = "form/" + doctype_slug; | |||||
route = doctype_slug; | |||||
} else { | } else { | ||||
if (!item.doc_view) { | if (!item.doc_view) { | ||||
if (frappe.model.is_tree(item.doctype)) { | if (frappe.model.is_tree(item.doctype)) { | ||||
@@ -1142,22 +1142,22 @@ Object.assign(frappe.utils, { | |||||
if (item.filters) { | if (item.filters) { | ||||
frappe.route_options = item.filters; | frappe.route_options = item.filters; | ||||
} | } | ||||
route = "list/" + doctype_slug; | |||||
route = doctype_slug; | |||||
break; | break; | ||||
case "Tree": | case "Tree": | ||||
route = "tree/" + doctype_slug; | |||||
route = `${doctype_slug}/view/tree`; | |||||
break; | break; | ||||
case "Report Builder": | case "Report Builder": | ||||
route = "list/" + doctype_slug + "/Report"; | |||||
route = `${doctype_slug}/view/report`; | |||||
break; | break; | ||||
case "Dashboard": | case "Dashboard": | ||||
route = "list/" + doctype_slug + "/Dashboard"; | |||||
route = `${doctype_slug}/view/dashboard`; | |||||
break; | break; | ||||
case "New": | case "New": | ||||
route = "form/" + doctype_slug + "/New " + item.doctype; | |||||
route = `${doctype_slug}/new`; | |||||
break; | break; | ||||
case "Calendar": | case "Calendar": | ||||
route = "list/" + doctype_slug + "/Calendar/Default"; | |||||
route = `${doctype_slug}/view/calendar/Default`; | |||||
break; | break; | ||||
default: | default: | ||||
frappe.throw({ message: __("Not a valid DocType view:") + item.doc_view, title: __("Unknown View") }); | frappe.throw({ message: __("Not a valid DocType view:") + item.doc_view, title: __("Unknown View") }); | ||||
@@ -1167,7 +1167,7 @@ Object.assign(frappe.utils, { | |||||
} else if (type === "report" && item.is_query_report) { | } else if (type === "report" && item.is_query_report) { | ||||
route = "query-report/" + item.name; | route = "query-report/" + item.name; | ||||
} else if (type === "report") { | } else if (type === "report") { | ||||
route = "List/" + frappe.router.slug(item.doctype) + "/Report/" + item.name; | |||||
route = frappe.router.slug(item.doctype) + "/view/report/" + item.name; | |||||
} else if (type === "page") { | } else if (type === "page") { | ||||
route = item.name; | route = item.name; | ||||
} else if (type === "dashboard") { | } else if (type === "dashboard") { | ||||
@@ -114,11 +114,12 @@ frappe.breadcrumbs = { | |||||
// no user listview for non-system managers and single doctypes | // no user listview for non-system managers and single doctypes | ||||
} else { | } else { | ||||
let route; | let route; | ||||
const doctype_route = frappe.router.slug(frappe.router.doctype_layout || breadcrumbs.doctype); | |||||
if (frappe.boot.treeviews.indexOf(breadcrumbs.doctype) !== -1) { | if (frappe.boot.treeviews.indexOf(breadcrumbs.doctype) !== -1) { | ||||
let view = frappe.model.user_settings[breadcrumbs.doctype].last_view || 'Tree'; | let view = frappe.model.user_settings[breadcrumbs.doctype].last_view || 'Tree'; | ||||
route = view + '/' + breadcrumbs.doctype; | |||||
route = `${doctype_route}/view/${view}`; | |||||
} else { | } else { | ||||
route = 'list/' + (frappe.router.doctype_layout || breadcrumbs.doctype); | |||||
route = doctype_route; | |||||
} | } | ||||
$(`<li><a href="/app/${route}">${doctype}</a></li>`) | $(`<li><a href="/app/${route}">${doctype}</a></li>`) | ||||
.appendTo($breadcrumbs) | .appendTo($breadcrumbs) | ||||
@@ -132,7 +133,7 @@ frappe.breadcrumbs = { | |||||
if (breadcrumbs.doctype && frappe.get_route()[0] === "print") { | if (breadcrumbs.doctype && frappe.get_route()[0] === "print") { | ||||
set_list_breadcrumb(breadcrumbs.doctype); | set_list_breadcrumb(breadcrumbs.doctype); | ||||
let docname = frappe.get_route()[2]; | let docname = frappe.get_route()[2]; | ||||
let form_route = `/app/form/${frappe.router.slug(breadcrumbs.doctype)}/${docname}`; | |||||
let form_route = `/app/${frappe.router.slug(breadcrumbs.doctype)}/${docname}`; | |||||
$(`<li><a href="${form_route}">${docname}</a></li>`) | $(`<li><a href="${form_route}">${docname}</a></li>`) | ||||
.appendTo($breadcrumbs); | .appendTo($breadcrumbs); | ||||
} | } | ||||
@@ -54,7 +54,7 @@ frappe.views.FormFactory = class FormFactory extends frappe.views.Factory { | |||||
} | } | ||||
const doc = frappe.get_doc(doctype, name); | const doc = frappe.get_doc(doctype, name); | ||||
if (doc && (doc.__islocal || frappe.model.is_fresh(doc))) { | |||||
if (doc && frappe.model.get_docinfo(doctype, name) && (doc.__islocal || frappe.model.is_fresh(doc))) { | |||||
// is document available and recent? | // is document available and recent? | ||||
this.render(doctype_layout, name); | this.render(doctype_layout, name); | ||||
} else { | } else { | ||||
@@ -200,7 +200,7 @@ | |||||
"is_published_field": "published", | "is_published_field": "published", | ||||
"links": [], | "links": [], | ||||
"max_attachments": 5, | "max_attachments": 5, | ||||
"modified": "2020-08-31 21:01:51.100349", | |||||
"modified": "2020-12-07 23:27:23.644512", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Website", | "module": "Website", | ||||
"name": "Blog Post", | "name": "Blog Post", | ||||
@@ -229,7 +229,7 @@ | |||||
"write": 1 | "write": 1 | ||||
} | } | ||||
], | ], | ||||
"route": "/blog", | |||||
"route": "blog-post", | |||||
"sort_field": "modified", | "sort_field": "modified", | ||||
"sort_order": "ASC", | "sort_order": "ASC", | ||||
"title_field": "title", | "title_field": "title", | ||||
@@ -14,6 +14,7 @@ from frappe.website.utils import (find_first_image, get_html_content_based_on_ty | |||||
class BlogPost(WebsiteGenerator): | class BlogPost(WebsiteGenerator): | ||||
website = frappe._dict( | website = frappe._dict( | ||||
route = 'blog', | |||||
order_by = "published_on desc" | order_by = "published_on desc" | ||||
) | ) | ||||