Initial view of PageControl

I’ve got simple piece of code:

<App>
    <JavaScript>
        var Observable = require("FuseJS/Observable");

        var Pages = Observable();
        var ActivePage = Observable("2");
        var DesiredPage = Observable();

        Pages.add(new Page("0"));
        Pages.add(new Page("1"));
        Pages.add(new Page("2"));
        Pages.add(new Page("3"));
        Pages.add(new Page("4"));
        Pages.add(new Page("5"));

        function Page(Title) {
            this.Title = Title;
        }

        function Next() {
            console.log("Next");
            ActivePage.value = DesiredPage.value;
        }

        module.exports = {
            ActivePage: ActivePage,
            DesiredPage: DesiredPage,
            Next: Next,
            Pages: Pages
        };
    </JavaScript>

    <ClientPanel>
        <Page ux:Class="Card" ux:Name="self" HitTestMode="LocalBoundsAndChildren">
            <StackPanel Alignment="VerticalCenter">
                <TextBox Value="{DesiredPage}" Margin="10"/>
                <Panel Color="#595FFF" Width="80%" Height="45">
                    <Text Value="{Title}" FontSize="22" Alignment="Center" TextColor="#fff"/>
                </Panel>

                <Panel Margin="0,50,0,0" Color="#000" Width="80%" Height="45">
                    <Clicked Handler="{Next}" />
                    <Text Value="Next" FontSize="22" Alignment="Center" TextColor="#fff"/>
                </Panel>
            </StackPanel>
        </Page>

        <PageControl ActiveIndex="{ActivePage}">
            <Each Items="{Pages}">
                <Card ux:Name="{Title}" />
            </Each>
        </PageControl>
    </ClientPanel>
</App>

It works as expected with one exception: no matter how the ActivePage is set initially,

        var ActivePage = Observable("2");

the app starts with the Page “0”. Is it possible to start PageControl with the given page, other then the one of index 0?

Hi Jacek,

Haven’t tested this, but I think the value should be an integer, so maybe it should be this?

var ActivePage = Observable( 2 );

Unfortunately, it does not matter. Both forms are accepted and both give the same effect.

Hi!

It is recommended to use Router to control navigation to avoid issues like this.

The problem in your case is that due to the async separation of UI and JS in Fuse, ActiveIndex is set by the data binding prior to the pages being created. The active index is then set to 0 again, as there is no page 2 at the time of writing. Then the pages are created. You could work around this by setting the ActivePage with a timer to make sure it happens after the pages are created, but that’s an ugly hack and I would recommend using the router instead.

If you can describe from a higher level what you are trying to acheive, there might be a more suitable approach.

This is a tricky one. As Anders correctly pointed out, the ActiveIndex and Active properties are usually set async after the script initialisation, so they get reset to the very first item. No go.

Luckily, there is a workaround:

<JavaScript>
...
// called when PageControl has finished rendering pages, seeks to the selected page index
function onCompleted() {
	// make sure you have someIndex ready
	nav.seekToPath(someIndex);
}
module.exports = {
	onCompleted: onCompleted
};
</JavaScript>
...
<PageControl ux:Name="nav">
	<Completed Handler="{onCompleted}" />
	...

Hi Anders,

thx for your reply. My idea is a simple master-detail view model, where i’ve got two pages: one with vertical list o items handled by StackPanel and the other one showing the same list of items but as a series of one page presentation for each item. Click on the item in the StackPanel switches view to the series of slides starting from the clicked item.
Something like that:

/* items.js */
var Observable = require("FuseJS/Observable");
var Items = Observable();

function Item(title, description) {
  this.title = title;
  this.description = description;
}

Items.add(new Item("title1", "long description 1..."));
Items.add(new Item("title2", "long description 2..."));
...
Items.add(new Item("titleN", "long description N..."));

module.exports.Items = Items;


/*  ListOfItems.ux  */
...
<Router ux:Dependency="router" />
<JavaScript>
var Items =  = require("items");
function gotoDetails(e) {
  var ndx = Items.indexOf(e.data);
  router.goto("datailsOfItem", {ndx: ndx});
}
module.exports = {
  Items: Items,
  gotoDetails: gotoDetails
}
</JavaScript>
...
<StackPanel>
  <Each Items="{Items}">
    <Button Text="{title}" Clicked="{gotoDetails}"/>
  </Each>
</StackPanel>
...

/*  DetailsOfItem.ux  */
...
<JavaScript>
  var Items = require("items");
  var itemNdx = this.Parameter.ndx;
  module.exports = {Items: Items, itemNdx: itemNdx}
</JavaScript>

<Panel ux:Class="ItemPresentation">
  <ScrollView>
    <StackPanel>
       <Text Value="{title}"/>
       <Text Value="{description}"/>
    </StackPanel>
  <ScrollView>
</Panel>
...
<PageControl ActiveIndex="{itemNdx}">
    <Each Items="{Items}">
         <ItemPresentation />
    </Each>
</PageControl>
...

This is an idea only, so, sorry about some lacks and syntactic mistakes.

Jacek, the snippets in my post will get you started in the right direction.

All you want to do is set the ActiveIndex after the pages in your PageControl have completed loading.

Hi Uldis, and sorry for the async comunication :), you answered me the same time i was writing answer to Anders.

The solution you presented works like a charm :), but… may be it is good idea to improve PageControl and add an attribute to set an initial startpoint? I think that my scenario is quite common and your workaroud is nice but not obvious.
By the way, you are doing the GREAT job and fusetools may be the solution i looked for a lot of time. Thx.

Good to know the suggestion worked for you. Also, thanks for the kind words!

The team is aware of this particular challenge and there will likely be some improvements on this in the future.

Unfortunately I was too hurry with applause ;), let’s step back to my first piece of code (I added Completed directive and onCompleted handler as suggested):

<App>
    <JavaScript>
        var Observable = require("FuseJS/Observable");

        var Pages = Observable();
        var ActivePage = Observable();
        var DesiredPage = Observable();

        Pages.add(new Page("0"));
        Pages.add(new Page("1"));
        Pages.add(new Page("2"));
        Pages.add(new Page("3"));
        Pages.add(new Page("4"));
        Pages.add(new Page("5"));

        function Page(Title) {
            this.Title = Title;
        }

        function Next() {
            console.log("Next");
            ActivePage.value = DesiredPage.value;
        }

        function onCompleted() {
        	ActivePage.value = "4";
                // nav.seekToPath(ActivePage);
         	debug_log("onCompleted, ActivePage.value="+ActivePage.value);
       }

        module.exports = {
            ActivePage: ActivePage,
            DesiredPage: DesiredPage,
            Next: Next,
            onCompleted: onCompleted,
            Pages: Pages
        };
    </JavaScript>

    <ClientPanel>
        <Page ux:Class="Card" ux:Name="self" HitTestMode="LocalBoundsAndChildren">
            <StackPanel Alignment="VerticalCenter">
                <TextBox Value="{DesiredPage}" Margin="10"/>
                <Panel Color="#595FFF" Width="80%" Height="45">
                    <Text Value="{Title}" FontSize="22" Alignment="Center" TextColor="#fff"/>
                </Panel>

                <Panel Margin="0,50,0,0" Color="#000" Width="80%" Height="45">
                    <Clicked Handler="{Next}" />
                    <Text Value="Next" FontSize="22" Alignment="Center" TextColor="#fff"/>
                </Panel>
            </StackPanel>
        </Page>

        <PageControl ux:Name="nav" ActiveIndex="{ActivePage}">
            <Completed Handler="{onCompleted}" />
            <Each Items="{Pages}">
                <Card ux:Name="{Title}" />
            </Each>
        </PageControl>
    </ClientPanel>
</App>

onCompleted handler is called twice - this is visible page movement from 0 to 4th page, made two times with 1-second interval between them.
Call of nav.seekToPath is not necessary, setting ActivePage (bound to ActiveIndex) is enough. seekToPath call throws an InternalError:

LOG: onCompleted, ActivePage.value=4
LOG: InternalError: Can not navigate to 'Fuse.Scripting.V8.Object', not found! in Fuse.Controls.PageControl, Name: nav</Users/jachu/Library/Application Support/Fusetools/Packages/Fuse.Controls.Navigation/0.47.7/$.uno:2292>
LOG: onCompleted, ActivePage.value=4
LOG: InternalError: Can not navigate to 'Fuse.Scripting.V8.Object', not found! in Fuse.Controls.PageControl, Name: nav</Users/jachu/Library/Application Support/Fusetools/Packages/Fuse.Controls.Navigation/0.47.7/$.uno:2292>

Is it possible to prevent from calling the handler twice?
what’s the difference between gotoPath and seekToPath methods?
what type should be used for seekToPath/gotoPath parameter? If I call it with simple type

nav.seekToPath(4);

the error is thrown:

LOG: InternalError: Can not navigate to '4', not found! in Fuse.Controls.PageControl, Name: nav</Users/jachu/Library/Application Support/Fusetools/Packages/Fuse.Controls.Navigation/0.47.7/$.uno:2292>
LOG: InternalError: Can not navigate to '4', not found! in Fuse.Controls.PageControl, Name: nav</Users/jachu/Library/Application Support/Fusetools/Packages/Fuse.Controls.Navigation/0.47.7/$.uno:2292>

Jacek, well I did say it was a starting point, did I not? :slight_smile:

Anyway, here’s a complete repro of a working solution that holds answers to all of your questions:

<App>
    <JavaScript>
        var Observable = require("FuseJS/Observable");

        var Pages = Observable();
        Pages.add(new Page("0"));
        Pages.add(new Page("1"));
        Pages.add(new Page("2"));
        Pages.add(new Page("3"));
        Pages.add(new Page("4"));
        Pages.add(new Page("5"));

        function Page(Title) {
            this.Title = Title;
        }

        // we need automated indexes, so we make a reactive indexed list of pages
        var IndexedPages = Pages.map(function(itm, idx) {
        	// return a new object for each page, contains index and the page object inside item
            return {index: idx, item: itm};
        });

        // active page does not need to be an observable
        var ActivePage = 4;
        // desired page needs to be an observable, because that is user input
        var DesiredPage = Observable();

        // a function to land on another page
        function Next() {
            console.log("Next, DesiredPage.value=" + DesiredPage.value);
            // note how we use gotoPath to get the scrolling animation, and we refer to the observable by its .value
            nav.gotoPath(DesiredPage.value);
        }

        // a function called when the PageControl has finished loading
        function onCompleted() {
            debug_log("onCompleted, ActivePage=" + ActivePage);
            // note how we use seekToPath to skip the scrolling animation, and we refer to the non-observable directly 
            nav.seekToPath(ActivePage);
        }

        // module.exports has changed too
        module.exports = {
            DesiredPage: DesiredPage,
            Next: Next,
            onCompleted: onCompleted,
            IndexedPages: IndexedPages
        };
    </JavaScript>

    <ClientPanel>
        <Page ux:Class="Card" HitTestMode="LocalBoundsAndChildren">
            <StackPanel Alignment="VerticalCenter">
                <TextBox Value="{DesiredPage}" Margin="10"/>
                <Panel Color="#595FFF" Width="80%" Height="45">
                	<!-- Title is now nested in the item object -->
                    <Text Value="{item.Title}" FontSize="22" Alignment="Center" TextColor="#fff"/>
                </Panel>

                <Panel Margin="0,50,0,0" Color="#000" Width="80%" Height="45">
                    <Clicked Handler="{Next}" />
                    <Text Value="Next" FontSize="22" Alignment="Center" TextColor="#fff"/>
                </Panel>
            </StackPanel>
        </Page>

        <PageControl ux:Name="nav">
            <Completed Handler="{onCompleted}" />
            <!-- the new IndexedPages -->
            <Each Items="{IndexedPages}">
            	<!-- woo, a Name, not ux:Name -->
                <Card Name="{index}" />
            </Each>
        </PageControl>
    </ClientPanel>
</App>

thx for your effort Uldis, the Card’s Name attribute is the key - it has to be equivalent to the path parameter of seekToPath/gotoPath methods. Now I understand what’s going on…