By Vijay Simha on May 26, 2021
Intermediate

Creating a new page in ERPNext

ERPNext allows mainly for three different types of customisations - doctypes, reports and pages. It also allows webpage and web form. Web page is similar to Page and web form is used less often.


In this article we will look at how to create a new page.


A new page is needed if the requirement goes beyond a simple report with multiple tables and charts. And may be some buttons and links to perform some system actions.


1. Creating a new page

Everything ERPNext is a doctype. So even a Page is a doctype. To create a new page go to Page List and add a new page. If the page is marked as standard then no changes can be done by users.




Assuming the setup is in developer mode ERPNext creates a folder with the name of the page in the applications folder under Page sub folder. It also creates a .js file with the same name as the page. 



Coding the Javascript file

The page Javascript file is expected to add the page event code to frappe.pages object. Given below is an example where on_page_load event is used to set up the page.


frappe.provide("frappe.credence_hr");

frappe.pages["employee-profile"].on_page_load = function (wrapper) {
 var profilepage = frappe.credence_hr.employee_profile;
 profilepage.setup(wrapper);
 $(wrapper).bind("show", () => {
   profilepage.refresh();
 });
};

frappe.credence_hr.employee_profile = {
   addfilters : function(){ ...},
   setup : function (){ ...},
   refresh : function(){ ...}
}


The line frappe.provide allows for creating a name space for storing the memory variables. For example the code related to the setting up the above page is stored in frappe.credence_hr.employee_profile as JS object with addfilters, setup and refresh methods.


Adding filters to the page

We can add filters to the page to allow user input parameters. Sample code to add input to the page is given below.


addfilters: function () {
   var employeepage = this;
   this.fields = {};
   this.fields.employee = this.page.add_field({
     label: "Employee",
     fieldname: "employee",
     fieldtype: "Link",
     options: "Employee",
     change() {
       employeepage.refresh(false);
     },
   });
}


To set up the main section the page pls check the code below.


 setup: function (wrapper, filters) {
   this.page = frappe.ui.make_app_page({
     parent: wrapper,
     title: "Employee Profile",
     single_column: true,
   });
   this.wrapper = $(wrapper);
   this.main_section = this.wrapper.find(".layout-main-section");
   this.page.set_title("Employee Profile");
   this.page.clear_fields();
   frappe.breadcrumbs.add("Credence");
   this.main_section.append(`<div id="employee-profile"> </div>`);
}


For more details on how to add fields and other page related documentation pls check the link.


How to create REST API end points

Page will have to be populated with data from REST end points. Frappe allows easy creation of REST end points. Example of how to create a REST end point is given below. This is created in a python module.


@frappe.whitelist()
def get_profile_data(employee):
   doc = frappe.get_doc("Employee", employee)
   if not doc:
       frappe.throw("You do not have access to this employee records !!!")
   data = {key: doc.get(key, "") for key in ["employee_name", "gender", "status", "employment_type", "grade", "resignation_letter_date"]}
   return data


Making AJAX call to REST end point

Frappe allows easy way to call a rest end point. Pls see the code below.


   frappe
     .call(
       "credence_hr.credence_hr.page.employee_profile.employee_profile.get_profile_data",
       {
         employee: employee,
       }
     )
     .then((data) => {
       var employee = data.message;})


The end point is called using frappe.call method in the JS file. The python module with complete path is provided. Pls note the argument object. Frappe framework convert this object and provides it as keyword argument to the python function. Please check the link for more details on how to make the AJAX calls using Frappe framework. 


Serving HTML

Frappe support Vue JS framework out of the box. Vue JS components can be created with reactivity. It also support server side rendering (SSR) with Jinja templates. Given that most of the the customisation development is likely to be for a single page rather than a Single Page Application (SPA) Jinja templates for rendering HTML would more suitable. This would mean preparing the HTML code on the server and serving the same via AJAX requests. Following code is an example of how to serve HTML from the server.


@frappe.whitelist()
def get_profile_html(employee, pagename="Profile"):
   doc = frappe.get_doc("Employee", empl_name)
   if doc:
       return frappe.render_template("credence_hr/page/employee_profile/templates/profile.html", {"employee": doc, "datetime": datetime, "relativedelta": relativedelta})
   else:
       return "Employee details not found or you do not have access to this employee !"


For more information on Jinja template support in Frappe pls check this link. For more information on Jinja please check here.


Note : Frappe loads all the HTML files in the main directory of the page when loading the page. This will create an error if the html file has any javascript code with single quotes. Its better to create a template folder and save all the html files in that folder. 


The Javascript code for requesting the HTML from the server and rendering the same is as given below.


   frappe
     .call(
       "credence_hr.credence_hr.page.employee_profile.employee_profile.get_profile_html",
       {
         employee: employee,
         pagename: pagename,
       }
     )
     .then((data) => {
       $("#employee-profile").html(data.message);
     });
 },


Page API supports JQuery hence all JQuery function can be used in the javascript file or HTML template. Pls see example below.


   $(".nav-item").on("click", function () {
     $(this)
       .siblings("div")
       .each(function () {
         $(this).css("background-color", "white");
       });


Points to note about Jinja

Apart from for loop and if condition Jinja provides set statement which can be used to assign a variable. This can be used to also call python object methods or function. Given below is an example.


{% set ageing_data = {"labels" : issue_ageing['issue_type'], "datasets" : []}  %}
{% for period in ["week", "fortnight", "month", "overmonth"] %}
{% set test = ageing_data["datasets"].append({"label": period,"data" : issue_ageing.data[period] }) %}
{% endfor %}


And also note that Jinja does not support list or dictionary comprehension.


Adding external libraries

One can add external JS libraries to a page code. For example in order to add chartjs to the page you need to download and add chartjs.min.js to public/js folder in your app. You also need to modify the hook.py in your app to add tge variable page_js. This line is present in the scaffolding created by Bench. Given here is an example of the modification.


# include js in page
page_js = {"amc-review" : "public/js/chart.min.js"}


This was an introduction as to how to create a Page in ERPNext. With access to Jquery, Bootstrap and AJAX more interactive pages can be easily created. To add external Javascript or CSS assets please see the link here. 


A few examples of page are given here.

  1. Code for Project Review page is here.  The python libary code is here.


To help develop pages in a structured way we have created some libraries. For example we use a javscript libary which creates a structure for the page. Visit the link for documentation about this library for "Review Page" structure.


Note : In order to design a web page layout it is important to understand the concept of grid and flexbox in HTML5. Pls check the link for Flexbox here and for Grid here.


More articles on ERP Development



More articles on ERP Development
Comments

No comments yet.

Add a comment
Ctrl+Enter to add comment