@@ -48,6 +48,7 @@ def get_bootinfo(): | |||
bootinfo.letter_heads = get_letter_heads() | |||
bootinfo.active_domains = frappe.get_active_domains() | |||
bootinfo.all_domains = [d.get("name") for d in frappe.get_all("Domain")] | |||
add_routes(bootinfo) | |||
bootinfo.module_app = frappe.local.module_app | |||
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 | |||
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(): | |||
role_list = frappe.get_all('Role', fields=['*'], filters=dict( | |||
name=['in', frappe.get_roles()] | |||
@@ -16,6 +16,7 @@ | |||
"is_tree", | |||
"editable_grid", | |||
"quick_entry", | |||
"route", | |||
"cb01", | |||
"track_changes", | |||
"track_seen", | |||
@@ -71,7 +72,6 @@ | |||
"has_web_view", | |||
"allow_guest_to_view", | |||
"index_web_pages_for_search", | |||
"route", | |||
"is_published_field", | |||
"advanced", | |||
"engine" | |||
@@ -132,7 +132,7 @@ | |||
"label": "Editable Grid" | |||
}, | |||
{ | |||
"default": "1", | |||
"default": "0", | |||
"depends_on": "eval:!doc.istable && !doc.issingle", | |||
"description": "Open a dialog with mandatory fields to create a new record quickly", | |||
"fieldname": "quick_entry", | |||
@@ -427,7 +427,7 @@ | |||
"label": "Allow Guest to View" | |||
}, | |||
{ | |||
"depends_on": "has_web_view", | |||
"depends_on": "eval:!doc.istable", | |||
"fieldname": "route", | |||
"fieldtype": "Data", | |||
"label": "Route" | |||
@@ -609,7 +609,7 @@ | |||
"link_fieldname": "reference_doctype" | |||
} | |||
], | |||
"modified": "2020-09-24 13:13:58.227153", | |||
"modified": "2020-12-07 11:48:57.395126", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "DocType", | |||
@@ -637,6 +637,7 @@ | |||
"write": 1 | |||
} | |||
], | |||
"route": "doctype", | |||
"search_fields": "module", | |||
"show_name_in_global_search": 1, | |||
"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.modules.import_file import get_file_path | |||
from frappe.model.meta import Meta | |||
from frappe.desk.utils import get_doctype_route | |||
class InvalidFieldNameError(frappe.ValidationError): pass | |||
@@ -63,15 +64,7 @@ class DocType(Document): | |||
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.set_default_in_list_view() | |||
self.set_default_translatable() | |||
@@ -79,10 +72,7 @@ class DocType(Document): | |||
self.validate_document_type() | |||
validate_fields(self) | |||
if self.istable: | |||
# no permission records for child table | |||
self.permissions = [] | |||
else: | |||
if not self.istable: | |||
validate_permissions(self) | |||
self.make_amendable() | |||
@@ -93,8 +83,6 @@ class DocType(Document): | |||
if not self.is_new(): | |||
self.before_update = frappe.get_doc('DocType', self.name) | |||
if not self.is_new(): | |||
self.setup_fields_to_fetch() | |||
check_email_append_to(self) | |||
@@ -102,14 +90,20 @@ class DocType(Document): | |||
if self.default_print_format and not self.custom: | |||
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): | |||
# clear user cache so that on the next reload this doctype is included in boot | |||
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): | |||
'''Set default in-list-view for first 4 mandatory fields''' | |||
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: | |||
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): | |||
'''Setup query to update values for newly set fetch values''' | |||
try: | |||
@@ -192,6 +190,12 @@ class DocType(Document): | |||
def validate_website(self): | |||
"""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: | |||
# route field must be present | |||
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", | |||
"field_order": [ | |||
"document_type", | |||
"route", | |||
"fields", | |||
"client_script" | |||
], | |||
@@ -31,11 +32,17 @@ | |||
"fieldname": "client_script", | |||
"fieldtype": "Code", | |||
"label": "Client Script" | |||
}, | |||
{ | |||
"fieldname": "route", | |||
"fieldtype": "Data", | |||
"label": "Route", | |||
"reqd": 1 | |||
} | |||
], | |||
"index_web_pages_for_search": 1, | |||
"links": [], | |||
"modified": "2020-11-17 15:49:49.669291", | |||
"modified": "2020-12-04 12:29:12.838656", | |||
"modified_by": "Administrator", | |||
"module": "Custom", | |||
"name": "DocType Layout", | |||
@@ -7,6 +7,10 @@ from __future__ import unicode_literals | |||
import frappe | |||
from frappe.model.document import Document | |||
from frappe.desk.utils import get_route | |||
class DocTypeLayout(Document): | |||
def validate(self): | |||
if not self.route: | |||
self.route = get_route(self.name) | |||
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(): | |||
name_map = {} | |||
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']): | |||
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 | |||
@@ -25,4 +25,7 @@ def get_doctype_name(name): | |||
# return the layout object | |||
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"); | |||
} else { | |||
// route to home page | |||
frappe.route(); | |||
frappe.router.route(); | |||
} | |||
}, | |||
@@ -161,7 +161,7 @@ frappe.ui.form.Dashboard = class FormDashboard { | |||
refresh() { | |||
this.reset(); | |||
if (this.frm.doc.__islocal) { | |||
if (this.frm.doc.__islocal || !frappe.boot.desk_settings.form_dashboard) { | |||
return; | |||
} | |||
@@ -1,4 +1,5 @@ | |||
frappe.provide('frappe.ui.form'); | |||
frappe.provide('frappe.model.docinfo'); | |||
import './quick_entry'; | |||
import './toolbar'; | |||
@@ -1668,7 +1669,7 @@ frappe.ui.form.Form = class FrappeForm { | |||
}); | |||
driver.defineSteps(steps); | |||
frappe.route.on('change', () => driver.reset()); | |||
frappe.router.on('change', () => driver.reset()); | |||
driver.start(); | |||
} | |||
@@ -121,7 +121,7 @@ frappe.form.formatters = { | |||
{onclick: docfield.link_onclick.replace(/"/g, '"'), value:value}); | |||
} else if(docfield && doctype) { | |||
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-name="${original_value}"> | |||
${__(options && options.label || value)}</a>` | |||
@@ -199,32 +199,39 @@ frappe.ui.form.Toolbar = Class.extend({ | |||
this.page.clear_icons(); | |||
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 | |||
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 || | |||
(allow_print_for_cancelled && docstatus == 2)|| | |||
(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() { | |||
me.frm.print_doc(); | |||
}, 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 | |||
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() { | |||
return this.frm.meta.allow_auto_repeat | |||
&& !this.frm.is_new() | |||
@@ -182,15 +182,17 @@ frappe.views.BaseList = class BaseList { | |||
'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() { | |||
@@ -871,7 +871,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||
? encodeURIComponent(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) { | |||
@@ -905,6 +905,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||
let escaped_subject = frappe.utils.escape_html(subject); | |||
const seen = this.get_seen_class(doc); | |||
console.log(this.get_form_link(doc)); | |||
let subject_html = ` | |||
<input class="level-item list-row-checkbox hidden-xs" type="checkbox" | |||
@@ -26,7 +26,7 @@ $(window).on('hashchange', function() { | |||
window.addEventListener('popstate', () => { | |||
// 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 | |||
@@ -53,18 +53,27 @@ $('body').on('click', 'a', function(e) { | |||
} | |||
// 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); | |||
} | |||
}); | |||
frappe.router = { | |||
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: {}, | |||
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() { | |||
// resolve the route from the URL or hash | |||
// translate it so the objects are well defined | |||
@@ -72,64 +81,81 @@ frappe.router = { | |||
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 { | |||
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 { | |||
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) { | |||
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.ui.hide_open_dialog(); | |||
}, | |||
render() { | |||
if (this.current_route[0]) { | |||
this.render_page(); | |||
} else { | |||
// Show home | |||
frappe.views.pageview.show(''); | |||
} | |||
}, | |||
render_page() { | |||
// create the page generator (factory) object and call `show` | |||
// if there is no generator, render the `Page` object | |||
// 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]); | |||
if (factory === 'Workspace') { | |||
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" | |||
// 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" | |||
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)) { | |||
window.history.back(); | |||
return true; | |||
@@ -186,32 +212,14 @@ frappe.router = { | |||
// set the route (push state) with given arguments | |||
// example 1: 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 => { | |||
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(() => { | |||
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 doctype | |||
// 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[1] = frappe.router.slug(route[1]); | |||
route[1] = this.slug(route[1]); | |||
} | |||
return route; | |||
}, | |||
make_url_from_list(params) { | |||
return $.map(params, function(a) { | |||
make_url(params) { | |||
return '/app/' + $.map(params, function(a) { | |||
if ($.isPlainObject(a)) { | |||
frappe.route_options = a; | |||
return null; | |||
@@ -248,10 +300,8 @@ frappe.router = { | |||
}).join('/'); | |||
}, | |||
push_state(sub_path) { | |||
push_state(url) { | |||
// change the URL and call the router | |||
const url = `/app/${sub_path}`; | |||
if (window.location.pathname !== url) { | |||
// cleanup any remenants of v1 routing | |||
window.location.hash = ''; | |||
@@ -260,19 +310,10 @@ frappe.router = { | |||
history.pushState(null, null, url); | |||
// 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) { | |||
// return clean sub_path from hash or url | |||
// supports both v1 and v2 routing | |||
@@ -280,7 +321,7 @@ frappe.router = { | |||
route = window.location.hash || window.location.pathname; | |||
} | |||
return frappe.router.strip_prefix(route); | |||
return this.strip_prefix(route); | |||
}, | |||
strip_prefix(route) { | |||
@@ -293,8 +334,8 @@ frappe.router = { | |||
}, | |||
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; | |||
}, | |||
@@ -334,10 +375,9 @@ frappe.router = { | |||
}; | |||
// global functions for backward compatibility | |||
frappe.route = frappe.router.route; | |||
frappe.get_route = () => frappe.router.current_route; | |||
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() { | |||
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); | |||
}; | |||
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 = []; | |||
const routes_to_skip = ['Form', 'social', 'setup-wizard', 'recorder']; | |||
@@ -16,7 +15,7 @@ const save_routes = frappe.utils.debounce(() => { | |||
}, 10000); | |||
frappe.route.on('change', () => { | |||
frappe.router.on('change', () => { | |||
const route = frappe.get_route(); | |||
if (is_route_useful(route)) { | |||
frappe.route_history_queue.push({ | |||
@@ -59,8 +59,8 @@ | |||
</span> | |||
</a> | |||
</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;"> | |||
{{ __("Help") }} | |||
<span> | |||
@@ -402,7 +402,7 @@ frappe.search.SearchDialog = class { | |||
frappe.set_route(result.route); | |||
// hashchange didn't fire! | |||
if (window.location.hash == previous_hash) { | |||
frappe.route(); | |||
frappe.router.route(); | |||
} | |||
} | |||
}); | |||
@@ -816,7 +816,7 @@ Object.assign(frappe.utils, { | |||
display_text = display_text || name; | |||
doctype = encodeURIComponent(doctype); | |||
name = encodeURIComponent(name); | |||
const route = ['/app/Form', doctype, name].join('/'); | |||
const route = `/app/${frappe.router.slug(doctype)}/${name}`; | |||
if (html) { | |||
return `<a href="${route}">${display_text}</a>`; | |||
} | |||
@@ -1127,7 +1127,7 @@ Object.assign(frappe.utils, { | |||
let doctype_slug = frappe.router.slug(item.doctype); | |||
if (frappe.model.is_single(item.doctype)) { | |||
route = "form/" + doctype_slug; | |||
route = doctype_slug; | |||
} else { | |||
if (!item.doc_view) { | |||
if (frappe.model.is_tree(item.doctype)) { | |||
@@ -1142,22 +1142,22 @@ Object.assign(frappe.utils, { | |||
if (item.filters) { | |||
frappe.route_options = item.filters; | |||
} | |||
route = "list/" + doctype_slug; | |||
route = doctype_slug; | |||
break; | |||
case "Tree": | |||
route = "tree/" + doctype_slug; | |||
route = `${doctype_slug}/view/tree`; | |||
break; | |||
case "Report Builder": | |||
route = "list/" + doctype_slug + "/Report"; | |||
route = `${doctype_slug}/view/report`; | |||
break; | |||
case "Dashboard": | |||
route = "list/" + doctype_slug + "/Dashboard"; | |||
route = `${doctype_slug}/view/dashboard`; | |||
break; | |||
case "New": | |||
route = "form/" + doctype_slug + "/New " + item.doctype; | |||
route = `${doctype_slug}/new`; | |||
break; | |||
case "Calendar": | |||
route = "list/" + doctype_slug + "/Calendar/Default"; | |||
route = `${doctype_slug}/view/calendar/Default`; | |||
break; | |||
default: | |||
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) { | |||
route = "query-report/" + item.name; | |||
} 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") { | |||
route = item.name; | |||
} else if (type === "dashboard") { | |||
@@ -114,11 +114,12 @@ frappe.breadcrumbs = { | |||
// no user listview for non-system managers and single doctypes | |||
} else { | |||
let route; | |||
const doctype_route = frappe.router.slug(frappe.router.doctype_layout || breadcrumbs.doctype); | |||
if (frappe.boot.treeviews.indexOf(breadcrumbs.doctype) !== -1) { | |||
let view = frappe.model.user_settings[breadcrumbs.doctype].last_view || 'Tree'; | |||
route = view + '/' + breadcrumbs.doctype; | |||
route = `${doctype_route}/view/${view}`; | |||
} else { | |||
route = 'list/' + (frappe.router.doctype_layout || breadcrumbs.doctype); | |||
route = doctype_route; | |||
} | |||
$(`<li><a href="/app/${route}">${doctype}</a></li>`) | |||
.appendTo($breadcrumbs) | |||
@@ -132,7 +133,7 @@ frappe.breadcrumbs = { | |||
if (breadcrumbs.doctype && frappe.get_route()[0] === "print") { | |||
set_list_breadcrumb(breadcrumbs.doctype); | |||
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>`) | |||
.appendTo($breadcrumbs); | |||
} | |||
@@ -54,7 +54,7 @@ frappe.views.FormFactory = class FormFactory extends frappe.views.Factory { | |||
} | |||
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? | |||
this.render(doctype_layout, name); | |||
} else { | |||
@@ -200,7 +200,7 @@ | |||
"is_published_field": "published", | |||
"links": [], | |||
"max_attachments": 5, | |||
"modified": "2020-08-31 21:01:51.100349", | |||
"modified": "2020-12-07 23:27:23.644512", | |||
"modified_by": "Administrator", | |||
"module": "Website", | |||
"name": "Blog Post", | |||
@@ -229,7 +229,7 @@ | |||
"write": 1 | |||
} | |||
], | |||
"route": "/blog", | |||
"route": "blog-post", | |||
"sort_field": "modified", | |||
"sort_order": "ASC", | |||
"title_field": "title", | |||
@@ -14,6 +14,7 @@ from frappe.website.utils import (find_first_image, get_html_content_based_on_ty | |||
class BlogPost(WebsiteGenerator): | |||
website = frappe._dict( | |||
route = 'blog', | |||
order_by = "published_on desc" | |||
) | |||