<commit_before>/* http://developer.apple.com/safari/library/documentation/iPhone/Conceptual/SafariJSDatabaseGuide/UsingtheJavascriptDatabase/UsingtheJavascriptDatabase.html */

function Migrator(db) {
    var migrations = [];
    var doReload = false;
    this.migration = function(number, func){
        migrations[number] = func;
    };
    var doMigration = function(number){
        if(migrations[number]){
            doReload = true;
            db.changeVersion(db.version, String(number), function(t){
                migrations[number](t);
            }, function(err){
                debug.log('DB changeVersion failed: ' + err.message);
            }, function(){
                debug.log('DB migration to ' + String(number) + ' successfull');
                doMigration(number+1);
            });
        } else {
            // migration done, reload
            if(doReload) {
                debug.log('reloading');
                window.location.reload(true);
            }
        }
    };
    this.doIt = function(){
        var initialVersion = parseInt(db.version) || 0;
        try {
            doMigration(initialVersion+1);
        } catch(e) {
            debug.log(e);
        }
    }
}

RadioDb = function() {

    var that = this;

    var db = null;

    var defaultStations = [
    ['SRF 1',        'http://stream.srg-ssr.ch/drs1/mp3_128.m3u', 'a'],
    ['SRF 2', 'http://stream.srg-ssr.ch/drs2/mp3_128.m3u', 'a'],
    ['SRF 3', 'http://stream.srg-ssr.ch/drs3/mp3_128.m3u', 'a'],
    ['SRF Virus',         'http://zlz-stream12.streamserver.ch/1/drsvirus/mp3_128', 'a'],
    ['Couleur 3',         'http://broadcast.infomaniak.net:80/rsr-couleur3-high.mp3', 'a'],
    ['Radio 1',           'http://stream.radio1.ch:8000/radio1', 'c'],
    ['Energy Zürich',     'http://statslive.infomaniak.ch/playlist/energyzuerich/energyzuerich-high.mp3/playlist.pls', 'c'],
    ['Energy Bern',       'http://statslive.infomaniak.ch/playlist/energybern/energybern-high.mp3/playlist.pls', 'c'],
    ['Radio Swiss Jazz',  'http://www.radioswissjazz.ch/live/mp3.m3u', 'c'],
    ['Radio Swiss Clas…', 'http://www.radioswissclassic.ch/live/mp3.m3u', 'c'],
    ['Rock Nation',       'http://105-stream-02.datacomm.ch:8000/rocknation', 'c'],
    ['RSR La Première',   'http://broadcast.infomaniak.net:80/rsr-la1ere-high.mp3', 'c'],
    ['Option Musique',    'http://broadcast.infomaniak.ch/rsr-optionmusique-high.mp3', 'c'],
    ['Frequence Banane',  'http://www.frequencebanane.ch/fb_128.m3u', 'c'],
    ['Backstageradio',    'http://broadcast.infomaniak.ch/backstageradio-high.mp3.m3u','c'],
    ['Radio 105',         'http://212.40.5.105:7000/listen.pls','c'],
    ['neo1',              'http://stream-02.neo1.ch/neo1.m3u','d'],
    ['Lounge-radio.com',  'http://www.lounge-radio.com/listen128.m3u','d'],
	['Radio Top',         'http://icecast.radiotop.ch/radiotop','d'],
	['MagicRadio.CH',     'http://www.magicradio.ch/iphoneAac.m3u','d'],
	['Radio FM1',         'http://radiofm1.ice.infomaniak.ch/playlists/radiofm1-128.mp3.pls','d'],
    ['Radio Swiss Pop',   'http://www.radioswisspop.ch/live/mp3.m3u','c'],
    ['DRS Musikwelle',    'http://stream.srg-ssr.ch/drsmw/mp3_128.m3u','c'],
	['Radio Zürisee',     'http://mp3.radio.ch/radiozuerisee128k','c'],
	['Radio X',           'http://mp3.radiox.ch/standard.mp3','d']

    ];

    var ERR_NONDB = 0;
    var ERR_OTHER = 1;
    var ERR_VERSION_MISMATCH = 2;
    var ERR_RESULTSIZE_EXCEEDED = 3;
    var ERR_MAXSIZE_EXCEEDED = 4;
    var ERR_LOCK_ERROR = 5; // contention
    var ERR_CONSTRAINT = 6;

    var upgradeSuccess = function() { debug.log('Successfully upgraded db.'); };
    var upgradeError = function(error) { debug.log('Failed upgrading db.'); return true; /* treat all errs as fatal */ };
    /** Return TRUE for fatal error */
    var errorHandler = function(t, error) {
        if(error.code == ERR_VERSION_MISMATCH) {
            // well, we just log&ignore it.. safari doesn't see the changeVersion effect until restart Oo
            debug.log('ERR_VERSION_MISMATCH');
            return false;
        }
        debug.log('Whoops, DB error: ' + error.message + ' (' + error.code + ')');
        return true; // fatal error
    };

    /** This is used as a data handler for a request that should return no data. */
    var nullDataHandler = function(t, results) {
        return false;
    };

    var insertDefaults = function(t) {
        var insertquery = 'INSERT INTO stations(name, stream, logo, listened_at) VALUES(?, ?, ?, NULL);';
        var selectquery = 'SELECT id, name FROM stations';
        var updatequery = 'UPDATE stations SET stream = ?, logo = ? where id = ?';
        var deletequery = 'DELETE FROM stations where id = ?';
        var defaultStationsName = [];
        for( var i=0; i < defaultStations.length; ++i ) {
            defaultStationsName[defaultStations[i][0]] = defaultStations[i];
            //first we try to insert, then update, this could be improved
            t.executeSql(insertquery, defaultStations[i], nullDataHandler, errorHandler);
        }
        t.executeSql(selectquery, [] , function(t, results) {
            if (results.rows.length == 0) {
                //
            } else {

                for (var j = 0; j < results.rows.length; ++j) {
                    var row = results.rows.item(j);
                    var station = defaultStationsName[row['name']];
                    if (typeof station != 'undefined') {
                        t.executeSql(updatequery,[station[1],station[2],row['id']], nullDataHandler,
                            function(t,error) {
                                console.log("ERRROR UPDATE");
                                console.log(error);
                                return true
                            }
                        );
                    } else {
                        t.executeSql(deletequery ,[row['id']], nullDataHandler,
                            function(t,error) {
                                console.log("ERRROR DELETE");
                                console.log(error);
                                return true
                            }
                        );
                    }

                }

            }
        },
        errorHandler
        );

    };

    this.init = function() {
        try {
            if(!window.openDatabase) {
                alert('Ihr Browser unterstützt kein Local Storage');
            } else {
                db = openDatabase('radios_db', '1', 'Radios Database', 524288); // 512KiB
                debug.log('Current DB Version: ' + db.version);
                db.transaction(function(t) {
                    t.executeSql('CREATE TABLE IF NOT EXISTS stations (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE ON CONFLICT IGNORE, stream TEXT NOT NULL, logo TEXT NOT NULL, listened_at DATE )',
                    [], insertDefaults, errorHandler);
                });
/*
                db = openDatabase('radios_db', '', 'Radios Database', 524288); // 512KiB
                debug.log('Current DB Version: ' + db.version);
                var M = new Migrator(db);
                M.migration(1, function(t) {
                    debug.log('ONE');
                    t.executeSql('CREATE TABLE stations(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE ON CONFLICT IGNORE, stream TEXT NOT NULL, logo TEXT NOT NULL, listened_at DATE )',
                    [], insertDefaults, errorHandler);
                });
                M.doIt();
                */
            }
            return db;
        } catch(e) {
            if(e.code == ERR_VERSION_MISMATCH) {
                debug.log('ERR_VERSION_MISMATCH');
            } else {
                alert('Error: ' + e);
            }
        }
    };

    this.updateListenAt = function(id) {
        db.transaction(function(t) {
            t.executeSql('UPDATE stations SET listened_at = datetime("now","localtime") WHERE id = ?', [ id ], nullDataHandler, errorHandler);
        });
    };

    this.getLastListenedStation = function(resultHandler) {
        db.transaction(function(t) {
            t.executeSql("SELECT id, name, stream, logo, listened_at FROM stations ORDER BY listened_at DESC LIMIT 1", [] , function(t, results) {
                resultHandler( results.rows.length == 0 ? null : results.rows.item(0) );
            }, errorHandler);
        });
    };

    var getStation = function(id, resultHandler, doUpdateListen) {
        if( doUpdateListen )
            that.updateListenAt(id);

        db.transaction(function(t) {
            t.executeSql("SELECT id, name, stream, logo, listened_at FROM stations WHERE id = ?", [ id ] , function(t, results) {
                resultHandler( results.rows.length == 0 ? null : results.rows.item(0) );
            }, errorHandler);
        });
    };

    var getStations = function(resultHandler, filter) {
        var where = '';
        if ( filter ) {
            where = " WHERE name LIKE '%" + filter + "%';"; // FIXME EEEEESCAPE!
        }
        db.transaction(function(t) {
            t.executeSql("SELECT id, name, stream, logo, listened_at FROM stations " + where + " ORDER BY logo ASC, name ASC", [ ] , function(t, results) {
                resultHandler(results.rows);
            }, errorHandler);
        });
    };

    this.insertStation = function(station) {
        db.transaction(function(t) {
            t.executeSql('INSERT INTO stations(name, stream, logo, listened_at) VALUES(?, ?, ?, NULL);', [ station.name, station.stream, station.logo ], nullDataHandler, errorHandler);
        });
    };

    this.autoSearch = function() {
        var searchel = document.getElementById('search-field');
        searchel.onkeyup = function(ev) {
            that.populateStations(searchel.value);
        };
    };

    this.populateStations = function(filter) {
        getStations(function(stations) {
            var ul = document.createElement('ul');
            var li, txt;
            var playing = document.createElement('span');
            var playingOn = document.createTextNode('On');
            playing.setAttribute('class', 'playing');
            playing.appendChild(playingOn);
            radio.playing = playing;
            for( var i=0; i<stations.length; ++i ) {
                li = document.createElement('li');
                txt = document.createTextNode(stations.item(i).name);
                li.appendChild(txt);
                li.setAttribute('id', 'station-'+ stations.item(i).id);
                li.onclick = function(ev) {

                    var id = this.getAttribute('id').split('-')[1];
                    getStation(id, function(station) {
                        var el = document.getElementById('station-'+station.id);

                        if (plugins.AudioStream.getStatus() == 'isPlaying' && station.name == radio.station) {
                            stopSound();
                        } else {
                            radio.station = station.name;
                            radio.stream = station.stream;
                            radio.playingEl = el;
                            radio.clear();
                            radio.noTrack(true);
                            playSound(station.stream);

                        }
                        // remove mute
                        document.getElementById("mute").setAttribute('class', '');
                    }, true);

                };
                ul.appendChild(li);
            }
            // remove current nav
            var statel = document.getElementById("scrollStations");
            if( statel.hasChildNodes() ) {
                while( statel.childNodes.length >= 1 )
                    statel.removeChild(statel.firstChild);
            }
            // append updated nav
            statel.appendChild(ul);
        }, filter);

    };
};

<commit_msg>New URLs for SRF Stations<commit_after>/* http://developer.apple.com/safari/library/documentation/iPhone/Conceptual/SafariJSDatabaseGuide/UsingtheJavascriptDatabase/UsingtheJavascriptDatabase.html */

function Migrator(db) {
    var migrations = [];
    var doReload = false;
    this.migration = function(number, func){
        migrations[number] = func;
    };
    var doMigration = function(number){
        if(migrations[number]){
            doReload = true;
            db.changeVersion(db.version, String(number), function(t){
                migrations[number](t);
            }, function(err){
                debug.log('DB changeVersion failed: ' + err.message);
            }, function(){
                debug.log('DB migration to ' + String(number) + ' successfull');
                doMigration(number+1);
            });
        } else {
            // migration done, reload
            if(doReload) {
                debug.log('reloading');
                window.location.reload(true);
            }
        }
    };
    this.doIt = function(){
        var initialVersion = parseInt(db.version) || 0;
        try {
            doMigration(initialVersion+1);
        } catch(e) {
            debug.log(e);
        }
    }
}

RadioDb = function() {

    var that = this;

    var db = null;

    var defaultStations = [
    ['SRF 1',             'http://stream.srg-ssr.ch/drs1/mp3_128.m3u', 'a'],
    ['SRF 2',             'http://stream.srg-ssr.ch/drs2/mp3_128.m3u', 'a'],
    ['SRF 3',             'http://stream.srg-ssr.ch/drs3/mp3_128.m3u', 'a'],
    ['SRF 4 News',        'http://stream.srg-ssr.ch/drs4news/mp3_128.m3u', 'a'],
    ['SRF Virus',         'http://stream.srg-ssr.ch/drsvirus/mp3_128.m3u', 'a'],
    ['Couleur 3',         'http://stream.srg-ssr.ch/couleur3/mp3_128.m3u', 'a'],
    ['Radio 1',           'http://stream.radio1.ch:8000/radio1', 'c'],
    ['Energy Zürich',     'http://statslive.infomaniak.ch/playlist/energyzuerich/energyzuerich-high.mp3/playlist.pls', 'c'],
    ['Energy Bern',       'http://statslive.infomaniak.ch/playlist/energybern/energybern-high.mp3/playlist.pls', 'c'],
    ['Radio Swiss Jazz',  'http://stream.srg-ssr.ch/rsj/mp3_128.m3u', 'c'],
    ['Radio Swiss Clas…', 'http://stream.srg-ssr.ch/rsc_de/mp3_128.m3u', 'c'],
    ['Rock Nation',       'http://105-stream-02.datacomm.ch:8000/rocknation', 'c'],
    ['RSR La Première',   'http://broadcast.infomaniak.net:80/rsr-la1ere-high.mp3', 'c'],
    ['Option Musique',    'http://broadcast.infomaniak.ch/rsr-optionmusique-high.mp3', 'c'],
    ['Frequence Banane',  'http://www.frequencebanane.ch/fb_128.m3u', 'c'],
    ['Backstageradio',    'http://broadcast.infomaniak.ch/backstageradio-high.mp3.m3u','c'],
    ['Radio 105',         'http://212.40.5.105:7000/listen.pls','c'],
    ['neo1',              'http://stream-02.neo1.ch/neo1.m3u','d'],
    ['Lounge-radio.com',  'http://www.lounge-radio.com/listen128.m3u','d'],
	['Radio Top',         'http://icecast.radiotop.ch/radiotop','d'],
	['MagicRadio.CH',     'http://www.magicradio.ch/iphoneAac.m3u','d'],
	['Radio FM1',         'http://radiofm1.ice.infomaniak.ch/playlists/radiofm1-128.mp3.pls','d'],
    ['Radio Swiss Pop',   'http://stream.srg-ssr.ch/rsp/mp3_128.m3u','c'],
    ['SRF Musikwelle',    'http://stream.srg-ssr.ch/drsmw/mp3_128.m3u','d'],
	['Radio Zürisee',     'http://mp3.radio.ch/radiozuerisee128k','c'],
	['Radio X',           'http://mp3.radiox.ch/standard.mp3','d']

    ];

    var ERR_NONDB = 0;
    var ERR_OTHER = 1;
    var ERR_VERSION_MISMATCH = 2;
    var ERR_RESULTSIZE_EXCEEDED = 3;
    var ERR_MAXSIZE_EXCEEDED = 4;
    var ERR_LOCK_ERROR = 5; // contention
    var ERR_CONSTRAINT = 6;

    var upgradeSuccess = function() { debug.log('Successfully upgraded db.'); };
    var upgradeError = function(error) { debug.log('Failed upgrading db.'); return true; /* treat all errs as fatal */ };
    /** Return TRUE for fatal error */
    var errorHandler = function(t, error) {
        if(error.code == ERR_VERSION_MISMATCH) {
            // well, we just log&ignore it.. safari doesn't see the changeVersion effect until restart Oo
            debug.log('ERR_VERSION_MISMATCH');
            return false;
        }
        debug.log('Whoops, DB error: ' + error.message + ' (' + error.code + ')');
        return true; // fatal error
    };

    /** This is used as a data handler for a request that should return no data. */
    var nullDataHandler = function(t, results) {
        return false;
    };

    var insertDefaults = function(t) {
        var insertquery = 'INSERT INTO stations(name, stream, logo, listened_at) VALUES(?, ?, ?, NULL);';
        var selectquery = 'SELECT id, name FROM stations';
        var updatequery = 'UPDATE stations SET stream = ?, logo = ? where id = ?';
        var deletequery = 'DELETE FROM stations where id = ?';
        var defaultStationsName = [];
        for( var i=0; i < defaultStations.length; ++i ) {
            defaultStationsName[defaultStations[i][0]] = defaultStations[i];
            //first we try to insert, then update, this could be improved
            t.executeSql(insertquery, defaultStations[i], nullDataHandler, errorHandler);
        }
        t.executeSql(selectquery, [] , function(t, results) {
            if (results.rows.length == 0) {
                //
            } else {

                for (var j = 0; j < results.rows.length; ++j) {
                    var row = results.rows.item(j);
                    var station = defaultStationsName[row['name']];
                    if (typeof station != 'undefined') {
                        t.executeSql(updatequery,[station[1],station[2],row['id']], nullDataHandler,
                            function(t,error) {
                                console.log("ERRROR UPDATE");
                                console.log(error);
                                return true
                            }
                        );
                    } else {
                        t.executeSql(deletequery ,[row['id']], nullDataHandler,
                            function(t,error) {
                                console.log("ERRROR DELETE");
                                console.log(error);
                                return true
                            }
                        );
                    }

                }

            }
        },
        errorHandler
        );

    };

    this.init = function() {
        try {
            if(!window.openDatabase) {
                alert('Ihr Browser unterstützt kein Local Storage');
            } else {
                db = openDatabase('radios_db', '1', 'Radios Database', 524288); // 512KiB
                debug.log('Current DB Version: ' + db.version);
                db.transaction(function(t) {
                    t.executeSql('CREATE TABLE IF NOT EXISTS stations (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE ON CONFLICT IGNORE, stream TEXT NOT NULL, logo TEXT NOT NULL, listened_at DATE )',
                    [], insertDefaults, errorHandler);
                });
/*
                db = openDatabase('radios_db', '', 'Radios Database', 524288); // 512KiB
                debug.log('Current DB Version: ' + db.version);
                var M = new Migrator(db);
                M.migration(1, function(t) {
                    debug.log('ONE');
                    t.executeSql('CREATE TABLE stations(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE ON CONFLICT IGNORE, stream TEXT NOT NULL, logo TEXT NOT NULL, listened_at DATE )',
                    [], insertDefaults, errorHandler);
                });
                M.doIt();
                */
            }
            return db;
        } catch(e) {
            if(e.code == ERR_VERSION_MISMATCH) {
                debug.log('ERR_VERSION_MISMATCH');
            } else {
                alert('Error: ' + e);
            }
        }
    };

    this.updateListenAt = function(id) {
        db.transaction(function(t) {
            t.executeSql('UPDATE stations SET listened_at = datetime("now","localtime") WHERE id = ?', [ id ], nullDataHandler, errorHandler);
        });
    };

    this.getLastListenedStation = function(resultHandler) {
        db.transaction(function(t) {
            t.executeSql("SELECT id, name, stream, logo, listened_at FROM stations ORDER BY listened_at DESC LIMIT 1", [] , function(t, results) {
                resultHandler( results.rows.length == 0 ? null : results.rows.item(0) );
            }, errorHandler);
        });
    };

    var getStation = function(id, resultHandler, doUpdateListen) {
        if( doUpdateListen )
            that.updateListenAt(id);

        db.transaction(function(t) {
            t.executeSql("SELECT id, name, stream, logo, listened_at FROM stations WHERE id = ?", [ id ] , function(t, results) {
                resultHandler( results.rows.length == 0 ? null : results.rows.item(0) );
            }, errorHandler);
        });
    };

    var getStations = function(resultHandler, filter) {
        var where = '';
        if ( filter ) {
            where = " WHERE name LIKE '%" + filter + "%';"; // FIXME EEEEESCAPE!
        }
        db.transaction(function(t) {
            t.executeSql("SELECT id, name, stream, logo, listened_at FROM stations " + where + " ORDER BY logo ASC, name ASC", [ ] , function(t, results) {
                resultHandler(results.rows);
            }, errorHandler);
        });
    };

    this.insertStation = function(station) {
        db.transaction(function(t) {
            t.executeSql('INSERT INTO stations(name, stream, logo, listened_at) VALUES(?, ?, ?, NULL);', [ station.name, station.stream, station.logo ], nullDataHandler, errorHandler);
        });
    };

    this.autoSearch = function() {
        var searchel = document.getElementById('search-field');
        searchel.onkeyup = function(ev) {
            that.populateStations(searchel.value);
        };
    };

    this.populateStations = function(filter) {
        getStations(function(stations) {
            var ul = document.createElement('ul');
            var li, txt;
            var playing = document.createElement('span');
            var playingOn = document.createTextNode('On');
            playing.setAttribute('class', 'playing');
            playing.appendChild(playingOn);
            radio.playing = playing;
            for( var i=0; i<stations.length; ++i ) {
                li = document.createElement('li');
                txt = document.createTextNode(stations.item(i).name);
                li.appendChild(txt);
                li.setAttribute('id', 'station-'+ stations.item(i).id);
                li.onclick = function(ev) {

                    var id = this.getAttribute('id').split('-')[1];
                    getStation(id, function(station) {
                        var el = document.getElementById('station-'+station.id);

                        if (plugins.AudioStream.getStatus() == 'isPlaying' && station.name == radio.station) {
                            stopSound();
                        } else {
                            radio.station = station.name;
                            radio.stream = station.stream;
                            radio.playingEl = el;
                            radio.clear();
                            radio.noTrack(true);
                            playSound(station.stream);

                        }
                        // remove mute
                        document.getElementById("mute").setAttribute('class', '');
                    }, true);

                };
                ul.appendChild(li);
            }
            // remove current nav
            var statel = document.getElementById("scrollStations");
            if( statel.hasChildNodes() ) {
                while( statel.childNodes.length >= 1 )
                    statel.removeChild(statel.firstChild);
            }
            // append updated nav
            statel.appendChild(ul);
        }, filter);

    };
};

<|endoftext|>


<commit_before>/* global angular, bootbox */

angular.module('chatCtrl', ['SocketFactory'])

.controller('chatController', function($scope, Socket) {
    Socket.connect();
    $scope.users = [];
    $scope.messages = [];
    // $scope.message = '';

    var promptUsername = function(message) {
        bootbox.prompt(message, function(name) {
            if (name != null && (name = name.trim()).length > 0) {
                Socket.emit('add-user', {username: name});
            } else {
                promptUsername('You must enter a username.');
            }
        });
    };

    $scope.sendMessage = function(msg) {
        if (msg != null && (msg = msg.trim()).length > 0) {
            Socket.emit('message', {
                message: msg
            });
        }
        $scope.msg = '';
    };

    promptUsername('What is your name?');

    Socket.emit('request-users', {});

    Socket.on('users', function(data) {
        $scope.users = data.users;
    });

    Socket.on('message', function(data) {
        $scope.messages.push(data.message);
    });

    Socket.on('add-user', function(data) {
        $scope.users.push(data.username);
        $scope.messages.push({
            username: data.username,
            message: 'has entered the channel.'
        });
    });

    Socket.on('remove-user', function(data) {
        if ($scope.users.indexOf(data.username) >= 0)
            $scope.users.splice($scope.users.indexOf(data.username));

        $scope.messages.push({
            username: data.username,
            message: 'has left the chat'
        });
    });
});<commit_msg>added 1 socket.io event listener, 1  event listener<commit_after>/* global angular, bootbox */

angular.module('chatCtrl', ['SocketFactory'])

.controller('chatController', function($scope, Socket) {
    Socket.connect();
    $scope.users = [];
    $scope.messages = [];
    // $scope.message = '';

    var promptUsername = function(message) {
        bootbox.prompt(message, function(name) {
            if (name != null && (name = name.trim()).length > 0) {
                Socket.emit('add-user', {username: name});
            } else {
                promptUsername('You must enter a username.');
            }
        });
    };

    $scope.sendMessage = function(msg) {
        if (msg != null && (msg = msg.trim()).length > 0) {
            Socket.emit('message', {
                message: msg
            });
        }
        $scope.msg = '';
    };

    promptUsername('What is your name?');

    Socket.emit('request-users', {});

    Socket.on('users', function(data) {
        $scope.users = data.users;
    });

    Socket.on('message', function(data) {
        $scope.messages.push(data);
    });

    Socket.on('add-user', function(data) {
        $scope.users.push(data.username);
        $scope.messages.push({
            username: data.username,
            message: 'has entered the channel.'
        });
    });

    Socket.on('remove-user', function(data) {
        if ($scope.users.indexOf(data.username) >= 0)
            $scope.users.splice($scope.users.indexOf(data.username));

        $scope.messages.push({
            username: data.username,
            message: 'has left the chat'
        });
    });

    Socket.on('prompt-username', function(data) {
        promptUsername(data.message);
    });

    $scope.$on('$locationChangeStart', function(event){
        Socket.disconnect(true);
    });
});<|endoftext|>


<commit_before>/**
 * The Pagination widget provides a set of controls to navigate through paged
 * data.
 *
 * @module aui-pagination
 */

var Lang = A.Lang,
    isBoolean = Lang.isBoolean,
    isFunction = Lang.isFunction,
    isNumber = Lang.isNumber,

    ACTIVE = 'active',
    BOUNDING_BOX = 'boundingBox',
    CHANGE_REQUEST = 'changeRequest',
    CIRCULAR = 'circular',
    CLICK = 'click',
    CONTENT_BOX = 'contentBox',
    CONTROL = 'control',
    DISABLED = 'disabled',
    FORMATTER = 'formatter',
    HIDE = 'hide',
    ITEMS = 'items',
    LI = 'li',
    NEXT = 'next',
    OFFSET = 'offset',
    PAGE = 'page',
    PAGINATION = 'pagination',
    PREV = 'prev',
    SHOW_CONTROLS = 'showControls',
    TOTAL = 'total',

    getCN = A.getClassName,

    CSS_ACTIVE = getCN(ACTIVE),
    CSS_DISABLED = getCN(DISABLED),
    CSS_HIDE = getCN(HIDE),
    CSS_PAGINATION_CONTROL = getCN(PAGINATION, CONTROL);

/**
 * A base class for Pagination, providing:
 *
 * - Widget Lifecycle (initializer, renderUI, bindUI, syncUI, destructor)
 * - Set of controls to navigate through paged data
 *
 * Check the [live demo](http://alloyui.com/examples/pagination/).
 *
 * @class A.Pagination
 * @param {Object} config Object literal specifying widget configuration
 *     properties.
 * @constructor
 * @include http://alloyui.com/examples/pagination/basic-markup.html
 * @include http://alloyui.com/examples/pagination/basic.js
 */
var Pagination = A.Component.create({
    /**
     * Static property provides a string to identify the class.
     *
     * @property NAME
     * @type String
     * @static
     */
    NAME: PAGINATION,

    /**
     * Static property used to define the default attribute configuration for
     * the `A.Pagination`.
     *
     * @property ATTRS
     * @type {Object}
     * @static
     */
    ATTRS: {

        /**
         * When enabled this property allows the navigation to go back to the
         * beggining when it reaches the last page, the opposite behavior is
         * also true. Incremental page navigation could happen clicking the
         * control arrows or invoking `.next()` and `.prev()` methods.
         *
         * @attribute circular
         * @default true
         * @type {Boolean}
         */
        circular: {
            validator: isBoolean,
            value: true
        },

        /**
         * A formatter function to format each pagination item.
         *
         * @attribute formatter
         * @type {Function}
         */
        formatter: {
            validator: isFunction,
            valueFn: '_formatterValueFn'
        },

        /**
         * Holds the page items as a `NodeList`. The list could be queried from
         * the DOM trough Widget `HTML_PARSER` or generated if
         * [total](A.Pagination.html#attr_total) is specified.
         *
         * @attribute items
         * @default undefined
         * @type {NodeList}
         */
        items: {},

        /**
         * Initial page offset.
         *
         * @attribute offset
         * @default 1
         * @type {Number}
         */
        offset: {
            setter: '_setInt',
            value: 1
        },

        /**
         * Page to display on initial paint.
         *
         * @attribute page
         * @default 0
         * @type {Number}
         */
        page: {
            setter: '_setInt',
            value: 0
        },

        /**
         * Total number of page links available. If set, the new
         * [items](A.Pagination.html#attr_items) node list will be rendered.
         *
         * @attribute total
         * @default 0
         * @type {Number}
         */
        total: {
            setter: '_setInt',
            value: 0
        },

        /**
         * Determines if pagination controls (Next and Prev) are rendered.
         *
         * @attribute page
         * @default true
         * @type {boolean}
         */
        showControls: {
            validator: isBoolean,
            value: true
        },

        /**
         * Collection of strings used to label elements of the UI.
         *
         * @attribute strings
         * @type {Object}
         */
        strings: {
            value: {
                next: 'Next',
                prev: 'Prev'
            }
        }
    },

    /**
     * Object hash, defining how attribute values are to be parsed from markup
     * contained in the widget's content box.
     *
     * @property HTML_PARSER
     * @type {Object}
     * @static
     */
    HTML_PARSER: {
        items: function(srcNode) {
            return this._queryItemsIfNotSet(srcNode);
        },
        total: function() {
            return this._countItemsInDoc();
        }
    },

    /**
     * Static property used to define the attributes for the bindUI lifecycle
     * phase.
     *
     * @property BIND_UI_ATTRS
     * @type {Array}
     * @static
     */
    BIND_UI_ATTRS: [OFFSET, TOTAL],

    /**
     * Static property used to define the UI attributes.
     *
     * @property UI_ATTRS
     * @type {Array}
     * @static
     */
    UI_ATTRS: [PAGE],

    prototype: {
        CONTENT_TEMPLATE: '

',
        ITEM_TEMPLATE: '
{content}
',
        TOTAL_CONTROLS: 2,

        items: null,
        lastState: null,

        /**
         * Sync the `A.Pagination` UI. Lifecycle.
         *
         * @method syncUI
         * @protected
         */
        syncUI: function() {
            var instance = this,
                page = instance.get(PAGE);

            if (page > 0) {
                instance._dispatchRequest({
                    page: page
                });
            }
        },

        /**
         * Bind the events on the `A.Pagination` UI. Lifecycle.
         *
         * @method bindUI
         * @protected
         */
        bindUI: function() {
            var instance = this,
                boundingBox = instance.get(BOUNDING_BOX);

            instance.on('pageChange', instance._onPageChange);
            instance.publish(CHANGE_REQUEST, {
                defaultFn: instance._defChangeRequest
            });
            boundingBox.delegate(CLICK, instance._onClickItem, LI, instance);
        },

        /**
         * Render the `A.Pagination` component instance. Lifecycle.
         *
         * @method renderUI
         * @protected
         */
        renderUI: function() {
            var instance = this;

            instance._renderItemsUI(instance.get(TOTAL));
        },

        /**
         * Retrieve the item node from the passesed item index parameter.
         * If passed item is a node instead of the index returns itself.
         *
         * @method getItem
         * @param {Node|Number} i Index or Node reference.
         * @return {Node} pagination item
         */
        getItem: function(i) {
            var instance = this;

            if (isNumber(i)) {
                var items = instance.get(ITEMS);
                if (items) {
                    i = items.item(i);
                }
            }

            return i;
        },

        /**
         * Retrieve page number including offset e.g., if offset is 100 and
         * active page is 5, this method returns 105.
         *
         * @method getOffsetPageNumber
         * @return {Number} current page number plus offset
         */
        getOffsetPageNumber: function() {
            var instance = this;

            return instance.get(OFFSET) + instance.get(PAGE);
        },

        /**
         * Retrieve total number of pages including offset e.g., if offset is
         * 100 and total 10, this method returns 110.
         *
         * @method getOffsetTotalPages
         * @return {Number} total page number plus offset
         */
        getOffsetTotalPages: function() {
            var instance = this;

            return instance.get(OFFSET) + instance.get(TOTAL);
        },

        /**
         * Retrieve total number of dom items representing the links, including
         * the arrow control items. Do not include the offset.
         *
         * @method getTotalItems
         * @return {Number} number of pagination items
         */
        getTotalItems: function() {
            var instance = this;

            return instance.get(TOTAL) + instance.TOTAL_CONTROLS;
        },

        /**
         * Navigate to the next page.
         *
         * @method next
         */
        next: function() {
            var instance = this,
                total = instance.get(TOTAL);

            if (total === 0) {
                return;
            }

            var page = instance.get(PAGE);

            instance._dispatchRequest({
                page: (instance.get(CIRCULAR) && (page === total)) ? 1 : Math.min(total, ++page)
            });

            return instance;
        },

        /**
         * Navigate to the previous page.
         *
         * @method prev
         */
        prev: function() {
            var instance = this,
                total = instance.get(TOTAL);

            if (total === 0) {
                return;
            }

            var page = instance.get(PAGE);

            instance._dispatchRequest({
                page: (instance.get(CIRCULAR) && (page === 1)) ? total : Math.max(1, --page)
            });

            return instance;
        },

        /**
         * Set the new pagination state. The state is a payload object
         * containing the page number, e.g. `{page:1}`.
         *
         * @method setState
         * @param {Object} state
         */
        setState: function(state) {
            var instance = this;

            instance.set(PAGE, state.page);
            instance.lastState = state;
        },

        /**
         * Returns number of items in doc.
         *
         * @method _countItemsInDoc
         * @protected
         * @return {Number} amount of items in doc
         */
        _countItemsInDoc: function() {
            var instance = this,
                srcNode = instance.get('srcNode');

            return Math.max(0, instance._queryItemsIfNotSet(srcNode).size() - instance.TOTAL_CONTROLS);
        },

        /**
         * Default `changeRequest` function, sets new state of pagination.
         *
         * @method _defChangeRequest
         * @param {EventFacade} event
         * @protected
         */
        _defChangeRequest: function(event) {
            var instance = this;

            instance.setState(event.state);
        },

        /**
         * Fires `changeRequest` event.
         *
         * @method _dispatchRequest
         * @param {Object} state
         * @protected
         */
        _dispatchRequest: function(state) {
            var instance = this;

            instance.fire(CHANGE_REQUEST, {
                lastState: instance.lastState,
                state: state
            });
        },

        /**
         * Formatter function that formats each pagination item.
         *
         * @method _formatterValueFn
         * @protected
         * @return {String} item template
         */
        _formatterValueFn: function() {
            return function(index) {
                var instance = this;

                return Lang.sub(
                    instance.ITEM_TEMPLATE, {
                        content: index,
                        cssClass: ''
                    }
                );
            };
        },

        /**
         * Returns all child list items of `srcNode`.
         *
         * @method _queryItemsIfNotSet
         * @param {Node} srcNode
         * @protected
         * @return {NodeList} list items
         */
        _queryItemsIfNotSet: function(srcNode) {
            var instance = this;

            if (!instance.items) {
                instance.items = srcNode.all(LI);
            }
            return instance.items;
        },

        /**
         * `onClick` handler for pagination items.
         *
         * @method _onClickItem
         * @param {EventFacade} event
         * @protected
         */
        _onClickItem: function(event) {
            var instance = this,
                item = event.currentTarget;

            event.preventDefault();

            if (item.hasClass(CSS_DISABLED) || item.hasClass(CSS_ACTIVE)) {
                return;
            }

            var items = instance.get(ITEMS),
                index = items.indexOf(item),
                lastIndex = items.size() - 1;

            switch (index) {
                case 0:
                    instance.prev();
                    break;
                case lastIndex:
                    instance.next();
                    break;
                default:
                    instance._dispatchRequest({
                        page: index
                    });
                    break;
            }
        },

        /**
         * Removes active class from previously selected item on page change.
         *
         * @method _onPageChange
         * @param {EventFacade} event
         * @protected
         */
        _onPageChange: function(event) {
            var instance = this;

            if (event.prevVal !== event.newVal) {
                var item = instance.getItem(event.prevVal);
                if (item) {
                    item.removeClass(CSS_ACTIVE);
                }
            }
        },

        /**
         * Renders pagination items UI.
         *
         * @method _renderItemsUI
         * @param {Number} total
         * @protected
         */
        _renderItemsUI: function(total) {
            var instance = this,
                tpl = instance.ITEM_TEMPLATE,
                formatter = instance.get(FORMATTER),
                offset = instance.get(OFFSET),
                i,
                buffer = '';

            buffer += Lang.sub(tpl, {
                content: instance.getString(PREV),
                cssClass: CSS_PAGINATION_CONTROL
            });

            for (i = offset; i <= (offset + total - 1); i++) {
                buffer += formatter.apply(instance, [i]);
            }

            buffer += Lang.sub(tpl, {
                content: instance.getString(NEXT),
                cssClass: CSS_PAGINATION_CONTROL
            });

            var items = A.NodeList.create(buffer);
            instance.set(ITEMS, items);
            instance.get(CONTENT_BOX).setContent(items);

            // When show controls is false, remove the first and last items from
            // the DOM in order to hide the controls, but keep the references
            // inside items NodeList in order to handle the items index the same
            // way when they are visible.
            if (!instance.get(SHOW_CONTROLS)) {
                items.first().remove();
                items.last().remove();
            }
        },

        /**
         * Setter for `offset`, `page`, and `total` attributes.
         *
         * @method _setInt
         * @param {Number} val
         * @protected
         */
        _setInt: function(val) {
            return Lang.toInt(val);
        },

        /**
         * Enables and disables next and prev items.
         *
         * @method _syncNavigationUI
         * @protected
         */
        _syncNavigationUI: function() {
            var instance = this,
                items = instance.get(ITEMS);

            items.first().toggleClass(
                CSS_DISABLED, instance.get(PAGE) === 1);

            items.last().toggleClass(
                CSS_DISABLED, instance.get(PAGE) === instance.get(TOTAL));
        },

        /**
         * Renders items UI based off current `total`.
         *
         * @method _uiSetOffset
         * @param {Number} val
         * @protected
         */
        _uiSetOffset: function(val) {
            var instance = this;

            instance._renderItemsUI(instance.get(TOTAL));
        },

        /**
         * Sets current page and syncs navigation UI.
         *
         * @method _uiSetPage
         * @param {Number} val
         * @protected
         */
        _uiSetPage: function(val) {
            var instance = this;

            if (!instance.get(CIRCULAR)) {
                instance._syncNavigationUI();
            }

            // Do not activate first and last items, they are used for controls.
            if (val === 0 || val === instance.getTotalItems()) {
                return;
            }

            var item = instance.getItem(val);

            if (item) {
                item.addClass(CSS_ACTIVE);
            }
        },

        /**
         * Setter for `total` attribute, renders pagination items UI.
         *
         * @method _uiSetTotal
         * @param {Number} val
         * @protected
         */
        _uiSetTotal: function(val) {
            var instance = this;

            instance._renderItemsUI(val);
        }
    }
});

A.Pagination = Pagination;
<commit_msg>AUI-1106 Add UI setter for showControls attribute<commit_after>/**
 * The Pagination widget provides a set of controls to navigate through paged
 * data.
 *
 * @module aui-pagination
 */

var Lang = A.Lang,
    isBoolean = Lang.isBoolean,
    isFunction = Lang.isFunction,
    isNumber = Lang.isNumber,

    ACTIVE = 'active',
    BOUNDING_BOX = 'boundingBox',
    CHANGE_REQUEST = 'changeRequest',
    CIRCULAR = 'circular',
    CLICK = 'click',
    CONTENT_BOX = 'contentBox',
    CONTROL = 'control',
    DISABLED = 'disabled',
    FORMATTER = 'formatter',
    HIDE = 'hide',
    ITEMS = 'items',
    LI = 'li',
    NEXT = 'next',
    OFFSET = 'offset',
    PAGE = 'page',
    PAGINATION = 'pagination',
    PREV = 'prev',
    SHOW_CONTROLS = 'showControls',
    TOTAL = 'total',

    getCN = A.getClassName,

    CSS_ACTIVE = getCN(ACTIVE),
    CSS_DISABLED = getCN(DISABLED),
    CSS_HIDE = getCN(HIDE),
    CSS_PAGINATION_CONTROL = getCN(PAGINATION, CONTROL);

/**
 * A base class for Pagination, providing:
 *
 * - Widget Lifecycle (initializer, renderUI, bindUI, syncUI, destructor)
 * - Set of controls to navigate through paged data
 *
 * Check the [live demo](http://alloyui.com/examples/pagination/).
 *
 * @class A.Pagination
 * @param {Object} config Object literal specifying widget configuration
 *     properties.
 * @constructor
 * @include http://alloyui.com/examples/pagination/basic-markup.html
 * @include http://alloyui.com/examples/pagination/basic.js
 */
var Pagination = A.Component.create({
    /**
     * Static property provides a string to identify the class.
     *
     * @property NAME
     * @type String
     * @static
     */
    NAME: PAGINATION,

    /**
     * Static property used to define the default attribute configuration for
     * the `A.Pagination`.
     *
     * @property ATTRS
     * @type {Object}
     * @static
     */
    ATTRS: {

        /**
         * When enabled this property allows the navigation to go back to the
         * beggining when it reaches the last page, the opposite behavior is
         * also true. Incremental page navigation could happen clicking the
         * control arrows or invoking `.next()` and `.prev()` methods.
         *
         * @attribute circular
         * @default true
         * @type {Boolean}
         */
        circular: {
            validator: isBoolean,
            value: true
        },

        /**
         * A formatter function to format each pagination item.
         *
         * @attribute formatter
         * @type {Function}
         */
        formatter: {
            validator: isFunction,
            valueFn: '_formatterValueFn'
        },

        /**
         * Holds the page items as a `NodeList`. The list could be queried from
         * the DOM trough Widget `HTML_PARSER` or generated if
         * [total](A.Pagination.html#attr_total) is specified.
         *
         * @attribute items
         * @default undefined
         * @type {NodeList}
         */
        items: {},

        /**
         * Initial page offset.
         *
         * @attribute offset
         * @default 1
         * @type {Number}
         */
        offset: {
            setter: '_setInt',
            value: 1
        },

        /**
         * Page to display on initial paint.
         *
         * @attribute page
         * @default 0
         * @type {Number}
         */
        page: {
            setter: '_setInt',
            value: 0
        },

        /**
         * Total number of page links available. If set, the new
         * [items](A.Pagination.html#attr_items) node list will be rendered.
         *
         * @attribute total
         * @default 0
         * @type {Number}
         */
        total: {
            setter: '_setInt',
            value: 0
        },

        /**
         * Determines if pagination controls (Next and Prev) are rendered.
         *
         * @attribute page
         * @default true
         * @type {boolean}
         */
        showControls: {
            validator: isBoolean,
            value: true
        },

        /**
         * Collection of strings used to label elements of the UI.
         *
         * @attribute strings
         * @type {Object}
         */
        strings: {
            value: {
                next: 'Next',
                prev: 'Prev'
            }
        }
    },

    /**
     * Object hash, defining how attribute values are to be parsed from markup
     * contained in the widget's content box.
     *
     * @property HTML_PARSER
     * @type {Object}
     * @static
     */
    HTML_PARSER: {
        items: function(srcNode) {
            return this._queryItemsIfNotSet(srcNode);
        },
        total: function() {
            return this._countItemsInDoc();
        }
    },

    /**
     * Static property used to define the attributes for the bindUI lifecycle
     * phase.
     *
     * @property BIND_UI_ATTRS
     * @type {Array}
     * @static
     */
    BIND_UI_ATTRS: [OFFSET, SHOW_CONTROLS, TOTAL],

    /**
     * Static property used to define the UI attributes.
     *
     * @property UI_ATTRS
     * @type {Array}
     * @static
     */
    UI_ATTRS: [PAGE],

    prototype: {
        CONTENT_TEMPLATE: '

',
        ITEM_TEMPLATE: '
{content}
',
        TOTAL_CONTROLS: 2,

        items: null,
        lastState: null,

        /**
         * Sync the `A.Pagination` UI. Lifecycle.
         *
         * @method syncUI
         * @protected
         */
        syncUI: function() {
            var instance = this,
                page = instance.get(PAGE);

            if (page > 0) {
                instance._dispatchRequest({
                    page: page
                });
            }
        },

        /**
         * Bind the events on the `A.Pagination` UI. Lifecycle.
         *
         * @method bindUI
         * @protected
         */
        bindUI: function() {
            var instance = this,
                boundingBox = instance.get(BOUNDING_BOX);

            instance.on('pageChange', instance._onPageChange);
            instance.publish(CHANGE_REQUEST, {
                defaultFn: instance._defChangeRequest
            });
            boundingBox.delegate(CLICK, instance._onClickItem, LI, instance);
        },

        /**
         * Render the `A.Pagination` component instance. Lifecycle.
         *
         * @method renderUI
         * @protected
         */
        renderUI: function() {
            var instance = this;

            instance._renderItemsUI(instance.get(TOTAL));
        },

        /**
         * Retrieve the item node from the passesed item index parameter.
         * If passed item is a node instead of the index returns itself.
         *
         * @method getItem
         * @param {Node|Number} i Index or Node reference.
         * @return {Node} pagination item
         */
        getItem: function(i) {
            var instance = this;

            if (isNumber(i)) {
                var items = instance.get(ITEMS);
                if (items) {
                    i = items.item(i);
                }
            }

            return i;
        },

        /**
         * Retrieve page number including offset e.g., if offset is 100 and
         * active page is 5, this method returns 105.
         *
         * @method getOffsetPageNumber
         * @return {Number} current page number plus offset
         */
        getOffsetPageNumber: function() {
            var instance = this;

            return instance.get(OFFSET) + instance.get(PAGE);
        },

        /**
         * Retrieve total number of pages including offset e.g., if offset is
         * 100 and total 10, this method returns 110.
         *
         * @method getOffsetTotalPages
         * @return {Number} total page number plus offset
         */
        getOffsetTotalPages: function() {
            var instance = this;

            return instance.get(OFFSET) + instance.get(TOTAL);
        },

        /**
         * Retrieve total number of dom items representing the links, including
         * the arrow control items. Do not include the offset.
         *
         * @method getTotalItems
         * @return {Number} number of pagination items
         */
        getTotalItems: function() {
            var instance = this;

            return instance.get(TOTAL) + instance.TOTAL_CONTROLS;
        },

        /**
         * Navigate to the next page.
         *
         * @method next
         */
        next: function() {
            var instance = this,
                total = instance.get(TOTAL);

            if (total === 0) {
                return;
            }

            var page = instance.get(PAGE);

            instance._dispatchRequest({
                page: (instance.get(CIRCULAR) && (page === total)) ? 1 : Math.min(total, ++page)
            });

            return instance;
        },

        /**
         * Navigate to the previous page.
         *
         * @method prev
         */
        prev: function() {
            var instance = this,
                total = instance.get(TOTAL);

            if (total === 0) {
                return;
            }

            var page = instance.get(PAGE);

            instance._dispatchRequest({
                page: (instance.get(CIRCULAR) && (page === 1)) ? total : Math.max(1, --page)
            });

            return instance;
        },

        /**
         * Set the new pagination state. The state is a payload object
         * containing the page number, e.g. `{page:1}`.
         *
         * @method setState
         * @param {Object} state
         */
        setState: function(state) {
            var instance = this;

            instance.set(PAGE, state.page);
            instance.lastState = state;
        },

        /**
         * Returns number of items in doc.
         *
         * @method _countItemsInDoc
         * @protected
         * @return {Number} amount of items in doc
         */
        _countItemsInDoc: function() {
            var instance = this,
                srcNode = instance.get('srcNode');

            return Math.max(0, instance._queryItemsIfNotSet(srcNode).size() - instance.TOTAL_CONTROLS);
        },

        /**
         * Default `changeRequest` function, sets new state of pagination.
         *
         * @method _defChangeRequest
         * @param {EventFacade} event
         * @protected
         */
        _defChangeRequest: function(event) {
            var instance = this;

            instance.setState(event.state);
        },

        /**
         * Fires `changeRequest` event.
         *
         * @method _dispatchRequest
         * @param {Object} state
         * @protected
         */
        _dispatchRequest: function(state) {
            var instance = this;

            instance.fire(CHANGE_REQUEST, {
                lastState: instance.lastState,
                state: state
            });
        },

        /**
         * Formatter function that formats each pagination item.
         *
         * @method _formatterValueFn
         * @protected
         * @return {String} item template
         */
        _formatterValueFn: function() {
            return function(index) {
                var instance = this;

                return Lang.sub(
                    instance.ITEM_TEMPLATE, {
                        content: index,
                        cssClass: ''
                    }
                );
            };
        },

        /**
         * Returns all child list items of `srcNode`.
         *
         * @method _queryItemsIfNotSet
         * @param {Node} srcNode
         * @protected
         * @return {NodeList} list items
         */
        _queryItemsIfNotSet: function(srcNode) {
            var instance = this;

            if (!instance.items) {
                instance.items = srcNode.all(LI);
            }
            return instance.items;
        },

        /**
         * `onClick` handler for pagination items.
         *
         * @method _onClickItem
         * @param {EventFacade} event
         * @protected
         */
        _onClickItem: function(event) {
            var instance = this,
                item = event.currentTarget;

            event.preventDefault();

            if (item.hasClass(CSS_DISABLED) || item.hasClass(CSS_ACTIVE)) {
                return;
            }

            var items = instance.get(ITEMS),
                index = items.indexOf(item),
                lastIndex = items.size() - 1;

            switch (index) {
                case 0:
                    instance.prev();
                    break;
                case lastIndex:
                    instance.next();
                    break;
                default:
                    instance._dispatchRequest({
                        page: index
                    });
                    break;
            }
        },

        /**
         * Removes active class from previously selected item on page change.
         *
         * @method _onPageChange
         * @param {EventFacade} event
         * @protected
         */
        _onPageChange: function(event) {
            var instance = this;

            if (event.prevVal !== event.newVal) {
                var item = instance.getItem(event.prevVal);
                if (item) {
                    item.removeClass(CSS_ACTIVE);
                }
            }
        },

        /**
         * Renders pagination items UI.
         *
         * @method _renderItemsUI
         * @param {Number} total
         * @protected
         */
        _renderItemsUI: function(total) {
            var instance = this,
                tpl = instance.ITEM_TEMPLATE,
                formatter = instance.get(FORMATTER),
                offset = instance.get(OFFSET),
                i,
                buffer = '';

            buffer += Lang.sub(tpl, {
                content: instance.getString(PREV),
                cssClass: CSS_PAGINATION_CONTROL
            });

            for (i = offset; i <= (offset + total - 1); i++) {
                buffer += formatter.apply(instance, [i]);
            }

            buffer += Lang.sub(tpl, {
                content: instance.getString(NEXT),
                cssClass: CSS_PAGINATION_CONTROL
            });

            var items = A.NodeList.create(buffer);
            instance.set(ITEMS, items);
            instance.get(CONTENT_BOX).setContent(items);

            // When show controls is false, remove the first and last items from
            // the DOM in order to hide the controls, but keep the references
            // inside items NodeList in order to handle the items index the same
            // way when they are visible.
            if (!instance.get(SHOW_CONTROLS)) {
                items.first().remove();
                items.last().remove();
            }
        },

        /**
         * Setter for `offset`, `page`, and `total` attributes.
         *
         * @method _setInt
         * @param {Number} val
         * @protected
         */
        _setInt: function(val) {
            return Lang.toInt(val);
        },

        /**
         * Enables and disables next and prev items.
         *
         * @method _syncNavigationUI
         * @protected
         */
        _syncNavigationUI: function() {
            var instance = this,
                items = instance.get(ITEMS);

            items.first().toggleClass(
                CSS_DISABLED, instance.get(PAGE) === 1);

            items.last().toggleClass(
                CSS_DISABLED, instance.get(PAGE) === instance.get(TOTAL));
        },

        /**
         * Renders items UI based off current `total`.
         *
         * @method _uiSetOffset
         * @param {Number} val
         * @protected
         */
        _uiSetOffset: function(val) {
            var instance = this;

            instance._renderItemsUI(instance.get(TOTAL));
        },

        /**
         * Sets current page and syncs navigation UI.
         *
         * @method _uiSetPage
         * @param {Number} val
         * @protected
         */
        _uiSetPage: function(val) {
            var instance = this;

            if (!instance.get(CIRCULAR)) {
                instance._syncNavigationUI();
            }

            // Do not activate first and last items, they are used for controls.
            if (val === 0 || val === instance.getTotalItems()) {
                return;
            }

            var item = instance.getItem(val);

            if (item) {
                item.addClass(CSS_ACTIVE);
            }
        },

        /**
         * Setter for `showControls` attribute.
         *
         * @method _uiSetShowControls
         * @param {Boolean} val
         * @protected
         */
        _uiSetShowControls: function(val) {
            var instance = this;

            instance._renderItemsUI(instance.get(TOTAL));
        },

        /**
         * Setter for `total` attribute, renders pagination items UI.
         *
         * @method _uiSetTotal
         * @param {Number} val
         * @protected
         */
        _uiSetTotal: function(val) {
            var instance = this;

            instance._renderItemsUI(val);
        }
    }
});

A.Pagination = Pagination;
<|endoftext|>