<commit_before>document.addEventListener("DOMContentLoaded", function() {
    window.smoothScrollTo = function() {
        var timer, start, factor;
        return function(target, duration) {
            var offset = window.pageYOffset, delta = target - window.pageYOffset;
            // Y-offset difference
            duration = duration || 700;
            // default 1 sec animation
            start = Date.now();
            // get start time
            factor = 0;
            if (timer) {
                clearInterval(timer);
            }
            function step() {
                var y;
                factor = (Date.now() - start) / duration;
                // get interpolation factor
                if (factor >= 1) {
                    clearInterval(timer);
                    // stop animation
                    factor = 1;
                }
                y = factor * delta + offset;
                window.scrollBy(0, y - window.pageYOffset);
            }
            timer = setInterval(step, 10);
            return timer;
        };
    }();
    var title = document.getElementById("title");
    title.addEventListener("click", function() {
        smoothScrollTo(0, 0);
    }, false);
    window.onload = function() {
        smoothScrollTo(0, 0);
    };
});//end of ready function

var app = angular.module("poolApp", []);

app.controller("MainController", function($scope) {
    $scope.poolbase = [ {
        title: "Google",
        tags: "googling tips",
        links: [ {
            name: "Google efficiently",
            href: "http://www.makeuseof.com/tag/google-power-user/"
        } ]
    },{
        title: "Technical Interview Questions",
        tags: "interviews questions tr technical awesome",
        links: [ {
            name: "Awesome Interviews",
            href: "https://github.com/MaximAbramchuck/awesome-interviews"
        } ]
    }, {
        title: "Html/Html5",
        tags: "markup",
        links: [ {
            name: "Introduction to HTML",
            href: "https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Introduction#History_of_HTML"
        }, {
            name: "Learn HTML",
            href: "http://learn.shayhowe.com/html-css/getting-to-know-html/"
        }, {
            name: "HTML5 in 5 mins",
            href: "https://msdn.microsoft.com/en-us/hh549253.aspx"
        }, {
            name: "HTML5-Canvas tutorial",
            href: "http://www.html5canvastutorials.com/"
        }, {
            name: "HTML5 + your browser compatibility",
            href: "https://html5test.com/"
        } ]
    }, {
        title: "Free Responsive Web Templates",
        tags: "website webpage",
        links: [ {
            name: "HTML5UP",
            href: "http://html5up.net/"
        }, {
            name: "Get Template",
            href: "http://www.gettemplate.com/"
        }, {
            name: "Foundation Templates",
            href: "http://foundation.zurb.com/templates.html"
        }, {
            name: "Bootstrap Templates",
            href: "http://startbootstrap.com/template-categories/all/"
        } ]
    }, {
        title: "Css/Css3",
        tags: "",
        links: [ {
            name: "Learn CSS",
            href: "http://learn.shayhowe.com/html-css/getting-to-know-css/"
        }, {
            name: "Learn Layout",
            href: "http://learnlayout.com/"
        }, {
            name: "CSS Properties",
            href: "http://www.cssportal.com/css-properties/index.php"
        }, {
            name: "30 CSS selectors to remember",
            href: "http://code.tutsplus.com/tutorials/the-30-css-selectors-you-must-memorize--net-16048"
        }, {
            name: "Game on CSS selectors",
            href: "http://flukeout.github.io/"
        } ]
    }, {
        title: "Choosing color for text",
        tags: "colors",
        links: [ {
            name: "Choose right color",
            href: "http://colorsafe.co/"
        }, {
            name: "Pick color online",
            href: "http://colourco.de/"
        } ]
    }, {
        title: "Css Generators",
        tags: "generator",
        links: [ {
            name: "Online CSS3 generator",
            href: "http://enjoycss.com/"
        }, {
            name: "CSS Matic",
            href: "http://www.cssmatic.com/"
        }, {
            name: "Generate CSS arrow",
            href: "http://cssarrowplease.com/"
        }, {
            name: "CSS Shapes",
            href: "https://css-tricks.com/examples/ShapesOfCSS/"
        }, {
            name: "CSS gradient generator",
            href: "http://www.colorzilla.com/gradient-editor/"
        } ]
    }, {
        title: "Css Frameworks",
        tags: "frameworks",
        links: [ {
            name: "Bootstrap",
            href: "http://getbootstrap.com/"
        }, {
            name: "Foundation",
            href: "http://foundation.zurb.com/"
        }, {
            name: "Metro UI",
            href: "http://metroui.org.ua/"
        }, {
            name: "Furtive-CSS micro framework",
            href: "http://furtive.co/"
        } ]
    }, {
        title: "JavaScript",
        tags: "js",
        links: [ {
            name: "All about JavaScript",
            href: "http://jstherightway.org/"
        }, {
            name: "Interactive way to learn JS",
            href: "http://www.codecademy.com/en/tracks/javascript"
        }, {
            name: "Learn Object oriented JavaScript",
            href: "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript"
        }, {
            name: "Javascript Char codes",
            href: "http://www.cambiaresearch.com/articles/15/javascript-char-codes-key-codes"
        } ]
    }, {
        title: "Javascript Libraries",
        tags: "js jquery",
        links: [ {
            name: "Interactive way to learn jQuery",
            href: "http://try.jquery.com/"
        } ]
    }, {
        title: "Javascript Frameworks",
        tags: "js",
        links: [ {
            name: "AngularJS in 60minutes",
            href: "http://fastandfluid.com/publicdownloads/AngularJSIn60MinutesIsh_DanWahlin_May2013.pdf"
        }, {
            name: "AngularJS examples",
            href: "http://tutorialzine.com/2013/08/learn-angularjs-5-examples/"
        } ]
    }, {
        title: "Regular Expressions",
        tags: "regex",
        links: [ {
            name: "Regexone - learn interactively",
            href: "http://regexone.com/"
        }, {
            name: "Online Regex builder",
            href: "http://regexr.com/"
        } ]
    }, {
        title: "Learn Command Line Interface",
        tags: "cli cmd",
        links: [ {
            name: "Learn CLI the hard way",
            href: "http://cli.learncodethehardway.org/book/"
        } ]
    }, {
        title: "Introduction to programming",
        tags: "beginner coding",
        links: [ {
            name: "Lets Play the game",
            href: "http://learn.code.org/hoc/1"
        } ]
    }, {
        title: "One stop for programming",
        tags: "code learn api",
        links: [ {
            name: "Learn X in Y minutes",
            href: "http://learnxinyminutes.com/"
        }, {
            name: "Api hub",
            href: "http://overapi.com/"
        } ]
    }, {
        title: "C",
        tags: "compiled",
        links: [ {
            name: "Learn C the hard way",
            href: "http://c.learncodethehardway.org/book/"
        }, {
            name: "Basic c programs",
            href: "http://www.programiz.com/c-programming/examples"
        } ]
    }, {
        title: "Python",
        tags: "interpreted",
        links: [ {
            name: "Learn python the hard way",
            href: "http://learnpythonthehardway.org/book/"
        }, {
            name: "Basic python programs",
            href: "http://www.programiz.com/python-programming/examples"
        } ]
    }, {
        title: "Ruby",
        tags: "interpreted",
        links: [ {
            name: "Learn ruby the hard way",
            href: "http://learnrubythehardway.org/book/"
        }, {
            name: "Interactive way to learn ruby",
            href: "http://tryruby.org/levels/1/challenges/0"
        } ]
    }, {
        title: "Git",
        tags: "version control system vcs",
        links: [ {
            name: "Git in 15mins",
            href: "https://try.github.io"
        } ]
    }, {
        title: "Online Tutorials",
        tags: "free online all science technology",
        links: [ {
            name: "Codecademy",
            href: "http://codecademy.com/"
        }, {
            name: "khanacademy",
            href: "https://www.khanacademy.org/"
        }, {
            name: "Codeschool",
            href: "https://www.codeschool.com/"
        }, {
            name: "Tutorials point",
            href: "http://tutorialspoint.com/"
        } ]
    }, {
        title: "Magazines for Web-development",
        tags: "web magazines smashing alistapart",
        links: [ {
            name: "Smashing magazine",
            href: "http://www.smashingmagazine.com/"
        }, {
            name: "Alistapart",
            href: "http://alistapart.com/topics"
        } ]
    }, {
        title: "Website Building",
        tags: "websites",
        links: [ {
            name: "Website creation guidelines by Google",
            href: "https://support.google.com/webmasters/answer/6001102"
        } ]
    }, {
        title: "Miscellaneous",
        tags: "grunt",
        links: [ {
            name: "Can I Use",
            href: "http://caniuse.com/"
        },{
            name: "Getting started with Grunt",
            href: "https://scotch.io/tutorials/a-simple-guide-to-getting-started-with-grunt"
        }, {
            name: "Grunt-Smashing Magazine",
            href: "http://www.smashingmagazine.com/2013/10/get-up-running-grunt/"
        }, {
            name: "Free e-books",
            href: "https://www.syncfusion.com/resources/techportal/ebooks"
        } ]
    } ];
    $scope.searchFilter = function(obj) {
        var re = new RegExp($scope.search, "i");
        return !$scope.search || re.test(obj.title) || re.test(obj.tags);
    };
});
<commit_msg>interview section title updated<commit_after>document.addEventListener("DOMContentLoaded", function() {
    window.smoothScrollTo = function() {
        var timer, start, factor;
        return function(target, duration) {
            var offset = window.pageYOffset, delta = target - window.pageYOffset;
            // Y-offset difference
            duration = duration || 700;
            // default 1 sec animation
            start = Date.now();
            // get start time
            factor = 0;
            if (timer) {
                clearInterval(timer);
            }
            function step() {
                var y;
                factor = (Date.now() - start) / duration;
                // get interpolation factor
                if (factor >= 1) {
                    clearInterval(timer);
                    // stop animation
                    factor = 1;
                }
                y = factor * delta + offset;
                window.scrollBy(0, y - window.pageYOffset);
            }
            timer = setInterval(step, 10);
            return timer;
        };
    }();
    var title = document.getElementById("title");
    title.addEventListener("click", function() {
        smoothScrollTo(0, 0);
    }, false);
    window.onload = function() {
        smoothScrollTo(0, 0);
    };
});//end of ready function

var app = angular.module("poolApp", []);

app.controller("MainController", function($scope) {
    $scope.poolbase = [ {
        title: "Google",
        tags: "googling tips",
        links: [ {
            name: "Google efficiently",
            href: "http://www.makeuseof.com/tag/google-power-user/"
        } ]
    },{
        title: "Technical Interview Q&A",
        tags: "interviews questions tr technical awesome",
        links: [ {
            name: "Awesome Interviews",
            href: "https://github.com/MaximAbramchuck/awesome-interviews"
        } ]
    }, {
        title: "Html/Html5",
        tags: "markup",
        links: [ {
            name: "Introduction to HTML",
            href: "https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Introduction#History_of_HTML"
        }, {
            name: "Learn HTML",
            href: "http://learn.shayhowe.com/html-css/getting-to-know-html/"
        }, {
            name: "HTML5 in 5 mins",
            href: "https://msdn.microsoft.com/en-us/hh549253.aspx"
        }, {
            name: "HTML5-Canvas tutorial",
            href: "http://www.html5canvastutorials.com/"
        }, {
            name: "HTML5 + your browser compatibility",
            href: "https://html5test.com/"
        } ]
    }, {
        title: "Free Responsive Web Templates",
        tags: "website webpage",
        links: [ {
            name: "HTML5UP",
            href: "http://html5up.net/"
        }, {
            name: "Get Template",
            href: "http://www.gettemplate.com/"
        }, {
            name: "Foundation Templates",
            href: "http://foundation.zurb.com/templates.html"
        }, {
            name: "Bootstrap Templates",
            href: "http://startbootstrap.com/template-categories/all/"
        } ]
    }, {
        title: "Css/Css3",
        tags: "",
        links: [ {
            name: "Learn CSS",
            href: "http://learn.shayhowe.com/html-css/getting-to-know-css/"
        }, {
            name: "Learn Layout",
            href: "http://learnlayout.com/"
        }, {
            name: "CSS Properties",
            href: "http://www.cssportal.com/css-properties/index.php"
        }, {
            name: "30 CSS selectors to remember",
            href: "http://code.tutsplus.com/tutorials/the-30-css-selectors-you-must-memorize--net-16048"
        }, {
            name: "Game on CSS selectors",
            href: "http://flukeout.github.io/"
        } ]
    }, {
        title: "Choosing color for text",
        tags: "colors",
        links: [ {
            name: "Choose right color",
            href: "http://colorsafe.co/"
        }, {
            name: "Pick color online",
            href: "http://colourco.de/"
        } ]
    }, {
        title: "Css Generators",
        tags: "generator",
        links: [ {
            name: "Online CSS3 generator",
            href: "http://enjoycss.com/"
        }, {
            name: "CSS Matic",
            href: "http://www.cssmatic.com/"
        }, {
            name: "Generate CSS arrow",
            href: "http://cssarrowplease.com/"
        }, {
            name: "CSS Shapes",
            href: "https://css-tricks.com/examples/ShapesOfCSS/"
        }, {
            name: "CSS gradient generator",
            href: "http://www.colorzilla.com/gradient-editor/"
        } ]
    }, {
        title: "Css Frameworks",
        tags: "frameworks",
        links: [ {
            name: "Bootstrap",
            href: "http://getbootstrap.com/"
        }, {
            name: "Foundation",
            href: "http://foundation.zurb.com/"
        }, {
            name: "Metro UI",
            href: "http://metroui.org.ua/"
        }, {
            name: "Furtive-CSS micro framework",
            href: "http://furtive.co/"
        } ]
    }, {
        title: "JavaScript",
        tags: "js",
        links: [ {
            name: "All about JavaScript",
            href: "http://jstherightway.org/"
        }, {
            name: "Interactive way to learn JS",
            href: "http://www.codecademy.com/en/tracks/javascript"
        }, {
            name: "Learn Object oriented JavaScript",
            href: "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript"
        }, {
            name: "Javascript Char codes",
            href: "http://www.cambiaresearch.com/articles/15/javascript-char-codes-key-codes"
        } ]
    }, {
        title: "Javascript Libraries",
        tags: "js jquery",
        links: [ {
            name: "Interactive way to learn jQuery",
            href: "http://try.jquery.com/"
        } ]
    }, {
        title: "Javascript Frameworks",
        tags: "js",
        links: [ {
            name: "AngularJS in 60minutes",
            href: "http://fastandfluid.com/publicdownloads/AngularJSIn60MinutesIsh_DanWahlin_May2013.pdf"
        }, {
            name: "AngularJS examples",
            href: "http://tutorialzine.com/2013/08/learn-angularjs-5-examples/"
        } ]
    }, {
        title: "Regular Expressions",
        tags: "regex",
        links: [ {
            name: "Regexone - learn interactively",
            href: "http://regexone.com/"
        }, {
            name: "Online Regex builder",
            href: "http://regexr.com/"
        } ]
    }, {
        title: "Learn Command Line Interface",
        tags: "cli cmd",
        links: [ {
            name: "Learn CLI the hard way",
            href: "http://cli.learncodethehardway.org/book/"
        } ]
    }, {
        title: "Introduction to programming",
        tags: "beginner coding",
        links: [ {
            name: "Lets Play the game",
            href: "http://learn.code.org/hoc/1"
        } ]
    }, {
        title: "One stop for programming",
        tags: "code learn api",
        links: [ {
            name: "Learn X in Y minutes",
            href: "http://learnxinyminutes.com/"
        }, {
            name: "Api hub",
            href: "http://overapi.com/"
        } ]
    }, {
        title: "C",
        tags: "compiled",
        links: [ {
            name: "Learn C the hard way",
            href: "http://c.learncodethehardway.org/book/"
        }, {
            name: "Basic c programs",
            href: "http://www.programiz.com/c-programming/examples"
        } ]
    }, {
        title: "Python",
        tags: "interpreted",
        links: [ {
            name: "Learn python the hard way",
            href: "http://learnpythonthehardway.org/book/"
        }, {
            name: "Basic python programs",
            href: "http://www.programiz.com/python-programming/examples"
        } ]
    }, {
        title: "Ruby",
        tags: "interpreted",
        links: [ {
            name: "Learn ruby the hard way",
            href: "http://learnrubythehardway.org/book/"
        }, {
            name: "Interactive way to learn ruby",
            href: "http://tryruby.org/levels/1/challenges/0"
        } ]
    }, {
        title: "Git",
        tags: "version control system vcs",
        links: [ {
            name: "Git in 15mins",
            href: "https://try.github.io"
        } ]
    }, {
        title: "Online Tutorials",
        tags: "free online all science technology",
        links: [ {
            name: "Codecademy",
            href: "http://codecademy.com/"
        }, {
            name: "khanacademy",
            href: "https://www.khanacademy.org/"
        }, {
            name: "Codeschool",
            href: "https://www.codeschool.com/"
        }, {
            name: "Tutorials point",
            href: "http://tutorialspoint.com/"
        } ]
    }, {
        title: "Magazines for Web-development",
        tags: "web magazines smashing alistapart",
        links: [ {
            name: "Smashing magazine",
            href: "http://www.smashingmagazine.com/"
        }, {
            name: "Alistapart",
            href: "http://alistapart.com/topics"
        } ]
    }, {
        title: "Website Building",
        tags: "websites",
        links: [ {
            name: "Website creation guidelines by Google",
            href: "https://support.google.com/webmasters/answer/6001102"
        } ]
    }, {
        title: "Miscellaneous",
        tags: "grunt",
        links: [ {
            name: "Can I Use",
            href: "http://caniuse.com/"
        },{
            name: "Getting started with Grunt",
            href: "https://scotch.io/tutorials/a-simple-guide-to-getting-started-with-grunt"
        }, {
            name: "Grunt-Smashing Magazine",
            href: "http://www.smashingmagazine.com/2013/10/get-up-running-grunt/"
        }, {
            name: "Free e-books",
            href: "https://www.syncfusion.com/resources/techportal/ebooks"
        } ]
    } ];
    $scope.searchFilter = function(obj) {
        var re = new RegExp($scope.search, "i");
        return !$scope.search || re.test(obj.title) || re.test(obj.tags);
    };
});
<|endoftext|>


<commit_before>'use strict';

var intlpnCtrl = function( $ionicModal, $scope, intlpnUtils ) {
    var self = $scope;
    self.intlpnHelper = intlpnUtils.getHelper( self.onlyCountry);

    self._updateDialCode = function(newDialCode) {
        var newNumber;

        // save having to pass this every time
        newDialCode = "+" + newDialCode;

        if (self.phone) {
          // if the previous number contained a valid dial code, replace it
          // (if more than just a plus character)
          var prevDialCode = self.intlpnHelper.getDialCode(self.phone);
          if (prevDialCode.length > 1) {
            newNumber = self.phone.replace(prevDialCode, newDialCode);
          } else {
            // if the previous number didn't contain a dial code, we should persist it
            // XXX: remove jquery
            var existingNumber = (self.phone.charAt(0) != "+") ? $.trim(self.phone) : "";
            newNumber = newDialCode + existingNumber;
          }
        } else {
          newNumber = newDialCode;
        }

        self.dialCode = newDialCode;
        self.phone = newNumber;
    };

    self.codeFromPhone = function( number ) {
        return self.intlpnHelper.getFlagFromNumber(number);
    };

    self.isValid = function( number ) {
        return intlTelInputUtils.isValidNumber(number);
    }
};

angular.module('intlpnIonic', ['ionic'])
.service('intlpnUtils', function() {
    var intlpnUtilsHelper = function( onlyCountry) {
        var self = this;
        self.countries = [];
        self.countryCodes = [];
        self.dialCodesByIso={};
        self.onlyCountry = onlyCountry||[];
        self._addCountryCode = function(iso2, dialCode, priority) {
            if( !(iso2 in self.dialCodesByIso) ) {
                self.dialCodesByIso[iso2] = dialCode;
            }
            if (!(dialCode in self.countryCodes)) {
                self.countryCodes[dialCode] = [];
            }
            var index = priority || 0;
            self.countryCodes[dialCode][index] = iso2;
        };
        self._initCountries = function() {
            //only countries
            if(  self.onlyCountry.length ) {
                angular.forEach( allCountries, function(value) {
                     angular.forEach( self.onlyCountry, function( restricted ) {
                         if( restricted === value.iso2 )  {
                             console.log( "select "+restricted );
                             self.countries.push( value );
                         }
                     });
                });
            } else {
                self.countries = allCountries;
            }
            for (i = 0; i < self.countries.length; i++) {
                  var c = self.countries[i];
                  self._addCountryCode(c.iso2, c.dialCode, c.priority);
                  // area codes
                  if (c.areaCodes) {
                    for (var j = 0; j < c.areaCodes.length; j++) {
                      // full dial code is country code + dial code
                      self._addCountryCode(c.iso2, c.dialCode + c.areaCodes[j]);
                    }
                  }
            };
        };
        self._getNumeric = function(s) {
            return s.replace(/\D/g, "");
        };
        self.getFlagFromNumber= function(number, updateDefault) {
            // try and extract valid dial code from input
            var dialCode = self.getDialCode(number);
            var countryCode = null;
            if (dialCode) {
              // check if one of the matching countries is already selected
              var codes = self.countryCodes[self._getNumeric(dialCode)];
              //alreadySelected = (this.selectedCountryData && $.inArray(this.selectedCountryData.iso2, countryCodes) != -1);
              // if a matching country is not already selected (or this is an unknown NANP area code): choose the first in the list
              //if (!alreadySelected || this._isUnknownNanp(number, dialCode)) {
                // if using onlyCountries option, countryCodes[0] may be empty, so we must find the first non-empty index
                for (var j = 0; j < codes.length; j++) {
                  if (codes[j]) {
                    countryCode = codes[j];
                    break;
                  }
                }
              //}
            } else if (number.charAt(0) == "+" && self._getNumeric(number).length) {
              // invalid dial code, so empty
              // Note: use getNumeric here because the number has not been formatted yet, so could contain bad shit
              countryCode = "";
            } else if (!number || number == "+") {
              // empty, or just a plus, so default
              countryCode = 'us';//this.options.defaultCountry.iso2;
            }

            if (countryCode !== null) {
              return countryCode;
            }
        };
        this.getDialCode= function(number) {
            var dialCode = "";
            // only interested in international numbers (starting with a plus)
            if (number.charAt(0) == "+") {
              var numericChars = "";
              // iterate over chars
              for (var i = 0; i < number.length; i++) {
                var c = number.charAt(i);
                // if char is number
                //XXX: replace by angular
                if ($.isNumeric(c)) {
                  numericChars += c;
                  // if current numericChars make a valid dial code
                  if (self.countryCodes[numericChars]) {
                    // store the actual raw string (useful for matching later)
                    dialCode = number.substr(0, i + 1);
                  }
                  // longest dial code is 4 chars
                  if (numericChars.length == 4) {
                    break;
                  }
                }
              }
            }
            return dialCode;
        }
    };
    return {
        getHelper: function( onlyCountry) {
            var helper = new intlpnUtilsHelper( onlyCountry);
            helper.allCountries = allCountries;
            helper._initCountries();
            return helper;
        }
    };
})
.directive('intlpnFormatter', function() {
    return {
        require: 'ngModel',
        link: function(scope, element, attrs, ngModelController) {
            var el = element[0];

            function clean(x) {
                return intlTelInputUtils.formatNumber( x );
            }

            ngModelController.$parsers.push(function(val) {
                var offset = 0;
                if( !/^\+/.test( val ) ) {
                    val = '+'+val;
                    offset = 1;
                }
                var cleaned = clean(val);

                // Avoid infinite loop of $setViewValue <-> $parsers
                var newStart = cleaned.length;
                var start = el.selectionStart + offset;
                var end = el.selectionEnd + offset;
                var selectionLength = end - start;
                var digitsOnTheRight = 0;
                for( var i = val.length; i >= start; i-- ) {
                    //console.log( val.charAt(i) );
                    if( /^\d$/.test( val.charAt(i) ) ) {
                        digitsOnTheRight ++;
                    }
                }
                while( digitsOnTheRight > 0 && newStart > 0 ) {
                    if( /^\d$/.test( cleaned.charAt( newStart - 1 ) ) ) {
                        digitsOnTheRight--;
                    }
                    newStart--;
                }
                //move to the left while space
                while(  /^[^0-9+]$/.test( cleaned.charAt( newStart - 1 ) ) && newStart > 0 )
                    newStart--;
                // element.val(cleaned) does not behave with
                // repeated invalid elements
                ngModelController.$setViewValue(cleaned);
                ngModelController.$render();
                el.setSelectionRange(newStart, newStart);
                return cleaned;
                //return val;
            });

        }
    }
})
.directive('intlpn', function( $ionicModal, $timeout ) {
    return {
        restrict: 'E',
        require: '^ngModel',
        scope: {
            ngModel: '=',
            placeholder: '@',
            defaultCountry: '@',
            onlyCountry: '='
        },
        controller: intlpnCtrl,
        link:function (scope, element, attrs, ngModelCtrl) {
            //When model value (outside world) is updated, set the view value (in ngModel directive)
            ngModelCtrl.$formatters.push(function(modelValue) {
                //from raw value to formatted value, parenthesis, dash, ...
                return  intlTelInputUtils.formatNumber(modelValue);
            });
            //from the value in ngModel directive to my directive
            ngModelCtrl.$render = function() {
                scope.phone = ngModelCtrl.$viewValue;
                scope.dialCode = scope.intlpnHelper.getDialCode( ngModelCtrl.$viewValue );
            };
            //from  view value (in ngModel directive) to model value (outside world)
            ngModelCtrl.$parsers.push(function(viewValue) {
                //clean everything that is not numeric or +
                return '+' + viewValue.replace(/[^0-9]/g, "");
            });
            //$setViewValue, from my directive to view value (in ngModel directive)
            scope.$watch('phone', function(newValue, oldValue) {
                ngModelCtrl.$setViewValue( scope.phone );
                if( scope.intlpnHelper.getDialCode(  scope.phone ) ) {
                    scope.dialCode = scope.intlpnHelper.getDialCode(  scope.phone );
                } else if( !scope.dialCode ) {
                    //default value
                    scope.dialCode = "+"+scope.intlpnHelper.dialCodesByIso[scope.defaultCountry];
                }
            });
            ngModelCtrl.$validators.validForm = function( modelValue, viewValue ) {
                //check if the cleaned value is correct
                return scope.isValid( modelValue );
            };
            //manage focus/blur of the phone field
            var input = element.find('input');
            input.bind('focus', function() {
                if( !scope.phone ) {
                    scope.$apply(function() {
                        scope.phone = scope.dialCode;
                        input[0].setSelectionRange( scope.dialCode.length );
                    });
                }
            })
            .bind('blur', function() {
                console.log('blur '+ scope.phone + " : " +  scope.dialCode);
                if( scope.phone === scope.dialCode || !scope.intlpnHelper.getDialCode(scope.phone) ) {
                    console.log("empty phone");
                    scope.$apply(function() {
                        scope.phone = '';
                    });
                }
            });
            var modalTemplate = '<ion-modal-view>' +
                '<ion-header-bar class="bar-positive">' +
                    '
Search
' +
                    '<button class="button button-clear icon ion-ios-close-empty" ng-click="modalScope.close()"></button>' +
                '</ion-header-bar>' +
                    '
' +
                        '
' +
                            '' +
                            '<input type="text" autocorrect="off" autocomplete="off" autocapitalize="off" spellcheck="false" placeholder="Search" ng-model="modalScope.pattern">' +
                            '' +
                        '
' +
                    '
' +
                 '<ion-content class="has-header has-subheader">' +
                    '<ion-list>' +
                        '<ion-item ng-repeat="country in modalScope.countries | filter:modalScope.pattern" ' +
                            'ng-click="modalScope.selectCountry( country.iso2, country.dialCode )" ' +
                            'class="item-icon-left" ng-class="(\'+\'+country.dialCode == modalScope.currentDialCode)?\'item-icon-right\':\'\'">' +
                                '' +
                                '{{::country.name}}' +
                                '' +
                        '</ion-item>' +
                    '</ion-list>' +
                '</ion-content>' +
                '</ion-modal-view>';
            scope.modalScope = {
                selectCountry: function( isocode, dialCode ) {
                    scope._updateDialCode( dialCode );
                    scope.modal.hide();
                    $timeout(function() { input.focus();});
                },
                close: function() {
                    scope.modal.hide();
                },
                countries: scope.intlpnHelper.countries
            };
            scope.modal = $ionicModal.fromTemplate( modalTemplate, {
                scope: scope
            });
            scope.pickCountry = function() {
                scope.modalScope.pattern = '';
                scope.modalScope.currentDialCode = scope.dialCode;
                scope.modal.show();
            };
        },
        replace:true,
        template: '
' +
                        ''+
                        '<input intlpn-formatter type="tel" placeholder="{{placeholder||\'test\'}}" ng-model="phone" >' +
                '
'
    };
})
<commit_msg>Improve country detection and changes<commit_after>'use strict';

var intlpnCtrl = function( $ionicModal, $scope, intlpnUtils ) {
    var self = $scope;
    self.intlpnHelper = intlpnUtils.getHelper( self.onlyCountry);

    self._updateDialCode = function(newDialCode) {
        var newNumber;

        // save having to pass this every time
        newDialCode = "+" + newDialCode;

        if (self.phone) {
          // if the previous number contained a valid dial code, replace it
          // (if more than just a plus character)
          var prevDialCode = self.intlpnHelper.getDialCode(self.phone);
          if (prevDialCode.length > 1) {
            newNumber = self.phone.replace(prevDialCode, newDialCode);
          } else {
            // if the previous number didn't contain a dial code, we should persist it
            // XXX: remove jquery
            var existingNumber = (self.phone.charAt(0) != "+") ? $.trim(self.phone) : "";
            newNumber = newDialCode + existingNumber;
          }
        } else {
          newNumber = newDialCode;
        }

        self.dialCode = newDialCode;
        self.phone = newNumber;
    };

    self.codeFromPhone = function( number ) {
        return self.intlpnHelper.getFlagFromNumber(number);
    };

    self.isValid = function( number ) {
        return intlTelInputUtils.isValidNumber(number);
    }
};

angular.module('intlpnIonic', ['ionic'])
.service('intlpnUtils', function() {
    var intlpnUtilsHelper = function( onlyCountry) {
        var self = this;
        self.countries = [];
        self.countryCodes = [];
        self.dialCodesByIso={};
        self.onlyCountry = onlyCountry||[];
        self._addCountryCode = function(iso2, dialCode, priority) {
            if( !(iso2 in self.dialCodesByIso) ) {
                self.dialCodesByIso[iso2] = dialCode;
            }
            if (!(dialCode in self.countryCodes)) {
                self.countryCodes[dialCode] = [];
            }
            var index = priority || 0;
            self.countryCodes[dialCode][index] = iso2;
        };
        self._initCountries = function() {
            //only countries
            if(  self.onlyCountry.length ) {
                angular.forEach( allCountries, function(value) {
                     angular.forEach( self.onlyCountry, function( restricted ) {
                         if( restricted === value.iso2 )  {
                             self.countries.push( value );
                         }
                     });
                });
            } else {
                self.countries = allCountries;
            }
            for (i = 0; i < self.countries.length; i++) {
                  var c = self.countries[i];
                  self._addCountryCode(c.iso2, c.dialCode, c.priority);
                  // area codes
                  if (c.areaCodes) {
                    for (var j = 0; j < c.areaCodes.length; j++) {
                      // full dial code is country code + dial code
                      self._addCountryCode(c.iso2, c.dialCode + c.areaCodes[j]);
                    }
                  }
            };
        };
        self._getNumeric = function(s) {
            return s.replace(/\D/g, "");
        };
        self.getFlagFromNumber= function(number, updateDefault) {
            // try and extract valid dial code from input
            var dialCode = self.getDialCode(number);
            var countryCode = null;
            if (dialCode) {
              // check if one of the matching countries is already selected
              var codes = self.countryCodes[self._getNumeric(dialCode)];
              //alreadySelected = (this.selectedCountryData && $.inArray(this.selectedCountryData.iso2, countryCodes) != -1);
              // if a matching country is not already selected (or this is an unknown NANP area code): choose the first in the list
              //if (!alreadySelected || this._isUnknownNanp(number, dialCode)) {
                // if using onlyCountries option, countryCodes[0] may be empty, so we must find the first non-empty index
                for (var j = 0; j < codes.length; j++) {
                  if (codes[j]) {
                    countryCode = codes[j];
                    break;
                  }
                }
              //}
            } else if (number.charAt(0) == "+" && self._getNumeric(number).length) {
              // invalid dial code, so empty
              // Note: use getNumeric here because the number has not been formatted yet, so could contain bad shit
              countryCode = "";
            } else if (!number || number == "+") {
              // empty, or just a plus, so default
              countryCode = 'us';//this.options.defaultCountry.iso2;
            }

            if (countryCode !== null) {
              return countryCode;
            }
        };
        this.getDialCode= function(number) {
            var dialCode = "";
            // only interested in international numbers (starting with a plus)
            if (number.charAt(0) == "+") {
              var numericChars = "";
              // iterate over chars
              for (var i = 0; i < number.length; i++) {
                var c = number.charAt(i);
                // if char is number
                //XXX: replace by angular
                if ($.isNumeric(c)) {
                  numericChars += c;
                  // if current numericChars make a valid dial code
                  if (self.countryCodes[numericChars]) {
                    // store the actual raw string (useful for matching later)
                    dialCode = number.substr(0, i + 1);
                  }
                  // longest dial code is 4 chars
                  if (numericChars.length == 4) {
                    break;
                  }
                }
              }
            }
            return dialCode;
        }
    };
    return {
        getHelper: function( onlyCountry) {
            var helper = new intlpnUtilsHelper( onlyCountry);
            helper.allCountries = allCountries;
            helper._initCountries();
            return helper;
        }
    };
})
.directive('intlpnFormatter', function() {
    return {
        require: 'ngModel',
        link: function(scope, element, attrs, ngModelController) {
            var el = element[0];

            function clean(x) {
                return intlTelInputUtils.formatNumber( x );
            }

            ngModelController.$parsers.push(function(val) {
                var offset = 0;
                if( !/^\+/.test( val ) ) {
                    val = '+'+val;
                    offset = 1;
                }
                var cleaned = clean(val);

                // Avoid infinite loop of $setViewValue <-> $parsers
                var newStart = cleaned.length;
                var start = el.selectionStart + offset;
                var end = el.selectionEnd + offset;
                var selectionLength = end - start;
                var digitsOnTheRight = 0;
                for( var i = val.length; i >= start; i-- ) {
                    if( /^\d$/.test( val.charAt(i) ) ) {
                        digitsOnTheRight ++;
                    }
                }
                while( digitsOnTheRight > 0 && newStart > 0 ) {
                    if( /^\d$/.test( cleaned.charAt( newStart - 1 ) ) ) {
                        digitsOnTheRight--;
                    }
                    newStart--;
                }
                //move to the left while space
                while(  /^[^0-9+]$/.test( cleaned.charAt( newStart - 1 ) ) && newStart > 0 )
                    newStart--;
                // element.val(cleaned) does not behave with
                // repeated invalid elements
                ngModelController.$setViewValue(cleaned);
                ngModelController.$render();
                el.setSelectionRange(newStart, newStart);
                return cleaned;
                //return val;
            });

        }
    }
})
.directive('intlpn', function( $ionicModal, $timeout ) {
    return {
        restrict: 'E',
        require: '^ngModel',
        scope: {
            ngModel: '=',
            placeholder: '@',
            defaultCountry: '@',
            onlyCountry: '='
        },
        controller: intlpnCtrl,
        link:function (scope, element, attrs, ngModelCtrl) {
            //When model value (outside world) is updated, set the view value (in ngModel directive)
            ngModelCtrl.$formatters.push(function(modelValue) {
                //from raw value to formatted value, parenthesis, dash, ...
                modelValue = modelValue.replace(/[^0-9]/g, "");
                return  modelValue?intlTelInputUtils.formatNumber('+'+modelValue):'';
            });
            //from the value in ngModel directive to my directive
            ngModelCtrl.$render = function() {
                scope.phone = ngModelCtrl.$viewValue;
                scope.dialCode = scope.intlpnHelper.getDialCode( ngModelCtrl.$viewValue );
                scope.isocode = scope.intlpnHelper.getFlagFromNumber( ngModelCtrl.$viewValue );

            };
            //from  view value (in ngModel directive) to model value (outside world)
            ngModelCtrl.$parsers.push(function(viewValue) {
                //clean everything that is not numeric or +
                viewValue = viewValue.replace(/[^0-9]/g, "");
                return viewValue?'+' + viewValue:'';
            });
            //$setViewValue, from my directive to view value (in ngModel directive)
            scope.$watch('phone', function(newValue, oldValue) {
                ngModelCtrl.$setViewValue( scope.phone );
                if( scope.intlpnHelper.getDialCode(  scope.phone ) ) {
                    scope.dialCode = scope.intlpnHelper.getDialCode(  scope.phone );
                    //from dialcode, validate current country
                    var countryCodes = scope.intlpnHelper.countryCodes[ scope.dialCode.replace(/[^0-9]/g, "") ];
                    var alreadySelected = (countryCodes.indexOf( scope.isocode ) > -1)?true:false;
                    if( !alreadySelected ) {
                        for (var j = 0; j < countryCodes.length; j++) {
                            if (countryCodes[j]) {
                                scope.isocode = countryCodes[j];
                                break;
                            }
                        }
                    }
                } else if( !scope.dialCode ) {
                    //default value
                    scope.dialCode = "+"+scope.intlpnHelper.dialCodesByIso[scope.defaultCountry];
                }
            });
            ngModelCtrl.$validators.validForm = function( modelValue, viewValue ) {
                //check if the cleaned value is correct
                return scope.isValid( modelValue );
            };
            //manage focus/blur of the phone field
            var input = element.find('input');
            input.bind('focus', function() {
                if( !scope.phone ) {
                    scope.$apply(function() {
                        scope.phone = scope.dialCode;
                        input[0].setSelectionRange( scope.dialCode.length );
                    });
                }
            })
            .bind('blur', function() {
                if( scope.phone === scope.dialCode || !scope.intlpnHelper.getDialCode(scope.phone) ) {
                    scope.$apply(function() {
                        scope.phone = '';
                    });
                }
            });
            var modalTemplate = '<ion-modal-view>' +
                '<ion-header-bar class="bar-positive">' +
                    '
Search
' +
                    '<button class="button button-clear icon ion-ios-close-empty" ng-click="modalScope.close()"></button>' +
                '</ion-header-bar>' +
                    '
' +
                        '
' +
                            '' +
                            '<input type="text" autocorrect="off" autocomplete="off" autocapitalize="off" spellcheck="false" placeholder="Search" ng-model="modalScope.pattern">' +
                            '' +
                        '
' +
                    '
' +
                 '<ion-content class="has-header has-subheader">' +
                    '<ion-list>' +
                        '<ion-item ng-repeat="country in modalScope.countries | filter:modalScope.pattern" ' +
                            'ng-click="modalScope.selectCountry( country )" ' +
                            'class="item-icon-left" ng-class="(country.iso2 == modalScope.currentCountry)?\'item-icon-right\':\'\'">' +
                                '' +
                                '{{::country.name}}' +
                                '' +
                        '</ion-item>' +
                    '</ion-list>' +
                '</ion-content>' +
                '</ion-modal-view>';
            scope.modalScope = {
                selectCountry: function( country ) {
                    scope.isocode = country.iso2;
                    scope._updateDialCode( country.dialCode );
                    scope.modal.hide();
                    $timeout(function() { input.focus();});
                },
                close: function() {
                    scope.modal.hide();
                },
                countries: scope.intlpnHelper.countries
            };
            scope.modal = $ionicModal.fromTemplate( modalTemplate, {
                scope: scope
            });
            scope.pickCountry = function() {
                scope.modalScope.pattern = '';
                scope.modalScope.currentCountry = scope.isocode;
                scope.modal.show();
            };
        },
        replace:true,
        template: '
' +
                        ''+
                        '<input intlpn-formatter type="tel" placeholder="{{placeholder||\'test\'}}" ng-model="phone" >' +
                '
'
    };
})
<|endoftext|>


<commit_before>---
# Do not remove`---` tags.These are the frontmatter tags for Jekyll variables.
---

/*
 * Copyright 2020, TeamDev. All rights reserved.
 *
 * Redistribution and use in source and/or binary forms, with or without
 * modification, must retain the above copyright notice and the following
 * disclaimer.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * This script contains helper functions for the agreement checkboxes on `Getting Help` page.
 *
 * Please see `/getting-help/service-section.html` for usage.
 */
'use strict';

$(
    function () {
        const $privacyConsent = $('#privacyConsent');
        const $supportAgreementConsent = $('#supportAgreementConsent');
        const $orderButton = $('#order-now-btn');
        const $orderButtonHolder = $('.pricing-btn-holder');
        const $redirectScreen = $('#redirectScreen');
        const $loader = $redirectScreen.find('.redirect-loader');
        const $errorContainer = $redirectScreen.find('.redirect-error');
        const $linkBack = $redirectScreen.find('#linkBack');
        const $consentCheckboxes = $("input[type='checkbox'].consent");
        const disabledBtnTitle = 'Read and agree to the terms to\xA0continue';

        const orderUrl = "{{site.data.payment_config.orderUrl}}";

        const apiUrl = getApiUrl();

        $orderButtonHolder.attr('data-original-title', disabledBtnTitle);

        $consentCheckboxes.change(() => {
            changeElementState($orderButton, $orderButtonHolder, isConsentObtained());
        });

        $orderButton.click(() => {
            if (isConsentObtained()) {
                submitOrder();
            }
        });

        $linkBack.click(e => {
            e.preventDefault();
            hideRedirect();
        });

        /**
         * Returns transaction API Url.
         *
         * @return {string} returns development or production API Url based on jekyll environment
         */
        function getApiUrl() {
            if ("{{jekyll.environment}}" === "development") {
                return "{{site.data.payment_config.devApiUrl}}";
            } else {
                return "{{site.data.payment_config.prodApiUrl}}";
            }
        }

        /**
         * Checks if all consent checkboxes are checked.
         *
         * @return {boolean} `true` if all checkboxes are checked, `false` otherwise
         */
        function isConsentObtained() {
            return $consentCheckboxes.length == $consentCheckboxes.filter(":checked").length;
        }

        /**
         * Changes element's disabled/enabled state.
         *
         * @param {jQuery} element the element to change the state for
         * @param {jQuery} elementHolder the element holder to apply or hide a tooltip
         * @param {boolean} enable denotes whether the element should be enabled.
         * If `true` — enables the element, otherwise — disables
         */
        function changeElementState(element, elementHolder, enable) {
            if (enable) {
                element.removeAttr('disabled');
                element.removeClass('disabled');
                elementHolder.removeAttr('data-original-title');
            } else {
                element.attr('disabled', true);
                element.addClass('disabled');
                elementHolder.attr('data-original-title', disabledBtnTitle);
            }
        }

        /**
         * Submits consent transaction.
         *
         *
Prepares Privacy, Support Agreement consents data and transaction Url for sending and
         * calls send transaction function.
         */
        function submitOrder() {
            const privacyConsent = $privacyConsent.prop("checked");
            const supportAgreementConsent = $supportAgreementConsent.prop("checked");
            const data = JSON.stringify({privacyConsent, supportAgreementConsent});
            const transactionUrl = `${apiUrl}/transaction`;
            sendPaymentTransaction(transactionUrl, data);
        }

        /**
         * The user's consent.
         *
         *
The user's consents for services and data processing flows.
         *
         * @typedef {Object} Consent
         * @property {boolean} privacyConsent indicates whether the user gives consent to
         * process his personal information
         * @property {boolean} supportAgreementConsent indicates whether the user agrees to be bound by the
         * terms of the Development Support Agreement
         */

        /**
         * Generates the payment processing transaction.
         *
         *
Sends the transaction data and returns the transaction ID. If the request is successful,
         * redirects to the Payment screen.
         *
         * @param {string} transactionUrl the transaction API URL
         * @param {Consent} data the Consent transaction data
         */
        function sendPaymentTransaction(transactionUrl, data) {
            showRedirect();
            $.ajax(transactionUrl, {
                type: 'POST',
                data: data,
                contentType: 'application/json',
                success: (data) => {
                    const obj = JSON.parse(data);
                    const paymentUrl = `${orderUrl}&CUSTOMERID=${obj.id}`;
                    window.location = paymentUrl;
                    hideRedirect();
                },
                error: (jqXhr) => {
                    console.error(`${jqXhr.status}: ${jqXhr.statusText}`);
                    showErrorMessage();
                }
            });
        }

        /**
         * Shows the redirect screen.
         */
        function showRedirect() {
            $errorContainer.hide();
            $loader.show();
            $redirectScreen.show();
        }

        /**
         * Hides the loader and shows the error message.
         */
        function showErrorMessage() {
            $loader.hide();
            $errorContainer.show();
        }

        /**
         *  Hides the redirect screen.
         */
        function hideRedirect() {
            $redirectScreen.hide();
        }
    }
);
<commit_msg>Find button inside the element<commit_after>---
# Do not remove`---` tags.These are the frontmatter tags for Jekyll variables.
---

/*
 * Copyright 2020, TeamDev. All rights reserved.
 *
 * Redistribution and use in source and/or binary forms, with or without
 * modification, must retain the above copyright notice and the following
 * disclaimer.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * This script contains helper functions for the agreement checkboxes on `Getting Help` page.
 *
 * Please see `/getting-help/service-section.html` for usage.
 */
'use strict';

$(
    function () {
        const $privacyConsent = $('#privacyConsent');
        const $supportAgreementConsent = $('#supportAgreementConsent');
        const $orderButtonHolder = $('.pricing-btn-holder');
        const $orderButton = $('#order-now-btn');
        const $redirectScreen = $('#redirectScreen');
        const $loader = $redirectScreen.find('.redirect-loader');
        const $errorContainer = $redirectScreen.find('.redirect-error');
        const $linkBack = $redirectScreen.find('#linkBack');
        const $consentCheckboxes = $("input[type='checkbox'].consent");
        const disabledBtnTitle = 'Read and agree to the terms to\xA0continue';

        const orderUrl = "{{site.data.payment_config.orderUrl}}";

        const apiUrl = getApiUrl();

        $orderButtonHolder.attr('data-original-title', disabledBtnTitle);

        $consentCheckboxes.change(() => {
            changeElementState($orderButtonHolder, isConsentObtained());
        });

        $orderButton.click(() => {
            if (isConsentObtained()) {
                submitOrder();
            }
        });

        $linkBack.click(e => {
            e.preventDefault();
            hideRedirect();
        });

        /**
         * Returns transaction API Url.
         *
         * @return {string} returns development or production API Url based on jekyll environment
         */
        function getApiUrl() {
            if ("{{jekyll.environment}}" === "development") {
                return "{{site.data.payment_config.devApiUrl}}";
            } else {
                return "{{site.data.payment_config.prodApiUrl}}";
            }
        }

        /**
         * Checks if all consent checkboxes are checked.
         *
         * @return {boolean} `true` if all checkboxes are checked, `false` otherwise
         */
        function isConsentObtained() {
            return $consentCheckboxes.length == $consentCheckboxes.filter(":checked").length;
        }

        /**
         * Changes element's disabled/enabled state.
         *
         * @param {jQuery} element the element to change the state for
         * @param {boolean} enable denotes whether the element should be enabled.
         * If `true` — enables the element, otherwise — disables
         */
        function changeElementState(element, enable) {
            if (enable) {
                element.find($orderButton).removeAttr('disabled');
                element.find($orderButton).removeClass('disabled');
                element.removeAttr('data-original-title');
            } else {
                element.find($orderButton).attr('disabled', true);
                element.find($orderButton).addClass('disabled');
                element.attr('data-original-title', disabledBtnTitle);
            }
        }

        /**
         * Submits consent transaction.
         *
         *
Prepares Privacy, Support Agreement consents data and transaction Url for sending and
         * calls send transaction function.
         */
        function submitOrder() {
            const privacyConsent = $privacyConsent.prop("checked");
            const supportAgreementConsent = $supportAgreementConsent.prop("checked");
            const data = JSON.stringify({privacyConsent, supportAgreementConsent});
            const transactionUrl = `${apiUrl}/transaction`;
            sendPaymentTransaction(transactionUrl, data);
        }

        /**
         * The user's consent.
         *
         *
The user's consents for services and data processing flows.
         *
         * @typedef {Object} Consent
         * @property {boolean} privacyConsent indicates whether the user gives consent to
         * process his personal information
         * @property {boolean} supportAgreementConsent indicates whether the user agrees to be bound by the
         * terms of the Development Support Agreement
         */

        /**
         * Generates the payment processing transaction.
         *
         *
Sends the transaction data and returns the transaction ID. If the request is successful,
         * redirects to the Payment screen.
         *
         * @param {string} transactionUrl the transaction API URL
         * @param {Consent} data the Consent transaction data
         */
        function sendPaymentTransaction(transactionUrl, data) {
            showRedirect();
            $.ajax(transactionUrl, {
                type: 'POST',
                data: data,
                contentType: 'application/json',
                success: (data) => {
                    const obj = JSON.parse(data);
                    const paymentUrl = `${orderUrl}&CUSTOMERID=${obj.id}`;
                    window.location = paymentUrl;
                    hideRedirect();
                },
                error: (jqXhr) => {
                    console.error(`${jqXhr.status}: ${jqXhr.statusText}`);
                    showErrorMessage();
                }
            });
        }

        /**
         * Shows the redirect screen.
         */
        function showRedirect() {
            $errorContainer.hide();
            $loader.show();
            $redirectScreen.show();
        }

        /**
         * Hides the loader and shows the error message.
         */
        function showErrorMessage() {
            $loader.hide();
            $errorContainer.show();
        }

        /**
         *  Hides the redirect screen.
         */
        function hideRedirect() {
            $redirectScreen.hide();
        }
    }
);
<|endoftext|>


<commit_before>(function(exports){
  $(document).ready(function(){
    $('.modal-trigger').leanModal();
  });

  const fakeDataMode = false;
  const CHART_FORMAT = '';
  const CONTRIBUTOR_MARKUP ='

${name}

';

  var projectId = $.url().param('id');
  var latestSensors;

  var gMap;
  var markerMap = new Map();

  var ctx = $("#sensor-data-chart").get(0).getContext("2d");

  var dataChartContainer = document.getElementById("sensor-data-chart-container");
  var dataChart;
  var chartName = $('#sensor-information .name');
  var chartDescription = $('#sensor-information .description');
  var chartValue = $('#sensor-information .value');
  var chartLatestUpdate = $('#sensor-information .latest-update');
  var chartCloseBtn = $('#chart-close-btn');

  chartCloseBtn.click(function () {
    dataChartContainer.classList.add('hide');
    dataChart && dataChart.destroy();
  });

  $(window).load(function handleClientLoad() {
    var auth = gapi.auth2.getAuthInstance();

    if (!auth) {
      return;
    }

    var loginBtn = $('#login-btn');
    var accountBtn = $('#account-btn');

    function btnState(isSignedIn) {
      if (isSignedIn) {
        var email = auth.currentUser.get().getBasicProfile().getEmail();
        $.ajax({
          url: API_URL + 'users?email=' + email,
          dataType: 'jsonp'
        })
        .done(function(result) {
          loginBtn.addClass('hide');
          accountBtn.removeClass('hide');
          accountBtn.attr('href', 'user-detail.html?userId=' + result.id);
        })
        .fail(function(err) {
          console.error(err)
        });
      } else {
        accountBtn.addClass('hide');
        loginBtn.removeClass('hide');
      }
    }

    btnState(auth.isSignedIn.get() || auth.currentUser.get().isSignedIn());
    auth.isSignedIn.listen(btnState);
  });

  function onSignIn(googleUser) {
    var userId = $('#user-id').val();

    if (!userId) {
      return;
    }

    var auth = googleUser.getAuthResponse();
    var profile = googleUser.getBasicProfile();
    var userData = {
      // token: auth.access_token, Do we really need this?
      id: userId,
      name: profile.getName(),
      email: profile.getEmail(),
      picture: profile.getImageUrl()
    };
    //TODO: create user in DB and get user profile

    $('#user-id').text();
    $('#google-sign-in-modal').closeModal();

    $.ajax({
      type: 'POST',
      url: API_URL + 'users',
      data: userData,
      dataType: 'jsonp'
    })
    .done(function() {
      window.location = 'user-detail.html?userId=' + userId;
    })
    .fail(function(err) {
      console.error(err)
    });
  }

  function dataConvertion(dataArray) {
    var config = {
      type: 'line',
      data: {
        datasets: [{
          label: "PM2.5 value",
    			 pointBorderWidth: .2,
          fill: false,
          data: dataArray.slice(dataArray.length-30,dataArray.length).map(function(d) {
            return { x: moment(d.datetime).format(CHART_FORMAT), y: d.pm25Index };
          })
        }]
      },
      options: {
        responsive: true,
        scales: {
          xAxes: [{
            type: "time",
            display: true,
            scaleLabel: {
              display: true,
              // labelString: 'Time'
            }
          }, ],
          yAxes: [{
            display: true,
            scaleLabel: {
              display: true,
              labelString: 'PM2.5 index(μg/m)'
            }
          }]
        }
      }
    };
    return config;
  }

  function updateMap(sensors) {
    if (!gMap || !sensors) {
      return;
    }

    sensors.forEach(function(sensor, index) {
      if (markerMap.has(sensor._id)) {
        return;
      }

      var coords = sensor.coords;
      var gMapMarker = new google.maps.Marker({
        position: { lat: Number(coords.lat), lng: Number(coords.lng) },
        map: gMap,
        title: sensor.name,
        zIndex: index + 1
      });

      gMapMarker.addListener('click', function() {
        chartName.html('' + sensor.name + '');
        chartDescription.text(sensor.description);
        chartValue.text(sensor.pm25Index);
        chartLatestUpdate.text(new Date(sensor.latestUpdate).toString());
        dataChartContainer.classList.remove('hide');

        if (fakeDataMode) {
          var fakeArray = [];
          for (var i=100; i>0; i--) {
            fakeArray.push({
              datetime: Date.now() - i * 60000,
              pm25Index: Math.random() * 100
            })
          }
          dataChart = new Chart(ctx, dataConvertion(fakeArray));
          // $('#sensor-information').height($('#sensor-chart').height());
          return;
        }

        $.ajax({
          url: API_URL + 'sensors/' + sensor._id + '/data',
          dataType: 'jsonp'
        })
        .done(function(dataArray) {
          dataChart = new Chart(ctx, dataConvertion(dataArray));
        })
        .fail(function(error) {
          console.error(error);
        });
      });

      markerMap.set(sensor._id, gMapMarker);
    });
  }

  function renderContributorList(contributors) {
    $.tmpl(CONTRIBUTOR_MARKUP, contributors).appendTo("#contributor-list");
  }

  function initMap() {
    // mapAPIReady = true;
    //
    // if (!latestSensorData) {
    //   return;
    // }
    // TODO: var location = latestSensorData.location;
    var location = {lat: 25.032506, lng: 121.5674536};

    gMap = new google.maps.Map(document.getElementById('sensors-location-map'), {
      zoom: 14,
      center: location,
      scrollwheel: false
    });

    if (fakeDataMode) {
      latestSensors = [
        {
          id: 1,
          name: 'Test 1',
          description: 'Test 1\'s data',
          location: {lat: 25.032506, lng: 121.5674536},
          pm25Index: Math.random()*100,
          latestUpdate: Date.now()
        },
        {
          id: 2,
          name: 'Test 2',
          description: 'Test 2\'s data',
          location: {lat: 25.0, lng: 121.6674536},
          pm25Index: Math.random()*100,
          latestUpdate: Date.now()
        },
        {
          id: 3,
          name: 'Test 3',
          description: 'Test 3\'s data',
          location: {lat: 25.05, lng: 121.5},
          pm25Index: Math.random()*100,
          latestUpdate: Date.now()
        }
      ];
    }

    updateMap(latestSensors);
  }

  // Fetch project detail, should set project ID as parameter
  $.ajax({
    url: API_URL + 'projects/sensorweb/pm25',
    dataType: 'jsonp'
  })
  .done(function(project) {
    $('#pm25 .description').text(project.description);
    $('#pm25 .creator').text(project.creator.name);
    // $('#pm25 .last-update').text(project.detail);
    $('#pm25 .created-date').text(project.createDate);
  })
  .fail(function(error) {
    console.error(error);
  });

  // Fetch sensor list
  $.ajax({
    url: API_URL + 'projects/sensorweb/pm25/sensors',
    dataType: 'jsonp'
  })
  .done(function(sensors) {
    latestSensors = sensors;
    updateMap(latestSensors);
  })
  .fail(function(error) {
    console.error(error);
  });

  // Fetch user list, should set project ID as parameter
  $.ajax({
    url: API_URL + 'projects/sensorweb/pm25/contributors',
    dataType: 'jsonp'
  })
  .done(function(contributors) {
    $('#pm25 .contributors').text(contributors.length);
    renderContributorList(contributors);
  })
  .fail(function(error) {
    console.error(error);
  });

  exports.initMap = initMap;
  exports.onSignIn = onSignIn;

})(window);
<commit_msg>Bug 1266015 - Polish time issue and fix chart bug<commit_after>(function(exports){
  $(document).ready(function(){
    $('.modal-trigger').leanModal();
  });

  const fakeDataMode = false;
  const CHART_FORMAT = 'LLL';
  const CONTRIBUTOR_MARKUP ='

${name}

';

  var projectId = $.url().param('id');
  var latestSensors;

  var gMap;
  var markerMap = new Map();

  var ctx = $("#sensor-data-chart").get(0).getContext("2d");

  var dataChartContainer = document.getElementById("sensor-data-chart-container");
  var dataChart;
  var chartName = $('#sensor-information .name');
  var chartDescription = $('#sensor-information .description');
  var chartValue = $('#sensor-information .value');
  var chartLatestUpdate = $('#sensor-information .latest-update');
  var chartCloseBtn = $('#chart-close-btn');

  chartCloseBtn.click(function () {
    dataChartContainer.classList.add('hide');
    dataChart && dataChart.destroy();
  });

  $(window).load(function handleClientLoad() {
    var auth = gapi.auth2.getAuthInstance();

    if (!auth) {
      return;
    }

    var loginBtn = $('#login-btn');
    var accountBtn = $('#account-btn');

    function btnState(isSignedIn) {
      if (isSignedIn) {
        var email = auth.currentUser.get().getBasicProfile().getEmail();
        $.ajax({
          url: API_URL + 'users?email=' + email,
          dataType: 'jsonp'
        })
        .done(function(result) {
          loginBtn.addClass('hide');
          accountBtn.removeClass('hide');
          accountBtn.attr('href', 'user-detail.html?userId=' + result.id);
        })
        .fail(function(err) {
          console.error(err)
        });
      } else {
        accountBtn.addClass('hide');
        loginBtn.removeClass('hide');
      }
    }

    btnState(auth.isSignedIn.get() || auth.currentUser.get().isSignedIn());
    auth.isSignedIn.listen(btnState);
  });

  function onSignIn(googleUser) {
    var userId = $('#user-id').val();

    if (!userId) {
      return;
    }

    var auth = googleUser.getAuthResponse();
    var profile = googleUser.getBasicProfile();
    var userData = {
      // token: auth.access_token, Do we really need this?
      id: userId,
      name: profile.getName(),
      email: profile.getEmail(),
      picture: profile.getImageUrl()
    };
    //TODO: create user in DB and get user profile

    $('#user-id').text();
    $('#google-sign-in-modal').closeModal();

    $.ajax({
      type: 'POST',
      url: API_URL + 'users',
      data: userData,
      dataType: 'jsonp'
    })
    .done(function() {
      window.location = 'user-detail.html?userId=' + userId;
    })
    .fail(function(err) {
      console.error(err)
    });
  }

  function dataConvertion(dataArray) {
    var config = {
      type: 'line',
      data: {
        datasets: [{
          label: "PM2.5 value",
    			 pointBorderWidth: .2,
          fill: false,
          data: dataArray.map(function(d) {
            return { x: moment(d.datetime).format(CHART_FORMAT), y: d.pm25Index };
          })
        }]
      },
      options: {
        responsive: true,
        scales: {
          xAxes: [{
            type: "time",
            display: true,
            scaleLabel: {
              display: true,
              // labelString: 'Time'
            }
          }, ],
          yAxes: [{
            display: true,
            scaleLabel: {
              display: true,
              labelString: 'PM2.5 index(μg/m)'
            }
          }]
        }
      }
    };
    return config;
  }

  function updateMap(sensors) {
    if (!gMap || !sensors) {
      return;
    }

    sensors.forEach(function(sensor, index) {
      if (markerMap.has(sensor._id)) {
        return;
      }

      var coords = sensor.coords;
      var gMapMarker = new google.maps.Marker({
        position: { lat: Number(coords.lat), lng: Number(coords.lng) },
        map: gMap,
        title: sensor.name,
        zIndex: index + 1
      });

      gMapMarker.addListener('click', function() {
        chartName.html('' + sensor.name + '');
        chartDescription.text(sensor.description);
        chartValue.text(sensor.pm25Index);
        chartLatestUpdate.text(moment(sensor.latestUpdate).fromNow());
        dataChartContainer.classList.remove('hide');

        if (fakeDataMode) {
          var fakeArray = [];
          for (var i=100; i>0; i--) {
            fakeArray.push({
              datetime: Date.now() - i * 60000,
              pm25Index: Math.random() * 100
            })
          }
          dataChart = new Chart(ctx, dataConvertion(fakeArray));
          // $('#sensor-information').height($('#sensor-chart').height());
          return;
        }

        $.ajax({
          url: API_URL + 'sensors/' + sensor._id + '/data',
          dataType: 'jsonp'
        })
        .done(function(dataArray) {
          dataChart = new Chart(ctx, dataConvertion(dataArray));
        })
        .fail(function(error) {
          console.error(error);
        });
      });

      markerMap.set(sensor._id, gMapMarker);
    });
  }

  function renderContributorList(contributors) {
    $.tmpl(CONTRIBUTOR_MARKUP, contributors).appendTo("#contributor-list");
  }

  function initMap() {
    // mapAPIReady = true;
    //
    // if (!latestSensorData) {
    //   return;
    // }
    // TODO: var location = latestSensorData.location;
    var location = {lat: 25.032506, lng: 121.5674536};

    gMap = new google.maps.Map(document.getElementById('sensors-location-map'), {
      zoom: 14,
      center: location,
      scrollwheel: false
    });

    if (fakeDataMode) {
      latestSensors = [
        {
          id: 1,
          name: 'Test 1',
          description: 'Test 1\'s data',
          location: {lat: 25.032506, lng: 121.5674536},
          pm25Index: Math.random()*100,
          latestUpdate: Date.now()
        },
        {
          id: 2,
          name: 'Test 2',
          description: 'Test 2\'s data',
          location: {lat: 25.0, lng: 121.6674536},
          pm25Index: Math.random()*100,
          latestUpdate: Date.now()
        },
        {
          id: 3,
          name: 'Test 3',
          description: 'Test 3\'s data',
          location: {lat: 25.05, lng: 121.5},
          pm25Index: Math.random()*100,
          latestUpdate: Date.now()
        }
      ];
    }

    updateMap(latestSensors);
  }

  // Fetch project detail, should set project ID as parameter
  $.ajax({
    url: API_URL + 'projects/sensorweb/pm25',
    dataType: 'jsonp'
  })
  .done(function(project) {
    $('#pm25 .description').text(project.description);
    $('#pm25 .creator').text(project.creator.name);
    // $('#pm25 .last-update').text(project.detail);
    $('#pm25 .created-date').text(moment(project.createDate).format('LL'));
  })
  .fail(function(error) {
    console.error(error);
  });

  // Fetch sensor list
  $.ajax({
    url: API_URL + 'projects/sensorweb/pm25/sensors',
    dataType: 'jsonp'
  })
  .done(function(sensors) {
    latestSensors = sensors;
    updateMap(latestSensors);
  })
  .fail(function(error) {
    console.error(error);
  });

  // Fetch user list, should set project ID as parameter
  $.ajax({
    url: API_URL + 'projects/sensorweb/pm25/contributors',
    dataType: 'jsonp'
  })
  .done(function(contributors) {
    $('#pm25 .contributors').text(contributors.length);
    renderContributorList(contributors);
  })
  .fail(function(error) {
    console.error(error);
  });

  exports.initMap = initMap;
  exports.onSignIn = onSignIn;

})(window);
<|endoftext|>


<commit_before>'use strict';

var I = require('immutable');
var DS = require('./delaney');
var Partition = require('../common/partition');

var _fold = function _fold(partition, a, b, matchP, spreadFn) {
  var p = partition;
  var q = I.List().push(I.List([a, b]));

  while (!q.isEmpty()) {
    var _tmp = q.first();
    var x = _tmp.get(0);
    var y = _tmp.get(1);

    q = q.rest();

    if (!matchP(x, y))
      return;
    else if (p.get(x) == p.get(y))
      continue;
    else {
      p = p.union(x, y);
      q = q.concat(I.List(spreadFn(x, y)).map(I.List));
    }
  }

  return p;
};

var _typeMap = function _typeMap(ds) {
  var base = I.Map(ds.elements().map(function(D) {
    return [D, I.List()];
  }));
  var idcs = DS.indices(ds);

  return base.withMutations(function(map) {
    idcs.zip(idcs.rest()).forEach(function(p) {
      var i = p[0];
      var j = p[1];

      DS.orbitReps2(ds, i, j).forEach(function(D) {
        var m = DS.m(ds, i, j, D);
        DS.orbit2(ds, i, j, D).forEach(function(E) {
          map.set(E, map.get(E).push(m));
        });
      });
    });
  });
};

var isMinimal = function isMinimal(ds) {
  var D0 = ds.elements().first();
  var tm = _typeMap(ds);

  var match = function(D, E) { return tm.get(D).equals(tm.get(E)); };
  var spread = function(D, E) {
    return ds.indices().map(function(i) {
      return [ds.s(i, D), ds.s(i, E)];
    });
  };

  return ds.elements().rest().every(function(D) {
    return _fold(Partition(), D0, D, match, spread) === undefined;
  });
};

var typePartition = function typePartition(ds) {
  var D0 = ds.elements().first();
  var tm = _typeMap(ds);

  var match = function(D, E) { return tm.get(D).equals(tm.get(E)); };
  var spread = function(D, E) {
    return ds.indices().map(function(i) {
      return [ds.s(i, D), ds.s(i, E)];
    });
  };

  return ds.elements().rest().reduce(
    function(p, D) {
      return _fold(p, D0, D, match, spread) || p;
    },
    Partition()
  );
};

var Traversal = function Traversal(ds, indices, seeds) {
  var seedsLeft = (seeds.constructor == Array) ? seeds.slice() : seeds.toJS();
  var todo = {};
  var seen = {};
  var emap = {};
  var n    = 1;
  indices = (indices.constructor == Array) ? indices : indices.toJS();
  indices.forEach(function(i) { seen[i] = {}; todo[i] = [] });
  seen[root] = {};

  return {
    next: function() {
      while (true) {
        var i = null, D = null;
        for (var k = 0; k < indices.length; ++k)
          if (todo[indices[k]].length > 0) {
            i = indices[k];
            D = todo[i].shift();
            break;
          }

        if (D == null && seedsLeft.length > 0)
          D = seedsLeft.pop();

        if (D == null)
          return { done: true };

        if (!seen[i][D]) {
          var Di = (i == root) ? D : ds.s(i, D);
          var Ei = emap[Di] || n;

          if (Ei == n) {
            emap[Di] = Ei;
            ++n;
          }

          indices.forEach(function(i) {
            if (!seen[i][Di]) {
              if (i < 2)
                todo[i].unshift(Di);
              else
                todo[i].push(Di);
            }
          });

          seen[root][Di] = true;
          seen[i][D]     = true;
          seen[i][Di]    = true;

          return { done: false, value: [D, i, Di, emap[D], Ei] };
        }
      }
    }
  };
};

var traversal = function traversal(ds, indices, seeds) {
  return I.Seq(Traversal(ds, indices, seeds));
};

var root = traversal.root = null;

var orbitReps = function orbitReps(ds, indices, seeds) {
  return traversal(ds, indices, seeds || ds.elements())
    .filter(function(e) { return e[1] == root; })
    .map(function(e) { return e[2]; });
};

var isConnected = function isConnected(ds) {
  return orbitReps(ds, ds.indices()).count() < 2;
};

var orbit = function orbit(ds, indices, seed) {
  var seen = I.Set().asMutable();
  var result = I.List().asMutable();

  traversal(ds, indices, [seed]).forEach(function(e) {
    var D = e[2];
    if (D && !seen.contains(D)) {
      seen.add(D);
      result.push(D);
    }
  });

  return result.asImmutable();
};

var partialOrientation = function partialOrientation(ds) {
  var ori = I.Map().asMutable();

  traversal(ds, ds.indices(), ds.elements()).forEach(function(e) {
    var Di = e[0];
    var i = e[1];
    var D = e[2];

    if (D && !ori.get(D))
      ori.set(D, i == root ? 1 : -ori.get(Di));
  });

  return ori.asImmutable();
};

var isLoopless = function isLoopless(ds) {
  return ds.elements().every(function(D) {
    return ds.indices().every(function(i) {
      return D != ds.s(i, D);
    });
  });
};

var isOriented = function isOriented(ds) {
  var ori = partialOrientation(ds);

  return ds.elements().every(function(D) {
    return ds.indices().every(function(i) {
      return ori.get(D) != ori.get(ds.s(i, D));
    });
  });
};

var isWeaklyOriented = function isWeaklyOriented(ds) {
  var ori = partialOrientation(ds);

  return ds.elements().every(function(D) {
    return ds.indices().every(function(i) {
      var Di = ds.s(i, D);
      return D == Di || ori.get(D) != ori.get(Di);
    });
  });
};

var _branchings = function _branchings(ds, D) {
  var idcs = DS.indices(ds);
  return idcs.zip(idcs.rest()).map(function(p) { return ds.v(p[0], p[1], D); });
};

var invariant = function invariant(ds) {
  var branchings = {};
  ds.elements().forEach(function(D) {
    branchings[D] = _branchings(ds, D).toJS();
  });

  var best = null;

  ds.elements().forEach(function(D0) {
    var trav    = Traversal(ds, DS.indices(ds), [D0]);
    var current = [];
    var keep    = true;

    while (keep) {
      var next = trav.next();
      if (next.done)
        break;

      var i = next.value[1];
      var D = next.value[2];
      if (D == null)
        continue;

      var m = current.length;

      current.push((i == root) ? -1 : i);
      current.push(next.value[3]);
      current.push(next.value[4]);

      var b = branchings[D];
      for (var k = 0; k < b.length; ++k)
        current.push(b[k]);

      for (var k = m; best && keep && k < current.length; ++k) {
        var d = current[k] - best[k];
        if (d > 0)
          keep = false;
        else if (d < 0)
          best = null;
      }
    }

    if (best == null)
      best = current;
  });

  return I.List(best);
};

module.exports = {
  isMinimal         : isMinimal,
  typePartition     : typePartition,
  traversal         : traversal,
  orbitReps         : orbitReps,
  isConnected       : isConnected,
  orbit             : orbit,
  partialOrientation: partialOrientation,
  isLoopless        : isLoopless,
  isOriented        : isOriented,
  isWeaklyOriented  : isWeaklyOriented,
  invariant         : invariant
};

if (require.main == module) {
  var test = function test(ds) {
    console.log('ds = '+ds);
    console.log();

    console.log('    symbol is '+(isConnected(ds) ? '' : 'not ')+'connected.');
    console.log('    symbol is '+(isMinimal(ds) ? '' : 'not ')+'minimal.');
    console.log('    symbol is '+(isLoopless(ds) ? '' : 'not ')+'loopless.');
    console.log('    symbol is '+(isOriented(ds) ? '' : 'not ')+'oriented.');
    console.log('    symbol is '+(isWeaklyOriented(ds) ? '' : 'not ')
                +'weakly oriented.');
    console.log('    type partition: '+typePartition(ds));
    var trav = traversal(ds, ds.indices(), ds.elements());
    console.log('    traversal: ' + trav);
    console.log('    invariant: ' + invariant(ds));
    console.log();

    console.log('    0,1 orbit reps: '+orbitReps(ds, [0, 1]));
    console.log('    1,2 orbit reps: '+orbitReps(ds, [1, 2]));
    console.log('    0,2 orbit reps: '+orbitReps(ds, [0, 2]));
    console.log();

    console.log('    0,1 orbit of 1: '+orbit(ds, [0, 1], 1));
    console.log('    1,2 orbit of 1: '+orbit(ds, [1, 2], 1));
    console.log('    0,2 orbit of 1: '+orbit(ds, [0, 2], 1));
    console.log();

    console.log('    partial orientation: '+partialOrientation(ds));
    console.log();
    console.log();
  };

  test(DS.parse(
    '<1.1:24:' +
      '2 4 6 8 10 12 14 16 18 20 22 24,' +
      '16 3 5 7 9 11 13 15 24 19 21 23,' +
      '10 9 20 19 14 13 22 21 24 23 18 17:' +
      '8 4,3 3 3 3>'));

  test(DS.parse('<1.1:3:1 2 3,1 3,2 3:4 8,3>'));
  test(DS.parse('<1.1:2 3:2,1 2,1 2,2:6,3 2,6>'));
  test(DS.parse('<1.1:6:4 6 5,5 4 6,4 6 5:3,6>'));
}
<commit_msg>in invariant(), use plain Javascript to pre-compute branchings<commit_after>'use strict';

var I = require('immutable');
var DS = require('./delaney');
var Partition = require('../common/partition');

var _fold = function _fold(partition, a, b, matchP, spreadFn) {
  var p = partition;
  var q = I.List().push(I.List([a, b]));

  while (!q.isEmpty()) {
    var _tmp = q.first();
    var x = _tmp.get(0);
    var y = _tmp.get(1);

    q = q.rest();

    if (!matchP(x, y))
      return;
    else if (p.get(x) == p.get(y))
      continue;
    else {
      p = p.union(x, y);
      q = q.concat(I.List(spreadFn(x, y)).map(I.List));
    }
  }

  return p;
};

var _typeMap = function _typeMap(ds) {
  var base = I.Map(ds.elements().map(function(D) {
    return [D, I.List()];
  }));
  var idcs = DS.indices(ds);

  return base.withMutations(function(map) {
    idcs.zip(idcs.rest()).forEach(function(p) {
      var i = p[0];
      var j = p[1];

      DS.orbitReps2(ds, i, j).forEach(function(D) {
        var m = DS.m(ds, i, j, D);
        DS.orbit2(ds, i, j, D).forEach(function(E) {
          map.set(E, map.get(E).push(m));
        });
      });
    });
  });
};

var isMinimal = function isMinimal(ds) {
  var D0 = ds.elements().first();
  var tm = _typeMap(ds);

  var match = function(D, E) { return tm.get(D).equals(tm.get(E)); };
  var spread = function(D, E) {
    return ds.indices().map(function(i) {
      return [ds.s(i, D), ds.s(i, E)];
    });
  };

  return ds.elements().rest().every(function(D) {
    return _fold(Partition(), D0, D, match, spread) === undefined;
  });
};

var typePartition = function typePartition(ds) {
  var D0 = ds.elements().first();
  var tm = _typeMap(ds);

  var match = function(D, E) { return tm.get(D).equals(tm.get(E)); };
  var spread = function(D, E) {
    return ds.indices().map(function(i) {
      return [ds.s(i, D), ds.s(i, E)];
    });
  };

  return ds.elements().rest().reduce(
    function(p, D) {
      return _fold(p, D0, D, match, spread) || p;
    },
    Partition()
  );
};

var Traversal = function Traversal(ds, indices, seeds) {
  var seedsLeft = (seeds.constructor == Array) ? seeds.slice() : seeds.toJS();
  var todo = {};
  var seen = {};
  var emap = {};
  var n    = 1;
  indices = (indices.constructor == Array) ? indices : indices.toJS();
  indices.forEach(function(i) { seen[i] = {}; todo[i] = [] });
  seen[root] = {};

  return {
    next: function() {
      while (true) {
        var i = null, D = null;
        for (var k = 0; k < indices.length; ++k)
          if (todo[indices[k]].length > 0) {
            i = indices[k];
            D = todo[i].shift();
            break;
          }

        if (D == null && seedsLeft.length > 0)
          D = seedsLeft.pop();

        if (D == null)
          return { done: true };

        if (!seen[i][D]) {
          var Di = (i == root) ? D : ds.s(i, D);
          var Ei = emap[Di] || n;

          if (Ei == n) {
            emap[Di] = Ei;
            ++n;
          }

          indices.forEach(function(i) {
            if (!seen[i][Di]) {
              if (i < 2)
                todo[i].unshift(Di);
              else
                todo[i].push(Di);
            }
          });

          seen[root][Di] = true;
          seen[i][D]     = true;
          seen[i][Di]    = true;

          return { done: false, value: [D, i, Di, emap[D], Ei] };
        }
      }
    }
  };
};

var traversal = function traversal(ds, indices, seeds) {
  return I.Seq(Traversal(ds, indices, seeds));
};

var root = traversal.root = null;

var orbitReps = function orbitReps(ds, indices, seeds) {
  return traversal(ds, indices, seeds || ds.elements())
    .filter(function(e) { return e[1] == root; })
    .map(function(e) { return e[2]; });
};

var isConnected = function isConnected(ds) {
  return orbitReps(ds, ds.indices()).count() < 2;
};

var orbit = function orbit(ds, indices, seed) {
  var seen = I.Set().asMutable();
  var result = I.List().asMutable();

  traversal(ds, indices, [seed]).forEach(function(e) {
    var D = e[2];
    if (D && !seen.contains(D)) {
      seen.add(D);
      result.push(D);
    }
  });

  return result.asImmutable();
};

var partialOrientation = function partialOrientation(ds) {
  var ori = I.Map().asMutable();

  traversal(ds, ds.indices(), ds.elements()).forEach(function(e) {
    var Di = e[0];
    var i = e[1];
    var D = e[2];

    if (D && !ori.get(D))
      ori.set(D, i == root ? 1 : -ori.get(Di));
  });

  return ori.asImmutable();
};

var isLoopless = function isLoopless(ds) {
  return ds.elements().every(function(D) {
    return ds.indices().every(function(i) {
      return D != ds.s(i, D);
    });
  });
};

var isOriented = function isOriented(ds) {
  var ori = partialOrientation(ds);

  return ds.elements().every(function(D) {
    return ds.indices().every(function(i) {
      return ori.get(D) != ori.get(ds.s(i, D));
    });
  });
};

var isWeaklyOriented = function isWeaklyOriented(ds) {
  var ori = partialOrientation(ds);

  return ds.elements().every(function(D) {
    return ds.indices().every(function(i) {
      var Di = ds.s(i, D);
      return D == Di || ori.get(D) != ori.get(Di);
    });
  });
};

var invariant = function invariant(ds) {
  var idcs = DS.indices(ds).toJS();
  var branchings = {};
  ds.elements().forEach(function(D) {
    var br = branchings[D] = [];
    for (var i = 0; i < idcs.length - 1; ++i)
      br.push(ds.v(idcs[i], idcs[i+1], D));
  });

  var best = null;

  ds.elements().forEach(function(D0) {
    var trav    = Traversal(ds, DS.indices(ds), [D0]);
    var current = [];
    var keep    = true;

    while (keep) {
      var next = trav.next();
      if (next.done)
        break;

      var i = next.value[1];
      var D = next.value[2];
      if (D == null)
        continue;

      var m = current.length;

      current.push((i == root) ? -1 : i);
      current.push(next.value[3]);
      current.push(next.value[4]);

      var b = branchings[D];
      for (var k = 0; k < b.length; ++k)
        current.push(b[k]);

      for (var k = m; best && keep && k < current.length; ++k) {
        var d = current[k] - best[k];
        if (d > 0)
          keep = false;
        else if (d < 0)
          best = null;
      }
    }

    if (best == null)
      best = current;
  });

  return I.List(best);
};

module.exports = {
  isMinimal         : isMinimal,
  typePartition     : typePartition,
  traversal         : traversal,
  orbitReps         : orbitReps,
  isConnected       : isConnected,
  orbit             : orbit,
  partialOrientation: partialOrientation,
  isLoopless        : isLoopless,
  isOriented        : isOriented,
  isWeaklyOriented  : isWeaklyOriented,
  invariant         : invariant
};

if (require.main == module) {
  var test = function test(ds) {
    console.log('ds = '+ds);
    console.log();

    console.log('    symbol is '+(isConnected(ds) ? '' : 'not ')+'connected.');
    console.log('    symbol is '+(isMinimal(ds) ? '' : 'not ')+'minimal.');
    console.log('    symbol is '+(isLoopless(ds) ? '' : 'not ')+'loopless.');
    console.log('    symbol is '+(isOriented(ds) ? '' : 'not ')+'oriented.');
    console.log('    symbol is '+(isWeaklyOriented(ds) ? '' : 'not ')
                +'weakly oriented.');
    console.log('    type partition: '+typePartition(ds));
    var trav = traversal(ds, ds.indices(), ds.elements());
    console.log('    traversal: ' + trav);
    console.log('    invariant: ' + invariant(ds));
    console.log();

    console.log('    0,1 orbit reps: '+orbitReps(ds, [0, 1]));
    console.log('    1,2 orbit reps: '+orbitReps(ds, [1, 2]));
    console.log('    0,2 orbit reps: '+orbitReps(ds, [0, 2]));
    console.log();

    console.log('    0,1 orbit of 1: '+orbit(ds, [0, 1], 1));
    console.log('    1,2 orbit of 1: '+orbit(ds, [1, 2], 1));
    console.log('    0,2 orbit of 1: '+orbit(ds, [0, 2], 1));
    console.log();

    console.log('    partial orientation: '+partialOrientation(ds));
    console.log();
    console.log();
  };

  test(DS.parse(
    '<1.1:24:' +
      '2 4 6 8 10 12 14 16 18 20 22 24,' +
      '16 3 5 7 9 11 13 15 24 19 21 23,' +
      '10 9 20 19 14 13 22 21 24 23 18 17:' +
      '8 4,3 3 3 3>'));

  test(DS.parse('<1.1:3:1 2 3,1 3,2 3:4 8,3>'));
  test(DS.parse('<1.1:2 3:2,1 2,1 2,2:6,3 2,6>'));
  test(DS.parse('<1.1:6:4 6 5,5 4 6,4 6 5:3,6>'));
}
<|endoftext|>