In this exercise we will be continuing with the example established in the basic training exercise.
Specifically, we will be extending the basic exercise functionality with the following:
Categories will now have the ability to have sub-categories using a self reference
We will add the ability to add notes and file attachments to tickets to understand how the default file uploader works and how it can be customized
We will allow for tickets to have sub tasks. These sub tasks will enable the ability to track a ticket's progress.
We will build a dashboard that will give us a nice overview of our tickets and their progress.
The new data model we will be making is as follows:
As you can see, the data model is starting to look more complicated. Let's break this down:
Adding the Note entity:
A note needs to be attached to a Ticket, and may have an attachment (linked to the FileDocument entity)
The attributes in the Note entity are NoteDescription and NoteCreatedDate
Adding the SubTask entity:
Each ticket can have many subtasks
The attributes in the SubTask entity are Description, SubTaskStatus and SubTaskDueDate.
SubTaskStatus will have the same drop down as TicketStatus
Updating the Category entity:
Add the attributes CategoryParentId and HierarchyPath.
This is done in order to allow for self referencing, where each category can have many subcategories.
The added attributes allow for relative and absolute identification of the category relationships.
Let us begin with the changes to the Category entity. As seen in the new data model, we have added new attributes.
Our old CRUD components will not reflect the changes unless we add them manually (or create new CRUD components).
In our case, this will not even be necassary as the attributes added need to be defined by the programmer.
We want the CategoryParentId to be assigned automatically, depending on what category we were in when we clicked '+ Category'.
Similarly with the HierarchyPath, we want this to be automatically generated for each category based on the trail of parent IDs.
This HierarchyPath value will be used in the frontend to indicate the actual category, including its entire hierarchy, for the ticket.
In the 'category_crud_create' component we can immediately add the code to auto-populate the two new attributes accordingly.
In the javascript component.js file, we override the saveEntity() function to NOT set the global constrain ID to the current one. This is because we want to keep the constrainID we initially started with in that variable. The rest of the code is default divbloxPHP functionality.
saveEntity(){
let current_component_obj =this.updateValues();
this.resetValidation();
if(!this.validateEntity()){
return;
}
// The parameter object we will send to the backend via dxRequestInternal()
let parameters_obj ={
f:"saveObjectData",
ObjectData:JSON.stringify(current_component_obj),
Id:this.getLoadArgument("entity_id")
};
// Checks if the component is constrained by an entity and subsequently honours the constraint
On the PHP side, we override the empty-by-default function doAfterSaveActions() to save both the CategoryParentId as well as the HierarchyPath.
The constraining (i.e. parent) category object is loaded from the database, and if this is not null, it's ID is saved into the CategoryParentId
attribute of the current category object. The doAfterSaveActions() is meant for exactly this type of functionality, which needs to query dadtabase values after the input is saved.
We then use the getBreadCrumbsRecursive() function (defined shortly) to return an array of the parental hierarchy of categories. Here is the function we add to the component.php file:
The function getBreadCrumbsRecursive() is created in the ProjectFunctions class to reduce code duplication,
as we will be using it again when displaying the breadcrumb trail on our 'category_update' page.
The ProjectFunctions class (project/assets/php/project_functions.php) is created for this very reason,
and is where you should house all your functions that will have multiple calls in your project.
The function getBreadCrumbsRecursive() is just a recursive function that returns the parental hierarchy of the input
category in a key-value pair array.
Now that our create component correctly saves all necessary information to the database, let us set up a 'category_update' page, a screenshot of which is presented below. It will house 3 components:
Breadcrumb trail for subcategories (yellow)
The update component (blue)
SubCategory list, based on the currently clicked category (green)
This is the page a user will be redirected to only by clicking on a category to edit on the main page, and will not be accessible via navigation bar. The reason we would want a seperate page to display and update our categories is because of their hierarchical nature, and it may be a lot easier for the user to visualize parent child relationships of categories in this way.
The breadcrumb basic component can be easily added via divbloxPHP's component builder. We will do this in the 'category_update' page component. The following default HTML code:
will be replaced with our custom HTML frame, ID'ed accordingly so we can populate it dynamically from our database based on which category is selected.
<navaria-label="breadcrumb">
<olid="CategoryBreadcrumbs"class="breadcrumb">
<liclass="breadcrumb-item">
<aid="AllCategories"href="#">All Categories</a>
</li>
<!-- BREADCRUMBS TO FOLLOW-->
</ol>
</nav>
The following code will be added to the 'category_update' page component's javascript component.js file. We add two event handlers, which are defined in the initCustomFunctions() function. This function is run only once upon initialization of the component.
The first event handler is to navigate back to the admin page when 'All Categories' is clicked
The second is to reset the global constraining ID for the entity 'Category' to the clicked on category and then refresh the page to load it up accordingly
note
Note how we attach the event to a click on the document, after which we specify where on the document the click should be.
This is because if we set the event handler to listen directly for a click on '.category-breadcrumb', we will get unexpected
output because during page load-up, this sub component is not defined yet.
// Fetches the stored category ID of form "CategoryId_categoryname"
// and stored only the name in a variable
let category_id =$(this).attr("id").replace("CategoryId_","");
// Sets new constraint ID and reloads page
setGlobalConstrainById("Category", category_id);
loadPageComponent("category_update");
});
}
The updateBreadCrumbs() function handles the request to the server using divbloxPHP's dxRequestInternal() function. The parameters we send to the backend are the function name we want to call and the category ID. The success function deals with formatting the returned array into HTML and displaying it.
The updateBreadCrumbs() function is the called in the page component's reset() function, meaning that every time the page or component refreshes, it will execute. We make use of this functionality in conjunction with the setGlobalConstrainById() and getGlobalConstrainById() functions to very easily and dynamically update components based on constraints.
Now in the component.php file, we define the getBreadCrumbs() function referenced in the javascript. It receives the category ID, and loads the category by ID. We then call the getBreadCrumbsRecursive() function we defined in the class ProjectFunctions previously and order the array.
On the 'category_update' page we want to also be able to display and manage the sub categories for the current category. In this section we will build the subcategory_list component displayed below.
We will also to create a modal pop up that will allow us to create more subcategories. (Note that this will have to reference the constraining category to make sure the hierarchy is correct).
For this, we will create a new list-only CRUD component for the 'Category' entity using the component builder, displaying only the category name and subsequent ticket count.
The component created is not constrained in any way. We need to constrain it to only display child categories of the category currently defined on the page. (That we defined using the setGlobalConstrainById() function).
We do this by overriding the buildQueryConditions() function in the component.php file to only query the constrained array of categories from the database. This function is quite long, and you can go through it on your own time. Below is the adaptation we made to the query.
// Change logic in the buildQueryConditions() function
On the javascript side, we do a few things. Below you will see the whole javascript.js file. We will break the changes into 3 things:
We change the behaviour of the on_item_clicked() function to reload the current page with the new category constraint ID.
There is a lot of auto-generated boilerplate code relating to the modal, which we will not use. However, we do want to put a 'category_create' component in the modal. This is again as easy as a few clicks in the the component builder.
Change the outcome of the event 'category_created'. divbloxPHP has built in boilerplate code for such events, which we fill in to both hide the modal and refresh the page.
With this, we have updated all the functionality needed for the Category entity, created a page to edit the categories as well as have visual aid with regard to the hierarchical structure of the categories. Here is a quick replay of what we built:
We have already set up our 'create' and 'update' components to work in a streamline way:
When creating a ticket, only having to input the ticket name and description,
after which you are navigated to the ticket_update page where you can complete all other relevant fields.
We now want to have a sub tasks list and a notes list in this component.
We first create the CRUD components for each using the divbloxPHP Component Builder,
after which we just insert them into our ticket_crud_update' component (in their own row, taking up equal 6 columns each in bootstrap terms).
The ticket progression idea is somewhat loosely defined as a concept, so we need to define what we actually mean by it.
The definition we will implement is as follows:
If a ticket has no subtasks, then if it's status is completed its TicketProgression will be 100% and if it is anything else it will be 0%.
If a ticket has subtasks, then the ticket's status is ignored and the number of subtasks with status Complete divided
by the number of subtasks that are not complete, will give us the percentage TicketProgression.
This logic will be defined directly in the Ticket entity Save() function, which can be overridden in the Ticket.class.php file (project/assets/php/data_model_orm/Ticket.class.php). Below are the Save() and Delete() functions with the changes made. You will also see the changes made to store the categoryCount value as explained in the basic training exercise.
The sub tasks are already constrained by the parent Ticket ID, so all we need to do is make the HTML formatting a little bit more to our liking.
You can make the input boxes full-width and arrange them in a bootstrap layout to your liking. We change the create layout to maximize the space we have:
The Note section is a bit more complicated, for a few reasons. Firstly, we want to be able to attach files here, which
need to be constrained to the currently opened ticket.
Let's start off by creating a 'note_attachment_uploader' component
which we will tailor to our needs, based off of the default 'default_file_upload' component.
This is done via the component builder, simply by creating a new component from the existing component "default_file_upload".
As before, there is a lot of background code functionality we will not discuss here, but encourage you to sift through to understand.
We will outline the changes made and why we made them. In the component.js file, we just had to add an additional input parameter
(note_id) into the JQuery upload in the initFileUploader() function, as we need this ID so as to constrain the attachment to the current note.
On the PHP side, we override the default functionality of the handleFilePost() function, adding the code snippet indicated
below. We query the Note table by the "note_id" passed, and proceed with a few checks. If the Note object is null, we delete
the corresponding file upload (this is to prevent having orphaned data). If the Note object exists, we delete whatever file
(if any) was uploaded before and save the new file. With this simple set up each note will only be able to have none or one attachment.
Now that we have prepared our file uploader to link to the current note, let's dig into the actual Note CRUD.
Firstly, we want to follow a similar approach as with the Ticket and Category create CRUD components,
whereby the initial create only requires limited fields, after which you are navigated to the update component to complete the process.
We do this by shifting the 'note_created' case of the eventTriggered() function in the 'note_crud' component to above the 'note_clicked' case, as before.
We can then add the two buttons we want via the component builder. These are:
A modal popup housing the custom file uploader
A download link, appearing only when there is actually an attachment.
First, we add a row with two columns in the update component.
We then add the modal using the component builder, and change relevant text and button text as well as make the modal button have classes
'full-width' and 'btn-link'. Below is a walk through of the step we take in the component builder:
We can also go ahead and remove the modal footer as those buttons are not needed.
Now we will proceed to inspect the necessary code changes applied to create the functionality we need. Starting with the update component javascript:
Firstly we want to make sure that the modal will always be closed until clicked, we do this in the component's reset() function
We then add functionality to our modal boilerplate code. We wish to pass the current note's note_id to the note_attachment_uploader inside the modal. This is done in the initCustomFunctions() function. This note_id is how we are able to constrain attachments to the current note.
Note that you generated modal ID will probably be different to the one below.
We also decide to override the eventTriggered() 'FileUploaded' case to reset the component, from a UX point of view. If i have described my note and added an attachment, I am most likely done with that note.
Finally, we also want to override the onAfterLoadEntity() function to populate our right column with a download link if and only if the attachment exists.
Now for the backend side. Below is the note update component.php file.
Firstly, we override the default getObjectData() function. What we added here is backend validation for the existence and validity of the attachment by checking the relational entity FileDocument. The only return values the front end can receive is an empty string or a valid attachment path string that points to a file that exists in the database.
Secondly, we need to make sure that if we delete any notes, we do not accidentally leave behind any orphaned files/images.
This is done using divbloxPHP's doBeforeDeleteActions() function.
We will now build a dashboard which should give us a summary overview of the tickets we have and their distribution into
categories and statuses. The first step is to create a page with a sidebar navigation component, and then proceed to add
the necessary link into the menu we created.
Once we have our page set up and navigation configured, we can proceed with our dashboard.
The final product will look something like this:
To create the dashboard we will create a few individual components, that can be reused anywhere:
A "Status Tile" component for the 6 status tiles seen in the top row
Let's create the tiles that display a summary of the breakdown of statuses of all tickets.
As you can see, we will create a single component, and reuse it on our Dashboard page with different input parameters. These input parameters are specified in the javascript of the parent element, i.e. our dashboard page itself. This is done in the subcomponent definitions of the page by adding a secondary load argument as seen below:
Now, to build our component. Once created, we add a fullwidth container and two rows, the first split into a 9-width bootstrap column and a 3-width bootstrap column, and the second a full 12-width column. These 3 sections will be the sections for the:
Status name
Number of tickets
Percentage of total tickets that status represents
We will look at the relevant component files individually. These are:
Note the IDs and class names which will be used to input information from the backend and deal with CSS styling. We also use built-in bootstrap classes such as float-left and ml-1, mr-4, etc to position our text as needed. Further CSS classes can and will be added if needed.
For any custom data list, there are quite a few things to consider, but when you understand those, using a default entity data list to construct anything you need becomes very quick and easy.
Firstly, we create a data list from the component builder for the Account entity, including the attributes:
FirstName
LastName
ProfilePicturePath
Then we will edit the default addRow() function in the component.js file (to change the generated HTML rows) as well as the getPage() function in the component.php (to add the 'In Progress' and 'Overdue' ticket counts to the information passed to the front end)
Again, we will look at each of the 5 components individually (omitting the unchanged .css and .json files).
Looking at the HTML that is generated by the component builder, we see that there isn't really anything we want to change. The HTML file gives us a template for the structure or the component, including pagination and search functionality. The only thing we do add is a heading describing the component purpose.
You might think that you need to restructure how your list will be displayed here, but that is done in the javascript and just written into the necessary DOM node. So looking at our javascript file, we note that the only changes we make to the default functionality lies in the addRow() function, where we will create each row to display the information we need in the format we want it.
Adding this component to our dashboard is shown below. Note that all of the CSS will be shown at the end, but is not the purpose of this exercise, so we encourange you to style it as you see fit.
Now looking at the component.php file. This is a long one. Feel free to browse through the entire code to familiarize yourself with the background process, but the changes we have made will be defined with prepended comments.
Done! It is normal for your components to look unpolished until you add the CSS necessary to scale the profile pictures, center the text, etc. You can spend some time to make it look as you please. You can do this in the one of three places:
In the current component's component.css file
In the parent (dashboard page) component.css file (These classes will be accessible to all child components in the page)
In the file themes.css (Accessible across your project)
Where you define your classes is obviously dependant on the scope in which you will be using them.
This component is constructed exactly the same way we constructed the account summary list. We create a list component using the component builder (including all the attributes we need).
Below is the component.js file, in which we again altered the addRow() function to display the information we want neatly. Since all the information we want to display is already directly stored in the database, we do not need to edit the component.php file.
We will now look at the two graph components we want to create for our dashboard.
Divblox uses the ChartJS library, and further documentation can be found here.
Divblox has a default chart component which is designed to be used as boilerplate code to be tweaked to create the necessary
graph instead of having to create new ones. Below is a video of how to create new graph components via the component builder.
Now that we have created (identical to the default) graph components, let's go through the changes we make to display the correct data and graph types.
This component is a bar chart showing the number of tickets in each of the status's.
We will not discuss the chartJS-specific code, as their documentation is thorough and well-defined. Below we show the ticket_status_graph component's component.js and component.php files.
In this exercise you learned about some of the more advanced concepts of a Divblox project.
If you understand all the functionality completely, you should now be able to build complex web applications,
from start to finish using Divblox.
If you would like to receive further hands-on training from the Divblox team, please reach out to us at support@divblox.com and we will arrange a consultation.