ViewPager Layout

layout:{
    type:'ViewPager'
}

Demo

SAMPLE
CODE

In the View Pager layout, only one child view is visible at a time.

You can navigate between child views by swiping or by using code.

Example:

ludo.get('viewPager').getLayout().nextPage();
ludo.get('viewPager').getLayout().previousPage();
ludo.get('viewPager').getLayout().goToPage(2);

By default, the first child view is initially displayed.

To set a different default selected child view, set layout.selected on your selected child view

{
    html: 'Child View B<br>Swipe to switch View',
    elCss:{'background-color': '#388E3C', color:'#FFF', padding:5 },
    layout:{
        selected:true
    }
}

ViewPager layout events

The View`Pager Layout fires events which you can listen to. These are the events:

  • showpage fired on navigating to a new page.
  • valid If displayed child view has form views, this event is fired when the value of all form elements are valid.
  • invalid If displayed child view has form views, this event is fired when the value of one of these is invalid.

You can add listeners to the layout events by creating a listeners object:

layout:{
    type:'ViewPager',
    listeners:{
        'showpage': function(layout, parentView, visibleChildView){
            // Do something           
        }
    }
}

Or during runtime with

ludo.$('<id-of-view>').getLayout().on('showpage', function(layout, parentView, visibleChildView){ 
    // do something
});

As you can see above, three arguments are sent with the event

  • layout reference to the ViewPager layout.
  • parentView reference to the View with the ViewPager layout.
  • visibleChildView reference to currently displayed child view.

Use case

The best way to learn is to practice. In this section, we are going to create our own reusable ludoJS view.

The View we are about to create is a navigator for the ViewPager layout. It will display a clickable dot for each of the child views in the PageViewer. Once we're finished, we'll end up with this ViewPager navigator.

If you're working in Google Chrome, I recommend open the developer tools(Ctrl+Shift+i on Windows, Command+Shift+i on Mac). If the console is at the bottom of the screen, you might want to move it to the right(click three dots and select Dock Side right).

Create an empty HTML page

We start by creating an HTML document where we include the ludojs files:

<html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>View Pager</title>
    <script type="text/javascript" src="../api/jquery/jquery-3.1.0.min.js"></script>
    <script type="text/javascript" src="../api/js/ludojs-minified.js"></script>
    <link rel="stylesheet" href="../api/css/ludojs-light-gray.css">
</head>
<body>
<script type="text/javascript">

</script>
</body>
</html>

In a normal situation we will put our css and javascript in separate files, but for now, we will do all our coding on our HTML page.

Create a new ludoJS View

We are going to create a new ludoJS View called myApp.view.ViewPagerNav. myApp.view is the namespace for our class. It's wise to use namespaces to avoid naming collisions.

To create the namespace, we call ludo.factory.ns

Add this code to the <script> tag of your <body>:

ludo.factory.ns('myApp.view');

and to create a new ludoJS View we add this code below the namespace:

myApp.view.ViewPagerNav = new Class({
    Extends: ludo.View
});

Here, we're creating a new View class which extends ludo.View, the basic View class of ludoJS.

To see the progress of our work, let's also add a Window view to our page which includes an instance of the Navigator View we're about to make. Add the following code to the <script> tag of your <body>;

    var w = new ludo.Window({
        id: 'myWindow',
        resizable: false,
        title: 'ViewPager use case',
        layout: {
            left: 50, top: 50,
            width: 400, height: 300,
            type: 'linear', orientation: 'vertical'
        },
        children: [
            {
                id: 'ViewPagerView',
                layout: {
                    weight: 1, // Height of body - height of navigator(30 pixels)
                    type: 'ViewPager'
                },
                children: [
                    {html: 'Page 1', elCss: {padding: 10, color: "#fff", 'background-color': '#E64A19'}},
                    {html: 'Page 2', elCss: {padding: 10, color: "#fff", 'background-color': '#5D4037'}},
                    {html: 'Page 3', elCss: {padding: 10, color: "#fff", 'background-color': '#FFA000'}},
                    {html: 'Page 4', elCss: {padding: 10, color: "#fff", 'background-color': '#827717'}},
                    {html: 'Page 5', elCss: {padding: 10, color: "#fff", 'background-color': '#388E3C'}},
                    {html: 'Page 6', elCss: {padding: 10, color: "#fff", 'background-color': '#0097A7'}},
                    {html: 'Page 7', elCss: {padding: 10, color: "#fff", 'background-color': '#303F9F'}},
                    {html: 'Page 8', elCss: {padding: 10, color: "#fff", 'background-color': '#00796B'}}
                ]
            },
            // Below is our View
            {
                id: 'navigator',
                type: 'myApp.view.ViewPagerNav',
                layout: {
                    height: 30
                },
                elCss: {
                    // Some spacing above and below the dot navigator
                    'margin-top': 8, 'margin-bottom': 8
                }
            }
        ]
    });

This will render a ludo.Window view to your page like this. The window contains two child views. The first one is the ViewPager, the second one our new navigator View(see type: 'myApp.view.ViewPagerNav').

In our view, we want to have a reference to the ViewPager layout, so let's add a setViewPager method:

myApp.view.ViewPagerNav = new Class({
    Extends: ludo.View,
    viewPager:undefined,
    setViewPager:function(viewPager){
        this.viewPager = viewPager;
    }
});

This method should be called when the ViewPager layout has rendered it's views. We can accomplish this by adding an event handler to the ViewPager layout:

layout: {
    weight: 1,
    type: 'ViewPager',
    listeners:{
      'rendered' :function(layout){
          ludo.$('navigator').setViewPager(layout);
      }
    }
}

The rendered event is fired when all child views has been added and resized. So at this point, we know how many pages/child views the view pager has.

As mentioned above, We want to render a dot for each of the child view, so let's add a renderDots function to our custom class and call it from ou setViewPager function.

    myApp.view.ViewPagerNav = new Class({
        Extends: ludo.View,
        viewPager: undefined,
        setViewPager: function (viewPager) {
            console.log('added view pager'); // debugging
            this.viewPager = viewPager;
            this.renderDots();
        },

        renderDots:function(){

        }
    });

the renderDots function will create two <div> elements for each of the dots, a parent and a child div. The reason why add two divs is for resizing purpose. The parent div will have a fixed size, the child div is the dot which will be resized. The dot for selected view should have a greater radius.

This is the renderDots function:

dots:undefined,
dotParents:undefined,

renderDots:function(){

    this.dotParents = [];
    this.dots = [];

    this.getBody().html('');

    for(var i=0;i<this.viewPager.count;i++){
        var parent = $('<div class="navigator-dot-parent" style="position:absolute"></div>');
        var dot = $('<div class="navigator-dot" style="position:absolute"></div>');

        parent.append(dot);
        this.getBody().append(parent);
        this.dotParents.push(parent);
        this.dots.push(dot);
    }

}

this.dots and this.dotParents are arrays which hold references to our dot divs. We add them to an array for quick lookup later. The index of these arrays corresponds with the index of child views in the ViewPager.

We call this.getBody().html('') to clear all HTML from the View. getBody() returns a reference to the div element our View. The reason why we do this is to add support for children added to the ViewPager during runtime with call to view.addChild. When this occurs, we re-render all the dots.

Finally, in the *for-loop, we create a dot for each of the child views in the ViewPager and add them to the body div of our View.

As you can see, the divs for our dots has a css class. Let's add a stylesheet for them with this code between <head> and </head>:

<style type="text/css">
    .navigator-dot-parent{
        cursor:pointer;
    }
    .navigator-dot{
        background-color:#388E3C;

    }

</style>

If you look at your page now, you will not see much result of your work. The reson for this is that our dot divs has not yet been positioned and resized. If you look at the DOM tree in your developer console, you should see them, but the dimension is 0x0 pixels.

So where do we resize and position the dots?

All views has a resize method. This method is called by the Layout of parent view on every resize and at least one time. This is a method we can override in our new View class.

So let's do that and resize and position our dots there.

resize:function(params){
    this.parent(params);
    this.resizeAndPositionDots();
},
resizeAndPositionDots:function(){

}

When we override ludoJS functions/methods, we should always call this.parent of the function. That will call the same function in the super class, here ludo.View.

the resizeAndPositionDots is where we will resize and position our dots.

The first we want to do in this function is to figure out how big our dots should be:

dotize:undefined,
dotSizeSelected:undefined,
selectedDot:undefined,
resizeAndPositionDots:function(){
    this.dotSizeSelected = this.getBody().height();
    this.dotSize = this.dotSizeSelected * 0.5;

}

Here, we set the size of selected dot to the height of the view and size of not selected dot to half the height.

We save them as properties of the view using this for quick lookup later. We also create a selectedDot property which will hold a reference to currently selected dot <div>.

Our dot divs are absolute positioned, so we can use the css left proerty to position them.

The first dot should be positioned at

*width / 2 - total width of dots / 2

Add the following code below: this.dotSize = this.dotSizeSelected * 0.5;

var totalWidthOfDots = this.dotSizeSelected * this.viewPager.count;
var left = (this.getBody().width() / 2) - (totalWidthOfDots / 2);

The next step is to loop though the dots and resize and position them. Add this code below var left=...

for(var i=0;i<this.viewPager.count;i++){

    this.dotParents[i].css({
        left: left,
        width: this.dotSizeSelected,
        height: this.dotSizeSelected
    });

    // Set left position for next dot
    left+= this.dotSizeSelected;
}

This will loop through all the dots and set the left,width and height css values. At the end of the loop, the left coordinate is incremented.

The size of the dot it's self depends on if it's corresponding ViewPager view is selected or not. It's selected if i in the loop equals this.viewPager.selectedIndex, so let's add

var selected = i == this.viewPager.selectedIndex;
if(selected){
    this.selectedDot = this.dots[i];
}
var size = selected ? this.dotSizeSelected : this.dotSize;

Here, the size of the dot will be this.dotSizeSelected for the selected dot, this.dotSize otherwise.

Dots that are not selected should also be offset a little from the top and left to have it's centered inside it's parent div.

var offset = selected ? 0 : size / 2;

Now, we're redy to resize the dot. Add this code above left+= this.dotSizeSelected;

this.dots[i].css({
    width: size,
    height: size,
    top:offset + 'px',
    left:offset+ 'px',
    'border-radius': (size/2) + 'px'
});

If you have followed me so far, you should have a View like this. If you're page doesn't look like this, look for errors in the developer console or copy and paste the code from the window above(and fix the <script src> and <link href> file references).

As you can see, it displays the dots properly, but nothing happens when you swipe the pages of the ViewPager. To accomplish this, let's create a navigate function in our class:

navigate:function(){
    console.log("navigate"); // debuging
}

We want to call this function every time the ViewPager navigates to a new page/child view. We can do that by adding a *showpage event handler to the ViewPager layout. the ViewPager layout's showpage event is fired every time a new child view is shown.

So change the listeners to:

listeners: {
    'rendered': function (layout) {
        ludo.$('nvigator').setViewPager(layout);
    },
    'showpage': function(layout){
        ludo.$('navigator').navigate();

    }
}

If you take a look at the developer console now, you should see the text navigate when you refresh the page.

So what should we do in the navigate function?

We want to highlight the dot for the selected PageViewer view and remove highlight from eventual previous selected view. We will do this using jQuery animations.

This is the new code for the navigate function:

navigate:function(){
    if(this.selectedDot != undefined){
        var size = this.dotSize;
        this.selectedDot.animate({
            width: size,
            height: size,
            top:(size/2) + 'px',
            left:(size/2)+ 'px',
            'border-radius': (size/2) + 'px'
        }, { duration: 200, queue: false });

    }
    this.selectedDot = this.dots[this.viewPager.selectedIndex];

    this.selectedDot.animate({
        width: this.dotSizeSelected,
        height: this.dotSizeSelected,
        top:0,
        left:0,
        'border-radius': (this.dotSizeSelected/2) + 'px'
    }, { duration: 200, queue: false });
}

The first thing we do is to check if we allready have a selected dot. If we do, let's animate it back to a non-selected state.

We know the index of currently displayed ViewPager view from the this.viewPager.selectedIndex property.

The dot with this index should be our new this.selectedDot and we animate it to selected state(bigger).

If you refresh your page now, you should see the animation occur while you swipe between the pages of your PageViewer. (My version so far.)

Adding click events to the dots.

Wouldn't it be nice if you could navigate the ViewPager by clicking on the dots in our navigator View?

Let's try to do that. in the renderDots function let's add a click event to our dots.

At the bottom of the for, loop add

parent.attr('data-index', i);
parent.on('click', this.clickDot.bind(this));

This adds a data-index attribute to the div and calls a method clickDot in our view when clicked. Let's create the clickDot function:

clikDot:function(e){
    var index = parseInt($(e.currentTarget).attr("data-index"));
    this.viewPager.goToPage(index);
}

Here, we collect the index attribute from the dot and call the goToPage method of the ViewPager with this index.

This will trigger the ViewPager#showpage event which then will trigger a call to our navigate method.

This is all we need to do to implement this cool feature.

Final touches

Our view is almost done now, but as a final touch let's add

  • A CSS class to the selected dot so that we can give it a different color than the other dots
  • Add support for spacing between the dots.

The CSS class can be handled in the navigate function. We want to give the selected dot a CSS class named navigator-dot-selected. When a dot is un-selected, we want to remove this css class from that dot.

in the navigate function, add this

this.selectedDot.removeClass('navigator-dot-selected');

below: if(this.selectedDot != undefined){

and add

this.selectedDot.addClass('navigator-dot-selected');

at the bottom of the function.

Also add:

.navigator-dot-selected{
    background-color:#1B5E20;
}

to the <style> declaration.

This should display the selected dot in a darker green color.

Spacing between dots

Spacing should be a config parameter, i.e. a value sent to the constructor. The constructor in ludoJS is a method called __construct. This is a method we can override, so let's do that and add support for spacing there.

spacing:undefined,

__construct:function(config){
    this.parent(config);
    this.spacing = config.spacing || 0;
    console.log(this.spacing); // debugging
}

Argument to a ludoJS constructor is more or less always an object {}. Here we set this.spacing to the constructor value or 0 if no spacing is defined.

Refresh your page now, and you should see the number "0" in the console.

To send in a custom spacing value, we need to update this code:

{
    id: 'navigator',
    type: 'myApp.view.ViewPagerNav',
    layout: {
        height: 30
    },
    elCss: {
        // Some spacing above and below the dot navigator
        'margin-top': 8, 'margin-bottom': 8
    }
}

This is the configuration of our ViewPager as we started with. These are the values which are sent to the __construct method. To support spacing, simply add it to this object.

{
    id: 'navigator',
    type: 'myApp.view.ViewPagerNav',
    layout: {
        height: 30
    },
    elCss: {
        // Some spacing above and below the dot navigator
        'margin-top': 8, 'margin-bottom': 8
    },
    spacing:3
}

Refresh your page, and you should see the number 3 in the console.

In the resizeAndPositionDots method we will add this.spacing into the positioning.

Below

this.dotSize = this.dotSizeSelected * 0.5;

add

var totalSpacing = this.spacing * this.viewPager.count;

Then change

var left = (this.getBody().width() / 2) - (totalWidthOfDots / 2);

to

var left = (this.getBody().width() / 2) - (totalWidthOfDots / 2) - (totalSpacing / 2);

and finally, change:

left+= this.dotSizeSelected;

to

left+= this.dotSizeSelected + this.spacing;

Other properties such as animation duration, animation easing etc. could also be implemented as config options in the same way.

This is my final version:

SAMPLE
CODE
Generated 2016-12-21 21:36:49