active = true content_container = ‘main’ base_paths = null flash_types = [‘notice’] dont_correct_url = false push_state_enabled = true loader_element = “<div class=‘ajaxify_loader’></div>”

ignore_hash_change = null load_page_from_hash = null

scroll_to_top = true display_loader = true

initial_history_state =

url: window.location.href
data:
  ajaxified: true

base_path_regexp_cache = null

# ——————————————————————————————————————– # public methods # ——————————————————————————————————————–

activate = (new_active = true) ->

active = new_active

get_content_container = ->

content_container

set_content_container = (new_content_container) ->

content_container = new_content_container

set_loader_element = (new_loader_element) ->

loader_element = new_loader_element

init = (options = {}) ->

base_paths = options.base_paths if 'base_paths' of options
flash_types = options.flash_types if 'flash_types' of options
push_state_enabled = options.push_state_enabled if 'push_state_enabled' of options
active = options.active if 'active' of options
content_container = options.content_container if 'content_container' of options
correct_url() unless $('meta[name="ajaxify:dont_correct_url"]').length > 0
scroll_to_top = options.scroll_to_top if 'scroll_to_top' of options
display_loader = options.display_loader if 'display_loader' of options
rails_ujs_fix()

ajaxify = ->

if active

  if load_page_from_hash
    load_page_from_hash = false
    on_hash_change()

  protocol_and_hostname = "#{window.location.protocol}//#{window.location.hostname}"

  $('body').on 'click', "a[href^='/']:not(.no_ajaxify), a[href^='#{protocol_and_hostname}']:not(.no_ajaxify)", ->
    $this = $(this)

    load
      url: $this.attr('href')
      type: $this.data('method')
      confirm: $this.data('confirm')
      element: $this
      scroll_to_top: set_scroll_to_top($this)
      display_loader: set_display_loader($this)

    false

  exclude_selector = ":not(.no_ajaxify):not([enctype='multipart/form-data'])"
  $('body').on 'submit', "form[action^='/']#{exclude_selector},
                          form[action^='#{protocol_and_hostname}']#{exclude_selector},
                          form[action='']#{exclude_selector}", ->

    $this = $(this)

    form_params = $(this).serialize()
    form_params += '&ajaxified=true'

    action = $this.attr('action')

    load
      url: if action != '' then action else '/'
      data: form_params
      type: $this.attr('method')
      confirm: $this.data('confirm')
      element: $this
      scroll_to_top: set_scroll_to_top($this)
      display_loader: set_display_loader($this)

    false

# (history interface browsers only)
$(window).on 'popstate', (e) ->
  e = e.originalEvent
  data = if e.state and e.state.data then url_params_as_object(e.state.data) else null
  if data and data.ajaxified
    e.state.cache = false
    load e.state, true

# (non history interface browsers only)
window.onhashchange = ->
  unless ignore_hash_change
    on_hash_change()
  else
    ignore_hash_change = false

load = (options, pop_state = false) ->

unless load_page_from_hash

  data = options.data || { ajaxified: true }

  if options.type and options.type == 'delete'
    type = 'post'
    if is_string(data)
      data += '&_method=delete'
    else
      data._method = 'delete'
  else
    type = options.type or 'get'

  if options.confirm
    if $.rails != undefined
      return false unless $.rails.confirm(options.confirm, options.element)
    else
      return false unless confirm(options.confirm)

  $(document).trigger 'ajaxify:before_load', [options, pop_state]

  $.ajax
    url: options.url
    dataType: 'html'
    data:  data
    type: type
    cache: true
    beforeSend: (xhr) ->
      options.display_loader = display_loader unless 'display_loader' of options
      $("##{content_container}").html( loader_element ) if options.display_loader
      options.scroll_to_top = scroll_to_top unless 'scroll_to_top' of options
      scroll_page_to_top() if options.scroll_to_top

    success: (data, status, jqXHR) ->
      on_ajaxify_success data, status, jqXHR, pop_state, options

    error: (data, status, jqXHR) ->
      $(document).trigger('ajaxify:load_error', [data, status, jqXHR, options.url])

# ——————————————————————————————————————– # private methods # ——————————————————————————————————————–

update_url = (options, pop_state = false) ->

get_request = (!options.type or options.type.toLowerCase() == 'get')

# unless back/forward arrowing or request method is not 'get'
if !pop_state and get_request

  if push_state()

    if initial_history_state != ''
      window.history.replaceState initial_history_state, ''
      initial_history_state = ''

    options.data ||= {}
    options.data.ajaxified = true
    window.history.pushState
      url: options.url
      data: options.data
      type: options.type
    ,'', options.url

  else
    ignore_hash_change = true  # for non histroy interface browsers: avoids loading the page for hash changes caused by link clicks
    hash = "#{options.url.replace(new RegExp(protocol_with_host()), '')}"

    if base_path_regexp()
      hash = hash.replace(base_path_regexp(), '')
      hash = "/#{hash}" unless hash == '' or hash.indexOf('/') == 0

    window.location.hash = hash

on_ajaxify_success = (data, status, jqXHR, pop_state, options) ->

$("##{content_container}").html data

if $('#ajaxify_content').length > 0  # it can happen that #ajaxify_content is not present at this point, on ajaxify redirects for example
  title = $('#ajaxify_content').data('page-title')
  flashes = $('#ajaxify_content').data('flashes')

  # Correct the url after a redirect and when it has the ajaxify param in it.
  # The latter can happen e.g. for pagination links that are auto generated.
  original_request_type = options.type
  current_url = $('#ajaxify_content #ajaxify_location').html()
  current_url = current_url.replace(/&amp;/g, '&')
  if options.url != current_url
    options.url = current_url.replace(/(&|\?)ajaxify_redirect=true/,'')
    options.type = 'GET'

  unless original_request_type and original_request_type.toLowerCase() == 'post'
    reload_page_if_assets_stale options.url, jqXHR

  update_url options, pop_state

  $(document).trigger 'ajaxify:content_inserted'

  $("##{content_container} #ajaxify_content").remove()

  if title
    document.title = title.replace /&amp;/g, '&'   # Todo: need to figure out what else needs to be unescaped

  show_flashes(flashes)

  $(document).trigger('ajaxify:content_loaded', [data, status, jqXHR, options.url])

correct_url = ->

if active
  if window.location.hash.indexOf('#') == 0   # if url has a '#' in it treat it as a non history interface hash based scheme url
    return unless window.location.hash.match(/^#(\/|\?)/) # check hash format

    if !push_state()
      load_page_from_hash = true   # notify Ajaxify that a hash will be loaded and ignore all other calls to load until hash url is loaded
    else
      # load proper url in case browser supports history api
      path = window.location.pathname
      path = '' if path == '/'
      path = path + window.location.hash.replace(/#/, "")
      window.location.href = "#{protocol_with_host()}#{path}"

  else if !push_state() # move path behind '#' for browsers without history api
    if base_path_regexp() and (match = window.location.pathname.match(base_path_regexp()))
      if match[0] == window.location.pathname
        if window.location.search == ''
          return   # we are on a base path here already, so don't do anything
        else
          path = match[0].replace(/\?/,'') + '#'
      else
        path = "#{match[0].replace(/\/$/,'')}#/#{window.location.pathname.replace(match[0],'')}"
    else if window.location.pathname == '/'
      if window.location.search != ''
        window.location.href = "#{protocol_with_host()}/#/#{window.location.search}" # move search behind #
      return
    else
      path = "/##{window.location.pathname}"

    window.location.href = "#{protocol_with_host()}#{path}#{window.location.search}"

show_flashes = (flashes) ->

$.each flash_types, ->
  if flashes and flashes[this]
    $("##{this}").html flashes[this]
    $("##{this}").show()
    $(document).trigger 'ajaxify:flash_displayed', [this, flashes[this] ]

  else
    $("##{this}").hide()

# load content from url hash (non history interface browsers) on_hash_change = ->

url = window.location.hash.replace(/#/, "")

if base_paths and base_paths.length > 0 and match = window.location.pathname.match(base_path_regexp())
  url = match[0] + url

url = '/' if url == ''
hash_changed = true

load
  url: url
, true

base_path_regexp = ->

return null unless base_paths
return base_path_regexp_cache if base_path_regexp_cache
# match starting and ending with base path, e.g. "^\/en$" (i.e. we are at the base path root) or
# starting with base path and continuing with '/', e.g. "^\/en\/" (i.e. we are NOT at the base path root) or
# starting with base path and continuing with '?', e.g. "^\/en\?" (i.e. we are at the base path root and have query params)
base_path_regexp_cache = new RegExp("^\/(#{ $.map(base_paths, (el) ->
  el = regexp_escape el
  "#{el}($|\/|\\?)"
).join('|')})", 'i')

is_string = (variable) ->

Object.prototype.toString.call(variable) == '[object String]'

url_params_as_object = (param_string_or_object) ->

return param_string_or_object unless is_string( param_string_or_object )
JSON.parse('{"' + decodeURI(param_string_or_object.replace(/&/g, "\",\"").replace(/\=/g,"\":\"")) + '"}')

regexp_escape = (str) ->

str.replace new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\-]', 'g'), '\\$&'

protocol_with_host = ->

loc = window.location
"#{loc.protocol}//#{loc.host}"

push_state = ->

push_state_enabled and window.history.pushState

set_scroll_to_top = ($link_or_form) ->

scroll = $link_or_form.hasClass('scroll_to_top')
no_scroll = $link_or_form.hasClass('no_scroll_to_top')
if scroll or no_scroll then return (scroll and !no_scroll) else return scroll_to_top

set_display_loader = ($link_or_form) ->

display = $link_or_form.hasClass('display_loader')
no_display = $link_or_form.hasClass('no_display_loader')
if display or no_display then return (display and !no_display) else return display_loader

scroll_page_to_top = ->

$('html, body').animate
  scrollTop:0
  , 500

reload_page_if_assets_stale = (url, jqXHR) ->

digest_header = jqXHR.getResponseHeader('Ajaxify-Assets-Digest')
meta_asset_digest = $("meta[name='ajaxify:assets-digest']").attr('content')
if digest_header and meta_asset_digest != '' and digest_header != meta_asset_digest
  document.location.href = url

# Override rails jquery_ujs on handling links with :data-method # Example: # <%= link_to ‘test’, test_path, :method => :delete %> rails_ujs_fix = ->

return false unless 'rails' of jQuery

jQuery.rails.handleMethod = (link) ->
  href = $.rails.href(link)
  method = link.data('method')
  target = link.attr('target')
  csrf_token = $('meta[name=csrf-token]').attr('content')
  csrf_param = $('meta[name=csrf-param]').attr('content')
  form = $("<form method='post' action='#{href}'></form>")
  metadata_input = "<input name='_method' value='#{method}' type='hidden' />"

  # Let's not use ajaxify in form submission if link
  # has `no_ajaxify` class.
  form.addClass('no_ajaxify') if link.hasClass('no_ajaxify')

  if csrf_param != undefined && csrf_token != undefined
    metadata_input += '<input name="' +
      csrf_param + '" value="' +
      csrf_token + '" type="hidden" />'

  form.attr('target', target) if target

  form.hide().append(metadata_input).appendTo('body')
  form.submit()

# ——————————————————————————————————————– # public interface # ——————————————————————————————————————–

@Ajaxify = { init, ajaxify, load, update_url, activate, set_content_container, get_content_container, set_loader_element, base_path_regexp }

# ——————————————————————————————————————– # ajaxify page on dom ready # ——————————————————————————————————————–

jQuery ->

Ajaxify.ajaxify()