Adding pages to NavigationPane as an object

by DeanLogic

As part of creating the Marketing application for work, I had to use a NavigationPane in Cascades.  The idea behind the NavigationPane is to stack Pages on top of Pages in order to navigate forward and backward through the pages.  In the application I am building, there are a bunch of questions and a comment section for the user to answer.  Each of the question pages uses a ListView to display a list of choices based on an xml page.

<model>
	<questionHeader title="Purchasing Timeframe?">
		<questionItem title="No Plans" code="NOPLANS" />
		<questionItem title="Up to 6 Months" code="6MON" />
		<questionItem title="7 to 12 Months" code="7-12MON"  />
		<questionItem title="Long Term Prospect" code="LONGTERM"  />
		<questionItem title="Unknown" code="UNKNWN"  />
	</questionHeader>
</model>

This was pretty straight forward when using the StandardListItem as the component. I had to make sure that the title for the component did use the appropriate xml element of title. I didn’t want to send the entire descriptive title to the database, so I created a code for each item that is sent, while the title is displayed.  Also, there is a check to see if any of the items have been selected, to make sure the user makes a choice before given the option to go to the next Page. This was done by adding a Trigger to toggle the enabled feature of the button.

(Syntax Highlighter does not have an option for Qt. I’m using JavaScript as the type, so the code might look a little odd)

ListView {
        id: lvQuestion1
        rootIndexPath: [0]
        dataModel: XmlDataModel {
            id: model
            source: "xml/questions.xml"  }
        multiSelectHandler.active: false

        listItemComponents: [
            ListItemComponent {
                type: "questionItem"
                StandardListItem {
                    title: ListItemData.title
                }
            }
        ]

        onTriggered: {
            clearSelection()
            toggleSelection(indexPath)
            if(aiNextButton.enabled == false) { aiNextButton.enabled = true }
        }
    }

The last part of the Page is to add the button action to push the next page. The next button is added to the Page actions area, with the next Page being defined in the
attached objects for the page. The code calls a C++ function that checks to see if the Lead ID and Question Number has already been entered. It then calls either the insert or update based on that check. The update/add uses both the descriptive title and the code for the answer, but only the code is sent in the web service.

actions: ActionItem {
	id: aiNextButton
	enabled: false
	title: qsTr("Question 2") + Retranslate.onLocaleOrLanguageChanged
	ActionBar.placement: ActionBarPlacement.OnBar

		onTriggered: {
			// Check to see if answered before, add it not, update if
			currentLeadID = _app.getLastLeadID();
			var selectedItem = model.data(lvQuestion1.selected());

			if(_app.answerValueExists(currentLeadID, questionNumber)) {
				_app.updateAnswersRecord(currentLeadID, questionNumber, selectedItem.code, selectedItem.title)
			} else {
				_app.createAnswersRecord(currentLeadID, questionNumber, selectedItem.code, selectedItem.title);
			}

			// A second Page is created and pushed when this action is triggered.
			navigationPane.push(decisionMakerDefinition.createObject());
		}
	imageSource: "asset:///icons/ic_next.png"
}

attachedObjects: [
	// Definition of the second Page, used to dynamically create the Page above.
	ComponentDefinition {
		id: decisionMakerDefinition
		//source: "Question2.qml"
		Question2 {

		}
	}
]

When the ComponentDefintion is set, instead of using a source, I just add the Page as an object. If the Page name is uppercase, then it will show up in the IDE.  By doing this, the IDE also connects the used Page with the NavigationPane and prevents errors and warnings from showing up during compiling the application.

I assume the reason to use source for the definition is to be able to change it dynamically.  I haven’t tried creating the ComponentDefinition dynamically, but my guess is the best option is to create all the possible definitions to be called and then just change the trigger or push action dynamically.

The main page that would hold the NavigationPane uses the same method of calling the next Page. In the code, I have all the form fields, but I am guessing, I could create a Page just for the form fields and call it as an object as well. But, to simplify things I added all the form fields with in the Page section.

import bb.cascades 1.2
import bb.system 1.0

NavigationPane {
    id: navigationPane

    onCreationCompleted: {
        _app.setLastLeadID(0);
    }

    Page {
        id: leadsInformation
        titleBar: TitleBar {
            // Localized text with the dynamic translation and locale updates support
            title: qsTr("Lead Information") + Retranslate.onLocaleOrLanguageChanged
            appearance: TitleBarAppearance.Branded;
        }

        ScrollView {
            scrollViewProperties.scrollMode: ScrollMode.Vertical
            Container {
                layoutProperties: FlowListLayoutProperties {}
                clipContentToBounds: false

                ...form items
			}
        }
        actions: [
            ActionItem {
                id: aiNextButton
                enabled: false
                title: qsTr("Purchasing Timeframe") + Retranslate.onLocaleOrLanguageChanged
                ActionBar.placement: ActionBarPlacement.OnBar
                imageSource: "asset:///icons/ic_next.png"

                onTriggered: {
                          // Create new Sales Leads
                        _app.createLeadRecord(...);
                        console.debug("New Sales Lead - Create")
                    } else {
                        // Update current Sales Lead
                        _app.updateLeadRecord(....);
                            console.debug("Existing Sales Lead - Update Sales Lead ID: " + currentLeadID)
                    }
                    _app.readLeadRecords(); // Refresh the list view.
                    navigationPane.push(question1frameDefinition.createObject());
                }
            }
        ]

        attachedObjects: [
            // Definition of the second Page, used to dynamically create the Page above.
            ComponentDefinition {
                id: question1frameDefinition
		//source: "Question1.qml"
                Question1 {

                }

            }
        ]
    }

    onPopTransitionEnded: {
        // Destroy the popped Page once the back transition has ended.
        page.destroy();
    }

    backButtonsVisible: false

}

Another important part of the NavigationPane is to include the page.destroy() call when a Pop happens. This clears out the Pages loaded when they are popped back. And to make sure you pop back all of your pages, then you need to add this code on the final trigger.

var poppgs = navigationPane.count() - 1;
for(pgs = 0; pgs < poppgs; pgs++) {
   navigationPane.pop();
}

About DeanLogic
Dean has been playing around with programming ever since his family got an IBM PC back in the early 80's. Things have changed since BASICA and Dean has dabbled in HTML, JavaScript, Action Script, Flex, Flash, PHP, C#, C++, J2ME and SQL. On this site Dean likes to share his adventures in coding. And since programming isn't enough of a time killer, Dean has also picked up the hobby of short film creation.

Leave a Reply