Browse files

refactorings, events instead of callbacks, version 0.1.2

  • Loading branch information...
1 parent 122878b commit 6ea6ec55d1292ad27ebe3d125ec38657ca111655 @ncri committed Oct 8, 2012
Showing with 231 additions and 239 deletions.
  1. +32 −25 README.md
  2. +1 −1 lib/ajaxify_rails/version.rb
  3. +198 −213 vendor/assets/javascripts/ajaxify_rails.js.coffee
View
57 README.md
@@ -17,6 +17,8 @@ Features:
Demo: http://ajaxify-demo.herokuapp.com/ (the first page load might take a while, as heroku needs to spin up a dyno)
+Demo source: https://github.com/ncri/ajaxify_rails_demo_app
+
Blog Post: http://rubyandrails.posterous.com/introducing-the-ajaxify-rails-gem
Inspired by the pjax_rails gem (https://github.com/rails/pjax_rails).
@@ -49,7 +51,6 @@ work most effectively.
The later you call `init()`, the later potential redirects from one scheme to another are performed,
which means the more unnecessary work the browser has to do.
-
### Content Area
Ajaxify assumes that your app has a content container html tag with the id `main`.
@@ -59,9 +60,14 @@ If yield doesn't have a wrapper in your app yet, you need to supply one to get a
#main
= yield
-You can change the content wrapper in your javascript by setting
+You can set the content container of your app when initializing Ajaxify:
+
+ Ajaxify.init
+ content_container: 'content_container_id'
- Ajaxify.content_container = 'content_container_id'
+or later using `set_content_container`:
+
+ Ajaxify.set_content_container('content_container_id')
### Loader Animation
@@ -81,18 +87,20 @@ use it to automatically update the title tag after the main content has changed.
It's a common use case to have a navigation that needs to change its appearence and possibly functioning when the user navigates
to a different section of the page. Ajaxify provides a success callback that is triggered after successful
-updates of the page's main content. Just hook into it in your javascript and make your layout changes:
+updates of the page's main content. Just bind to the `ajaxify:content_loaded' event and make your layout changes:
- Ajaxify.success ->
+ $('body').on 'ajaxify:content_loaded', ->
# update navigation and/or other layout elements
### Flash Messages
Ajaxify correctly displays your flash messages after ajaxified requests.
-By default, only `flash[:notice]` is supported. If you are using for example `flash[:warning]` as well you have to set:
+By default, only `flash[:notice]` is supported. If you are using for example `flash[:warning]` as well you have to add the flash_types
+option to the `Ajaxify.init()` call:
- Ajaxify.flash_types = ['notice', 'warning']
+ Ajaxify.init
+ flash_types: ['notice', 'warning']
Also make sure that you supply invisible wrapper tags in your layout for each flash type you use, with the id set to the type, e.g.:
@@ -124,7 +132,8 @@ paths.
Example: if your app's root url potentially redirects to `your_domain.com/en/` and `your_domain.com/de/`
you need to hint Ajaxyfiy like this:
- Ajaxify.base_paths = ['de', 'en']
+ Ajaxify.init
+ base_paths = ['de', 'en']
Important: `Ajaxify.base_paths` need to be set before `Ajaxify.init()` is called!
@@ -146,9 +155,9 @@ For example you could provide url for a widget in the layout like this:
"<div id='my_fancy_widget_html'> some html </div>"
end
-And then, on the client side hook into Ajaxify using the `handle_extra_content` callback and select the widget html via `#ajaxify_content`:
+And then, on the client side bind to the `ajaxify:content_inserted` event and select the widget html via `#ajaxify_content`:
- Ajaxify.handle_extra_content = ->
+ $('body').on 'ajaxify:content_inserted', ->
$('#my_fancy_widget').html $('#ajaxify_content #my_fancy_widget_html').html()
Tip: you can call view helpers to render extra content from within your controller using the view_context:
@@ -157,27 +166,25 @@ Tip: you can call view helpers to render extra content from within your controll
view_context.my_fancy_widget
end
-### Reference: All Javascript Options and Callbacks
+### Ajaxify Events
+
+Ajaxify provides a few jQuery events you can bind to:
+
+* `ajaxify:before_load` => Triggered before the ajaxify request is started. Params: `url`.
+* `ajaxify:content_loaded` => Triggered after an ajaxify request finished successfully. Params: `data, status, jqXHR, url`.
+* `ajaxify:content_inserted` => Triggered after an ajaxify request finished but before extra content is stripped from the response.
+* `ajaxify:flash_displayed` => Triggered after a flash message is displayed. Parameters: `flash_type`.
-Here is a reference of all options and callbacks you can set on the client side via Ajaxify.<i>option_or_callback</i> = :
- Option/Callback Default Description
+### Javascript
- active true Switches Ajaxify on or off. Needs to be set before Ajaxify.init().
- content_container 'main' Id of the container to insert the main content into ("yield wrapper").
- base_paths null Base path segments for applications with root url redirects.
- Needs to be set before Ajaxify.init().
+Put your javascript into an `ajaxify:content_loaded` event handler, to make sure it is executed after content has
+loaded via Ajaxify. You can't use dom ready for example as that only gets triggered for full page relaods.
- on_before_load null Callback: Called before the ajaxify request is started.
- on_success null Callback: Called when an ajaxify requests finished successfully.
- on_success_once null Callback: Like on_success but only called once.
- handle_extra_content null Callback: Called before extra content is stripped from the Ajax request's response.
- flash_types ['notice'] Flash types your Rails app uses. E.g. ['notice', 'warning', 'error']
- flash_effect null Callback: Called for each flash type after flash is set.
- clear_flash_effect null Callback: Called for each flash type whenever flash message is not present
+### Toggle Ajaxify
-Also check the example app source code for usage: https://github.com/ncri/ajaxify_rails_demo_app
+You can temporarily deactivate Ajaxify by calling `Ajaxify.activate(false)`. You can switch it on again with `Ajaxify.activate()`.
## Contributing
View
2 lib/ajaxify_rails/version.rb
@@ -1,3 +1,3 @@
module AjaxifyRails
- VERSION = "0.1.1"
+ VERSION = "0.1.2"
end
View
411 vendor/assets/javascripts/ajaxify_rails.js.coffee
@@ -1,291 +1,276 @@
-@Ajaxify =
- # options
- #
- active: true
- content_container: 'main'
- handle_extra_content: null
- base_paths: null
+active = true
+content_container = 'main'
+base_paths = null
+flash_types = ['notice']
- # callbacks
- #
- on_before_load: null
- on_success: null
- on_success_once: null
+ignore_hash_change = null
+load_page_from_hash = null
- # flash
- #
- flash_types: ['notice']
- flash_effect: null
- clear_flash_effect: null
+initial_history_state =
+ url: window.location.href
+ data:
+ ajaxified: true
- # internal use only
- #
- ignore_hash_change: null
- load_page_from_hash: null
- initial_history_state:
- url: window.location.href
- data:
- ajaxified: true
+activate = (new_active = true) ->
+ active = new_active
+get_content_container = ->
+ content_container
- ajaxify: ->
+set_content_container = (new_content_container) ->
+ content_container = new_content_container
- if this.active
- if this.load_page_from_hash
- this.load_page_from_hash = false
- this.on_hash_change()
+ajaxify = ->
- self = this
+ if active
- protocol_and_hostname = "#{window.location.protocol}//#{window.location.hostname}"
+ if load_page_from_hash
+ load_page_from_hash = false
+ on_hash_change()
- $('body').on 'click', "a[href^='/']:not(.no_ajaxify), a[href^='#{protocol_and_hostname}']:not(.no_ajaxify)", ->
+ protocol_and_hostname = "#{window.location.protocol}//#{window.location.hostname}"
- $this = $(this)
- self.load
- url: $this.attr('href')
- type: $this.data('method')
- confirm: $this.data('confirm')
+ $('body').on 'click', "a[href^='/']:not(.no_ajaxify), a[href^='#{protocol_and_hostname}']:not(.no_ajaxify)", ->
- false
+ $this = $(this)
+ load
+ url: $this.attr('href')
+ type: $this.data('method')
+ confirm: $this.data('confirm')
- 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}", ->
+ false
- $this = $(this)
- form_params = $(this).serialize()
- form_params += '&ajaxified=true'
+ 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}", ->
- action = $this.attr('action')
+ $this = $(this)
+ form_params = $(this).serialize()
+ form_params += '&ajaxified=true'
- self.load
- url: if action != '' then action else '/'
- data: form_params
- type: $this.attr('method')
- confirm: $this.data('confirm')
+ action = $this.attr('action')
- false
+ load
+ url: if action != '' then action else '/'
+ data: form_params
+ type: $this.attr('method')
+ confirm: $this.data('confirm')
+ false
- # (history interface browsers only)
- window.onpopstate = (e) ->
- if e.state
- e.state.cache = false
- self.load e.state, true
+ # (history interface browsers only)
+ window.onpopstate = (e) ->
+ if e.state
+ e.state.cache = false
+ load e.state, true
- # (non history interface browsers only)
- window.onhashchange = ->
- unless self.ignore_hash_change
- self.on_hash_change()
- else
- self.ignore_hash_change = false
+ # (non history interface browsers only)
+ window.onhashchange = ->
+ unless ignore_hash_change
+ on_hash_change()
+ else
+ ignore_hash_change = false
- # load content from url hash (non history interface browsers)
- on_hash_change: ->
- url = window.location.hash.replace(/#/, "")
- base_path_regexp = this.base_path_regexp()
- if match = window.location.pathname.match(base_path_regexp)
- url = match[0] + url
+# load content from url hash (non history interface browsers)
+on_hash_change = ->
+ url = window.location.hash.replace(/#/, "")
- url = '/' if url == ''
- this.hash_changed = true
- this.load
- url: url
- , true
+ base_path_regexp = base_path_regexp()
+ if match = window.location.pathname.match(base_path_regexp)
+ url = match[0] + url
+ url = '/' if url == ''
+ hash_changed = true
- load: (options, pop_state = false) ->
+ load
+ url: url
+ , true
- unless this.load_page_from_hash
- self = this
- data = options.data || { ajaxified: true }
+load = (options, pop_state = false) ->
- if options.type and options.type == 'delete'
- type = 'post'
- if self.is_string(data)
- data += '&_method=delete'
- else
- data._method = 'delete'
+ 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
- type = options.type or 'get'
+ data._method = 'delete'
+ else
+ type = options.type or 'get'
- if options.confirm
- return false unless confirm options.confirm
+ if options.confirm
+ return false unless confirm options.confirm
- if self.on_before_load
- self.on_before_load options.url
+ $('body').trigger 'ajaxify:before_load', [options.url]
- $.ajax
- url: options.url
- dataType: 'html'
- data: data
- type: type
- cache: true
- beforeSend: (xhr) ->
- $("##{self.content_container}").html( "<div class='ajaxify_loader'></div>" )
- $('html, body').animate
- scrollTop:0
- , 500
-
- success: (data, status, jqXHR) ->
- self.on_ajaxify_success data, status, jqXHR, pop_state, options
-
-
- show_flashes: (flashes) ->
- self = this
- $.each this.flash_types, ->
- if flashes and flashes[this]
- $("##{this}").html flashes[this]
- $("##{this}").show()
- if self.flash_effect
- if self.clear_flash_effect
- self.clear_flash_effect this
- self.flash_effect this
- else
- $("##{this}").hide()
+ $.ajax
+ url: options.url
+ dataType: 'html'
+ data: data
+ type: type
+ cache: true
+ beforeSend: (xhr) ->
+ $("##{content_container}").html( "<div class='ajaxify_loader'></div>" )
+ $('html, body').animate
+ scrollTop:0
+ , 500
+ success: (data, status, jqXHR) ->
+ on_ajaxify_success data, status, jqXHR, pop_state, options
- on_ajaxify_success: (data, status, jqXHR, pop_state, options) ->
- $("##{this.content_container}").html data
+show_flashes = (flashes) ->
+ $.each flash_types, ->
+ if flashes and flashes[this]
+ $("##{this}").html flashes[this]
+ $("##{this}").show()
+ $('body').trigger 'ajaxify:flash_displayed', [this]
- title = $('#ajaxify_content').data('page-title')
- flashes = $('#ajaxify_content').data('flashes')
+ else
+ $("##{this}").hide()
- # 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.
- current_url = $('#ajaxify_content #ajaxify_location').html()
- if options.url != current_url
- options.url = current_url.replace(/(&|&amp;|\?)ajaxify_redirect=true/,'')
- options.type = 'GET'
- this.update_url options, pop_state
+on_ajaxify_success = (data, status, jqXHR, pop_state, options) ->
- if this.handle_extra_content
- this.handle_extra_content()
+ $("##{content_container}").html data
- $("##{this.content_container} #ajaxify_content").remove()
+ title = $('#ajaxify_content').data('page-title')
+ flashes = $('#ajaxify_content').data('flashes')
- if title
- document.title = title.replace /&amp;/, '&' # Todo: need to figure out what else needs to be unescaped
+ # 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.
+ current_url = $('#ajaxify_content #ajaxify_location').html()
+ if options.url != current_url
+ options.url = current_url.replace(/(&|&amp;|\?)ajaxify_redirect=true/,'')
+ options.type = 'GET'
- this.show_flashes(flashes)
+ update_url options, pop_state
- if this.on_success
- this.on_success( data, status, jqXHR, options.url )
+ $('body').trigger 'ajaxify:content_inserted'
- if this.on_success_once
- this.on_success_once( data, status, jqXHR )
- this.on_success_once = null
+ $("##{content_container} #ajaxify_content").remove()
+ if title
+ document.title = title.replace /&amp;/, '&' # Todo: need to figure out what else needs to be unescaped
- update_url: (options, pop_state = false) ->
+ show_flashes(flashes)
- get_request = (!options.type or options.type.toLowerCase() == 'get')
+ $('body').trigger('ajaxify:content_loaded', [data, status, jqXHR, options.url])
- # unless back/forward arrowing or request method is not 'get'
- if !pop_state and get_request
- if window.history.pushState
+update_url = (options, pop_state = false) ->
- if this.initial_history_state != ''
- window.history.replaceState this.initial_history_state, ''
- this.initial_history_state = ''
+ get_request = (!options.type or options.type.toLowerCase() == 'get')
- window.history.pushState
- url: options.url
- data: options.data
- type: options.type
- ,'', options.url
+ # unless back/forward arrowing or request method is not 'get'
+ if !pop_state and get_request
- else
- this.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(this.protocol_with_host()), '')}"
+ if window.history.pushState
- base_path_regexp = this.base_path_regexp()
- if base_path_regexp
- hash = hash.replace(base_path_regexp, '')
- hash = "/#{hash}" unless hash == '' or hash.indexOf('/') == 0
+ if initial_history_state != ''
+ window.history.replaceState initial_history_state, ''
+ initial_history_state = ''
- window.location.hash = hash
+ 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()), '')}"
- base_path_regexp: ->
- return null unless this.base_paths
- # 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)
- self = this
- new RegExp("^\/(#{ $.map(this.base_paths, (el) ->
- el = self.regexp_escape el
- "##{el}($|\/|\\?)"
- ).join('|')})", 'i')
+ base_path_regexp = base_path_regexp()
+ if base_path_regexp
+ hash = hash.replace(base_path_regexp, '')
+ hash = "/#{hash}" unless hash == '' or hash.indexOf('/') == 0
+ window.location.hash = hash
- correct_url: ->
- if this.active
- if window.location.hash.indexOf('#') == 0 # if url has a '#' in it treat it as a non history interface hash based scheme url
+base_path_regexp = ->
+ return null unless base_paths
+ # 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)
+ new RegExp("^\/(#{ $.map(base_paths, (el) ->
+ el = regexp_escape el
+ "##{el}($|\/|\\?)"
+ ).join('|')})", 'i')
- return unless window.location.hash.match(/^#(\/|\?)/) # check hash format
- if !window.history.pushState
- Ajaxify.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 = "#{this.protocol_with_host()}#{path}"
-
- else if !window.history.pushState # move path behind '#' for browsers without history api
-
- if window.location.pathname == '/'
- if window.location.search != ''
- window.location.href = "#{this.protocol_with_host()}/#/#{window.location.search}" # move search behind #
- return
-
- base_path_regexp = this.base_path_regexp()
- 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(/\?/,'') + '#'
+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 !window.history.pushState
+ Ajaxify.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 !window.history.pushState # move path behind '#' for browsers without history api
+
+ if window.location.pathname == '/'
+ if window.location.search != ''
+ window.location.href = "#{protocol_with_host()}/#/#{window.location.search}" # move search behind #
+ return
+
+ base_path_regexp = base_path_regexp()
+ 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(/\/$/,'')}#/#{window.location.pathname.replace(match[0],'')}"
+ path = match[0].replace(/\?/,'') + '#'
else
- path = "/##{window.location.pathname}"
+ path = "#{match[0].replace(/\/$/,'')}#/#{window.location.pathname.replace(match[0],'')}"
+ else
+ path = "/##{window.location.pathname}"
- window.location.href = "#{this.protocol_with_host()}#{path}#{window.location.search}"
+ window.location.href = "#{protocol_with_host()}#{path}#{window.location.search}"
+init = (options = {}) ->
+ base_paths = options.base_paths if 'base_paths' of options
+ flash_types = options.flash_types if 'flash_types' of options
+ active = options.active if 'active' of options
+ content_container = options.content_container if 'content_container' of options
+ correct_url()
- init: ->
- this.correct_url()
+#
+# utility functions
+#
+is_string = (variable) ->
+ Object.prototype.toString.call(variable) == '[object String]'
- #
- # utility functions
- #
+regexp_escape = (str) ->
+ str.replace new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\-]', 'g'), '\\$&'
- is_string: (variable) ->
- Object.prototype.toString.call(variable) == '[object String]'
+protocol_with_host = ->
+ loc = window.location
+ "#{loc.protocol}//#{loc.host}"
- regexp_escape: (str) ->
- str.replace new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\-]', 'g'), '\\$&'
- protocol_with_host: ->
- loc = window.location
- "#{loc.protocol}//#{loc.host}"
+@Ajaxify = { init, ajaxify, load, activate, set_content_container, get_content_container }
jQuery ->

0 comments on commit 6ea6ec5

Please sign in to comment.