Monday, April 15, 2013

Easy Task Navigation With PageStack (Fixed)


Thanks to an update to the documentation for Ubuntu Components, I discovered that I was using PageStack all wrong. I've gone ahead and deleted my old blog post about PageStack, and now here is a corrected one.

My Feedzilla app has a very simply page by page structure. Each bit of UI that the user interacts with is a wholly separate task so can take over the whole screen to present to the user.

Turns out, that Ubuntu Components have a component to support this in a standard, and very convenient, manner. It's called PageStack.

Here's how it works.

First, I created a PageStack component, and named it "rootStack". The categories view is always the starting point of the app, so I made it a Page inside the PageStack, and named it "rootPage". Then I added SubCategoriesPage (which is a ListView that shows the list of Technology sub-categories from Feedzilla:

It looks like this.
    PageStack
    {
        id: rootStack
        Page
        {
            title: "Categories"
            id: rootPage
            SubCategoriesComponent
            {
                id: subCategoriesComponent
                anchors.fill: parent
            }
        }
    }

Now, even though rootPage is visible, it's not actually on the PageStack yet. PageStack is a "stack" in the programming sense. That means you can push items on top of the stack, and pop items off. So I need to write some code to push rootPage onto the PageStack. I do this by adding an onCompleted handler to the PageStack and pushing the rootPage onto the PageStack there.

    PageStack
    {
        id: rootStack
        Page
        {
            title: "Categories"
            id: rootPage
            SubCategoriesComponent
            {
                id: subCategoriesComponent
                anchors.fill: parent
            }
        }
      Component.onCompleted:
      {
            push(rootPage)
      }
   }

So, how does the user navigate? I added a signal to SubCategoriesComponent for when the user has selected a category.

I made two other Components. One for displaying a list of articles and one for displaying articles themselves. I host these components inside of pages. I also set the pages to be invisible.


    Page
    {
       visible: false
       id: articlesListPage;
       ArticlesListComponent
        {
            id: articlesList
            anchors.fill: parent;
            onArticleSelected:
            {
                articleComponent.url = url
                rootStack.push(articlePage)
            }
            onTitleChanged:
            {
                articlesListPage.title = articlesList.title
            }
        }
      }
      Page
      {
        visible: false
        id: articlePage
        ArticleComponent {
            id: articleComponent
            anchors.fill: parent;
        }
      }

When the user selects a category, they need to see a list of articles. I made ArticlesListComponent todo that job. When they select a specific article, the user should see the article, I created ArticleComponent for that. Note:

So, getting back to the story, how do we push these onto the PageStack?

        Page
        {
            title: "Categories"
            id: rootPage
            SubCategoriesComponent
            {
                id: subCategoriesComponent
                anchors.fill: parent
                onSubCategorySelected:
                {
                    articlesList.subCategoryId = subCategoryId;
                    rootStack.push(articlesListPage)
                }
            }
        }

You may recall that I added a onSubCategorySelected signal to my SubCategoriesComponent component. All I have to do is respond to that signal. First I configure my ArticlesListPage to use the sunCategoryId passed into the signal handler. Then I tell the PageStack to push articlesListPage (the instance of ArticlesListPage declared in the QML above). Pushing 'pushes' pushes the component on top of the categories list.

I use similar code to push the ArticlesComponent when the user selects an article.
            onArticleSelected:
            {
                articleComponent.url = url
                rootStack.push(articlePage)
            }

The ArticleComponent can only be on top, so it doesn't push anything.

But what about going back? That's where popping comes in. I added an action to rootPage which simply tells rootStack to pop. Pop is opposite of push. Instead of adding something to the top of the stack, it takes whatever is on top off. It pops off the top. rootPage can't be popped off, though, because it is at the root. This makes the code easy. If I wanted to, I could handle popping myself with code like this:
            tools: ToolbarActions
            {
                Action
                {
                    id: backAction
                    text: "Back"
                    iconSource: "back.png"
                    onTriggered:
                    {
                        rootStack.pop();
                    }
                }
            }
     

However, this is not necessary because PageStack automatically creates and maintains a back button for me!

I pushed a branch with my corrected code.

No comments:

Post a Comment