MeeGo 1.2 Harmattan Developer Documentation Develop for the Nokia N9

QML PageStack Element

PageStack implements common stack-based navigation model More...

Inherits Window

This element was introduced in qt-components 4.7.

Properties

Methods

Detailed Description

The PageStack item implements common navigational patterns in an application that uses a stack-based navigation model. A stack-based navigation model means that pages in an application are pushed onto a stack when the user navigates deeper into the application page hierarchy. The user can then go back to previous pages, which from a stack point of view means popping pages from the top of the stack and re-activating them (that is, making them be the current page).

A transition animation is performed as the application transfers from one page to another. When moving deeper into the application page hierarchy and pages are pushed onto the stack, a swipe gesture from the right is used. And when moving back, the previous page is shown with a swipe gesture from the left and they are popped off the stack.

The PageStack works together with a toolbar so that when pages are pushed and popped onto/off the page stack, the toolbar is kept synchronized with the pages. Whatever tools are defined for a page is automatically placed in the toolbar. The toolbar transition animation is also automatically correct for the navigation direction. For example if you move deeper into the application then the new tools cross-fade and slide in from the right, whereas popping cross-fades and slides them in from the left and so on. Note that the animations for toolbars and pages may not be identical even if they are matched in terms of the direction of the navigation.

Pages can - but do not have to - use the Page item as the root item. The Page defines a contract for how the page and page stack work together. The page can be notified about when it becomes active or inactive using two signals: activated and deactivated. Using these, the page can perform whatever operations are necessary as the page is coming into view or moving out of the view. Only the page that is currently on top of the stack is active. It should be noted that activated and deactivated correspond fairly closely to visibility, however the two are not identical. For example a page that is popped off the stack is deactivated but will still be visible while it is being animated out.

In addition to the activated and deactivated signals a page can also define tool items for the page specific toolbar. These items are used if the property 'toolbar' has been set for the Page. If this toolbar property has been set for the PageStack then the PageStack automatically looks at the tools property of any Page that it is activating and sets those tools to the toolbar with the correct transition animations. If the page does not have any tools (tools property value is null) then the the toolbar is automatically hidden.

Note: pages are kept in PageStack, analogous to the history functionality of web browser. If the user does not go back in the history, then Pages are not popped from PageStack and memory usage grows. PageStack component itself does not limit the stack size.

The PageStack in the Application

Using the PageStack in the application is typically a simple matter of adding the PageStack item as a child of the top-level QML item. The stack is usually anchored to the edges of the screen, except at the top or bottom where it might be anchored to a status bar, toolbar, tab bar, or some other similar UI component. The stack can then be used by invoking its navigation methods. The first page to show in the PageStack is commonly loaded by calling PageStack.push() in the Component.onCompleted signal.

Basic Navigation

There are three primary navigation methods in PageStack: push, pop and replace. These correspond to classic stack operations where push adds an item to the top of a stack, pop removes the top item from the stack, and replace is like a pop followed by a push in that it replaces the topmost item on the stack with a new item. The topmost item in the stack corresponds to the one that is "active", that is, the one that is on the screen. That means that push() is the logical equivalent of navigating forward or deeper into the application, pop() is the equivalent of navigation back and replace() is the equivalent of replacing the current page with a different page.

Sometimes it is necessary to go back more than a single step in the stack, for example to return to the main page in the application. For this use case pop() can be provided with a page to pop to. This is called an unwind operation as the stack gets unwound to the specified page. If the page is not found then the stack unwinds until there is only a single page in the stack, which becomes the current page. To explicitly unwind to the bottom of the stack it is recommended to use pop(null), though technically any non-existant page works.

Given the stack [A, B, C]:

  • push(D) => [A, B, C, D] - "push" transition animation between C and D
  • pop() => [A, B] - "pop" transition animation between C and B
  • replace(D) => [A, B, D] - "replace" transition between C and D
  • pop(A) => [A] - "pop" transition between C and A

Note that when the stack is empty, a push() or replace() do not perform a transition animation in part because there is nothing to transition from and in part because basically the only time this happens is when an application starts and shows the initial page. At startup no page transition should be shown and thus this is the correct behavior. A pop() on a stack with length 1 or 0 is a no-operation.

Calling push() or replace() returns the page that was pushed onto the stack. Calling pop() returns the page that was popped off the stack. When pop() is called in an unwind operation the top-most page (the first page that was popped) is returned.

Deep Linking

Deep linking means launching an application into a particular state. For example a Newspaper application can be launched into showing a particular article, bypassing the front page (and possible a section page) that would normally have to be navigated through to get to the desired article. In terms of page stacks deep linking means the ability to modify the state of the stack so that you for example push a set of pages to the top of the stack or that you completely reset the stack to a given state.

The API for deep linking in PageStack is the same as for basic navigation. If you push an array instead of a single page, all the pages in that array are pushed onto the stack. The transition animation, however, will be conducted as if only the last page in the array was pushed onto the stack. The normal semantics of the push() and replace() functions apply for deep linking, meaning that push() adds whatever you push onto the stack and replace() replaces the current top-most item with whatever you specify.

In the case where the page stack is reset to a particular state the stack is emptied using the clear() method in PageStack prior to issuing a push() or replace() call.

This gives us the following result, given the stack [A, B, C]:

  • push([D, E, F]) => [A, B, C, D, E, F] - "push" transition animation between C and F
  • replace([D, E, F] => [A, B, D, E, F] - "replace" transition animation between C and F
  • clear(); push([D, E, F]) => [D, E, F] - no transition animation (since the stack was empty)
  • clear(); replace([D, E, F]) => [D, E, F] - no transition animation

Declaring Pages

Pages are declared as either QML items or components (the Component element). In simple use cases with shallow stacks using QML items is simpler and more desirable. Pages are declared anywhere (with any parent). If QML items are used for pages then the page gets re-parented to the page stack when the page is pushed to make it active. When the page is popped off the stack it gets re-parented back to its original owner. When a page is declared as a component the actual page must be created as an QML item from that component. This happens automatically when the page is pushed onto the stack and the page stack takes ownership of that item. When the page is popped off the stack the page QML item is also automatically destroyed. The component that declared the page, by contrast, remains in the ownership of the application and is not destroyed by the page stack.

One way to think of this is to think of the QML Component element like a class whereas QML items are like objects, created from a class. With this analogy pages can be defined as either objects or classes. If classes are used then the page stack creates objects from the class automatically and destroys these objects when they are popped off the stack. If objects are used then the page stack re-parents them into the page stack but does not clone them, destroy them, and so on.

Components are more flexible than items as you can have any number of instances of pages created from a single component. They are also more powerful if an application has many page types, since those page types do not need to be instantiated when the application starts but rather lazily when the page is used. Using components when an application has many pages is also desirable because it allows for more efficient memory management. Only those pages that are currently on the stack need to be allocated and when pages are pushed off the stack they automatically get destroyed and the memory is freed.

Given a page of type ExamplePage with a property "greeting", one might declare this page as an item as follows:

 ExamplePage {
     id: examplePage
     greeting: "Hello World!"
 }

Declaring this same page as a component would look like this:

 Component {
     id: examplePage
     ExamplePage {
         greeting: "Hello World!"
     }
 }

In both cases the page would be pushed onto the stack as follows:

 pageStack.push(examplePage);

In the case of the item the item itself (examplePage) is the return value for the push() call. In the case of the component the return value is the QML item (the instance) that is created from the component.

Normal property bindings is used whenever possible instead of passing parameters from one page to another. However push() and replace() support giving a map of parameters as a 2nd argument to those functions. These parameters are copied into the page at the time time push() or replace() function is invoked. This works for example as follows:

 pageStack.push(examplePage, { foo: bar, foz: baz });

This pushes the examplePage onto the stack and set the foo property in the page to bar and the foz property to baz. This can be used both with component or item-based pages.

Note that if the page is declared in an QML item that is destroyed - even if a component was used from which a page instance was created - then that page also gets destroyed. This follows normal Qt parent-child destruction rules but sometimes comes as a suprise for developers. In practice this means that for example if you declare a page B as a child of page A and then do a replace() from page A to page B, then page B is destroyed when page A was destroyed (as it was popped off the stack) and the application is effectively switching to a page that has been destroyed.

Lifecycle

The page lifecycle goes from instantiation to activation to deactivation to destruction, moving any number of times between activation and deactivation. When a page is activated it means it is visible on the screen and can be considered to be the current page. A page in a page stack that is not visible is not activated even if the page is currently the top-most page in the stack. When the stack becomes visible the top-most page gets activated. Likewise if the page stack is hidden the top-most page is deactivated. Popping the page off the top of the stack at this point does not result in further deactivation since the page is not active.

There is a status property that tracks the lifecycle. The value of the status property is an enumeration with values PageStatus.Inactive, PageStatus.Activating, PageStatus.Active and PageStatus.Deactivating. Combined with the normal Component.onComplete and Component.onDestruction signals the entire lifecycle is thus:

  • Created: Component.onCompleted()
  • Activating: onStatusChanged (status is PageStatus.Activating)
  • Activated: onStatusChanged (status is PageStatus.Active)
  • Deactivating: onStatusChanged (status is PageStatus.Deactivating)
  • Deactivated: onStatusChanged (status is PageStatus.Inactive)
  • Destruction: Component.onDestruction()

Finding Pages

Sometimes it is necessary to search for a page, for example in order to unwind the stack to a page to which the application does not have a reference. This is facilitated using a function find() in the page stack. The find() function takes a callback function as its only argument. The callback gets invoked for each page in the stack (starting at the top). When the callback returns true, it signals that a match has been found and the find() function returns that page. If the callback fails to return true (that is, no match is found) then find() returns null.

The code below searches for a page named "foo" in the stack and then unwinds to that page. Note that since find() returns null if no page is found and since pop unwinds to the bottom of the stack if null is given as the target page, the code works well even in the case that no matching page was found.

 pageStack.pop(pageStack.find(function(page) {
     return page.name == "foo";
 }));

Blocking User Actions During Page Transitions

Developers may want to block user actions during page transitions. In order to facilitate this the page stack has a property called "busy", which has a value of true whenever there is a page transition ongoing. Applications are easily bound to this in order to disable user actions in whatever way they desire. An easy way to do this in many applications is to place a MouseArea that covers the entire screen and is declared with a z-order that is sufficiently high to be on top of everything else. If the enabled property of the mouse area is bound to the busy property of the page stack then user clicks will be blocked during page transitions. The code looks as follows:

 MouseArea {
     anchors.fill: parent
     enabled: pageStack.busy
 }

Advanced Usage

You can also choose to not only defer instantiation but also parsing of pages until the pages are actually needed. You can do this in two ways, either by calling Qt.createComponent() to load and parse the component or by specifying the page to push or replace with a string (the URL of the page .qml file), in which case the page stack loads the page component. In both cases normal property bindings cannot be used since the page component is not declared in the using .qml file. Instead the 2nd argument of push() and replace() (the "property" argument) can be used to specify a map of properties for the page. This works as follows:

 pageStack.push(Qt.resolvedUrl("foo.qml"), {
     foo: bar, foz: baz
 });

The code above loads a page foo.qml from the same directory as the invoking code and sets to foo property to bar and the foz property to baz in the page created from foo.qml.

If deep linking is used an multiple pages are pushed in an array, properties are defined by having each item in the array specified as map, as follows:

 pageStack.push([
     { page: pageOne, properties: { one: 1 } },
     { page: Qt.resolvedUrl("two.qml"), properties: { two: 2 }},
     pageThree
 ]);

The code above pushes three pages: pageOne (declared as an item or component) with a property "one" with value 1, a page loaded from the file "two.qml" with a property "two" with value 2, and finally pageThree (declared as an item or component).

Advanced page example for PageStack, which content is dynamically created and destroyed depending on visibility

  // FramePage.qml
  import Qt 4.7
  import com.nokia.meego 1.1

  Page {
      id: root
      anchors.margins: 0

      Item {
          id: container
      }

      property Item containerObject;

      onVisibleChanged: {
          if (visible) {
              // create component
              console.log("Page content created.");
              var object = componentDynamic.createObject(container);
              containerObject = object;
          } else {
              // destroy component
              console.log("Page content destroyed.");
              containerObject.destroy();
          }
      }

      // Page content inside component, this is created dynamically when page is visible
      Component {
          id: componentDynamic
          Item {
              id: content
              Column {
                  id: contentColumn
                  anchors.fill: parent
                  anchors.margins: 32
                  spacing: 24

                  Label {
                      text: "Page content"
                  }

                  Button {
                      id: buttonPush
                      text: "Push page FramePage.qml"
                      onClicked: { openFile("FramePage.qml"); }
                  }

                  Button {
                      id: buttonPop
                      text: "Pop page"
                      onClicked: { pageStack.pop(); }
                  }
              }
          }
      }

      function openFile(file) {
          var component = Qt.createComponent(file)

          if (component.status == Component.Ready)
              pageStack.push(component);
          else
              console.log("Error loading component:", component.errorString());
      }
  }

Property Documentation

busy : bool

Indicates whether there is an ongoing page transition.


currentPage : Item

The currently active page.


depth : int

Page stack depth.


toolBar : ToolBar

The application toolbar. Can be automatically animated to match the set of items in the current active page in the page stack.


Method Documentation

PageStack::clear ()

Clears the page stack.


PageStack::find ( func )

Iterates through all pages (top to bottom) and invokes the specified function func. If the specified function returns true the search stops and the find function returns the page that the iteration stopped at. If no matching page is found, null is returned.


PageStack::pop ( page, immediate )

Pops a page off the stack. If the page is specified then the stack is unwound to that page; null to unwind the to first page. If the immediate argument is true then no transition animation is performed. Returns the page instance that was popped off the stack.


PageStack::push ( page, properties, immediate )

Pushes a page on the stack. The page can be defined as a component, item or string. If an item is used then the page will get re-parented. If a string is used then it is interpreted as a url that is used to load a page component.

The page can also be given as an array of pages. In this case all those pages will be pushed onto the stack. The items in the stack can be components, items or strings just like for single pages. Additionally an object can be used, which specifies a page and an optional properties property. This can be used to push multiple pages while still giving each of them properties. When an array is used the transition animation will only be to the last page.

The properties argument is optional and allows defining a map of properties to set on the page. If the immediate argument is true then no transition animation is performed. Returns the page instance.


PageStack::replace ( page, properties, immediate )

Replaces a page on the stack. See push() for details.