<commit_before>

//check if an image exists
function imageExists(image_url){
    var http = new XMLHttpRequest();
    http.open('HEAD', image_url, false);
    http.send();
    return http.status != 404;
}

function fixFeaturedMediaOffset(){
	    if ($(window).width() >= 1200 && ($('.featured-media .figure').height() >= 5 )  ) {
		console.log(">1200");
		var mediaHeight=  $('.featured-media .figure').outerHeight();
			console.log(mediaHeight);
		var sectionHeight=  $(".featured-media .ad-box").outerHeight();;
				console.log(sectionHeight);
		if (  (sectionHeight - mediaHeight) >= 0) {
			console.log("islarger difference is :" +(sectionHeight - mediaHeight) + "px");

		    var mediamargin =  mediaHeight - sectionHeight + 30;
					console.log("margin is" +mediamargin);
		    $("section.featured-media").css("margin-bottom", mediamargin);
		}
	    } else {
		 $("section.featured-media").css("margin-bottom", "inherit");
	    }
	}
// Wrap IIFE around your code
(function($, viewport){
    $(document).ready(function() {

	$('a').click(function(e){
			e.preventDefault();
			var href = $(this).attr("href");
    $('html, body').animate({
        scrollTop: $('[name="' + $.attr(this, 'href').substr(1) + '"]').offset().top
    }, 500, function(){
			if (href.indexOf("comments") >= 0) {
		$(".comments-section button").click();
	}

			});

	    return false;

});

	//fix offset
var windowWidth = $(window).width();

	fixFeaturedMediaOffset();

        //Do not kill the dropdowns when users click in them)
        $('.dropdown-menu').children().click(function(e){
            e.stopPropagation();
        });

        //*======= ALLOW MULTIPLE NAV ITEMS TO BE OPEN AT ONCE IN MOBILE
        if ($(window).width() < 992){
            $('.dropdown.keep-open').on({
                "shown.bs.dropdown": function() { this.closable = false; },
                    "click":             function() { this.closable = true; },
                    "hide.bs.dropdown":  function() { return this.closable; }
            });
        } else {
// this function not related to above just piggybacking on the if statement. It pushes the main content down when latest stories is open
var menuheight = 0;
		$('.col-sm-12 .dropdown').on('show.bs.dropdown', function(event) {
			if ($(window).width() > 991) {
			menuheight = $(this).children(".dropdown-menu").outerHeight();
			$(".article__header").animate({
				marginTop: "+=" +menuheight
				 }, 250, function() {
  			  // Animation complete.
 				 });
			}
		});
		$('.col-sm-12 .dropdown').on('hide.bs.dropdown', function(event) {
			if ($(window).width() > 991) {
				$(".article__header").animate({
				marginTop: "-=" +menuheight
				 }, 250, function() {
  			  // Animation complete.
 				 });
			}
			});
		}
		// resets margin-top on .article__header when window resized to prevent stuck margin on resize/orientation shift
function resizedw(){
//modified to fire after only 100ms so that it doesn't go multiple times per resize
    // Store the window width
    // Resize Event
        // Check window width has actually changed and it's not just iOS triggering a resize event on scroll
        if ($(window).width() != windowWidth) {
            // Update the window width for next time
            windowWidth = $(window).width();
            // Do stuff here
    $(".open").removeClass("open");
   $(".article__header").css("margin-top", 0);
   fixFeaturedMediaOffset();
        }
        // Otherwise do nothing
};
var doit;
window.onresize = function(){
  clearTimeout(doit);
  doit = setTimeout(resizedw, 100);
};

        //Moves focus directly to search field when user begins typing
        $('.search-block').on('show.bs.dropdown', function(event) {

		   setTimeout(function(){
 				$('input#menu__search--input').focus();
				}, 500);
        });

        //LATEST STORIES

        jQuery.fn.reverse = function() {
            return this.pushStack(this.get().reverse(), arguments);
        };

        //GLOBALS
        var totalStoryPositions;
        var counter =0;
        var topCounter = 0;
        var bottomCounter = 0;
        var response;
        var recentItems;
        var returnedStories = [];
        var storyObjects = [];
        var firstStory;
        var topPrevCounter = 0;
        var bottomPrevCounter = 0;

        //make storyObjects global, so I don't have to keep hitting the API
        storyObjects = storyObjects;

        //Returned stories from the API
        function getLatestStories(){
            storiesRequested = 50;
            returnedStories = $.ajax({
                method: 'POST',
                url: 'http://api.thetyee.ca/v1/latest/' + storiesRequested,
                dataType: 'jsonp',
                data: response,
                crossDomain: true,
                success: function(response){
                    //console.log(response);
                    returnedStories = returnedStories.responseJSON.hits.hits;
                    //pass data to the create function so I can create my own story objects
                    recentItems = createStoryObjects(returnedStories);
                    return recentItems;
                },
                error: function(){
                    //console.log('NO DICE SISTER');
                }
            });
        }//end lateststories()

        //Format the json data for ease of manipulation
        function createStoryObjects(returnedStories, callback){
            //remove the placeholder classes
            $('.latest-stories__hed').removeClass('hed_preload_placeholder');
            $('.latest-stories__dek').removeClass('dek_preload_placeholder');

            //API data, to feed into an array of custom story objects
            recentItems = returnedStories;

            //for each item from the API, plug info into a story object
            $.each(recentItems, function(key, value){

                latestStoryImage = value._source.related_media[0].uri;

				// get the smallest image > 200px available
				var bestWidth = value._source.related_media[0].width;
				var bestHeight = value._source.related_media[0].height;
				for (var key in value._source.related_media[0].thumbnails) {
				  var thumb = value._source.related_media[0].thumbnails[key];
				  					thumb.uri = thumb.uri.replace("thetyee.cachefly.net", "thetyee.ca");
					if (
					// re-enable live to filter out not yet published thumbnails
		//			imageExists(thumb.uri) == true &&
					 thumb.width >= 200 && thumb.width <= bestWidth) {
					bestWidth = thumb.width;
					bestHeight = thumb.height;
					latestStoryImage = thumb.uri;  }
				}

                //Format the API img uri's so they don't point at cachefly
                latestStoryImage = latestStoryImage.replace("thetyee.cachefly.net", "thetyee.ca");

                //Use moment.js to format the date
                formattedDate = moment(value._source.storyDate).format("DD MMM");

                //Set default values for the Story object
                var Story = {
                    urlPath : 'link',
                    hed : 'title',
                    image : 'image',
                    dek : 'test',
                    date : 'date',
                    authour : 'authour'
                };

                //Populate Story objects with API data
                Story.urlPath = value._source.path;
                Story.hed = value._source.title;
                Story.image = latestStoryImage;
                Story.dek = value._source.teaser;
                Story.date = formattedDate;
                Story.authour = value._source.byline;
				Story.imageWidth = bestWidth;
				Story.imageHeight = bestHeight;

                //Put all objects into a new array for easier handling
                storyObjects.push(Story);
            });
            // on load, populate the DOM
            $('.latest-stories__media-wrapper').each(function(key, index){
                $(this).parent().find('li').each(function(i, details){

                    if (key === 0){
                        // TODO swap this out when closer to production
                        //$(this).find('a').attr('href', '#');
                        // Old value
                        $(this).find('a').attr('href', "http://thetyee.ca" + storyObjects[topCounter].urlPath);
                        $(this).find('img').attr('src', storyObjects[topCounter].image);
						$(this).find('img').attr('width',storyObjects[topCounter].imageWidth);
						$(this).find('img').attr('height', storyObjects[topCounter].imageHeight);
                        $(this).find('h4').html(storyObjects[topCounter].hed);
                        $(this).find('p').html(storyObjects[topCounter].dek);
                        $(this).find('.latest-stories__date').html(storyObjects[topCounter].date);
                        $(this).find('.latest-stories__authour').html(storyObjects[topCounter].authour);
                        topCounter++;
                    }
                    if (key == 1){
                        // TODO swap this out when closer to production
                        //$(this).find('a').attr('href', '#');
                        // Old value
                        $(this).find('a').attr('href', "http://thetyee.ca" + storyObjects[bottomCounter].urlPath);
                        $(this).find('img').attr('src', storyObjects[bottomCounter].image);
						$(this).find('img').attr('width',storyObjects[topCounter].imageWidth);
						$(this).find('img').attr('height', storyObjects[topCounter].imageHeight);
                        $(this).find('h4').html(storyObjects[bottomCounter].hed);
                        $(this).find('p').html(storyObjects[bottomCounter].dek);
                        $(this).find('.latest-stories__date').html(storyObjects[bottomCounter].date);
                        $(this).find('.latest-stories__authour').html(storyObjects[bottomCounter].authour);
                        bottomCounter++;

                    }

                });
            });
            //control the scrolling
            scrollLatestStories();
        }

        //Make Ajax call and put data into the returnedStories var for manipulation.
        getLatestStories();

        //takes parameters of current slider
        function scrollLatestStories(){
            //Get the next group of stories on click

            //console.log(key + ' ' + value);
            $('.latest-stories__media-wrapper').each(function(key, index){
                $(this).on('click', '.next', function(event){
                    //	console.log($(this).find('li'));

                    $(this).parent().find('li').each(function(i, details){

                        if (key === 0){
                            $(this).find('a').attr('href', storyObjects[topCounter].urlPath);
                            $(this).find('img').attr('src', storyObjects[topCounter].image);
                            $(this).find('h4').html(storyObjects[topCounter].hed);
                            $(this).find('p').html(storyObjects[topCounter].dek);
                            $(this).find('.latest-stories__date').html(storyObjects[topCounter].date);
                            $(this).find('.latest-stories__authour').html(storyObjects[topCounter].authour);

                            topCounter++;

                            if (topCounter >=50){
                                topCounter = 0;
                            }

                        }

                        if (key == 1){

                            $(this).find('a').attr('href', storyObjects[bottomCounter].urlPath);
                            $(this).find('img').attr('src', storyObjects[bottomCounter].image);
                            $(this).find('h4').html(storyObjects[bottomCounter].hed);
                            $(this).find('p').html(storyObjects[bottomCounter].dek);
                            $(this).find('.latest-stories__date').html(storyObjects[bottomCounter].date);
                            $(this).find('.latest-stories__authour').html(storyObjects[bottomCounter].authour);
                            bottomCounter++;

                            if (bottomCounter >=50){
                                bottomCounter = 0;
                            }
                        }

                    });
                    //reset the counter so I can scroll the other way
                    topPrevCounter = topCounter - 7;
                    bottomPrevCounter = bottomCounter - 7;

                    //reset the click event

                });

                $(this).on('click', '.prev', function(event){
                    $(this).parent().find('li').reverse().each(function(i, details){
                        //infinite backwards scroll
                        if (topPrevCounter <=0){
                            topPrevCounter = 49;
                        }
                        if (key === 0){
                            $(this).find('a').attr('href', storyObjects[topPrevCounter].urlPath);
                            $(this).find('img').attr('src', storyObjects[topPrevCounter].image);
                            $(this).find('h4').html(storyObjects[topPrevCounter].hed);
                            $(this).find('p').html(storyObjects[topPrevCounter].dek);
                            $(this).find('.latest-stories__date').html(storyObjects[topPrevCounter].date);
                            $(this).find('.latest-stories__authour').html(storyObjects[topPrevCounter].authour);
                            topPrevCounter--;
                        }

                        if (bottomPrevCounter <= 0){
                            bottomPrevCounter = 49;
                        }

                        if (key == 1){
                            //console.log('left click - ' + bottomPrevCounter);
                            $(this).find('a').attr('href', storyObjects[bottomPrevCounter].urlPath);
                            $(this).find('img').attr('src', storyObjects[bottomPrevCounter].image);
                            $(this).find('h4').html(storyObjects[bottomPrevCounter].hed);
                            $(this).find('p').html(storyObjects[bottomPrevCounter].dek);
                            $(this).find('.latest-stories__date').html(storyObjects[bottomPrevCounter].date);
                            $(this).find('.latest-stories__authour').html(storyObjects[bottomPrevCounter].authour);
                            bottomPrevCounter--;
                        }

                    });
                    //reset the counter so i can go the other way
                    topCounter = topPrevCounter + 7;
                    bottomCounter = bottomPrevCounter + 7;
                    //reset the counter for infinite scrolling

                });
            });

        }//END LATEST STORIES SCROLLER

        // Show Disqus comments
        $(".comments-section .btn").click(function(e) {
            e.preventDefault();
            var el = $('.comments-section');
            var disqus_div = $('#disqus_thread');
            var anim_height = disqus_div.height() + 40;
            el.css({
                "height": 400,
                "max-height": 9999
            })
            .animate({
                "height": anim_height
            });

            // fade out read-more
            $('.read-more').fadeOut();
        });

    });

})(jQuery, ResponsiveBootstrapToolkit);
<commit_msg>fix overreach of comment link<commit_after>

//check if an image exists
function imageExists(image_url){
    var http = new XMLHttpRequest();
    http.open('HEAD', image_url, false);
    http.send();
    return http.status != 404;
}

function fixFeaturedMediaOffset(){
	    if ($(window).width() >= 1200 && ($('.featured-media .figure').height() >= 5 )  ) {
		console.log(">1200");
		var mediaHeight=  $('.featured-media .figure').outerHeight();
			console.log(mediaHeight);
		var sectionHeight=  $(".featured-media .ad-box").outerHeight();;
				console.log(sectionHeight);
		if (  (sectionHeight - mediaHeight) >= 0) {
			console.log("islarger difference is :" +(sectionHeight - mediaHeight) + "px");

		    var mediamargin =  mediaHeight - sectionHeight + 30;
					console.log("margin is" +mediamargin);
		    $("section.featured-media").css("margin-bottom", mediamargin);
		}
	    } else {
		 $("section.featured-media").css("margin-bottom", "inherit");
	    }
	}
// Wrap IIFE around your code
(function($, viewport){
    $(document).ready(function() {
	$('a.btn-comment').click(function(e){
    $('html, body').animate({
        scrollTop: $('[name="' + $.attr(this, 'href').substr(1) + '"]').offset().top
    }, 500, function(){
		$(".comments-section button").click();
			});
	    return false;
});

	//fix offset
var windowWidth = $(window).width();

	fixFeaturedMediaOffset();

        //Do not kill the dropdowns when users click in them)
        $('.dropdown-menu').children().click(function(e){
            e.stopPropagation();
        });

        //*======= ALLOW MULTIPLE NAV ITEMS TO BE OPEN AT ONCE IN MOBILE
        if ($(window).width() < 992){
            $('.dropdown.keep-open').on({
                "shown.bs.dropdown": function() { this.closable = false; },
                    "click":             function() { this.closable = true; },
                    "hide.bs.dropdown":  function() { return this.closable; }
            });
        } else {
// this function not related to above just piggybacking on the if statement. It pushes the main content down when latest stories is open
var menuheight = 0;
		$('.col-sm-12 .dropdown').on('show.bs.dropdown', function(event) {
			if ($(window).width() > 991) {
			menuheight = $(this).children(".dropdown-menu").outerHeight();
			$(".article__header").animate({
				marginTop: "+=" +menuheight
				 }, 250, function() {
  			  // Animation complete.
 				 });
			}
		});
		$('.col-sm-12 .dropdown').on('hide.bs.dropdown', function(event) {
			if ($(window).width() > 991) {
				$(".article__header").animate({
				marginTop: "-=" +menuheight
				 }, 250, function() {
  			  // Animation complete.
 				 });
			}
			});
		}
		// resets margin-top on .article__header when window resized to prevent stuck margin on resize/orientation shift
function resizedw(){
//modified to fire after only 100ms so that it doesn't go multiple times per resize
    // Store the window width
    // Resize Event
        // Check window width has actually changed and it's not just iOS triggering a resize event on scroll
        if ($(window).width() != windowWidth) {
            // Update the window width for next time
            windowWidth = $(window).width();
            // Do stuff here
    $(".open").removeClass("open");
   $(".article__header").css("margin-top", 0);
   fixFeaturedMediaOffset();
        }
        // Otherwise do nothing
};
var doit;
window.onresize = function(){
  clearTimeout(doit);
  doit = setTimeout(resizedw, 100);
};

        //Moves focus directly to search field when user begins typing
        $('.search-block').on('show.bs.dropdown', function(event) {

		   setTimeout(function(){
 				$('input#menu__search--input').focus();
				}, 500);
        });

        //LATEST STORIES

        jQuery.fn.reverse = function() {
            return this.pushStack(this.get().reverse(), arguments);
        };

        //GLOBALS
        var totalStoryPositions;
        var counter =0;
        var topCounter = 0;
        var bottomCounter = 0;
        var response;
        var recentItems;
        var returnedStories = [];
        var storyObjects = [];
        var firstStory;
        var topPrevCounter = 0;
        var bottomPrevCounter = 0;

        //make storyObjects global, so I don't have to keep hitting the API
        storyObjects = storyObjects;

        //Returned stories from the API
        function getLatestStories(){
            storiesRequested = 50;
            returnedStories = $.ajax({
                method: 'POST',
                url: 'http://api.thetyee.ca/v1/latest/' + storiesRequested,
                dataType: 'jsonp',
                data: response,
                crossDomain: true,
                success: function(response){
                    //console.log(response);
                    returnedStories = returnedStories.responseJSON.hits.hits;
                    //pass data to the create function so I can create my own story objects
                    recentItems = createStoryObjects(returnedStories);
                    return recentItems;
                },
                error: function(){
                    //console.log('NO DICE SISTER');
                }
            });
        }//end lateststories()

        //Format the json data for ease of manipulation
        function createStoryObjects(returnedStories, callback){
            //remove the placeholder classes
            $('.latest-stories__hed').removeClass('hed_preload_placeholder');
            $('.latest-stories__dek').removeClass('dek_preload_placeholder');

            //API data, to feed into an array of custom story objects
            recentItems = returnedStories;

            //for each item from the API, plug info into a story object
            $.each(recentItems, function(key, value){

                latestStoryImage = value._source.related_media[0].uri;

				// get the smallest image > 200px available
				var bestWidth = value._source.related_media[0].width;
				var bestHeight = value._source.related_media[0].height;
				for (var key in value._source.related_media[0].thumbnails) {
				  var thumb = value._source.related_media[0].thumbnails[key];
				  					thumb.uri = thumb.uri.replace("thetyee.cachefly.net", "thetyee.ca");
					if (
					// re-enable live to filter out not yet published thumbnails
		//			imageExists(thumb.uri) == true &&
					 thumb.width >= 200 && thumb.width <= bestWidth) {
					bestWidth = thumb.width;
					bestHeight = thumb.height;
					latestStoryImage = thumb.uri;  }
				}

                //Format the API img uri's so they don't point at cachefly
                latestStoryImage = latestStoryImage.replace("thetyee.cachefly.net", "thetyee.ca");

                //Use moment.js to format the date
                formattedDate = moment(value._source.storyDate).format("DD MMM");

                //Set default values for the Story object
                var Story = {
                    urlPath : 'link',
                    hed : 'title',
                    image : 'image',
                    dek : 'test',
                    date : 'date',
                    authour : 'authour'
                };

                //Populate Story objects with API data
                Story.urlPath = value._source.path;
                Story.hed = value._source.title;
                Story.image = latestStoryImage;
                Story.dek = value._source.teaser;
                Story.date = formattedDate;
                Story.authour = value._source.byline;
				Story.imageWidth = bestWidth;
				Story.imageHeight = bestHeight;

                //Put all objects into a new array for easier handling
                storyObjects.push(Story);
            });
            // on load, populate the DOM
            $('.latest-stories__media-wrapper').each(function(key, index){
                $(this).parent().find('li').each(function(i, details){

                    if (key === 0){
                        // TODO swap this out when closer to production
                        //$(this).find('a').attr('href', '#');
                        // Old value
                        $(this).find('a').attr('href', "http://thetyee.ca" + storyObjects[topCounter].urlPath);
                        $(this).find('img').attr('src', storyObjects[topCounter].image);
						$(this).find('img').attr('width',storyObjects[topCounter].imageWidth);
						$(this).find('img').attr('height', storyObjects[topCounter].imageHeight);
                        $(this).find('h4').html(storyObjects[topCounter].hed);
                        $(this).find('p').html(storyObjects[topCounter].dek);
                        $(this).find('.latest-stories__date').html(storyObjects[topCounter].date);
                        $(this).find('.latest-stories__authour').html(storyObjects[topCounter].authour);
                        topCounter++;
                    }
                    if (key == 1){
                        // TODO swap this out when closer to production
                        //$(this).find('a').attr('href', '#');
                        // Old value
                        $(this).find('a').attr('href', "http://thetyee.ca" + storyObjects[bottomCounter].urlPath);
                        $(this).find('img').attr('src', storyObjects[bottomCounter].image);
						$(this).find('img').attr('width',storyObjects[topCounter].imageWidth);
						$(this).find('img').attr('height', storyObjects[topCounter].imageHeight);
                        $(this).find('h4').html(storyObjects[bottomCounter].hed);
                        $(this).find('p').html(storyObjects[bottomCounter].dek);
                        $(this).find('.latest-stories__date').html(storyObjects[bottomCounter].date);
                        $(this).find('.latest-stories__authour').html(storyObjects[bottomCounter].authour);
                        bottomCounter++;

                    }

                });
            });
            //control the scrolling
            scrollLatestStories();
        }

        //Make Ajax call and put data into the returnedStories var for manipulation.
        getLatestStories();

        //takes parameters of current slider
        function scrollLatestStories(){
            //Get the next group of stories on click

            //console.log(key + ' ' + value);
            $('.latest-stories__media-wrapper').each(function(key, index){
                $(this).on('click', '.next', function(event){
                    //	console.log($(this).find('li'));

                    $(this).parent().find('li').each(function(i, details){

                        if (key === 0){
                            $(this).find('a').attr('href', storyObjects[topCounter].urlPath);
                            $(this).find('img').attr('src', storyObjects[topCounter].image);
                            $(this).find('h4').html(storyObjects[topCounter].hed);
                            $(this).find('p').html(storyObjects[topCounter].dek);
                            $(this).find('.latest-stories__date').html(storyObjects[topCounter].date);
                            $(this).find('.latest-stories__authour').html(storyObjects[topCounter].authour);

                            topCounter++;

                            if (topCounter >=50){
                                topCounter = 0;
                            }

                        }

                        if (key == 1){

                            $(this).find('a').attr('href', storyObjects[bottomCounter].urlPath);
                            $(this).find('img').attr('src', storyObjects[bottomCounter].image);
                            $(this).find('h4').html(storyObjects[bottomCounter].hed);
                            $(this).find('p').html(storyObjects[bottomCounter].dek);
                            $(this).find('.latest-stories__date').html(storyObjects[bottomCounter].date);
                            $(this).find('.latest-stories__authour').html(storyObjects[bottomCounter].authour);
                            bottomCounter++;

                            if (bottomCounter >=50){
                                bottomCounter = 0;
                            }
                        }

                    });
                    //reset the counter so I can scroll the other way
                    topPrevCounter = topCounter - 7;
                    bottomPrevCounter = bottomCounter - 7;

                    //reset the click event

                });

                $(this).on('click', '.prev', function(event){
                    $(this).parent().find('li').reverse().each(function(i, details){
                        //infinite backwards scroll
                        if (topPrevCounter <=0){
                            topPrevCounter = 49;
                        }
                        if (key === 0){
                            $(this).find('a').attr('href', storyObjects[topPrevCounter].urlPath);
                            $(this).find('img').attr('src', storyObjects[topPrevCounter].image);
                            $(this).find('h4').html(storyObjects[topPrevCounter].hed);
                            $(this).find('p').html(storyObjects[topPrevCounter].dek);
                            $(this).find('.latest-stories__date').html(storyObjects[topPrevCounter].date);
                            $(this).find('.latest-stories__authour').html(storyObjects[topPrevCounter].authour);
                            topPrevCounter--;
                        }

                        if (bottomPrevCounter <= 0){
                            bottomPrevCounter = 49;
                        }

                        if (key == 1){
                            //console.log('left click - ' + bottomPrevCounter);
                            $(this).find('a').attr('href', storyObjects[bottomPrevCounter].urlPath);
                            $(this).find('img').attr('src', storyObjects[bottomPrevCounter].image);
                            $(this).find('h4').html(storyObjects[bottomPrevCounter].hed);
                            $(this).find('p').html(storyObjects[bottomPrevCounter].dek);
                            $(this).find('.latest-stories__date').html(storyObjects[bottomPrevCounter].date);
                            $(this).find('.latest-stories__authour').html(storyObjects[bottomPrevCounter].authour);
                            bottomPrevCounter--;
                        }

                    });
                    //reset the counter so i can go the other way
                    topCounter = topPrevCounter + 7;
                    bottomCounter = bottomPrevCounter + 7;
                    //reset the counter for infinite scrolling

                });
            });

        }//END LATEST STORIES SCROLLER

        // Show Disqus comments
        $(".comments-section .btn").click(function(e) {
            e.preventDefault();
            var el = $('.comments-section');
            var disqus_div = $('#disqus_thread');
            var anim_height = disqus_div.height() + 40;
            el.css({
                "height": 400,
                "max-height": 9999
            })
            .animate({
                "height": anim_height
            });

            // fade out read-more
            $('.read-more').fadeOut();
        });

    });

})(jQuery, ResponsiveBootstrapToolkit);
<|endoftext|>


<commit_before>function _AbstractPool(options) {
  Stream.call(this);

  this._queueLim = get(options, 'queueLim', 0);
  this._concurLim = get(options, 'concurLim', -1);
  this._drop = get(options, 'drop', 'new');
  if (this._concurLim === 0) {
    throw new Error('options.concurLim can\'t be 0');
  }

  var $ = this;
  this._$handleSubAny = function(event) {  $._handleSubAny(event)  };

  this._queue = [];
  this._curSources = [];
  this._activating = false;
}

inherit(_AbstractPool, Stream, {

  _name: 'abstractPool',

  _add: function(obj, toObs) {
    toObs = toObs || id;
    if (this._concurLim === -1 || this._curSources.length < this._concurLim) {
      this._addToCur(toObs(obj));
    } else {
      if (this._queueLim === -1 || this._queue.length < this._queueLim) {
        this._addToQueue(toObs(obj));
      } else if (this._drop === 'old') {
        this._removeOldest();
        this._add(toObs(obj));
      }
    }
  },
  _addAll: function(obss) {
    var $ = this;
    forEach(obss, function(obs) {  $._add(obs)  });
  },
  _remove: function(obs) {
    if (this._removeCur(obs) === -1) {
      this._removeQueue(obs);
    }
  },

  _addToQueue: function(obs) {
    this._queue = concat(this._queue, [obs]);
  },
  _addToCur: function(obs) {
    this._curSources = concat(this._curSources, [obs]);
    if (this._active) {  this._subscribe(obs)  }
  },
  _subscribe: function(obs) {
    var $ = this;
    obs.onAny(this._$handleSubAny);
    obs.onEnd(function() {  $._removeCur(obs)  }, [this, obs]);
  },
  _unsubscribe: function(obs) {
    obs.offAny(this._$handleSubAny);
    obs.offEnd(null, [this, obs]);
  },
  _handleSubAny: function(event) {
    if (event.type === VALUE || event.type === ERROR) {
      this._send(event.type, event.value, event.current && this._activating);
    }
  },

  _removeQueue: function(obs) {
    var index = find(this._queue, obs);
    this._queue = remove(this._queue, index);
    return index;
  },
  _removeCur: function(obs) {
    if (this._active) {  this._unsubscribe(obs)  }
    var index = find(this._curSources, obs);
    this._curSources = remove(this._curSources, index);
    if (index !== -1) {
      if (this._queue.length !== 0) {
        this._pullQueue();
      } else if (this._curSources.length === 0) {
        this._onEmpty();
      }
    }
    return index;
  },
  _removeOldest: function() {
    this._removeCur(this._curSources[0]);
  },

  _pullQueue: function() {
    if (this._queue.length !== 0) {
      this._queue = cloneArray(this._queue);
      this._addToCur(this._queue.shift());
    }
  },

  _onActivation: function() {
    var sources = this._curSources
      , i;
    this._activating = true;
    for (i = 0; i < sources.length; i++) {  this._subscribe(sources[i])  }
    this._activating = false;
  },
  _onDeactivation: function() {
    var sources = this._curSources
      , i;
    for (i = 0; i < sources.length; i++) {  this._unsubscribe(sources[i])  }
  },

  _isEmpty: function() {  return this._curSources.length === 0  },
  _onEmpty: function() {},

  _clear: function() {
    Stream.prototype._clear.call(this);
    this._queue = null;
    this._curSources = null;
    this._$handleSubAny = null;
  }

});

// .merge()

var MergeLike = {
  _onEmpty: function() {
    if (this._initialised) {  this._send(END, null, this._activating)  }
  }
};

function Merge(sources) {
  _AbstractPool.call(this);
  if (sources.length === 0) {  this._send(END)  } else {  this._addAll(sources)  }
  this._initialised = true;
}

inherit(Merge, _AbstractPool, extend({_name: 'merge'}, MergeLike));

Kefir.merge = function(obss) {
  return new Merge(obss);
}

Observable.prototype.merge = function(other) {
  return Kefir.merge([this, other]);
}

// .concat()

function Concat(sources) {
  _AbstractPool.call(this, {concurLim: 1, queueLim: -1});
  if (sources.length === 0) {  this._send(END)  } else {  this._addAll(sources)  }
  this._initialised = true;
}

inherit(Concat, _AbstractPool, extend({_name: 'concat'}, MergeLike));

Kefir.concat = function(obss) {
  return new Concat(obss);
}

Observable.prototype.concat = function(other) {
  return Kefir.concat([this, other]);
}

// .pool()

function Pool() {
  _AbstractPool.call(this);
}
Kefir.Pool = Pool;

inherit(Pool, _AbstractPool, {

  _name: 'pool',

  plug: function(obs) {
    this._add(obs);
    return this;
  },
  unplug: function(obs) {
    this._remove(obs);
    return this;
  }

});

Kefir.pool = function() {
  return new Pool();
}

// .bus()

function Bus() {
  _AbstractPool.call(this);
}
Kefir.Bus = Bus;

inherit(Bus, _AbstractPool, {

  _name: 'bus',

  plug: function(obs) {
    this._add(obs);
    return this;
  },
  unplug: function(obs) {
    this._remove(obs);
    return this;
  },

  emit: function(x) {
    this._send(VALUE, x);
    return this;
  },
  error: function(x) {
    this._send(ERROR, x);
    return this;
  },
  end: function() {
    this._send(END);
    return this;
  }

});

Kefir.bus = function() {
  return new Bus();
}

// .flatMap()

function FlatMap(source, fn, options) {
  _AbstractPool.call(this, options);
  this._source = source;
  this._fn = fn || id;
  this._mainEnded = false;
  this._lastCurrent = null;

  var $ = this;
  this._$handleMainSource = function(event) {  $._handleMainSource(event)  };
}

inherit(FlatMap, _AbstractPool, {

  _onActivation: function() {
    _AbstractPool.prototype._onActivation.call(this);
    if (this._active) {
      this._activating = true;
      this._source.onAny(this._$handleMainSource);
      this._activating = false;
    }
  },
  _onDeactivation: function() {
    _AbstractPool.prototype._onDeactivation.call(this);
    this._source.offAny(this._$handleMainSource);
  },

  _handleMainSource: function(event) {
    if (event.type === VALUE) {
      if (!event.current || this._lastCurrent !== event.value) {
        this._add(event.value, this._fn);
      }
      this._lastCurrent = event.value;
    }
    if (event.type === ERROR) {
      this._send(ERROR, event.value, event.current);
    }
    if (event.type === END) {
      if (this._isEmpty()) {
        this._send(END, null, event.current);
      } else {
        this._mainEnded = true;
      }
    }
  },

  _onEmpty: function() {
    if (this._mainEnded) {  this._send(END)  }
  },

  _clear: function() {
    _AbstractPool.prototype._clear.call(this);
    this._source = null;
    this._lastCurrent = null;
    this._$handleMainSource = null;
  }

});

Observable.prototype.flatMap = function(fn) {
  return new FlatMap(this, fn)
    .setName(this, 'flatMap');
}

Observable.prototype.flatMapLatest = function(fn) {
  return new FlatMap(this, fn, {concurLim: 1, drop: 'old'})
    .setName(this, 'flatMapLatest');
}

Observable.prototype.flatMapFirst = function(fn) {
  return new FlatMap(this, fn, {concurLim: 1})
    .setName(this, 'flatMapFirst');
}

Observable.prototype.flatMapConcat = function(fn) {
  return new FlatMap(this, fn, {queueLim: -1, concurLim: 1})
    .setName(this, 'flatMapConcat');
}

Observable.prototype.flatMapConcurLimit = function(fn, limit) {
  var result;
  if (limit === 0) {
    result = Kefir.never();
  } else {
    if (limit < 0) {  limit = -1  }
    result = new FlatMap(this, fn, {queueLim: -1, concurLim: limit});
  }
  return result.setName(this, 'flatMapConcurLimit');
}

// .zip()

function Zip(sources, combinator) {
  Stream.call(this);
  if (sources.length === 0) {
    this._send(END);
  } else {
    this._buffers = map(sources, function(source) {
      return isArray(source) ? cloneArray(source) : [];
    });
    this._sources = map(sources, function(source) {
      return isArray(source) ? Kefir.never() : source;
    });
    this._combinator = combinator ? spread(combinator, this._sources.length) : id;
    this._aliveCount = 0;
  }
}

inherit(Zip, Stream, {

  _name: 'zip',

  _onActivation: function() {
    var i, length = this._sources.length;
    this._drainArrays();
    this._aliveCount = length;
    for (i = 0; i < length; i++) {
      this._sources[i].onAny(this._bindHandleAny(i), [this, i]);
    }
  },

  _onDeactivation: function() {
    for (var i = 0; i < this._sources.length; i++) {
      this._sources[i].offAny(null, [this, i]);
    }
  },

  _emit: function(isCurrent) {
    var values = new Array(this._buffers.length);
    for (var i = 0; i < this._buffers.length; i++) {
      values[i] = this._buffers[i].shift();
    }
    this._send(VALUE, this._combinator(values), isCurrent);
  },

  _isFull: function() {
    for (var i = 0; i < this._buffers.length; i++) {
      if (this._buffers[i].length === 0) {
        return false;
      }
    }
    return true;
  },

  _emitIfFull: function(isCurrent) {
    if (this._isFull()) {
      this._emit(isCurrent);
    }
  },

  _drainArrays: function() {
    while (this._isFull()) {
      this._emit(true);
    }
  },

  _bindHandleAny: function(i) {
    var $ = this;
    return function(event) {  $._handleAny(i, event)  };
  },

  _handleAny: function(i, event) {
    if (event.type === VALUE) {
      this._buffers[i].push(event.value);
      this._emitIfFull(event.current);
    }
    if (event.type === ERROR) {
      this._send(ERROR, event.value, event.current);
    }
    if (event.type === END) {
      this._aliveCount--;
      if (this._aliveCount === 0) {
        this._send(END, null, event.current);
      }
    }
  },

  _clear: function() {
    Stream.prototype._clear.call(this);
    this._sources = null;
    this._buffers = null;
    this._combinator = null;
  }

});

Kefir.zip = function(sources, combinator) {
  return new Zip(sources, combinator);
}

Observable.prototype.zip = function(other, combinator) {
  return new Zip([this, other], combinator);
}

// .sampledBy()

function SampledBy(passive, active, combinator) {
  Stream.call(this);
  if (active.length === 0) {
    this._send(END);
  } else {
    this._passiveCount = passive.length;
    this._sources = concat(passive, active);
    this._combinator = combinator ? spread(combinator, this._sources.length) : id;
    this._aliveCount = 0;
    this._currents = new Array(this._sources.length);
    fillArray(this._currents, NOTHING);
    this._activating = false;
    this._emitAfterActivation = false;
    this._endAfterActivation = false;
  }
}

inherit(SampledBy, Stream, {

  _name: 'sampledBy',

  _onActivation: function() {
    var length = this._sources.length,
        i;
    this._aliveCount = length - this._passiveCount;
    this._activating = true;
    for (i = 0; i < length; i++) {
      this._sources[i].onAny(this._bindHandleAny(i), [this, i]);
    }
    this._activating = false;
    if (this._emitAfterActivation) {
      this._emitAfterActivation = false;
      this._emitIfFull(true);
    }
    if (this._endAfterActivation) {
      this._send(END, null, true);
    }
  },

  _onDeactivation: function() {
    var length = this._sources.length,
        i;
    for (i = 0; i < length; i++) {
      this._sources[i].offAny(null, [this, i]);
    }
  },

  _emitIfFull: function(isCurrent) {
    if (!contains(this._currents, NOTHING)) {
      var combined = cloneArray(this._currents);
      combined = this._combinator(combined);
      this._send(VALUE, combined, isCurrent);
    }
  },

  _bindHandleAny: function(i) {
    var $ = this;
    return function(event) {  $._handleAny(i, event)  };
  },

  _handleAny: function(i, event) {
    if (event.type === VALUE) {
      this._currents[i] = event.value;
      if (i >= this._passiveCount) {
        if (this._activating) {
          this._emitAfterActivation = true;
        } else {
          this._emitIfFull(event.current);
        }
      }
    }
    if (event.type === ERROR) {
      this._send(ERROR, event.value, event.current);
    }
    if (event.type === END) {
      if (i >= this._passiveCount) {
        this._aliveCount--;
        if (this._aliveCount === 0) {
          if (this._activating) {
            this._endAfterActivation = true;
          } else {
            this._send(END, null, event.current);
          }
        }
      }
    }
  },

  _clear: function() {
    Stream.prototype._clear.call(this);
    this._sources = null;
    this._currents = null;
    this._combinator = null;
  }

});

Kefir.sampledBy = function(passive, active, combinator) {
  return new SampledBy(passive, active, combinator);
}

Observable.prototype.sampledBy = function(other, combinator) {
  return Kefir.sampledBy([this], [other], combinator || id);
}

// .combine()

Kefir.combine = function(sources, combinator) {
  return new SampledBy([], sources, combinator).setName('combine');
}

Observable.prototype.combine = function(other, combinator) {
  return Kefir.combine([this, other], combinator);
}
<commit_msg>added Pool and Bus as publicly available constructors<commit_after>function _AbstractPool(options) {
  Stream.call(this);

  this._queueLim = get(options, 'queueLim', 0);
  this._concurLim = get(options, 'concurLim', -1);
  this._drop = get(options, 'drop', 'new');
  if (this._concurLim === 0) {
    throw new Error('options.concurLim can\'t be 0');
  }

  var $ = this;
  this._$handleSubAny = function(event) {  $._handleSubAny(event)  };

  this._queue = [];
  this._curSources = [];
  this._activating = false;
}

inherit(_AbstractPool, Stream, {

  _name: 'abstractPool',

  _add: function(obj, toObs) {
    toObs = toObs || id;
    if (this._concurLim === -1 || this._curSources.length < this._concurLim) {
      this._addToCur(toObs(obj));
    } else {
      if (this._queueLim === -1 || this._queue.length < this._queueLim) {
        this._addToQueue(toObs(obj));
      } else if (this._drop === 'old') {
        this._removeOldest();
        this._add(toObs(obj));
      }
    }
  },
  _addAll: function(obss) {
    var $ = this;
    forEach(obss, function(obs) {  $._add(obs)  });
  },
  _remove: function(obs) {
    if (this._removeCur(obs) === -1) {
      this._removeQueue(obs);
    }
  },

  _addToQueue: function(obs) {
    this._queue = concat(this._queue, [obs]);
  },
  _addToCur: function(obs) {
    this._curSources = concat(this._curSources, [obs]);
    if (this._active) {  this._subscribe(obs)  }
  },
  _subscribe: function(obs) {
    var $ = this;
    obs.onAny(this._$handleSubAny);
    obs.onEnd(function() {  $._removeCur(obs)  }, [this, obs]);
  },
  _unsubscribe: function(obs) {
    obs.offAny(this._$handleSubAny);
    obs.offEnd(null, [this, obs]);
  },
  _handleSubAny: function(event) {
    if (event.type === VALUE || event.type === ERROR) {
      this._send(event.type, event.value, event.current && this._activating);
    }
  },

  _removeQueue: function(obs) {
    var index = find(this._queue, obs);
    this._queue = remove(this._queue, index);
    return index;
  },
  _removeCur: function(obs) {
    if (this._active) {  this._unsubscribe(obs)  }
    var index = find(this._curSources, obs);
    this._curSources = remove(this._curSources, index);
    if (index !== -1) {
      if (this._queue.length !== 0) {
        this._pullQueue();
      } else if (this._curSources.length === 0) {
        this._onEmpty();
      }
    }
    return index;
  },
  _removeOldest: function() {
    this._removeCur(this._curSources[0]);
  },

  _pullQueue: function() {
    if (this._queue.length !== 0) {
      this._queue = cloneArray(this._queue);
      this._addToCur(this._queue.shift());
    }
  },

  _onActivation: function() {
    var sources = this._curSources
      , i;
    this._activating = true;
    for (i = 0; i < sources.length; i++) {  this._subscribe(sources[i])  }
    this._activating = false;
  },
  _onDeactivation: function() {
    var sources = this._curSources
      , i;
    for (i = 0; i < sources.length; i++) {  this._unsubscribe(sources[i])  }
  },

  _isEmpty: function() {  return this._curSources.length === 0  },
  _onEmpty: function() {},

  _clear: function() {
    Stream.prototype._clear.call(this);
    this._queue = null;
    this._curSources = null;
    this._$handleSubAny = null;
  }

});

// .merge()

var MergeLike = {
  _onEmpty: function() {
    if (this._initialised) {  this._send(END, null, this._activating)  }
  }
};

function Merge(sources) {
  _AbstractPool.call(this);
  if (sources.length === 0) {  this._send(END)  } else {  this._addAll(sources)  }
  this._initialised = true;
}

inherit(Merge, _AbstractPool, extend({_name: 'merge'}, MergeLike));

Kefir.merge = function(obss) {
  return new Merge(obss);
}

Observable.prototype.merge = function(other) {
  return Kefir.merge([this, other]);
}

// .concat()

function Concat(sources) {
  _AbstractPool.call(this, {concurLim: 1, queueLim: -1});
  if (sources.length === 0) {  this._send(END)  } else {  this._addAll(sources)  }
  this._initialised = true;
}

inherit(Concat, _AbstractPool, extend({_name: 'concat'}, MergeLike));

Kefir.concat = function(obss) {
  return new Concat(obss);
}

Observable.prototype.concat = function(other) {
  return Kefir.concat([this, other]);
}

// .pool()

function Pool() {
  _AbstractPool.call(this);
}
Kefir.Pool = Pool;

inherit(Pool, _AbstractPool, {

  _name: 'pool',

  plug: function(obs) {
    this._add(obs);
    return this;
  },
  unplug: function(obs) {
    this._remove(obs);
    return this;
  }

});

Kefir.pool = function() {
  return new Pool();
}

// .bus()

function Bus() {
  _AbstractPool.call(this);
}
Kefir.Bus = Bus;

inherit(Bus, _AbstractPool, {

  _name: 'bus',

  plug: function(obs) {
    this._add(obs);
    return this;
  },
  unplug: function(obs) {
    this._remove(obs);
    return this;
  },

  emit: function(x) {
    this._send(VALUE, x);
    return this;
  },
  error: function(x) {
    this._send(ERROR, x);
    return this;
  },
  end: function() {
    this._send(END);
    return this;
  }

});

Kefir.bus = function() {
  return new Bus();
}

// .flatMap()

function FlatMap(source, fn, options) {
  _AbstractPool.call(this, options);
  this._source = source;
  this._fn = fn || id;
  this._mainEnded = false;
  this._lastCurrent = null;

  var $ = this;
  this._$handleMainSource = function(event) {  $._handleMainSource(event)  };
}

inherit(FlatMap, _AbstractPool, {

  _onActivation: function() {
    _AbstractPool.prototype._onActivation.call(this);
    if (this._active) {
      this._activating = true;
      this._source.onAny(this._$handleMainSource);
      this._activating = false;
    }
  },
  _onDeactivation: function() {
    _AbstractPool.prototype._onDeactivation.call(this);
    this._source.offAny(this._$handleMainSource);
  },

  _handleMainSource: function(event) {
    if (event.type === VALUE) {
      if (!event.current || this._lastCurrent !== event.value) {
        this._add(event.value, this._fn);
      }
      this._lastCurrent = event.value;
    }
    if (event.type === ERROR) {
      this._send(ERROR, event.value, event.current);
    }
    if (event.type === END) {
      if (this._isEmpty()) {
        this._send(END, null, event.current);
      } else {
        this._mainEnded = true;
      }
    }
  },

  _onEmpty: function() {
    if (this._mainEnded) {  this._send(END)  }
  },

  _clear: function() {
    _AbstractPool.prototype._clear.call(this);
    this._source = null;
    this._lastCurrent = null;
    this._$handleMainSource = null;
  }

});

Observable.prototype.flatMap = function(fn) {
  return new FlatMap(this, fn)
    .setName(this, 'flatMap');
}

Observable.prototype.flatMapLatest = function(fn) {
  return new FlatMap(this, fn, {concurLim: 1, drop: 'old'})
    .setName(this, 'flatMapLatest');
}

Observable.prototype.flatMapFirst = function(fn) {
  return new FlatMap(this, fn, {concurLim: 1})
    .setName(this, 'flatMapFirst');
}

Observable.prototype.flatMapConcat = function(fn) {
  return new FlatMap(this, fn, {queueLim: -1, concurLim: 1})
    .setName(this, 'flatMapConcat');
}

Observable.prototype.flatMapConcurLimit = function(fn, limit) {
  var result;
  if (limit === 0) {
    result = Kefir.never();
  } else {
    if (limit < 0) {  limit = -1  }
    result = new FlatMap(this, fn, {queueLim: -1, concurLim: limit});
  }
  return result.setName(this, 'flatMapConcurLimit');
}

// .zip()

function Zip(sources, combinator) {
  Stream.call(this);
  if (sources.length === 0) {
    this._send(END);
  } else {
    this._buffers = map(sources, function(source) {
      return isArray(source) ? cloneArray(source) : [];
    });
    this._sources = map(sources, function(source) {
      return isArray(source) ? Kefir.never() : source;
    });
    this._combinator = combinator ? spread(combinator, this._sources.length) : id;
    this._aliveCount = 0;
  }
}

inherit(Zip, Stream, {

  _name: 'zip',

  _onActivation: function() {
    var i, length = this._sources.length;
    this._drainArrays();
    this._aliveCount = length;
    for (i = 0; i < length; i++) {
      this._sources[i].onAny(this._bindHandleAny(i), [this, i]);
    }
  },

  _onDeactivation: function() {
    for (var i = 0; i < this._sources.length; i++) {
      this._sources[i].offAny(null, [this, i]);
    }
  },

  _emit: function(isCurrent) {
    var values = new Array(this._buffers.length);
    for (var i = 0; i < this._buffers.length; i++) {
      values[i] = this._buffers[i].shift();
    }
    this._send(VALUE, this._combinator(values), isCurrent);
  },

  _isFull: function() {
    for (var i = 0; i < this._buffers.length; i++) {
      if (this._buffers[i].length === 0) {
        return false;
      }
    }
    return true;
  },

  _emitIfFull: function(isCurrent) {
    if (this._isFull()) {
      this._emit(isCurrent);
    }
  },

  _drainArrays: function() {
    while (this._isFull()) {
      this._emit(true);
    }
  },

  _bindHandleAny: function(i) {
    var $ = this;
    return function(event) {  $._handleAny(i, event)  };
  },

  _handleAny: function(i, event) {
    if (event.type === VALUE) {
      this._buffers[i].push(event.value);
      this._emitIfFull(event.current);
    }
    if (event.type === ERROR) {
      this._send(ERROR, event.value, event.current);
    }
    if (event.type === END) {
      this._aliveCount--;
      if (this._aliveCount === 0) {
        this._send(END, null, event.current);
      }
    }
  },

  _clear: function() {
    Stream.prototype._clear.call(this);
    this._sources = null;
    this._buffers = null;
    this._combinator = null;
  }

});

Kefir.zip = function(sources, combinator) {
  return new Zip(sources, combinator);
}

Observable.prototype.zip = function(other, combinator) {
  return new Zip([this, other], combinator);
}

// .sampledBy()

function SampledBy(passive, active, combinator) {
  Stream.call(this);
  if (active.length === 0) {
    this._send(END);
  } else {
    this._passiveCount = passive.length;
    this._sources = concat(passive, active);
    this._combinator = combinator ? spread(combinator, this._sources.length) : id;
    this._aliveCount = 0;
    this._currents = new Array(this._sources.length);
    fillArray(this._currents, NOTHING);
    this._activating = false;
    this._emitAfterActivation = false;
    this._endAfterActivation = false;
  }
}

inherit(SampledBy, Stream, {

  _name: 'sampledBy',

  _onActivation: function() {
    var length = this._sources.length,
        i;
    this._aliveCount = length - this._passiveCount;
    this._activating = true;
    for (i = 0; i < length; i++) {
      this._sources[i].onAny(this._bindHandleAny(i), [this, i]);
    }
    this._activating = false;
    if (this._emitAfterActivation) {
      this._emitAfterActivation = false;
      this._emitIfFull(true);
    }
    if (this._endAfterActivation) {
      this._send(END, null, true);
    }
  },

  _onDeactivation: function() {
    var length = this._sources.length,
        i;
    for (i = 0; i < length; i++) {
      this._sources[i].offAny(null, [this, i]);
    }
  },

  _emitIfFull: function(isCurrent) {
    if (!contains(this._currents, NOTHING)) {
      var combined = cloneArray(this._currents);
      combined = this._combinator(combined);
      this._send(VALUE, combined, isCurrent);
    }
  },

  _bindHandleAny: function(i) {
    var $ = this;
    return function(event) {  $._handleAny(i, event)  };
  },

  _handleAny: function(i, event) {
    if (event.type === VALUE) {
      this._currents[i] = event.value;
      if (i >= this._passiveCount) {
        if (this._activating) {
          this._emitAfterActivation = true;
        } else {
          this._emitIfFull(event.current);
        }
      }
    }
    if (event.type === ERROR) {
      this._send(ERROR, event.value, event.current);
    }
    if (event.type === END) {
      if (i >= this._passiveCount) {
        this._aliveCount--;
        if (this._aliveCount === 0) {
          if (this._activating) {
            this._endAfterActivation = true;
          } else {
            this._send(END, null, event.current);
          }
        }
      }
    }
  },

  _clear: function() {
    Stream.prototype._clear.call(this);
    this._sources = null;
    this._currents = null;
    this._combinator = null;
  }

});

Kefir.sampledBy = function(passive, active, combinator) {
  return new SampledBy(passive, active, combinator);
}

Observable.prototype.sampledBy = function(other, combinator) {
  return Kefir.sampledBy([this], [other], combinator || id);
}

// .combine()

Kefir.combine = function(sources, combinator) {
  return new SampledBy([], sources, combinator).setName('combine');
}

Observable.prototype.combine = function(other, combinator) {
  return Kefir.combine([this, other], combinator);
}
<|endoftext|>


<commit_before>// Copied from Vuex’s util.js
function deepClone (obj) {
  if (Array.isArray(obj)) {
    return obj.map(deepClone)
  } else if (obj && typeof obj === 'object') {
    var cloned = {}
    var keys = Object.keys(obj)
    for (var i = 0, l = keys.length; i < l; i++) {
      var key = keys[i]
      cloned[key] = deepClone(obj[key])
    }
    return cloned
  } else {
    return obj
  }
}

module.exports = {
  data () {
    return {
      search: '',
      isOpen: false,
      value: [],
      loading: false
    }
  },
  props: {
    /**
     * Array of available options: Objects, Strings or Integers.
     * If array of objects, visible label will default to option.label.
     * If `labal` prop is passed, label will equal option['label']
     * @type {Array}
     */
    options: {
      type: Array,
      required: true
    },
    /**
     * Equivalent to the `multiple` attribute on a `<select>` input.
     * @default false
     * @type {Boolean}
     */
    multiple: {
      type: Boolean,
      default: false
    },
    /**
     * Required. Presets the selected options. Add `.sync` to
     * update parent value. If this.onChange callback is present,
     * this will not update. In that case, the parent is responsible
     * for updating this value.
     * @type {Object||Array||String||Integer}
     */
    selected: {
      required: true
    },
    /**
     * Key to compare objects
     * @default 'id'
     * @type {String}
     */
    key: {
      type: String,
      default: false
    },
    /**
     * Label to look for in option Object
     * @default 'label'
     * @type {String}
     */
    label: {
      type: String,
      default: false
    },
    /**
     * Enable/disable search in options
     * @default true
     * @type {Boolean}
     */
    searchable: {
      type: Boolean,
      default: true
    },
    /**
     * Clear the search input after select()
     * @default true
     * @type {Boolean}
     */
    clearOnSelect: {
      type: Boolean,
      default: true
    },
    /**
     * Hide already selected options
     * @default false
     * @type {Boolean}
     */
    hideSelected: {
      type: Boolean,
      default: false
    },
    /**
     * Equivalent to the `placeholder` attribute on a `<select>` input.
     * @default 'Select option'
     * @type {String}
     */
    placeholder: {
      type: String,
      default: 'Select option'
    },
    /**
     * Sets maxHeight style value of the dropdown
     * @default 300
     * @type {Integer}
     */
    maxHeight: {
      type: Number,
      default: 300
    },
    /**
     * Allow to remove all selected values
     * @default true
     * @type {Boolean}
     */
    allowEmpty: {
      type: Boolean,
      default: true
    },
    /**
     * Callback function to call after this.value changes
     * @callback onChange
     * @default false
     * @param {Array||Object||String||Integer} Current this.value
     * @param {Integer} $index of current selection
     * @type {Function}
     */
    onChange: {
      type: Function,
      default: false
    },
    /**
     * Callback function to call after this.search changes
     * @callback onSearchChange
     * @default false
     * @param {String} Pass current search String
     * @type {Function}
     */
    onSearchChange: {
      type: Function,
      default: false
    },
    /**
     * Value that indicates if the dropdown has been used.
     * Useful for validation.
     * @default false
     * @type {Boolean}
     */
    touched: {
      type: Boolean,
      default: false
    },
    /**
     * Reset this.value, this.search, this.selected after this.value changes.
     * Useful if want to create a stateless dropdown, that fires the this.onChange
     * callback function with different params.
     * @default false
     * @type {Boolean}
     */
    resetAfter: {
      type: Boolean,
      default: false
    },
    /**
     * Enable/disable closing after selecting an option
     * @default true
     * @type {Boolean}
     */
    closeOnSelect: {
      type: Boolean,
      default: true
    },
    /**
     * Function to interpolate the custom label
     * @default false
     * @type {Function}
     */
    customLabel: {
      type: Function,
      default: false
    },
    /**
     * Disable / Enable tagging
     * @default false
     * @type {Boolean}
     */
    taggable: {
      type: Boolean,
      default: false
    },
    /**
     * Callback function to run when attemting to add a tag
     * @default suitable for primitive values
     * @param {String} Tag string to build a tag
     * @type {Function}
     */
    onTag: {
      type: Function,
      default: function (tag) {
        this.options.push(tag)
        this.value.push(tag)
      }
    },
    /**
     * String to show when highlighting a potential tag
     * @default 'Press enter to create a tag'
     * @type {String}
    */
    tagPlaceholder: {
      type: String,
      default: 'Press enter to create a tag'
    },
    /**
     * Number of allowed selected options. No limit if false.
     * @default False
     * @type {Number}
    */
    max: {
      type: Number,
      default: false
    }
  },
  created () {
    if (!this.selected) {
      this.$set('value', this.multiple ? [] : null)
    } else {
      this.value = deepClone(this.selected)
    }
    if (this.searchable && !this.multiple) {
      this.search = this.getOptionLabel(this.value)
    }
  },
  computed: {
    filteredOptions () {
      let search = this.search ? this.search : ''
      var options = this.hideSelected
        ? this.options.filter(this.isNotSelected)
        : this.options
      options = this.$options.filters.filterBy(options, search)
      if (this.taggable && search > 0 && !this.isExistingOption(search)) {
        options.unshift({ isTag: true, label: search })
      }
      return options
    },
    valueKeys () {
      if (this.key) {
        return this.multiple
          ? this.value.map(element => element[this.key])
          : this.value[this.key]
      } else {
        return this.value
      }
    },
    optionKeys () {
      return this.label
        ? this.options.map(element => element[this.label])
        : this.options
    }
  },
  watch: {
    'value' () {
      if (this.onChange && JSON.stringify(this.value) !== JSON.stringify(this.selected)) {
        this.onChange(deepClone(this.value))
      } else {
        this.$set('selected', deepClone(this.value))
      }
      if (this.resetAfter) {
        this.$set('value', null)
        this.$set('search', null)
        this.$set('selected', null)
      }
      if (!this.multiple && this.searchable && this.clearOnSelect) {
        this.search = this.getOptionLabel(this.value)
      }
    },
    'search' () {
      if (this.onSearchChange) {
        this.onSearchChange(this.search)
        this.loading = true
      }
    },
    'options' () {
      this.onSearchChange && (this.loading = false)
    },
    'selected' (newVal, oldVal) {
      if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
        this.value = deepClone(this.selected)
      }
    }
  },
  methods: {
    /**
     * Finds out if the given query is already present
     * in the available options
     * @param  {String}
     * @returns {Boolean} returns true if element is available
     */
    isExistingOption (query) {
      return !this.options
        ? false
        : this.optionKeys.indexOf(query) > -1
    },
    /**
     * Finds out if the given element is already present
     * in the result value
     * @param  {Object||String||Integer} option passed element to check
     * @returns {Boolean} returns true if element is selected
     */
    isSelected (option) {
      /* istanbul ignore else */
      if (!this.value) return false
      const opt = this.key
        ? option[this.key]
        : option

      if (this.multiple) {
        return this.valueKeys.indexOf(opt) > -1
      } else {
        return this.valueKeys === opt
      }
    },
    /**
     * Finds out if the given element is NOT already present
     * in the result value. Negated isSelected method.
     * @param  {Object||String||Integer} option passed element to check
     * @returns {Boolean} returns true if element is not selected
     */
    isNotSelected (option) {
      return !this.isSelected(option)
    },
    /**
     * Returns the option[this.label]
     * if option is Object. Otherwise check for option.label.
     * If non is found, return entrie option.
     *
     * @param  {Object||String||Integer} Passed option
     * @returns {Object||String}
     */
    getOptionLabel (option) {
      if (typeof option === 'object' && option !== null) {
        if (this.customLabel) {
          return this.customLabel(option)
        } else {
          if (this.label && option[this.label]) {
            return option[this.label]
          } else if (option.label) {
            return option.label
          }
        }
      } else {
        return option
      }
    },
    /**
     * Add the given option to the list of selected options
     * or sets the option as the selected option.
     * If option is already selected -> remove it from the results.
     *
     * @param  {Object||String||Integer} option to select/deselect
     */
    select (option) {
      if (this.max && this.multiple && this.value.length === this.max) return
      if (option.isTag) {
        this.onTag(option.label)
        this.search = ''
      } else {
        if (this.multiple) {
          if (!this.isNotSelected(option)) {
            this.removeElement(option)
          } else {
            this.value.push(option)
            if (this.clearOnSelect) { this.search = '' }
          }
        } else {
          this.$set('value',
            !this.isNotSelected(option) && this.allowEmpty
              ? null
              : option
          )
          if (this.closeOnSelect) {
            this.searchable
              ? this.$els.search.blur()
              : this.$el.blur()
          }
        }
      }
    },
    /**
     * Removes the given option from the selected options.
     * Additionally checks this.allowEmpty prop if option can be removed when
     * it is the last selected option.
     *
     * @param  {type} option description
     * @returns {type}        description
     */
    removeElement (option) {
      /* istanbul ignore else */
      if (this.allowEmpty || this.value.length > 1) {
        if (this.multiple && typeof option === 'object') {
          const index = this.valueKeys.indexOf(option[this.key])
          this.value.splice(index, 1)
        } else {
          this.value.$remove(option)
        }
      }
    },
    /**
     * Calls this.removeElement() with the last element
     * from this.value (selected element Array)
     *
     * @fires this#removeElement
     */
    removeLastElement () {
      /* istanbul ignore else */
      if (this.search.length === 0 && Array.isArray(this.value)) {
        this.removeElement(this.value[this.value.length - 1])
      }
    },
    /**
     * Opens the multiselect’s dropdown.
     * Sets this.isOpen to TRUE
     */
    activate () {
      /* istanbul ignore else */
      if (!this.isOpen) {
        this.isOpen = true
        /* istanbul ignore else  */
        if (this.searchable) {
          this.search = ''
          this.$els.search.focus()
        } else {
          this.$el.focus()
        }
      }
    },
    /**
     * Closes the multiselect’s dropdown.
     * Sets this.isOpen to FALSE
     */
    deactivate () {
      /* istanbul ignore else */
      if (this.isOpen) {
        this.isOpen = false
        this.touched = true
        /* istanbul ignore else  */
        if (this.searchable) {
          this.$els.search.blur()
          this.search = this.multiple
            ? ''
            : this.getOptionLabel(this.value)
        } else {
          this.$el.blur()
        }
      }
    },
    /**
     * Call this.activate() or this.deactivate()
     * depending on this.isOpen value.
     *
     * @fires this#activate || this#deactivate
     * @property {Boolean} isOpen indicates if dropdown is open
     */
    toggle () {
      this.isOpen
        ? this.deactivate()
        : this.activate()
    }
  }
}
<commit_msg>Woops fix: forgot .length<commit_after>// Copied from Vuex’s util.js
function deepClone (obj) {
  if (Array.isArray(obj)) {
    return obj.map(deepClone)
  } else if (obj && typeof obj === 'object') {
    var cloned = {}
    var keys = Object.keys(obj)
    for (var i = 0, l = keys.length; i < l; i++) {
      var key = keys[i]
      cloned[key] = deepClone(obj[key])
    }
    return cloned
  } else {
    return obj
  }
}

module.exports = {
  data () {
    return {
      search: '',
      isOpen: false,
      value: [],
      loading: false
    }
  },
  props: {
    /**
     * Array of available options: Objects, Strings or Integers.
     * If array of objects, visible label will default to option.label.
     * If `labal` prop is passed, label will equal option['label']
     * @type {Array}
     */
    options: {
      type: Array,
      required: true
    },
    /**
     * Equivalent to the `multiple` attribute on a `<select>` input.
     * @default false
     * @type {Boolean}
     */
    multiple: {
      type: Boolean,
      default: false
    },
    /**
     * Required. Presets the selected options. Add `.sync` to
     * update parent value. If this.onChange callback is present,
     * this will not update. In that case, the parent is responsible
     * for updating this value.
     * @type {Object||Array||String||Integer}
     */
    selected: {
      required: true
    },
    /**
     * Key to compare objects
     * @default 'id'
     * @type {String}
     */
    key: {
      type: String,
      default: false
    },
    /**
     * Label to look for in option Object
     * @default 'label'
     * @type {String}
     */
    label: {
      type: String,
      default: false
    },
    /**
     * Enable/disable search in options
     * @default true
     * @type {Boolean}
     */
    searchable: {
      type: Boolean,
      default: true
    },
    /**
     * Clear the search input after select()
     * @default true
     * @type {Boolean}
     */
    clearOnSelect: {
      type: Boolean,
      default: true
    },
    /**
     * Hide already selected options
     * @default false
     * @type {Boolean}
     */
    hideSelected: {
      type: Boolean,
      default: false
    },
    /**
     * Equivalent to the `placeholder` attribute on a `<select>` input.
     * @default 'Select option'
     * @type {String}
     */
    placeholder: {
      type: String,
      default: 'Select option'
    },
    /**
     * Sets maxHeight style value of the dropdown
     * @default 300
     * @type {Integer}
     */
    maxHeight: {
      type: Number,
      default: 300
    },
    /**
     * Allow to remove all selected values
     * @default true
     * @type {Boolean}
     */
    allowEmpty: {
      type: Boolean,
      default: true
    },
    /**
     * Callback function to call after this.value changes
     * @callback onChange
     * @default false
     * @param {Array||Object||String||Integer} Current this.value
     * @param {Integer} $index of current selection
     * @type {Function}
     */
    onChange: {
      type: Function,
      default: false
    },
    /**
     * Callback function to call after this.search changes
     * @callback onSearchChange
     * @default false
     * @param {String} Pass current search String
     * @type {Function}
     */
    onSearchChange: {
      type: Function,
      default: false
    },
    /**
     * Value that indicates if the dropdown has been used.
     * Useful for validation.
     * @default false
     * @type {Boolean}
     */
    touched: {
      type: Boolean,
      default: false
    },
    /**
     * Reset this.value, this.search, this.selected after this.value changes.
     * Useful if want to create a stateless dropdown, that fires the this.onChange
     * callback function with different params.
     * @default false
     * @type {Boolean}
     */
    resetAfter: {
      type: Boolean,
      default: false
    },
    /**
     * Enable/disable closing after selecting an option
     * @default true
     * @type {Boolean}
     */
    closeOnSelect: {
      type: Boolean,
      default: true
    },
    /**
     * Function to interpolate the custom label
     * @default false
     * @type {Function}
     */
    customLabel: {
      type: Function,
      default: false
    },
    /**
     * Disable / Enable tagging
     * @default false
     * @type {Boolean}
     */
    taggable: {
      type: Boolean,
      default: false
    },
    /**
     * Callback function to run when attemting to add a tag
     * @default suitable for primitive values
     * @param {String} Tag string to build a tag
     * @type {Function}
     */
    onTag: {
      type: Function,
      default: function (tag) {
        this.options.push(tag)
        this.value.push(tag)
      }
    },
    /**
     * String to show when highlighting a potential tag
     * @default 'Press enter to create a tag'
     * @type {String}
    */
    tagPlaceholder: {
      type: String,
      default: 'Press enter to create a tag'
    },
    /**
     * Number of allowed selected options. No limit if false.
     * @default False
     * @type {Number}
    */
    max: {
      type: Number,
      default: false
    }
  },
  created () {
    if (!this.selected) {
      this.$set('value', this.multiple ? [] : null)
    } else {
      this.value = deepClone(this.selected)
    }
    if (this.searchable && !this.multiple) {
      this.search = this.getOptionLabel(this.value)
    }
  },
  computed: {
    filteredOptions () {
      let search = this.search ? this.search : ''
      var options = this.hideSelected
        ? this.options.filter(this.isNotSelected)
        : this.options
      options = this.$options.filters.filterBy(options, search)
      if (this.taggable && search.length > 0 && !this.isExistingOption(search)) {
        options.unshift({ isTag: true, label: search })
      }
      return options
    },
    valueKeys () {
      if (this.key) {
        return this.multiple
          ? this.value.map(element => element[this.key])
          : this.value[this.key]
      } else {
        return this.value
      }
    },
    optionKeys () {
      return this.label
        ? this.options.map(element => element[this.label])
        : this.options
    }
  },
  watch: {
    'value' () {
      if (this.onChange && JSON.stringify(this.value) !== JSON.stringify(this.selected)) {
        this.onChange(deepClone(this.value))
      } else {
        this.$set('selected', deepClone(this.value))
      }
      if (this.resetAfter) {
        this.$set('value', null)
        this.$set('search', null)
        this.$set('selected', null)
      }
      if (!this.multiple && this.searchable && this.clearOnSelect) {
        this.search = this.getOptionLabel(this.value)
      }
    },
    'search' () {
      if (this.onSearchChange) {
        this.onSearchChange(this.search)
        this.loading = true
      }
    },
    'options' () {
      this.onSearchChange && (this.loading = false)
    },
    'selected' (newVal, oldVal) {
      if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
        this.value = deepClone(this.selected)
      }
    }
  },
  methods: {
    /**
     * Finds out if the given query is already present
     * in the available options
     * @param  {String}
     * @returns {Boolean} returns true if element is available
     */
    isExistingOption (query) {
      return !this.options
        ? false
        : this.optionKeys.indexOf(query) > -1
    },
    /**
     * Finds out if the given element is already present
     * in the result value
     * @param  {Object||String||Integer} option passed element to check
     * @returns {Boolean} returns true if element is selected
     */
    isSelected (option) {
      /* istanbul ignore else */
      if (!this.value) return false
      const opt = this.key
        ? option[this.key]
        : option

      if (this.multiple) {
        return this.valueKeys.indexOf(opt) > -1
      } else {
        return this.valueKeys === opt
      }
    },
    /**
     * Finds out if the given element is NOT already present
     * in the result value. Negated isSelected method.
     * @param  {Object||String||Integer} option passed element to check
     * @returns {Boolean} returns true if element is not selected
     */
    isNotSelected (option) {
      return !this.isSelected(option)
    },
    /**
     * Returns the option[this.label]
     * if option is Object. Otherwise check for option.label.
     * If non is found, return entrie option.
     *
     * @param  {Object||String||Integer} Passed option
     * @returns {Object||String}
     */
    getOptionLabel (option) {
      if (typeof option === 'object' && option !== null) {
        if (this.customLabel) {
          return this.customLabel(option)
        } else {
          if (this.label && option[this.label]) {
            return option[this.label]
          } else if (option.label) {
            return option.label
          }
        }
      } else {
        return option
      }
    },
    /**
     * Add the given option to the list of selected options
     * or sets the option as the selected option.
     * If option is already selected -> remove it from the results.
     *
     * @param  {Object||String||Integer} option to select/deselect
     */
    select (option) {
      if (this.max && this.multiple && this.value.length === this.max) return
      if (option.isTag) {
        this.onTag(option.label)
        this.search = ''
      } else {
        if (this.multiple) {
          if (!this.isNotSelected(option)) {
            this.removeElement(option)
          } else {
            this.value.push(option)
            if (this.clearOnSelect) { this.search = '' }
          }
        } else {
          this.$set('value',
            !this.isNotSelected(option) && this.allowEmpty
              ? null
              : option
          )
          if (this.closeOnSelect) {
            this.searchable
              ? this.$els.search.blur()
              : this.$el.blur()
          }
        }
      }
    },
    /**
     * Removes the given option from the selected options.
     * Additionally checks this.allowEmpty prop if option can be removed when
     * it is the last selected option.
     *
     * @param  {type} option description
     * @returns {type}        description
     */
    removeElement (option) {
      /* istanbul ignore else */
      if (this.allowEmpty || this.value.length > 1) {
        if (this.multiple && typeof option === 'object') {
          const index = this.valueKeys.indexOf(option[this.key])
          this.value.splice(index, 1)
        } else {
          this.value.$remove(option)
        }
      }
    },
    /**
     * Calls this.removeElement() with the last element
     * from this.value (selected element Array)
     *
     * @fires this#removeElement
     */
    removeLastElement () {
      /* istanbul ignore else */
      if (this.search.length === 0 && Array.isArray(this.value)) {
        this.removeElement(this.value[this.value.length - 1])
      }
    },
    /**
     * Opens the multiselect’s dropdown.
     * Sets this.isOpen to TRUE
     */
    activate () {
      /* istanbul ignore else */
      if (!this.isOpen) {
        this.isOpen = true
        /* istanbul ignore else  */
        if (this.searchable) {
          this.search = ''
          this.$els.search.focus()
        } else {
          this.$el.focus()
        }
      }
    },
    /**
     * Closes the multiselect’s dropdown.
     * Sets this.isOpen to FALSE
     */
    deactivate () {
      /* istanbul ignore else */
      if (this.isOpen) {
        this.isOpen = false
        this.touched = true
        /* istanbul ignore else  */
        if (this.searchable) {
          this.$els.search.blur()
          this.search = this.multiple
            ? ''
            : this.getOptionLabel(this.value)
        } else {
          this.$el.blur()
        }
      }
    },
    /**
     * Call this.activate() or this.deactivate()
     * depending on this.isOpen value.
     *
     * @fires this#activate || this#deactivate
     * @property {Boolean} isOpen indicates if dropdown is open
     */
    toggle () {
      this.isOpen
        ? this.deactivate()
        : this.activate()
    }
  }
}
<|endoftext|>


<commit_before>( function( angular ) {
    'use strict';
    if( !angular ) {
        throw 'Missing something? Please add angular.js to your project or move this script below the angular.js reference';
    }

    var directiveId = 'ngRemoteValidate',
        remoteValidate = function( $http, $timeout, $q ) {
            return {
                restrict: 'A',
                require: [ '^form','ngModel' ],
                scope: {
                    ngRemoteInterceptors: '=?'
                },
                link: function( scope, el, attrs, ctrls ) {
                    var cache = {},
                        handleChange,
                        setValidation,
                        addToCache,
                        request,
                        shouldProcess,
                        ngForm = ctrls[ 0 ],
                        ngModel = ctrls[ 1 ],
                        options = {
                            ngRemoteThrottle: 400,
                            ngRemoteMethod: 'POST'
                        };

                    angular.extend( options, attrs );
					//TODO: Use Cain of Responsibility to reduce complexity.
                    if( options.ngRemoteValidate.charAt( 0 ) === '[' ) {
                        options.urls = eval( options.ngRemoteValidate );
                    } else if (options.ngRemoteValidate.charAt( 0 ) === '{') {
                        options.keys = eval( '(' + options.ngRemoteValidate + ')' );
                        options.urls = Object.keys( options.keys );
                    } else {
                        options.urls = [ options.ngRemoteValidate ];
                    }

                    addToCache = function( response ) {
                        var value = response[ 0 ].data.value;
                        if ( cache[ value ] ) return cache[ value ];
                        cache[ value ] = response;
                    };

                    shouldProcess = function( value ) {
                        var otherRulesInValid = false;
                        for ( var p in ngModel.$error ) {
                            var checkedKey = !options.hasOwnProperty( 'keys' ) ||
                                             !( Object.keys(options.keys )
								.filter( function( k ) {
                                    return options.keys[ k ] === p;
                                } )[ 0 ] );
                            if ( ngModel.$error[ p ] && p != directiveId && checkedKey ) {
                                otherRulesInValid = true;
                                break;
                            }
                        }
                        return !( ngModel.$pristine || otherRulesInValid );
                    };

                    setValidation = function( response, skipCache ) {
                        var i = 0,
                            l = response.length,
                            useKeys = options.hasOwnProperty( 'keys' ),
                            isValid = true;
                        for( ; i < l; i++ ) {

                            if( scope.ngRemoteInterceptors && scope.ngRemoteInterceptors.response ) {
                                response[ i ] = scope.ngRemoteInterceptors.response( response[ i ] );
                            }

                            if( !response[ i ].data.isValid ) {
                                isValid = false;
                                if (!useKeys) {
                                    break;
                                }
                            }

                            var canSetKey = ( useKeys &&
                                              response[ i ].hasOwnProperty( 'config' ) &&
                                              options.keys[ response[ i ].config.url ] );

                            if ( canSetKey ) {
                                var key = options.keys[ response[ i ].config.url ];
                                ngModel.$setValidity( key, response[ i ].data.isValid );
                            }

							if( response[ i ].data.formattedValue ) {
								ngModel.$setViewValue(response[ i ].data.formattedValue);
								ngModel.$render();
							}
                        }
                        if( !skipCache ) {
                            addToCache( response );
                        }
                        ngModel.$setValidity( directiveId, isValid );
                        ngModel.$processing = ngModel.$pending = ngForm.$pending = false;
                    };

                    handleChange = function( value ) {
                        if( typeof value === 'undefined' || value === '' ) {
                            ngModel.$setPristine();
                            return;
                        }

                        if ( !shouldProcess( value ) ) {
                            return setValidation( [ { data: { isValid: true, value: value } } ], true );
                        }

                        if ( cache[ value ] ) {
                            return setValidation( cache[ value ], true );
                        }

                        //Set processing now, before the delay.
                        //Check first to reduce DOM updates
                        if( !ngModel.$pending ) {
                            ngModel.$processing = ngModel.$pending = ngForm.$pending = true;
                        }

                        if ( request ) {
                            $timeout.cancel( request );
                        }

                        request = $timeout( function( ) {
							var calls = [],
                                i = 0,
                                l = options.urls.length,
                                toValidate = { value: value },
                                httpOpts = { method: options.ngRemoteMethod };

                            if ( scope[ el[0].name + 'SetArgs' ] ) {
                                toValidate = scope[el[0].name + 'SetArgs'](value, el, attrs, ngModel);
                            }

                            if(options.ngRemoteMethod == 'POST'){
                                httpOpts.data = toValidate;
                            } else {
                                httpOpts.params = toValidate;
                            }

                            for( ; i < l; i++ ) {
                                httpOpts.url =  options.urls[ i ];

                                if(scope.ngRemoteInterceptors && scope.ngRemoteInterceptors.request){
                                    httpOpts = scope.ngRemoteInterceptors.request(httpOpts);
                                }

                                calls.push( $http( httpOpts ) );
                            }

                            $q.all( calls ).then( setValidation );

                        }, options.ngRemoteThrottle );
                        return true;
                    };

                    //ngModel.$parsers.unshift( handleChange );
                    scope.$watch( function( ) {
                        return ngModel.$viewValue;
                    }, handleChange );
                }
            };
        };

    angular.module( 'remoteValidation', [] )
           .constant('MODULE_VERSION', '##_version_##')
           .directive( directiveId, [ '$http', '$timeout', '$q', remoteValidate ] );

})( this.angular );<commit_msg>Converted Tabs to Spaces<commit_after>( function( angular ) {
    'use strict';
    if( !angular ) {
        throw 'Missing something? Please add angular.js to your project or move this script below the angular.js reference';
    }

    var directiveId = 'ngRemoteValidate',
        remoteValidate = function( $http, $timeout, $q ) {
            return {
                restrict: 'A',
                require: [ '^form','ngModel' ],
                scope: {
                    ngRemoteInterceptors: '=?'
                },
                link: function( scope, el, attrs, ctrls ) {
                    var cache = {},
                        handleChange,
                        setValidation,
                        addToCache,
                        request,
                        shouldProcess,
                        ngForm = ctrls[ 0 ],
                        ngModel = ctrls[ 1 ],
                        options = {
                            ngRemoteThrottle: 400,
                            ngRemoteMethod: 'POST'
                        };

                    angular.extend( options, attrs );
                    //TODO: Use Cain of Responsibility to reduce complexity.
                    if( options.ngRemoteValidate.charAt( 0 ) === '[' ) {
                        options.urls = eval( options.ngRemoteValidate );
                    } else if (options.ngRemoteValidate.charAt( 0 ) === '{') {
                        options.keys = eval( '(' + options.ngRemoteValidate + ')' );
                        options.urls = Object.keys( options.keys );
                    } else {
                        options.urls = [ options.ngRemoteValidate ];
                    }

                    addToCache = function( response ) {
                        var value = response[ 0 ].data.value;
                        if ( cache[ value ] ) return cache[ value ];
                        cache[ value ] = response;
                    };

                    shouldProcess = function( value ) {
                        var otherRulesInValid = false;
                        for ( var p in ngModel.$error ) {
                            var checkedKey = !options.hasOwnProperty( 'keys' ) ||
                                             !( Object.keys(options.keys )
                                .filter( function( k ) {
                                    return options.keys[ k ] === p;
                                } )[ 0 ] );
                            if ( ngModel.$error[ p ] && p != directiveId && checkedKey ) {
                                otherRulesInValid = true;
                                break;
                            }
                        }
                        return !( ngModel.$pristine || otherRulesInValid );
                    };

                    setValidation = function( response, skipCache ) {
                        var i = 0,
                            l = response.length,
                            useKeys = options.hasOwnProperty( 'keys' ),
                            isValid = true;
                        for( ; i < l; i++ ) {

                            if( scope.ngRemoteInterceptors && scope.ngRemoteInterceptors.response ) {
                                response[ i ] = scope.ngRemoteInterceptors.response( response[ i ] );
                            }

                            if( !response[ i ].data.isValid ) {
                                isValid = false;
                                if (!useKeys) {
                                    break;
                                }
                            }

                            var canSetKey = ( useKeys &&
                                              response[ i ].hasOwnProperty( 'config' ) &&
                                              options.keys[ response[ i ].config.url ] );

                            if ( canSetKey ) {
                                var key = options.keys[ response[ i ].config.url ];
                                ngModel.$setValidity( key, response[ i ].data.isValid );
                            }

                            if( response[ i ].data.formattedValue ) {
                                ngModel.$setViewValue( response[ i ].data.formattedValue );
                                ngModel.$render( );
                            }
                        }
                        if( !skipCache ) {
                            addToCache( response );
                        }
                        ngModel.$setValidity( directiveId, isValid );
                        ngModel.$processing = ngModel.$pending = ngForm.$pending = false;
                    };

                    handleChange = function( value ) {
                        if( typeof value === 'undefined' || value === '' ) {
                            ngModel.$setPristine();
                            return;
                        }

                        if ( !shouldProcess( value ) ) {
                            return setValidation( [ { data: { isValid: true, value: value } } ], true );
                        }

                        if ( cache[ value ] ) {
                            return setValidation( cache[ value ], true );
                        }

                        //Set processing now, before the delay.
                        //Check first to reduce DOM updates
                        if( !ngModel.$pending ) {
                            ngModel.$processing = ngModel.$pending = ngForm.$pending = true;
                        }

                        if ( request ) {
                            $timeout.cancel( request );
                        }

                        request = $timeout( function( ) {
                            var calls = [],
                                i = 0,
                                l = options.urls.length,
                                toValidate = { value: value },
                                httpOpts = { method: options.ngRemoteMethod };

                            if ( scope[ el[0].name + 'SetArgs' ] ) {
                                toValidate = scope[el[0].name + 'SetArgs'](value, el, attrs, ngModel);
                            }

                            if(options.ngRemoteMethod == 'POST'){
                                httpOpts.data = toValidate;
                            } else {
                                httpOpts.params = toValidate;
                            }

                            for( ; i < l; i++ ) {
                                httpOpts.url =  options.urls[ i ];

                                if(scope.ngRemoteInterceptors && scope.ngRemoteInterceptors.request){
                                    httpOpts = scope.ngRemoteInterceptors.request(httpOpts);
                                }

                                calls.push( $http( httpOpts ) );
                            }

                            $q.all( calls ).then( setValidation );

                        }, options.ngRemoteThrottle );
                        return true;
                    };

                    //ngModel.$parsers.unshift( handleChange );
                    scope.$watch( function( ) {
                        return ngModel.$viewValue;
                    }, handleChange );
                }
            };
        };

    angular.module( 'remoteValidation', [] )
           .constant('MODULE_VERSION', '##_version_##')
           .directive( directiveId, [ '$http', '$timeout', '$q', remoteValidate ] );

})( this.angular );
<|endoftext|>