
/* --- $DJAX --- */

/*
 * jQuery djax
 *
 * @version v0.122
 *
 * Copyright 2012, Brian Zeligson
 * Released under the MIT license.
 * http://www.opensource.org/licenses/mit-license.php
 *
 * Homepage:
 *   http://beezee.github.com/djax.html
 *
 * Authors:
 *   Brian Zeligson
 *
 * Contributors:
 *  Gary Jones @GaryJones
 *
 * Maintainer:
 *   Brian Zeligson github @beezee
 *
 */

/*jslint browser: true, indent: 4, maxerr: 50, sub: true */
/*jshint bitwise:true, curly:true, eqeqeq:true, forin:true, immed:true, latedef:true, noarg:true, noempty:true, nomen:true, nonew:true, onevar:true, plusplus:true, regexp:true, smarttabs:true, strict:true, trailing:true, undef:true, white:true, browser:true, jquery:true, indent:4, maxerr:50, */
/*global jQuery */

// ==ClosureCompiler==
// @compilation_level ADVANCED_OPTIMIZATIONS
// @output_file_name jquery.djax.js
// @externs_url http://closure-compiler.googlecode.com/svn/trunk/contrib/externs/jquery-1.7.js
// ==/ClosureCompiler==
// http://closure-compiler.appspot.com/home

(function ($, exports) {
    'use strict';

    $.support.cors = true;

    $.fn.djax = function (selector, exceptions, replaceBlockWithFunc) {

        // If browser doesn't support pushState, abort now
        if (!history.pushState) {
            return $(this);
        }

        var self = this,
          blockSelector = selector,
          excludes = (exceptions && exceptions.length) ? exceptions : [],
          replaceBlockWith = (replaceBlockWithFunc) ? replaceBlockWithFunc : $.fn.replaceWith,
          djaxing = false;

        // Ensure that the history is correct when going from 2nd page to 1st
        window.history.replaceState(
          {
              'url' : window.location.href,
              'title' : $('title').text()
          },
          $('title').text(),
          window.location.href
        );

        //if (globalDebug) {console.log("djax::replaceState url:" + window.location.href);}

        self.clearDjaxing = function() {
            self.djaxing = false;
        }

        // Exclude the link exceptions
        self.attachClick = function (element, event) {

            var link = $(element),
              exception = false;

            $.each(excludes, function (index, exclusion) {
                if (link.attr('href').indexOf(exclusion) !== -1) {
                    exception = true;
                }
                if (window.location.href.indexOf(exclusion) !== -1) {
                    exception = true;
                }
            });

            if ( $(element).is('[target^=_blank], [rel^=external]') ) {
              exception = true;
            }

            // If the link is one of the exceptions, return early so that
            // the link can be clicked and a full page load as normal
            if (exception) {
                return $(element);
            }

            // From this point on, we handle the behaviour
            event.preventDefault();

            // If we're already doing djaxing, return now and silently fail
            if (self.djaxing) {
                setTimeout(self.clearDjaxing, 1000);
                return $(element);
            }

            $(window).trigger('djaxClick', [element]);
            self.reqUrl = link.attr('href');
            self.triggered = false;
            self.navigate(link.attr('href'), true);
        };

        // Handle the navigation
        self.navigate = function (url, add) {

            var blocks = $(blockSelector);

            self.djaxing = true;

            // Get the new page
            $(window).trigger(
              'djaxLoading',
              [{
                  'url' : url
              }]
            );

            var replaceBlocks = function (response) {
                if (url !== self.reqUrl) {
                    self.navigate(self.reqUrl, false);
                    return true;
                }

                var result = $(response),
                  newBlocks = $(result).find(blockSelector);

                if ( add === true ) {
                    window.history.pushState(
                      {
                          'url' : url,
                          'title' : $(result).filter('title').text()
                      },
                      $(result).filter('title').text(),
                      url
                    );

                    //if (globalDebug) {console.log("djax::pushState url:" + url);}
                }

                // Set page title as new page title
                // Set title cross-browser:
                // - $('title').text(title_text); returns an error on IE7
                //
                document.title = $(result).filter('title').text();

                // Loop through each block and find new page equivalent
                blocks.each(function () {

                    var id = '#' + $(this).attr('id'),
                      newBlock = newBlocks.filter(id),
                      block = $(this);

                    $('a', newBlock).filter(function () {
                        return this.hostname === location.hostname;
                    }).addClass('dJAX_internal').on('click', function (event) {
                        return self.attachClick(this, event);
                    });

                    if (newBlock.length) {
                        if (block.html() !== newBlock.html()) {
                            replaceBlockWith.call(block, newBlock);

                        }
                    } else {
                        block.remove();
                    }

                });

                // Loop through new page blocks and add in as needed
                $.each(newBlocks, function () {

                    var newBlock = $(this),
                      id = '#' + $(this).attr('id'),
                      $previousSibling;

                    // If there is a new page block without an equivalent block
                    // in the old page, we need to find out where to insert it
                    if (!$(id).length) {

                        // Find the previous sibling
                        $previousSibling = $(result).find(id).prev();

                        if ($previousSibling.length) {
                            // Insert after the previous element
                            newBlock.insertAfter('#' + $previousSibling.attr('id'));
                        } else {
                            // There's no previous sibling, so prepend to parent instead
                            newBlock.prependTo('#' + newBlock.parent().attr('id'));
                        }

                        // Only add a class to internal links
                        $('a', newBlock).filter(function () {
                            return this.hostname === location.hostname;
                        }).addClass('dJAX_internal').on('click', function (event) {
                            return self.attachClick(this, event);
                        });
                    }

                });


                // Trigger djaxLoad event as a pseudo ready()
                if (!self.triggered) {
                    $(window).trigger(
                      'djaxLoad',
                      [{
                          'url' : url,
                          'title' : $(result).filter('title').text(),
                          'response' : response
                      }]
                    );
                    self.triggered = true;
                    self.djaxing = false;
                }

                // Trigger a djaxLoaded event when done
                $(window).trigger(
                  'djaxLoaded',
                  [{
                      'url' : url,
                      'title' : $(result).filter('title').text(),
                      'response' : response
                  }]
                );
            };

            $.ajax({
                'url' : url,
                'success' : function (response) {
                    replaceBlocks(response);
                },
                'error' : function (response, textStatus, errorThrown) {
                    // handle error
                    console.log('error', response, textStatus, errorThrown);
                    replaceBlocks(response['responseText']);
                }
            });
        }; /* End self.navigate */

        // Only add a class to internal links
        $(this).find('a').filter(function () {
          return this.hostname === location.hostname;
        }).addClass('dJAX_internal');
        // attachClick to links with '.dJAX_internal'. Includes links loaded via ajax.
        $(this).on('click', '.dJAX_internal', function (event) {
          if (this.hostname !== location.hostname)
              return;
          return self.attachClick(this, event);
        });

        // On new page load
        $(window).bind('popstate', function (event) {
            self.triggered = false;
            if (event.originalEvent.state) {
                self.reqUrl = event.originalEvent.state.url;
                self.navigate(event.originalEvent.state.url, false);
            }
        });

    };

}(jQuery, window));