Create the quote page

At this point, we've created the main UI of our app, along with the page that lets users add a new quote. Now, we're going to implement the custom QML component that actually displays a quote and its author.

Create the QuotePage.qml file and add controls

In the assets folder of your project, create a folder called QuotePage. This subfolder will contain several custom components, which together form the quote page. Inside the QuotePage folder, create a .qml file named QuotePage.qml. This file represents the main screen of the quote page.

We start with a Page as the root control of the component, and add a Container that uses a DockLayout to the page.

Screen showing the quote page of the Quotes app.

We use the background property to set the background of the container to an image asset, which is located in the assets/images folder. It's a good practice to define backgrounds by using an ImagePaintDefinition, which loads the image asset efficiently, so we use that approach here. To learn more about images in Cascades, see Images.

import bb.cascades 1.0

Page {
    Container {
        background: backgroundPaint.imagePaint
        
        attachedObjects: [
            ImagePaintDefinition {
                id: backgroundPaint
                imageSource: "asset:///images/background.png"
            }
        ]

        layout: DockLayout {
        }

The only control inside our container is another custom QML component, QuoteBubble. This component represents the quote bubble and author name. We'll define this component shortly.

    QuoteBubble {
        id: quoteBubble
        horizontalAlignment: HorizontalAlignment.Center
        verticalAlignment: VerticalAlignment.Center
    }
} // end of Container

Customize the back button

Screen showing the custom back button.

For a bit of customization, we change the default back button of this page to use the text "Names" instead. When a user taps this back button, we pop the page off of the main NavigationPane stack to return to the list of quote authors.

We also set the value of the editMode property. This property is a custom property in QuoteBubble and indicates whether edit mode is enabled or not. In edit mode, the user can change the quote text, author first name, and author last name. If edit mode was enabled when the user taps the back button, we want to disable edit mode before returning to the list of quote authors.

paneProperties: NavigationPaneProperties {
    backButton: ActionItem {
        title: "Names"
        onTriggered: {
            nav.pop();
            quoteBubble.editMode = false;
        }
    }
}

Add actions and attached objects

This page includes two actions: an Edit action and a Delete action. The Edit action uses a custom icon and is placed on the action bar. When a user taps Edit, edit mode is enabled. The Delete action uses the predefined DeleteActionItem class and is placed in the action menu. When a user taps this action, we delete the current quote by calling the C++ function deleteRecord(), which we'll define later. Edit mode is also disabled.

actions: [
    ActionItem {            
        title: "Edit"
        imageSource: "asset:///images/Edit.png"
        ActionBar.placement: ActionBarPlacement.OnBar
            
        onTriggered: {
            quoteBubble.editMode = true
        }
    },
    DeleteActionItem {
        objectName: "DeleteAction"
        title: "Delete"
            
        onTriggered: {
            _quoteApp.deleteRecord();
            quoteBubble.editMode = false
        }
    }
]

Lastly, we make a small addition to the attachedObjects list of our page. This page in our app uses a slightly different visual theme than the main page, so we create a TextStyleDefinition that defines a reusable text style. We'll use this text style in the next custom components that we create.

    attachedObjects: [
        TextStyleDefinition {
            id: quoteStyleLightBody
            base: SystemDefaults.TextStyles.BodyText
            color: Color.create("#fafafa")
        }
    ]
} // end of Page

Create the QuoteBubble.qml file and a custom property

The next step is to create the QuoteBubble component. As mentioned above, this component displays the quote text (inside its bubble image) and author name. In the assets/QuotePage folder in your project, create a .qml file called QuoteBubble.qml. This component uses a Container at its root, and includes the custom property editMode and some padding properties.

We've already mentioned editMode, and it's an important part of this component. Custom properties can be very powerful, letting you change many different aspects of your app at the same time. Throughout the QuoteBubble code below, we bind the editMode property to several other properties. When editMode changes, these other properties change as well. Take note of where editMode is used and how it simplifies our code.

import bb.cascades 1.0

Container {
    id: quoteBubble
    property bool editMode: false
    
    topPadding: 30
    bottomPadding: topPadding
    rightPadding: topPadding
    leftPadding: topPadding

Add a custom component and define signal handlers

When edit mode is enabled (that is, editMode is true), two additional buttons appear at the top of the quote area. These buttons, Cancel and Update, are included in another custom component called EditControls. We add an EditControls component to our container, and bind its visible property to the editMode property. This means that the EditControls component is visible only when edit mode is enabled.

Diagram showing edit mode.
EditControls {
    id: editControls
    visible: quoteBubble.editMode

The EditControls component contains two custom signals, cancel() and update(), which are emitted when a user taps Cancel and Update, respectively. When the user taps Cancel, we want to revert the quote and author fields back to the data that's stored in the contentView property. Remember that the contentView property includes the full data for the selected quote from the data model. When the user taps Update, we want to update the data model with the values that the user typed in the fields. To update the values, we use the C++ function updateSelectedRecord(), which we'll define later. Also, regardless of which button the user taps, we disable edit mode.

    onCancel: {
        longText.text = contentView.quote;
        _quoteApp.updateSelectedRecord(contentView.firstname,
                                       contentView.lastname,
                                       contentView.quote);
        quoteBubble.editMode = false;
    }
        
    onUpdate: {
        _quoteApp.updateSelectedRecord(editName.firstName,
                                       editName.lastName,
                                       longText.text);
        quoteBubble.editMode = false;
    }
} // end of EditControls

Add controls for the quote

Next, we add a Container to hold the actual quote. This container uses a DockLayout, and we add an ImageView that represents the bubble image behind the quote text. This image scales automatically depending on the length of the quote, so we use a nine-sliced image to ensure that only the white area in the middle of the image is scaled (not the quotation marks).

Screen showing the quote bubble.

The quote text is placed in a TextArea inside a Container, which lets us add padding around the text. We bind the editable property of the TextArea to our editMode property, and we populate the text using the quote field from our data model.

Container {
    horizontalAlignment: HorizontalAlignment.Center
        
    layout: DockLayout {
    }

    ImageView {
        imageSource: "asset:///images/border_bubble.amd"
        verticalAlignment: VerticalAlignment.Fill
        horizontalAlignment: HorizontalAlignment.Fill
    }

    Container {
        topPadding: 54
        bottomPadding: 85
        rightPadding: 30
        leftPadding: rightPadding
            
        TextArea {
            id: longText
            preferredWidth: 520
            editable: quoteBubble.editMode

            text: contentView.quote                
        }
    } // end of text area Container
} // end of quote Container

Our QuoteBubble is almost finished. We just need to add a Container with fields that represent the quote author. When edit mode is disabled, the author's name appears as a simple Label. This time, we bind the visible property to the inverse of the editMode property, meaning that the Label is visible only when edit mode is disabled. We also use the text style that we defined at the end of our custom QuotePage component.

Container {
    topPadding: 15
        
    layout: DockLayout {
    }
                
    Label {
       id: nameLabel
       visible: ! quoteBubble.editMode


       text: contentView.firstname + " " + contentView.lastname
       textStyle.base: quoteStyleLightBody.style
    }

When edit mode is enabled, we need to change the appearance of the author's name to use editable text fields instead of a Label. To do this, we use another custom component called EditName. Again, we bind the visible property of EditName to editMode. The EditName component includes a custom signal called enableSave(), which indicates whether the Update button in our EditControls should be enabled or not. Depending on the values of the author name fields, we might not want to allow the updated quote to be saved; you'll see why when we define the EditName component a bit later on. For now, we handle this signal by using the onEnableSave signal handler.

        EditName {
            id: editName
            visible: quoteBubble.editMode
            onEnableSave: {
                if (enable) {
                    editControls.updateEnabled = true;
                    longText.enabled = true;
                } else {
                    editControls.updateEnabled = false;
                    longText.enabled = false;
                }
            }
        }
    } // end of quote author Container
} // end of actual quote Container

Define the EditControls custom component

The final task in creating our quote page is to define the supporting components, EditControls and EditName, that we used above. In the assets/QuotePage folder in your project, create a .qml file called EditControls.qml.

The EditControls component consists of a Cancel button, a Label with the text "Edit", and an Update button.

Screen showing the EditControls component.

This component includes a Boolean property called updateEnabled, which indicates whether the Update button should be enabled. This component also includes the custom signals update() and cancel(), and uses a left-to-right stack layout.

import bb.cascades 1.0

Container {
    id: editControls
    property bool updateEnabled: false
    signal update()
    signal cancel()
    bottomPadding: 40
    
    layout: StackLayout {
        orientation: LayoutOrientation.LeftToRight
    }

Let's add the Cancel button to the component. By using a space quota of 1, we ensure that this button receives the same amount of horizontal space in the layout as the Edit label and Update button. When a user taps this button, our component emits the cancel() signal.

Button {
    text: "Cancel"
        
    layoutProperties: StackLayoutProperties {
        spaceQuota: 1
    }
        
    onClicked: {
        editControls.cancel();
    }
}

Next, we add the Edit label. This label uses the same text style as the other components on our quote page.

Label {
    id: editLabel
    text: "Edit"
    horizontalAlignment: HorizontalAlignment.Center
    verticalAlignment: VerticalAlignment.Center

    layoutProperties: StackLayoutProperties {
        spaceQuota: 1
    }

    textStyle.base: quoteStyleLightBody.style
}

Lastly, we add the Update button. We bind its enabled property to the updateEnabled property, and when a user taps this button, our component emits the update() signal.

    Button {
        id: updateButton
        text: "Update"
        enabled: updateEnabled
        
        layoutProperties: StackLayoutProperties {
            spaceQuota: 1
        }
        
        onClicked: {
            editControls.update();
        }
    }
} // end of Container

Define the EditName custom component

Let's move on to our final custom component, EditName. In the assets/QuotePage folder of your project, create a .qml file called EditName.qml.

This component represents an editable version of the author name, which is achieved by using TextField controls for both the first name and last name.

Screen showing the editable version of the author name fields.

We define alias properties called firstName and lastName and bind these properties to the text property of each TextField. This approach lets us access these properties outside of the EditName component. We also include the custom signal enableSave().

import bb.cascades 1.0

Container {
    id: editName
    property alias firstName: firstNameField.text
    property alias lastName: lastNameField.text
    signal enableSave(bool enable)
    
    layout: StackLayout {
        orientation: LayoutOrientation.LeftToRight
    }

We add a TextField that represents the author's first name, and populate its text property with the firstname field from our data model.

TextField {
    id: firstNameField
    hintText: "First name"

    layoutProperties: StackLayoutProperties {
        spaceQuota: 1
    }

    text: _contentView.firstname
}

Now, we add a TextField that represents the author's last name. If you recall, we mentioned above that, depending on the values of the author name fields, we might not want to allow an updated quote to be saved. In our data model, we use the author's last name as the primary key. In other words, every quote entry in the data model must include a non-empty value in the lastname field. So, when a user types in this TextField, we check to see whether the length of the text is greater than zero. If so, we have a valid primary key and we allow the quote to be saved. If not, we disable the Update button in our EditControls component until the user types a valid last name.

    TextField {
        id: lastNameField
        hintText: "Last name"        
        text: contentView.lastname
                
        layoutProperties: StackLayoutProperties {
            spaceQuota: 1
        }
        
        onTextChanging: {
            if (text.length > 0) {
                editName.enableSave(true);
                firstNameField.enabled = true;
            } else {
                editName.enableSave(false);
                firstNameField.enabled = false;
            }
        }
    }
} // end of Container

At this point, we've finished all of the UI components that our app uses. In the next sections, we'll use C++ to implement the supporting classes and functions that our app needs.

Last modified: 2014-06-24



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus