LogoLogo
What's NewDeveloper CommunitySupportStatus
  • 🚀Get Started
    • What is JourneyApps Platform?
    • Tutorial: Build your First App
      • 1. Introduction
      • 2. Create a new App
      • 3. OXIDE IDE Overview
      • 4. Hello World app
      • 5. The Data Model
      • 6. View Components
      • 7. Queries and Data Sync
      • 8. Simple Navigation
      • 9. View Stack
      • 10. Input Validation
      • 11. View Parameters
      • 12. Data Manipulation
      • 13. Responsive Apps
      • 14. Styling
      • 15. Lists
      • 16. GPS Capturing
      • 17. Relationships
      • 18. Multiple User Roles
      • 19. Deployment and Users
      • 20. Version Control
      • 21. CSV and APIs
      • 22. Conclusion
    • JourneyApps Platform Fundamentals
      • Creating a New App
        • Git-enabled Apps
      • What are Views?
      • What is the Data Model?
      • JourneyApps Syntax Basics
      • Access the Database (DB)
        • Manipulate DB Objects
        • Query DB Objects
      • View Navigation
        • Deep Linking
      • CloudCode Overview
      • OXIDE (Online IDE)
  • 💻Build your App
    • JourneyApps Syntax
      • Syntax Basics
      • Access the DB
      • View Navigation
      • Async & Await
      • TypeScript Apps (Beta)
        • runtime-build package
        • TypeScript App Troubleshooting
      • What's New in V4
        • Updating to the V4 API
    • Configure your Data Model
      • What is the data model?
      • Reference: model
        • field
        • belongs-to
        • has-many
        • index
      • Data Rules
        • Data Buckets
        • Sync Rules - Limit data synced to devices
        • Data ACLs - Limit access to data
        • Real-world example for Data Rules
        • ❔FAQs
        • Migrate to Data Rules
      • App Indexes
      • Webhooks
    • UI Components
      • All UI Components
        • actionSheet
        • Attachments
        • button
        • button-group
        • capture-coordinates
          • marker
          • marker-query
        • capture-file
        • capture-photo
        • capture-signature
        • card
          • accent
          • action
        • columns
          • column
        • component
        • context-menu
          • divider
          • item
        • CSV
        • date-input
        • datetime-input
        • dialog
          • body
        • display-3d-model
          • 📖display-3d-model Guides
            • Guide 1: Initialize and layout a 3D model in a view
            • Guide 2: Control playback position
            • Guide 3: Troubleshooting controls
        • display-coordinates
        • display-file
        • display-image
        • display-photo
        • display-signature
        • heading
        • html
          • HTML Advanced Topics
          • ❔HTML FAQs
          • 📖Guide: HTML & JourneyApps iFrame Client
        • icons
        • info
        • info-table
          • row
        • journey.photos (capture multiple photos)
        • JourneyPrinter (print PDFs)
        • grid
          • cell
          • 📖grid Examples
        • list
          • list-item
            • accent
            • asset
            • pills
              • pill
            • action
        • multiple-choice-checklist
        • navigation (Navigation drawer)
          • general-section
            • item
          • section
            • item
              • item
          • ❔navigation FAQs
        • notification
        • object-dropdown
        • object-list
          • action
        • object-repeat
        • object-table
          • action
          • column
            • action
            • edit-boolean
            • edit-date
            • edit-datetime
            • edit-integer
            • edit-number
            • edit-select
            • edit-text
            • edit-time
            • edit-typeahead
              • action
            • header-action
          • column-group
          • empty-action
          • 📖object-table Guides
            • Actions
            • Cell callouts
            • Column groups
            • Columns
            • Controlled object-table
            • Controls
            • Copy & paste data
            • Edit cells
            • Filters
            • Frozen columns
            • Fullscreen object-table
            • Mode
            • State
            • Styles
        • optionList
        • PhotonSync (transfer data offline)
        • power-bi
          • 📖Guide: PowerBI Embedding
        • scan-barcode
        • shortcut
        • sidebar
        • single-choice-dropdown
        • single-choice-radio
        • template
        • text-input
        • time-input
        • toggle
        • view
      • JS/TS Events
      • Show / Hide UI Components
      • View Templates
      • XML Fields (Attributes)
        • align-content
        • align-controls
        • align-label
        • bind
        • clear-button-visibility
        • control-order
        • disabled
        • error-message
        • icon-position
        • id
        • hide-if
        • modifier-text
        • label
        • label-case
        • label-color
        • on-change
        • on-press
        • placeholder
        • required
        • show-if
    • JS / TS APIs
      • Attachment
      • Bluetooth (Beta)
      • Broadcast
      • component
      • CSV
      • DB
      • HardwareBarcode
      • journey
        • journey.config
        • journey.container
        • journey.device
        • journey.diagnostics
        • journey.dialog
        • journey.files
        • journey.hardware
        • journey.photos
        • journey.runtime
        • journey.sensors
        • journey.viewStack
      • JourneyPrinter
      • KeyboardBarcode
      • LocalDB
      • NFC
      • OnlineDB
      • PhotonSync
      • SerialPort
      • ShortcutManager
      • TCPSocket
      • user
    • Extend your App with Custom Code
      • App packages
        • App packages overview
        • PDF report package
        • TypeScript library & unit tests
        • Manage External Dependencies
      • Custom HTML
    • Style & Customize your App
      • Style & configure UI components
        • Overview
        • Understand extendable themes
        • Use themes on a view
        • Theme specific components on a view
        • Examples
        • Debugging
        • ❔FAQs
      • Change your App Font
      • Custom Branding
        • Custom Container Features
        • Special Requirements for iOS Containers
    • Integrate your App
      • Backend integrations with CloudCode
      • Barcode Scanning
        • Barcode Scanning using Keyboard Emulation
        • Hardware Barcode Scanning
        • scan-barcode
      • Bluetooth Low Energy (BLE)
      • Broadcast API
      • HTTP requests (Fetch API)
      • JourneyApps Print (Android)
      • Maps and navigation
      • NFC
      • Opening external links/apps
      • Serial Port
      • TCP Sockets
    • Design Intuitive Apps
      • UX Guidelines
      • Write Effective Copy
  • 📱App Features
    • RealWear® Voice Control
      • Automatic Voice Commands
        • Automatic Voice Commands - Advanced
      • Manual Voice Commands
    • App, Runtime and Container Updates
    • Batch Operations (App)
    • Call JS/TS Functions from XML
    • Capture GPS Locations
    • Push Notifications
    • Translations
    • XML Format Strings
    • Webhooks (External)
  • 🌐CloudCode
    • CloudCode Overview
    • Trigger a CloudCode Task
      • Trigger CC with a Schedule
      • Trigger CC via a Webhook
      • Trigger CC from an App
      • Trigger CC from Another Task
      • Trigger CC via HTTP
    • Attachments in CloudCode
    • Timezones
    • Advanced CloudCode Topics
      • Access Multiple DBs in CloudCode Tasks
      • Batch API (CloudCode)
      • CloudCode Dependencies
      • Configure HTTPS in CloudCode
      • Deployment environment variables
      • Local CloudCode Development
      • PDF Reports using CloudCode
      • Shared CloudCode Tasks
      • Translations in CloudCode
  • 📥Backend API
    • Introduction
    • API Reference
      • Retrieve All Objects
      • Query Objects
      • Sort Results
      • Limit and Skip
      • Count Objects
      • Create a New Object
      • Retrieve a Single Object
      • Update a Single Object
      • Delete a Single Object
      • Batch Operations (v4 API)
      • Oplog API
      • Retrieve the App Data Model
      • Manage App Users and Sessions
      • Field Representation
      • Error Responses
    • API Limits
    • Update to the V4 API
  • ⚙️Technical
    • Data Synchronization Priority
    • Device Diagnostics
    • JSON1 Query Engine
    • Improve App Performance
    • Security Measures
    • Supported Platforms
      • Web Container
      • Windows Installer
    • Domain Whitelist
  • 🖥️OXIDE
    • Get started with OXIDE
      • OXIDE Overview
      • Components of OXIDE
    • Configure Testing Deployments
    • Edit and Manage Files
      • How to Navigate to a Function
      • Manage External Dependencies
    • Create and Manage App Containers
    • Debugging & Troubleshooting
      • Common Troubleshooting Pointers
      • App Diagnostics Reports
      • Build Logs
    • OXIDE Workspaces
      • OXIDE Trees
  • ❕Deprecated Features
    • Deprecated Features and Components
Powered by GitBook
On this page
  • Punch List App: Next Steps
  • Relationships
  • Add a New Object Type
  • Add a New Relationship
  • See Changes in Data Browser
  • Set Category on "Add New Item" Screen
  • Test on Mobile Device
  • View Relationship on App Backend Data Browser
  • Display Item Category on "Main" and "View Punch Item"
  • Test on Device
  • Enable Filtering by Category
  • Test on Device
  1. Get Started
  2. Tutorial: Build your First App

17. Relationships

Punch List App: Next Steps

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

App Design: Construction Punch List

When more and more punches are captured in the punch list app, it will become problematic to browse all the punches in a single list. We want to allow users to organize punches into categories, and then to filter the lists of punches by category.

Relationships

Therefore, we want to make it possible for categories to be created in our database, and then each punch list item must be assigned to a particular category. In order to implement this functionality, we'll make use of Relationships in our Data Model. Relationships allow us to define how our different object types relate to each other. If we create a new Category object type, we'll say that each category has many punch list items that belong to that category. This is called a one-to-many relationship.

Add a New Object Type

Let's head over to the Data Model workspace and update the schema.xml to include a new Category model. This model will only have one field for now, a field that will store the 'name' of the category - we will also use this field as the default display value. Like this.

<model name="category" label="Category">
    <field name="name" label="Name" type="text" />
    
    <display>{name}</display>
</model>

Add a New Relationship

Next, we'll create the one-to-many relationship between the two object types. This is done by adding a belongs-to tag to the Item object type, and a has-many to the Category object type as shown below. Relationship tags should be listed after all the fields in a model but before the <display> tag. Note that the name="" property on the has-many allows you to specify a name for the whole collection of items that belong to a category. Using the pluralized form of the object type (in this case, "items") is a good convention to use for the collection name. On the belongs-to side you can also specify a name="" for the relationship, but if you don't the system will default the name to the name of the associated model - in our case category.

<model name="item" label="Item">
    <field name="comments" label="Comments" type="text" />
    <field name="photo" label="Photo" type="photo" />
    <field name="status" label="Status" type="single-choice">
        <option key="Open">Open</option>
        <option key="Closed">Closed</option>
    </field>
    <field name="gps_location" label="Gps Location" type="location" />
    <field name="created_at" label="Created At" type="datetime" />

    <belongs-to model="category" />
    <display>{status} - {comments}</display>
</model>

<model name="category" label="Category">
    <field name="name" label="Name" type="text" />
    
    <has-many model="item" name="items" />
    <display>{name}</display>
</model>

Your ERD should also visualize these changes, like this.

While we are at it, let's also add a relationship between user and item to signify which user created which punch items. To do this we add another belongs-to tag on our item model, this time pointing to the user model, and we add the has-many tag to the user model. For this belongs-to relationship let's not use the system default name, and instead specify it as name="created_by". At this point your schema.xml file should look like this.

schema.xml
<?xml version="1.0" encoding="UTF-8"?>
<data-model>
    <!-- Do not remove this - it is used to store information about your app's users: -->
    <model name="user" label="User">
        <field name="name" label="Name" type="text:name" />

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

    <!-- Used for Push Notifications - to send a push notification, create a 'push_notification' object -->
    <!--                               and specify the recipient in the 'belongs to user' relationship -->
    <!-- For more details, refer to: http://resources.journeyapps.com/v4/push-notifications -->
    <model name="push_notification" label="Push Notification">
        <field name="message" label="Message" type="text" />
        <field name="received_at" label="Received At" type="datetime" />
        <field name="created_at" label="Created At" type="datetime" />

        <belongs-to model="user" />
        <display>{message} ({user})</display>
        <notify-user message="{message}" received-field="received_at" recipient-field="user" />
    </model>

    <!-- ADD MODELS HERE: -->
    
    <model name="item" label="Item">
        <field name="comments" label="Comments" type="text" />
        <field name="photo" label="Photo" type="photo" />
        <field name="status" label="Status" type="single-choice">
            <option key="Open">Open</option>
            <option key="Closed">Closed</option>
        </field>
        <field name="gps_location" label="Gps Location" type="location" />
        <field name="created_at" label="Created At" type="datetime" />

        <belongs-to model="category" />
        <belongs-to model="user" name="created_by" />
        <display>{status} - {comments}</display>
    </model>
    
    <model name="category" label="Category">
        <field name="name" label="Name" type="text" />
        
        <has-many model="item" name="items" />
        <display>{name}</display>
    </model>
</data-model>

See Changes in Data Browser

If you go to the Data Browser for the "Testing" environment, you'll notice that there is a new Category object type shown on the left, and that clicking on an Item shows a Belongs to Category field and a Belongs to User - created_by field at the bottom (if your Data Browser was already open in a different tab then you will need to first refresh that browser tab before you will see the changes):

Click on the Category tab on the left and let's create a couple of categories:

Set Category on "Add New Item" Screen

Now let's add the category selection to the Add New Item view. Under the "Variables go here" section of the add_new.view.xml file, add a new variable called all_categories which will be of type query:category. Like this.

<!-- Variables go here: -->
<var name="item" type="item" />
<var name="all_categories" type="query:category" />
// This function is called when the app navigates to this view (using a link)
function init() {
    // initialize any data here that should be available when the view is shown
    view.all_categories = DB.category.all().orderBy("name");
    view.item = DB.item.create();
    view.item.status = "Open";
    view.item.created_at = new Date();
    view.item.created_by(user);
}

Finally, add a <object-dropdown> View Component, above the <text-input> component, allowing the user to select the category. Note that:

  • We specify query="all_categories" so that the list will show all the categories that we've pulled using our Query.

  • We specify bind="item.category" so that the category that the user selects will be stored in the category relationship on our item variable.

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

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

    <!-- Components go here: -->

    <columns>
        <column>
            <capture-photo bind="item.photo" required="true" />
        </column>
        <column>
            <object-dropdown query="all_categories" bind="item.category" label="Category" empty-message="Your items will appear here" required="false" />
            <text-input bind="item.comments" required="true" />
            <button label="Save Item" on-press="saveItem" validate="true" style="solid" />
        </column>
    </columns>
    <capture-coordinates bind="item.gps_location" />
</view>

Test on Mobile Device

At "Add New Item", you should now see a drop-down list to select a category:

View Relationship on App Backend Data Browser

After you've added a new item, if you open up the Data Browser for the "Testing" environment again, you'll see "Belongs to Category" for the item shows the category that you've selected. Go ahead and update the other punch items and make each of them belong-to a category as well. Like this:

Display Item Category on "Main" and "View Punch Item"

Let's update the list component on our Main view to display the category inside of the <content> tag, and while we are at it, let's also add the created_at timestamp in the <footer> tag. So, in your main.view.xml, update the list component like this.

<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>

If we want to show the category on the View punch Item view, we could adjust our table component there as follows:

<info-table>
    <row label="Item Comments" bind="item.comments" />
    <row label="Category" bind="item.category" />
    <row label="Item Status" bind="item.status" />
    <row label="Current Time" value="{$:showTime()}" />
</info-table>

Test on Device

Enable Filtering by Category

Lastly, we want the user to be able to browse punch items by category. We will do this in a new view called browse_items, but first let's add a button on our Main view to allow the user to navigate to the Browse Items view. So, on your main.view.xml, add a new button to the <button-group> below the 'Add New Item' button, and hook it up like this:

<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>

You will notice we have included the necessary JS navigation logic directly in the on-press attribute by using the $: notation to execute raw JS from our View XML.

Next, let's create a new View with the name browse_items (remember, you can create views using the command palette, ctrl+shift+p / cmd+shift+p to open the command palette and then start typing create and choose the Create view action), and then once created update the <view title="Browse Punch Items by Category"> as before.

Now we can add some components to our new view. Firstly, we are going to need to present the user with a list of all categories (we will need a query for this), let the user select a category (we will need a category variable for this), and then show the user all the punch items that belong to the selected category (we will need another query for this). So, in your newly created browse_items.view.xml specify all these variables, like this.

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

    <!-- Variables go here: -->
    <var name="all_categories" type="query:category" />
    <var name="selected_category" type="category" />
    <var name="filtered_items" type="query:item" />

    <!-- Components go here: -->

</view>
<!-- Components go here: -->
<object-list hide-if="selected_category" query="all_categories" bind="selected_category" label="Please select a category to view related punch items" empty-message="Your items will appear here" required="false" />

<heading show-if="selected_category">{selected_category.name}: Open Punch Items</heading>
<list show-if="selected_category" empty-message="Your items will appear here">
    <list-item query="filtered_items">
        <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>

Next we need to add a way for the user to clear their selection (we will use a button for that - using show-if to only display the button if the user has already made a selection). After that we also need to populate the various queries from our JS. Populating the all_categories variable can be done from the init() function, but we can only populate filtered_items once the user makes a selection, and we need to re-populate it every time the user makes a new selection. To do this we will make use of the on-change="" attribute of our <object-list> component. (Note, the on-change="" attribute is available on all input components, but note that for capture-photo and scan-barcode it is referenced as on-capture and on-scan instead). So, update your browse_items.view.xml to the following:

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

    <!-- Variables go here: -->
    <var name="all_categories" type="query:category" />
    <var name="selected_category" type="category" />
    <var name="filtered_items" type="query:item" />

    <!-- Components go here: -->
    <object-list on-change="filterCategories" hide-if="selected_category" query="all_categories" bind="selected_category" label="Please select a category to view related punch items" empty-message="Your items will appear here" required="false" />

    <heading show-if="selected_category">{selected_category.name}: Open Punch Items</heading>
    <list show-if="selected_category" empty-message="Your items will appear here">
        <list-item query="filtered_items">
            <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="Back" on-press="dismiss" icon="fa-arrow-left" validate="false" style="outline" />
        <button show-if="selected_category" label="Clear Selection" on-press="clearSelection" icon="fa-times" validate="false" style="solid" />
    </button-group>
</view>

Let's update our browse_items.js file next to populate our queries and handle the on-change event, as well as the clearSelection and selectItem function calls (let's reuse the selectItem functions from the main view). Like this

browse_items.js
// This function is called when the app navigates to this view (using a link)
function init() {
    // initialize any data here that should be available when the view is shown
    view.all_categories = DB.category.all().orderBy("name");
}

// This function is called when the user returns to this view from another view
function resume(from) {
    // from.back       (true/false) if true, the user pressed the "Back" button to return to this view
    // from.dismissed  (true/false) if true, the app dismissed to return to this view
    // from.path       contains the path of the view that the user returned from
    // if any data needs to be refreshed when the user returns to this view, you can do that here:
}

function filterCategories(selectedCategory) {
    if (selectedCategory) { // we add the null check to catch the situation where the user 'clears' their selection - which will trigger the on-change
        view.filtered_items = selectedCategory.items;
    }
}

function clearSelection() {
    view.selected_category = null;
}

function selectItem(selectedItem) {
    var userOptions = ["View Item Details", "Delete Selected Item"];
    var userSelection = optionList(userOptions);
    if (userSelection === 0) { // user selected 'View Item Details'
        viewItem(selectedItem);
    } else if (userSelection === 1) { // user selected 'Delete Selected Item'
        deleteItem(selectedItem);
    } else { // user cancelled or clicked away
        return; // exit the function pro-actively
    }
}

function viewItem(item) {
    navigate.link('view_item', item);
}

function deleteItem(item) {
    if (confirmDialog("Delete Item?", "Are you sure?", "YES", "NO")) {
        item.destroy();
    }
}

Test on Device

Let's test these changes. Your app should now look like this:

Well done, you have effectively completed the Punch List App for the scope we originally envisioned. A user can view open punches, capture new punches, view details for existing punches and mark them as closed. Next up we are going to add a new user role to our app, an App Admin, that should be able to easily manage all the data in the application.

Previous16. GPS CapturingNext18. Multiple User Roles

Last updated 2 years ago

Then change the init() function to populate the categories using a and also populate the relationship to the user model. To set the belongs-to relationship in JS we use a helper method that is created for us on the model in question, this method will use the value of name that is specified in the in the Data Model in the <belongs-to> tag. So in our case, we specified the relationship as <belongs-to model="user" name="created_by" /> and so we will use the created_by method to set the relationship to the user model, like this.

Now let's add the necessary view components. We want to display all the the categories to the user and then once they select one, show all the related punch items for the selected category. A nice way to do this is to make use of the JourneyApps show-if and hide-if logic available to any view component (syntax reference for show-if and hide-if available ). We will display an <object-list> for all the categories but once the user selects one we will hide the <object-list> and display a <list> of related items instead (re-using the list we defined on our main view). So, in your browse_items.view.xml, add the following code:

🚀
Query
here