<repo_name>miensol/conf2013<file_sep>/server/wiki-search.js
var superAgent      = require("superagent"),
    search = function(query, callback){
        var queryParams = {
            action:'opensearch',
            search: query
        };
        superAgent.get('http://en.wikipedia.org/w/api.php')
            .query(queryParams)
            .end(function(res){
                if (res.ok) {
                    console.log('opensearch result', res.text);
                    var jsonData = JSON.parse(res.text);
                    callback(null,jsonData[1][0])
                } else {
                    process.nextTick(function () {
                        return callback(new Error("Unexpected HTTP status received [status=" + res.status + "]"));
                    });
                }
            });


    };

module.exports.search = search;<file_sep>/backbonejs/app/scripts/confApp.js
define('confApp', [
    'backbone',
    'confApp.index',
    'confApp.more',
    'confApp.country'], function(Backbone, index, more, country){
    var currentView = null;

    var countryData = country.countryData;

    var ConfAppRouter = Backbone.Router.extend({
        routes: {
            ':countryCode': 'default',
            ':countryCode/more': 'more',
            '*actions': 'default'
        },

        'default': function(countryCode){
            if(currentView instanceof index.MainView && countryData.isCountrySelected(countryCode)){
                return;
            }
            if(currentView){
                currentView.remove();
            }
            currentView = new index.MainView({
                el: this.newContainerChild(),
                router: this,
                selectedCountryCode: countryCode
            }).render();
        },

        newContainerChild:function(){
            var child = $('<div>');
            $('.container').append(child);
            return child;
        },

        'more': function(countryCode){
            if(currentView){
                currentView.remove();
            }
            currentView = new more.MoreView({
                el: this.newContainerChild(),
                model: new country.Country({code: countryCode})
            }).render();
        }

    });


    return {
        Router: ConfAppRouter
    };

});<file_sep>/server/app.js
var fs = require('fs');
var path = require('path');
var express = require('express');
var wikiArticle = require("wikipedia-js");
var wikiSearch = require('./wiki-search.js');
var worldMap = require('./world.js');
var app = express();


var countryList = function () {
    var result = Object.keys(worldMap.names).map(function (code) {
        var country = {
            code: code,
            name: worldMap.names[code],
            svgPath: worldMap.shapes[code],
            value: 1 + Math.floor(Math.random() * 100)
        };
        country.valueFormatted = country.value + '%';
        return  country;
    });
    return result;
};


var findCountryByCode = function (code) {
    return countryList().filter(function (country) {
        return country.code === code;
    })[0];
};

app.get('/country/:code', function(req, res) {
    var country = findCountryByCode(req.params.code);
    var term = country.name;
    wikiSearch.search(term, function(err, articleName) {
        var article = '';
        if (articleName) {
            console.log('searching for article', articleName);
            wikiArticle.searchArticle({
                    query: articleName,
                    format: "html"
                },
                function(err, htmlWikiText) {
                    if (err) {
                        country.article = 'No article found';
                    } else {
                        country.article = htmlWikiText;
                    }
                    res.json(country);
                });
        } else {
            country.article = 'No article found';
            res.json(country);
        }
    });
});

app.get('/country', function(req,res){
    var countries = countryList();
    res.json(countries);
});



app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.errorHandler({
    dumpExceptions: true,
    showStack: true
}));

module.exports = app;<file_sep>/backbonejs/app/scripts/confApp.index.js
define('confApp.index', [
    'backbone',
    'handlebars',
    'hbs!../views/main',
    'confApp.map',
    'confApp.table',
    'confApp.country'], function (Backbone, Handlebars, mainHtml, map, table, country) {
    var countryData = country.countryData;

    var MainView = Backbone.View.extend({
        initialize: function () {
            countryData.once('reset', this.selectCountryByCodeInOptions, this);
            countryData.fetch({
                reset: true
            });
        },
        selectCountryByCodeInOptions: function(){
            var countryCode = this.options.selectedCountryCode;
            if(countryCode){
                countryData.selectCountryByCode(countryCode);
            }
        },
        render: function () {
            var that = this;
            this.$el.append(mainHtml({}));
            new map.MapView({
                model: countryData,
                el: this.$el.find('.map'),
                clickHandler: function (country) {
                    countryData.selectCountry(country);
                    that.options.router.navigate(country.get('code'), {trigger: true});
                }
            }).render();
            new table.TableView({
                model: countryData,
                el: this.$el.find('.table-container'),
                router: this.options.router
            }).render();
            new map.ShapeView({
                el: this.$el.find('.country-description'),
                model: countryData,
                router: this.options.router
            }).render();
            return this;
        }
    });


    return {
        MainView: MainView
    };
});<file_sep>/backbonejs/app/scripts/confApp.more.js
define('confApp.more', [
    'backbone', 'confApp.country', 'hbs!../views/more'], function(Backbone, countryData, moreTemplate){

    var MoreView = Backbone.View.extend({
        initialize:function(){
            this.model.bind('change', this.renderFullyLoaded, this);
            this.model.fetch();
        },
        render:function(){
            this.$el.text('Loading...');
            return this;
        },

        renderFullyLoaded: function(){
            this.$el.html(moreTemplate({selectedCountry: this.model.toJSON()}));
            return this;
        }
    });


    return {
        MoreView: MoreView
    };

});<file_sep>/backbonejs/app/scripts/confApp.country.js
define('confApp.country', ['backbone'], function (Backbone) {
    var Country = Backbone.Model.extend({
        urlRoot: '/country',
        notSelectedStroke: { color: "#ccc6ae", width: 1 },
        selectedStroke: { color: "#ff4400", width: 5 },
        idAttribute: 'code',

        initialize: function () {
            var relative = ((this.get('value') - 1) / 100.0 * 225).toFixed(0);

            this.set({
                color: 'rgb(' + relative + ',' + relative + ',' + 255 + ')',
                isSelected: false,
                isHighlighted: false
            });

            this.stopHighlighting();
            this.unselect();
        },

        getStrokeBasedOnSelected: function (other) {
            return this.get('isSelected') ? this.selectedStroke : other;
        },
        select: function () {
            this.set('isSelected', true);
            this.set('stroke', this.getStrokeBasedOnSelected(this.notSelectedStroke));
        },
        unselect: function () {
            this.set('isSelected', false);
            this.set('stroke', this.getStrokeBasedOnSelected(this.notSelectedStroke));
        },
        startHighlighting: function () {
            this.set('isHighlighted', true);
            this.set('stroke', this.getStrokeBasedOnSelected({ color: "#ff8800", width: 4 }));
        },

        stopHighlighting: function () {
            this.set('isHighlighted', false);
            this.set('stroke', this.getStrokeBasedOnSelected({ color: "#ccc6ae", width: 1 }));
        },
        hasCodeEqualTo: function (code) {
            return this.get('code') === code;
        },
        matchesFilter: function (text) {
            if (!text) {
                return true;
            }
            text = text.toLowerCase();
            return this.get('name').toLowerCase().indexOf(text) !== -1
                || this.get('code').toLowerCase().indexOf(text) !== -1;
        }
    });


    var CountryData = Backbone.Collection.extend({
        model: Country,
        url: '/country',

        initialize: function () {

        },

        isCountrySelected: function (countryCode) {
            var result = false;
            this.forEach(function (country) {
                if (country.get('isSelected') && country.hasCodeEqualTo(countryCode)) {
                    result = true;
                }
            }, this);
            return result;
        },

        selectCountry: function (countryToSelect) {
            if (countryToSelect.get('isSelected')) {
                return;
            }
            this.forEach(function (country) {
                country.unselect();
            });
            countryToSelect.select();
            this.trigger('selectedCountryChanged', countryToSelect);
        },
        selectCountryByCode: function (countryCode) {
            this.forEach(function (country) {
                if (country.hasCodeEqualTo(countryCode)) {
                    this.selectCountry(country);
                }
            }, this);
        }
    });

    return {
        countryData: new CountryData(),
        Country: Country
    };
});<file_sep>/backbonejs/app/scripts/confApp.map.js
define('confApp.map',
    ['backbone',
        'raphael',
        'handlebars',
        'confApp.country'],
    function(Backbone, Raphael, Handlebars, country){
        var countryData = country.countryData;

        var MapView = Backbone.View.extend({
            constWidth: 1000,
            constHeight: 400,
            applySizeForAutoScale: function () {
                this.$el.css({
                    width: this.constWidth,
                    height: this.constHeight
                });
                this.paper.setViewBox(0, 0, this.$el.width(), this.$el.height(), true);
                var svg = this.$el.find("svg");
                svg.removeAttr("width");
                svg.removeAttr("height");
            },


            render: function () {
                var dom = this.$el[0];
                this.paper = Raphael(dom, this.constWidth, this.constHeight);
                countryData.bind('reset', this.renderSvg, this);
                return this;
            },

            renderSvg: function () {
                this.applySizeForAutoScale();
                this.paper.setStart();
                this.model.forEach(function (shape) {
                    var that = this;
                    var countryPath = this.paper.path(shape.get('svgPath'))
                        .attr({
                            stroke: shape.get('stroke').color,
                            fill: shape.get('color'),
                            'stroke-opacity': 0.9,
                            "stroke-width": shape.get('stroke').width
                        });
                    shape.bind('change:stroke', function (child, newStroke) {
                        countryPath.stop().animate({
                            stroke: newStroke.color,
                            'stroke-width': newStroke.width
                        }, 200);
                    });
                    countryPath.hover(function () {
                        shape.startHighlighting();
                    },function () {
                        shape.stopHighlighting();
                    }).click(function (event) {
                            event.stopPropagation();
                            that.options.clickHandler(shape);
                        });
                }, this);
                var world = this.paper.setFinish();
                this.$el.css({
                    width: '',
                    height: ''
                });

            }
        });

        var ShapeView = Backbone.View.extend({
            tmpl: Handlebars.compile('<p>\
    <span>Name:</span><strong>{{selectedCountry.name}}</strong>\
</p>\
<p class="country-shape">\
</p>\
<div class="country-value">{{selectedCountry.valueFormatted}}</div>\
<p>\
    <a ng-href="#/{{selectedCountry.code}}/more">Read more...</a>\
</p>'),

            events: {
                'click a': function (event) {
                    if(this.selectedCountry){
                        this.options.router.navigate(this.selectedCountry.get('code') + '/more', {trigger: true});
                    }
                }
            },

            initialize: function () {
                this.selectedCountry = null;
                this.model.bind('selectedCountryChanged', function (country) {
                    this.selectedCountry = country;
                    this.render(country.toJSON());
                }, this);
            },

            render: function (countryJson) {
                if (countryJson) {
                    this.$el.empty().html(this.tmpl({selectedCountry: countryJson}));
                    var svgContainer = this.$el.find('.country-shape');
                    var paper = Raphael(svgContainer[0]);
                    var shapePath = paper.path(countryJson.svgPath);
                    var bb = shapePath.getBBox();
                    shapePath.attr({
                        stroke: "#ccc6ae",
                        fill: countryJson.color,
                        'stroke-opacity': 0.9
                    });
                    paper.setViewBox(bb.x, bb.y, bb.width, bb.height, true);
                    return this;
                }
            }

        });



        return {
            MapView: MapView,
            ShapeView: ShapeView
        };
    });<file_sep>/angularjs/app/scripts/app.js
'use strict';

angular.module('confApp', ['ngResource'])
    .config(function ($routeProvider) {
        $routeProvider
            .when('/:countryCode', {
                templateUrl: 'views/main.html',
                controller: 'MainCtrl'
            })
            .when('/:countryCode/more/', {
                templateUrl: 'views/more.html',
                controller: 'MoreController'
            })
            .otherwise({
                redirectTo: '/'
            });
    });
<file_sep>/angularjs/app/scripts/controllers/table.js
(function(){
    var confApp = angular.module('confApp');

    confApp.controller('TableController', function($scope, countryData){

        $scope.countries = countryData.loadCountries();

        $scope.filterText = "";
        $scope.applyFiltering = function(){
            $scope.countries = countryData.loadCountries().filter(function(country){
                return country.matchesFilter($scope.filterText);
            });
        }.throttle(100);

        $scope.classForCountry = function(country){
            if(country.isSelected){
                return "country-selected";
            }
            if(country.isHighlighted){
                return "country-highlighted";
            }
            return "";
        };

    });
}());<file_sep>/backbonejs/app/scripts/main.js
/*global require*/
'use strict';

require.config({
    shim: {
        underscore: {
            exports: '_'
        },
        backbone: {
            deps: [
                'underscore',
                'jquery'
            ],
            exports: 'Backbone'
        },
        bootstrap: {
            deps: ['jquery'],
            exports: 'jquery'
        },
        confApp: {
            deps: ['backbone'],
            exports: 'confApp'
        }
    },
    paths: {
        jquery: '../bower_components/jquery/jquery',
        backbone: '../bower_components/backbone/backbone',
        underscore: '../bower_components/underscore/underscore',
        bootstrap: 'vendor/bootstrap',
        text : "../bower_components/requirejs-text/text",
        hbs : "../bower_components/require-handlebars-plugin/hbs",
        i18nprecompile: "../bower_components/require-handlebars-plugin/hbs/i18nprecompile",
        json2 : "../bower_components/require-handlebars-plugin/hbs/json2",
        handlebars : "../bower_components/require-handlebars-plugin/Handlebars",
        raphael: "../bower_components/raphael/raphael"
    },
    hbs: {
        disableI18n: true,
        templateExtension: "html"
    }
});

require([
    'backbone', 'confApp'
], function (Backbone, confApp) {

    confApp.router = new confApp.Router();

    Backbone.history.start();
});
<file_sep>/backbonejs/app/scripts/confApp.table.js
define('confApp.table',
        ['backbone', 'handlebars'],
function(Backbone, Handlebars){


    var TableView = Backbone.View.extend({

        events: {
            'keydown thead input, change thead input': 'filterByText'
        },

        filterText: '',
        filterByText: function () {
            this.filterText = this.$el.find('thead input').val();
            this.render();
        }.throttle(200),

        initialize: function () {
            this.rowTemplate = Handlebars.compile('<tr ng-repeat="country in countries" \
            class="country"> \
                <td>{{country.code}}</td> \
                <td><div class="country-name">{{country.name}}</div></td> \
            </tr>');
            this.model.bind('reset', this.render, this);
        },

        getClassForCountry: function (country) {
            var result = "country";
            if (country.get('isSelected')) {
                result += " country-selected";
            } else if (country.get('isHighlighted')) {
                result += " country-highlighted";
            }
            return result;
        },

        render: function () {
            var $body = this.$el.find('tbody');
            $body.empty();
            var that = this;

            this.model.filter(function (item) {
                return item.matchesFilter(this.filterText);
            }, this).forEach(function (item) {
                    var itemAsJson = item.toJSON();
                    var $row = $(this.rowTemplate({country: itemAsJson}))
                        .bind({
                            click: function () {
                                that.model.selectCountry(item);
                                that.options.router.navigate(item.get('code'), {trigger: true});
                            },
                            mouseenter: item.startHighlighting.bind(item),
                            mouseleave: item.stopHighlighting.bind(item)
                        });
                    item.bind('change:isSelected change:isHighlighted', function () {
                        $row.attr('class', this.getClassForCountry(item));
                    }, this);
                    $row.attr('class', this.getClassForCountry(item));
                    $body.append($row);
                }, this);


            return this;
        }
    });

    return {
        TableView: TableView
    };
});<file_sep>/angularjs/app/scripts/controllers/main.js
'use strict';

angular.module('confApp')
  .controller('MainCtrl', function ($scope, countryData, $route, $routeParams, $location) {
        var lastRoute = $route.current;
        $scope.$on('$locationChangeSuccess', function(event) {
            if(!$route.current.$$route.redirectTo){
                lastRoute.params.countryCode = $scope.selectedCountry.code;
                $route.current = lastRoute;
            }
        });

        $scope.selectCountry = function(country){
            countryData.selectCountry(country);
            $scope.selectedCountry = country;
            $location.path("/" + country.code);
        };

        $scope.selectCountryByCode = function(countryCode){
            countryData.onCountryWithCodeDo(countryCode, function(country){
                $scope.selectCountry(country);
            });
        };

        var countryCode = $routeParams.countryCode;
        if(countryCode){
            $scope.selectCountryByCode(countryCode);
        }


  });
<file_sep>/angularjs/app/scripts/controllers/more.js
(function(){
    var confApp = angular.module('confApp');

    confApp.controller('MoreController', function($scope, $routeParams, countryData){
        $scope.isLoading = true;
        countryData.onCountryWithCodeDo($routeParams.countryCode, function(country){
            $scope.selectedCountry = country;
            countryData.loadFullCountry(country.code).then(function(country){
                $scope.isLoading = false;
                $scope.fullCountry = country;
            });
        });

    });

}());<file_sep>/knockoutjs/app/map.js
define('map',
    ['knockout', 'raphael'],
    function (ko, Raphael) {


        var MapViewModel = function (countryData) {
            this.shapes = countryData.countries;

            this.clickCountry = function (country) {
                countryData.selectCountry(country);
            };
        };

        var MapView = function ($element, mapViewModel) {
            var dom = $element[0];
            ko.applyBindings(mapViewModel, dom);
            var constWidth = 1000;
            var constHeight = 400;
            var paper = Raphael(dom, constWidth, constHeight);
            var clickHandler = mapViewModel.clickCountry;

            function applySizeForAutoScale() {
                $element.css({
                    width: constWidth,
                    height: constHeight
                });
                paper.setViewBox(0, 0, $element.width(), $element.height(), true);
                var svg = document.querySelector("svg");
                svg.removeAttribute("width");
                svg.removeAttribute("height");
            }

            mapViewModel.shapes.subscribe(function (shapes) {
                applySizeForAutoScale();
                paper.setStart();
                shapes.forEach(function (shape) {
                    var stroke = shape.stroke();
                    var countryPath = paper.path(shape.svgPath)
                        .attr({
                            stroke: stroke.color,
                            fill: shape.color(),
                            'stroke-opacity': 0.9,
                            "stroke-width": stroke.width
                        });
                    shape.stroke.subscribe(function (newStroke, oldStroke) {
                        countryPath.stop().animate({
                            stroke: newStroke.color,
                            'stroke-width': newStroke.width
                        }, 200);
                    });
                    countryPath.hover(function () {
                        shape.startHighlighting();
                    },function () {
                        shape.stopHighlighting();
                    }).click(function (event) {
                            event.stopPropagation();
                            clickHandler(shape);
                        });
                });
                paper.setFinish();
                $element.css({
                    width: '',
                    height: ''
                });

            });

        };




        return {
            MapViewModel: MapViewModel,
            MapView: MapView
        };
    });<file_sep>/knockoutjs/app/table.js
define('table', ['knockout'], function(ko){

    var TableItem = function(country){
        this.country = country;
        this.name = country.name;
        this.classNames = ko.computed(function(){
            var result = "country";
            if(country.isSelected()){
                result += " country-selected";
            }
            if(country.isHighlighted()){
                result += " country-highlighted";
            }
            return result;
        });

        this.code = country.code;

        this.startHighlighting = function(){
            country.startHighlighting();
        };
        this.stopHighlighting = function(){
            country.stopHighlighting();
        };
    };

    var TableViewModel = function(countryData){
        var that = this;
        this.filterText = ko.observable('');
        this.filterText.subscribe(function(text){
            that.filteredCountries();
        }.throttle(100));

        this.filteredCountries = ko.computed(function(){
            var text = this.filterText();
            return countryData.loadCountries().filter(function(country){
                return country.matchesFilter(text);
            }).map(function(country){
                    var tableItem = new TableItem(country);
                    tableItem.selectCountry = function(){
                        countryData.selectCountry(country);
                    };
                    return  tableItem;
            });
        }, this);
    };

    return {
        TableViewModel: TableViewModel
    };

});<file_sep>/knockoutjs/app/main.js
// to depend on a bower installed component:
// define(['component/componentName/file'])

define(["jquery", "knockout", 'utils', "text!main.html", 'country', 'map', 'table'],
function ($, ko,utils, mainHtml, country, map, table) {

    country.countryData.loadCountries();

    var $container = $('.container');
    $container.empty().html(mainHtml);

    var mapViewModel = new map.MapViewModel(country.countryData);
    var mapView = new map.MapView($container.find('.map'), mapViewModel);

    var tableViewModel = new table.TableViewModel(country.countryData);
    ko.applyBindings(tableViewModel, $container.find('.table-container')[0]);
});
<file_sep>/Gruntfile.js
'use strict';
var path = require('path');

module.exports = function(grunt) {
	grunt.initConfig({
		complexity: {

		options: {
			halstead: 12
		},
			angularjs: {
				jsLintXML: 'angular-report.xml',
				src : ['./angularjs/app/scripts/**/*.js']
			},
			backbonejs: {
				jsLintXML: 'backbone-report.xml',
				src : ['./backbonejs/app/scripts/*.js']
			}
		}
	});
 	grunt.loadNpmTasks('grunt-complexity');


 	grunt.registerTask('default', ['complexity:angularjs', 'complexity:backbonejs']);
};