18. Multiple User Roles

Punch List App: Next Steps

Let's look at further requirements for our Punch List app:

App Design: Construction Punch List

We want to introduce a new user role, and App Admin, which needs to have the ability to easily manage all the data that has been captured in the application.

Roles, Permissions, Branching

There are multiple ways in which you can implement user roles, permissions and branching logic in JourneyApps. There are no predefined concepts or limits, and so for every app you can determine how complex your roles, permissions and branching logic needs to be.

One way is for every user role to have dedicated views and workflows, this means that every view that you create will only ever be used by one type of user role. Another option is to have shared views but change what is displayed / rendered on every view based on the roles / permissions of the user currently viewing it. These same concepts can also be applied to the business logic side of your applications. In some instances it will be easier to have dedicated business logic for every user role, whereas in others it will be easier to share the business logic but make it behave differently based on the roles / permissions of the current user.

Punch List App: App Admin User Role

For the Punch List App's Admin user role we don't need anything fancy, in fact, all we want the app admin to do is to be able to manage all the data that is used in the application. In our Punch List App, that means we want the Admin to be able to create, update, and delete items and categories (we will ignore user management for now). This can be easily accomplished using one very powerful component, the <object-table>. The JourneyApps <object-table> is an extremely powerful and dynamic component, and you will find yourself using it quite often as a result. So, the plan is to have our App Admins simply manage all the data on the 'Main' screen using an <object-table> component. To make this work we first need to decide how we are going to differentiate between normal users and admin users.

Add Role to Data Model

Let's head over to the Data Model workspace and update the schema.xml file to include a new Role field on our user model. This field will be another single-choice type, so remember, the easiest way to add a single-choice type is to look for the auto-complete suggestion for single-choice. Once you have added the field, populate it with two options, one for 'Normal' and one for 'Admin', like this.

<model name="user" label="User">
    <field name="name" label="Name" type="text:name" />
    <field name="role" label="Role" type="single-choice">
        <option key="Normal">Normal</option>
        <option key="Admin">Admin</option>
    </field>

    <has-many model="item" name="items" />
    <display>{name}</display>
</model>

Update Main View for Admin Role

Next, we'll update our 'Main' view to look and work one way if the user is an 'Admin' and another way, the way it's currently working, of the user is a 'Normal' user. To do this we will once again be making use of our show-if and hide-if functionality. For testing purposes we will also want a way to change our user role directly in the application, a button and a JS function will work for this.

So, head over to your Views workspace, select the 'main' view and add another button right at the bottom of the view to change the current user's user role. Like this:

<button label="Change Role" on-press="changeRole" icon="fa-cogs" validate="false" style="outline" />

main.view.xml

<?xml version="1.0" encoding="UTF-8"?>
<view title="Punch List">
    <!-- Parameters go here: -->

    <!-- Variables go here: -->
    <var name="open_punches" type="query:item" />

    <!-- Components go here: -->
    <html src="html/blue-line.html" show-fullscreen-button="false" height="15px"/>

    <columns>
        <column>
            <display-image src="images/deep-c-corp-main-logo.png" />
        </column>
        <column>
            <display-image src="images/img-welcome-light.png" />
            <heading>Welcome to the Punch List App</heading>

            <info>A list of all the punches that have not yet been taken care of are shown below</info>

            <list empty-message="Your items will appear here">
                <list-item query="open_punches">
                    <header>{comments}</header>
                    <content>{category}</content>
                    <footer>{created_at}</footer>
                    <accent label="{status}" color="info" />
                    <asset src="{photo}" />
                    <action on-press="$:selectItem($selection)" />
                </list-item>
            </list>

            <button-group>
                <button label="Add New Item" icon="fa-plus" on-press="goToNew" validate="false" style="solid" />
                <button label="Browse by Category" on-press="$:navigate.link('browse_items')" icon="fa-filter" validate="false" style="solid" />
            </button-group>
        </column>
    </columns>

    <button label="Change Role" on-press="changeRole" icon="fa-cogs" validate="false" style="outline" />
</view>

Now, let's create the changeRole JS function. Like this:

function changeRole() {
    user.role = user.role === 'Admin' ? 'Normal' : 'Admin';
    user.save();
    notification.success("Role changed to: " + user.role);
}

Next, let's only show our current components if the current user's role is 'Normal' (except for the button to change roles). You can specify the show-if tags on individual components, or on a column and then all components within that column will be affected. For our show-if logic we need to determine if the user's role is equal to 'Normal' and the easiest way to do this is to create a function to do that for us. We will need to update the XML by adding some show-if tags, and then create a JS function that we can use to trigger the show-if attributes. So, update your main.view.xml like this.

<?xml version="1.0" encoding="UTF-8"?>
<view title="Punch List">
    <!-- Parameters go here: -->

    <!-- Variables go here: -->
    <var name="open_punches" type="query:item" />

    <!-- Components go here: -->
    <html src="html/blue-line.html" show-fullscreen-button="false" height="15px"/>

    <columns>
        <column show-if="$:isNormalUser()">
            <display-image src="images/deep-c-corp-main-logo.png" />
        </column>
        <column show-if="$:isNormalUser()">
            <display-image src="images/img-welcome-light.png" />
            <heading>Welcome to the Punch List App</heading>

            <info>A list of all the punches that have not yet been taken care of are shown below</info>

            <list empty-message="Your items will appear here">
                <list-item query="open_punches">
                    <header>{comments}</header>
                    <content>{category}</content>
                    <footer>{created_at}</footer>
                    <accent label="{status}" color="info" />
                    <asset src="{photo}" />
                    <action on-press="$:selectItem($selection)" />
                </list-item>
            </list>

            <button-group>
                <button label="Add New Item" icon="fa-plus" on-press="goToNew" validate="false" style="solid" />
                <button label="Browse by Category" on-press="$:navigate.link('browse_items')" icon="fa-filter" validate="false" style="solid" />
            </button-group>
        </column>
    </columns>

    <button label="Change Role" on-press="changeRole" icon="fa-cogs" validate="false" style="outline" />
</view>

And then create the JS function in your main.js file, like this:

function isNormalUser() {
    return user.role === 'Normal';
}

Next up, let's add the components for our Admin Role. We will want an <object-table> to display all the Punch Items, and not just the open ones so we will need a new variable. Let's start with that and a new <heading> to welcome our Admin users to our app. This time, we are going to use the View Designer to make our lives simple. In order to update our 'main' view in the designer we need to open it in the designer. So, access your command palette using ctrl+shift+p / cmd+shift+p (or shift+shift) then type designer and choose the action 'Open view in designer'. Then, when prompted for a view model, choose 'main'. Like this.

Once you have the main view open in the designer, scroll down until you can see the '+' icon at the very button of the designer beneath the button for 'Change Role'.

Use that button to first add a new <heading> component and then a new <object-table> component. When you add the <object-table> in this manner you will be prompted to select a Schema Model that you want to base the initial contents of the <object-table> on - in this case select the item model. Once done, your designer should look like this.

Now let's get back to our XML to see what has changed. To do this simply click on the 'Main' view in the left hand Views Panel - this will bring you back to the XML / JS files for your 'Main' view. Once there, you should see this.

<?xml version="1.0" encoding="UTF-8"?>
<view title="Punch List">
    <!-- Parameters go here: -->

    <!-- Variables go here: -->
    <var name="open_punches" type="query:item" />

    <!-- Components go here: -->
    <html src="html/blue-line.html" show-fullscreen-button="false" height="15px" />

    <columns>
        <column show-if="$:isNormalUser()">
            <display-image src="images/deep-c-corp-main-logo.png" />
        </column>
        <column show-if="$:isNormalUser()">
            <display-image src="images/img-welcome-light.png" />
            <heading show-if="true" hide-if="false">Welcome to the Punch List App</heading>

            <info>A list of all the punches that have not yet been taken care of are shown below</info>

            <list empty-message="Your items will appear here">
                <list-item query="open_punches">
                    <header>{comments}</header>
                    <content>{category}</content>
                    <footer>{created_at}</footer>
                    <accent label="{status}" color="info" />
                    <asset src="{photo}" />
                    <action on-press="$:selectItem($selection)" />
                </list-item>
            </list>

            <button-group>
                <button label="Add New Item" icon="fa-plus" on-press="goToNew" validate="false" style="solid" show-if="true" hide-if="false" />
                <button label="Browse by Category" on-press="$:navigate.link('browse_items')" icon="fa-filter" validate="false" style="solid" show-if="true" hide-if="false" />
            </button-group>
        </column>
    </columns>

    <button label="Change Role" on-press="changeRole" icon="fa-cogs" validate="false" style="outline" show-if="true" hide-if="false" />
    <heading show-if="true" hide-if="false">Heading</heading>
    <object-table query="open_punches">
        <column heading="Comments">{comments}</column>
        <column heading="Photo">{photo}</column>
        <column heading="Status">{status}</column>
        <column heading="Gps location">{gps_location}</column>
        <column heading="Created at">{created_at}</column>
    </object-table>

</view>

You will see the code for the <heading> and <object-table> components have been added for us, now we just have to polish it. First we need to use a better heading to welcome out Admins, secondly we need to define and use a different variable for all our punches, not just the open ones, and then we also need to move the Change Role button to below our new <object-table>. Like this

<?xml version="1.0" encoding="UTF-8"?>
<view title="Punch List">
    <!-- Parameters go here: -->

    <!-- Variables go here: -->
    <var name="open_punches" type="query:item" />
    <var name="all_punches" type="query:item" />

    <!-- Components go here: -->
    <html src="html/blue-line.html" show-fullscreen-button="false" height="15px" />

    <columns>
        <column show-if="$:isNormalUser()">
            <display-image src="images/deep-c-corp-main-logo.png" />
        </column>
        <column show-if="$:isNormalUser()">
            <display-image src="images/img-welcome-light.png" />
            <heading show-if="true" hide-if="false">Welcome to the Punch List App</heading>

            <info>A list of all the punches that have not yet been taken care of are shown below</info>

            <list empty-message="Your items will appear here">
                <list-item query="open_punches">
                    <header>{comments}</header>
                    <content>{category}</content>
                    <footer>{created_at}</footer>
                    <accent label="{status}" color="info" />
                    <asset src="{photo}" />
                    <action on-press="$:selectItem($selection)" />
                </list-item>
            </list>

            <button-group>
                <button label="Add New Item" icon="fa-plus" on-press="goToNew" validate="false" style="solid" show-if="true" hide-if="false" />
                <button label="Browse by Category" on-press="$:navigate.link('browse_items')" icon="fa-filter" validate="false" style="solid" show-if="true" hide-if="false" />
            </button-group>
        </column>
    </columns>

    <heading show-if="true" hide-if="false">Welcome App Admin</heading>
    <object-table query="all_punches">
        <column heading="Comments">{comments}</column>
        <column heading="Photo">{photo}</column>
        <column heading="Status">{status}</column>
        <column heading="Gps location">{gps_location}</column>
        <column heading="Created at">{created_at}</column>
    </object-table>

    <button label="Change Role" on-press="changeRole" icon="fa-cogs" validate="false" style="outline" show-if="true" hide-if="false" />
</view>

Next up we need to specify the show-if logic for our new components to only show for Admin users, similar to how we did it before (again, leave the Change Role button alone). Like this:

<heading show-if="$:isAdminUser()">Welcome App Admin</heading>
<object-table show-if="$:isAdminUser()" query="all_punches">
    <column heading="Comments">{comments}</column>
    <column heading="Photo">{photo}</column>
    <column heading="Status">{status}</column>
    <column heading="Gps location">{gps_location}</column>
    <column heading="Created at">{created_at}</column>
</object-table>

<button label="Change Role" on-press="changeRole" icon="fa-cogs" validate="false" style="outline" show-if="true" hide-if="false" />

Now, let's add the new JS function for isAdminUser, like this.

function isAdminUser() {
    return user.role === 'Admin';
}

And we also need to populate our new variable, we can also do that in the init function. Like this:

function init() {
    // initialize any data here that should be available when the view is shown
    view.open_punches = DB.item.where('status = ?', 'Open');
    view.all_punches = DB.item.all().orderBy("-created_at");
}

Test on Device

Let's see these changes in action. Remember, we have just made some major changes to our 'Main' view and so it may be necessary for you to close your app completely and open it again for these changes to take affect. Once updated, your app should look and work like this.

Give Admin Power (ADVANCED)

Next up we are going to give our admin some power by changing that <object-table> into an editable <object-table>. As mentioned before, object-table is extremely powerful and there is a lot that you can do with them. Unfortunately most of that we won't be covering in this tutorial, and as a result we will not be able to explain everything that is going to happen in the next section, but we wanted to give you a sneak peak of the power of object-table with the following code. Please read the reference documentation for more information or contact your Customer Success Manager for more information about the <object-table> Showcase Template App.

In short, we are going to add some additional columns to our object-table to give the data more context (for example a column for the user who captured the punch item and a column for the category of the item), we are also going to allow the admin to update the comments in-line, change the status in-line and change the created_at timestamp in-line. Finally we will allow Admin's to permanently delete 'closed' items and export the contents of the table to CSV. This will be done using a combination of XML and JS.

So, in your main.xml update the <object-table> to the following.

main.xml
<object-table show-if="$:isAdminUser()" query="all_punches">
    <column heading="Comments" display="{comments}">
        <edit-text value="$object.comments" on-change="$:handleEdit($object, newValue, oldValue, 'comments')" />
    </column>
    <column filter="true" heading="Status" display="{status}">
        <edit-select value="$object.status" options="$:listStatuses()" on-change="$:handleEdit($object, newValue, oldValue, 'status')" />
    </column>
    <column filter="true" heading="Created By User">{created_by.name}</column>
    <column filter="true" heading="Category">{category}</column>
    <column heading="Gps location">{gps_location}</column>
    <column heading="Created at" display="{created_at}">
        <edit-datetime value="$object.created_at" on-change="$:handleEdit($object, newValue, oldValue, 'created_at')" />
    </column>

    <column heading="" display="" icon="{$:getDeleteIcon($object)}" style-icon-color="warning">
        <action on-press="$:deleteFromTable($object)" />
    </column>

    <button-group>
        <button label="Export to CSV" on-press="$:exportCSV($filteredData, filteredDisplayData, controls, 'punch_items.csv')" icon="fa-download" validate="false" />
    </button-group>
</object-table>

Then, add the following JS functions to your main.js file

main.js
function handleEdit(object, newValue, oldValue, field) {
    object[field] = newValue;
    object.save();
}

function listStatuses() {
    var statuses = ["Open", "Closed"];
    return statuses;
}

function exportCSV(filteredData, filteredDisplayData, controls, filename) {
    var data = CSV.stringify(filteredDisplayData);
    saveFile(data, filename);
}

function getDeleteIcon(item) {
    return item.status === 'Closed' ? 'fa-trash' : '';
}

function deleteFromTable(item) {
    if (item.status === 'Closed') {
        deleteItem(item);
    } else {
        return;
    }
}

Done - Really this Time

Ok, that should do it. Of course there is a lot more we can do, and hopefully you are already thinking about all the additional bits of functionality that can and needs to be added to this app to make it truly great. But for now, we have done enough. Let's stop the scope creep and rather touch on some of the other fundamentals of JourneyApps that we haven't worked with yet.

Last updated