├── .rspec ├── cover.png ├── lib ├── render_async │ ├── version.rb │ ├── engine.rb │ ├── configuration.rb │ └── view_helper.rb └── render_async.rb ├── spec ├── spec_helper.rb ├── render_async │ ├── version_spec.rb │ ├── configuration_spec.rb │ └── view_helper_spec.rb └── render_async_spec.rb ├── Gemfile ├── .github ├── FUNDING.yml ├── workflows │ └── test.yml └── CONTRIBUTING.md ├── Rakefile ├── bin ├── setup ├── console └── integration-tests ├── .gitignore ├── .gitmodules ├── render_async.gemspec ├── LICENSE ├── app └── views │ └── render_async │ ├── _render_async.html.erb │ ├── _request_jquery.js.erb │ └── _request_vanilla.js.erb ├── .all-contributorsrc ├── CHANGELOG.md └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikolalsvk/render_async/HEAD/cover.png -------------------------------------------------------------------------------- /lib/render_async/version.rb: -------------------------------------------------------------------------------- 1 | module RenderAsync 2 | VERSION = "2.1.11".freeze 3 | end 4 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) 2 | require "render_async" 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in render_async.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: nikolalsvk 2 | tidelift: rubygems/render_async 3 | custom: "https://www.paypal.me/nikolalsvk/10" 4 | -------------------------------------------------------------------------------- /lib/render_async/engine.rb: -------------------------------------------------------------------------------- 1 | module RenderAsync 2 | class Engine < Rails::Engine 3 | # Make it a Rails Engine 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task :default => :spec 7 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /spec/render_async/version_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe RenderAsync do 4 | it "has a version number" do 5 | expect(RenderAsync::VERSION).not_to be nil 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/render_async/configuration_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe RenderAsync::Configuration do 4 | it 'initializes jquery setting to false' do 5 | expect(RenderAsync::Configuration.new.jquery).to eq false 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | .DS_Store 19 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "spec/fixtures/rails-5-base-app"] 2 | path = spec/fixtures/rails-5-base-app 3 | url = git@github.com:nikolalsvk/rails-5-base-app.git 4 | branch = render-async 5 | [submodule "spec/fixtures/rails-6-base-app"] 6 | path = spec/fixtures/rails-6-base-app 7 | url = git@github.com:nikolalsvk/rails-6-base-app.git 8 | branch = render-async 9 | -------------------------------------------------------------------------------- /lib/render_async/configuration.rb: -------------------------------------------------------------------------------- 1 | module RenderAsync 2 | class Configuration 3 | attr_accessor :jquery, :turbolinks, :turbo, :replace_container, :nonces 4 | 5 | def initialize 6 | @jquery = false 7 | @turbolinks = false 8 | @turbo = false 9 | @replace_container = true 10 | @nonces = false 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "render_async" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start 15 | -------------------------------------------------------------------------------- /bin/integration-tests: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | git submodule init 5 | git submodule update 6 | 7 | gem build render_async.gemspec 8 | gem install render_async*.gem 9 | 10 | cd spec/fixtures/rails-5-base-app 11 | ls 12 | bundle install 13 | bundle exec cucumber 14 | 15 | cd ../../../spec/fixtures/rails-6-base-app 16 | ls 17 | bundle install 18 | yarn install 19 | RAILS_ENV=test bundle exec rails webpacker:compile 20 | bundle exec cucumber 21 | -------------------------------------------------------------------------------- /lib/render_async.rb: -------------------------------------------------------------------------------- 1 | require "render_async/version" 2 | require "render_async/view_helper" 3 | require "render_async/engine" if defined? Rails 4 | require "render_async/configuration" 5 | 6 | ActionView::Base.send :include, RenderAsync::ViewHelper if defined? ActionView::Base 7 | 8 | module RenderAsync 9 | class << self 10 | attr_accessor :configuration 11 | end 12 | 13 | def self.configuration 14 | @configuration ||= RenderAsync::Configuration.new 15 | end 16 | 17 | def self.reset 18 | @configuration = RenderAsync::Configuration.new 19 | end 20 | 21 | def self.configure 22 | yield(configuration) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | rspec: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | ruby-version: ["3.1"] 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Ruby ${{ matrix.ruby-version }} 20 | uses: ruby/setup-ruby@v1 21 | with: 22 | ruby-version: ${{ matrix.ruby-version }} 23 | bundler-cache: true 24 | 25 | - name: Install dependencies 26 | run: | 27 | gem install bundler 28 | bin/setup 29 | 30 | - name: Run RSpec tests 31 | run: bundle exec rspec 32 | -------------------------------------------------------------------------------- /spec/render_async_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe RenderAsync do 4 | describe '.configuration' do 5 | it 'initializes jquery setting to false' do 6 | expect(RenderAsync.configuration.jquery).to eq false 7 | end 8 | 9 | it 'initializes turbolinks setting to false' do 10 | expect(RenderAsync.configuration.turbolinks).to eq false 11 | end 12 | 13 | it 'initializes turbo setting to false' do 14 | expect(RenderAsync.configuration.turbo).to eq false 15 | end 16 | end 17 | 18 | describe '.reset' do 19 | it 'resets jquery setting to false' do 20 | RenderAsync.configuration.jquery = true 21 | 22 | RenderAsync.reset 23 | 24 | expect(RenderAsync.configuration.jquery).to eq false 25 | end 26 | end 27 | 28 | describe '.configure' do 29 | it 'sets jquery value' do 30 | RenderAsync.configure do |config| 31 | config.jquery = true 32 | end 33 | 34 | expect(RenderAsync.configuration.jquery).to eq true 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /render_async.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'render_async/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "render_async" 8 | spec.version = RenderAsync::VERSION 9 | spec.authors = ["Kasper Grubbe", "nikolalsvk"] 10 | spec.email = ["nikolaseap@gmail.com"] 11 | 12 | spec.summary = "Render parts of the page asynchronously with AJAX" 13 | spec.description = "Load parts of your page through simple JavaScript and Rails pipeline" 14 | spec.homepage = "https://github.com/renderedtext/render_async" 15 | spec.license = "MIT" 16 | 17 | spec.files = `git ls-files -z`.split("\x0").reject do |f| 18 | f.match(%r{^(test|spec|features)/}) 19 | end 20 | spec.bindir = "exe" 21 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 22 | spec.require_paths = ["lib"] 23 | 24 | spec.add_development_dependency "rake", "~> 10.0" 25 | spec.add_development_dependency "rspec", "~> 3.2" 26 | end 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Rendered Text 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/views/render_async/_render_async.html.erb: -------------------------------------------------------------------------------- 1 | <<%= html_element_name %> 2 | id="<%= container_id %>" 3 | class="<%= container_class %>" 4 | > 5 | <%= placeholder %> 6 | <%= html_element_name %>> 7 | 8 | <% content_for content_for_name do %> 9 | <%= javascript_tag html_options do %> 10 | <% locals = { container_id: container_id, 11 | replace_container: replace_container, 12 | path: path, 13 | method: method, 14 | data: data, 15 | event_name: event_name, 16 | toggle: toggle, 17 | headers: headers, 18 | error_message: error_message, 19 | error_event_name: error_event_name, 20 | retry_count: retry_count, 21 | retry_count_header: retry_count_header, 22 | retry_delay: retry_delay, 23 | interval: interval, 24 | turbolinks: RenderAsync.configuration.turbolinks, 25 | turbo: RenderAsync.configuration.turbo} %> 26 | 27 | <% if RenderAsync.configuration.jquery %> 28 | <%= render partial: 'render_async/request_jquery', 29 | formats: [:js], 30 | locals: locals %> 31 | <% else %> 32 | <%= render partial: 'render_async/request_vanilla', 33 | formats: [:js], 34 | locals: locals %> 35 | <% end %> 36 | <% end %> 37 | <% end %> 38 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # :pray: Contributing 2 | 3 | Thank you for considering or deciding to contribute, this is much appreciated! 4 | Any kind of bug reports and pull requests are encouraged and welcome on GitHub at 5 | https://github.com/renderedtext/render_async. 6 | 7 | ## :inbox_tray: Installing dependencies 8 | 9 | You can install all needed dependencies by running `bin/setup`. 10 | 11 | ## :runner: Running RSpec tests 12 | 13 | You can run either `rake spec` or `bundle exec rspec` to run all the RSpec tests 14 | in the project. 15 | 16 | ## :running_woman: Running integration tests 17 | 18 | There is a simple command `bin/integration-tests` which sets up 2 submodules, 19 | and runs Cucumber features in them. 20 | 21 | There are 2 submodules for render_async. The submodules are Rails 5 and Rails 6 22 | projects which are located in: 23 | 24 | - `spec/fixtures/rails-5-base-app`, and 25 | - `spec/fixtures/rails-6-base-app`. 26 | 27 | You can find [Rails 5 base app here](https://github.com/nikolalsvk/rails-5-base-app/tree/render-async), 28 | and the [Rails 6 base app here](https://github.com/nikolalsvk/rails-6-base-app/tree/render-async). 29 | 30 | Each of them have different use cases of render_async defined in `app/views/render_asyncs/_use_cases.html.erb` in their repos. 31 | All the feature tests are inside `features/render_async.feature` and `features/render_async_jquery.feature` files. 32 | 33 | If you are adding one or more feature tests or use cases, make sure to make a 34 | PR on those repos as well and include them in the PR on the render_async repo. 35 | 36 | ## :sos: Need help? 37 | 38 | Got any issues or difficulties? 39 | Join [render_async's Discord channel](https://discord.gg/SPfbeRm) 40 | and ask questions there. We will try to respond to you as quickly as possible. 41 | -------------------------------------------------------------------------------- /lib/render_async/view_helper.rb: -------------------------------------------------------------------------------- 1 | require 'securerandom' 2 | 3 | module RenderAsync 4 | module ViewHelper 5 | def render_async_cache_key(path) 6 | "render_async_#{path}" 7 | end 8 | 9 | def render_async_cache(path, options = {}, &placeholder) 10 | cached_view = Rails.cache.read("views/#{render_async_cache_key(path)}") 11 | 12 | if cached_view.present? 13 | render :html => cached_view.html_safe 14 | else 15 | render_async(path, options, &placeholder) 16 | end 17 | end 18 | 19 | def render_async(path, options = {}, &placeholder) 20 | event_name = options.delete(:event_name) 21 | placeholder = capture(&placeholder) if block_given? 22 | 23 | render 'render_async/render_async', **container_element_options(options), 24 | path: path, 25 | html_options: html_options(options), 26 | event_name: event_name, 27 | placeholder: placeholder, 28 | **request_options(options), 29 | **error_handling_options(options), 30 | **retry_options(options), 31 | **polling_options(options), 32 | **content_for_options(options) 33 | end 34 | 35 | private 36 | 37 | def container_element_options(options) 38 | { html_element_name: options[:html_element_name] || 'div', 39 | container_id: options[:container_id] || generate_container_id, 40 | container_class: options[:container_class], 41 | replace_container: replace_container(options) } 42 | end 43 | 44 | def html_options(options) 45 | set_options = options.delete(:html_options) || {} 46 | 47 | set_options[:nonce] = configuration.nonces if set_options[:nonce].nil? 48 | 49 | set_options 50 | end 51 | 52 | def request_options(options) 53 | { method: options[:method] || 'GET', 54 | data: options[:data], 55 | headers: options[:headers] || {} } 56 | end 57 | 58 | def error_handling_options(options) 59 | { error_message: options[:error_message], 60 | error_event_name: options[:error_event_name] } 61 | end 62 | 63 | def retry_options(options) 64 | { 65 | retry_count: options.delete(:retry_count) || 0, 66 | retry_count_header: options.delete(:retry_count_header), 67 | retry_delay: options.delete(:retry_delay) 68 | } 69 | end 70 | 71 | def polling_options(options) 72 | { interval: options[:interval], 73 | toggle: options[:toggle] } 74 | end 75 | 76 | def content_for_options(options) 77 | { 78 | content_for_name: options[:content_for_name] || :render_async 79 | } 80 | end 81 | 82 | def generate_container_id 83 | "render_async_#{SecureRandom.hex(5)}#{Time.now.to_i}" 84 | end 85 | 86 | def replace_container(options) 87 | return options[:replace_container] unless options[:replace_container].nil? 88 | 89 | configuration.replace_container 90 | end 91 | 92 | def configuration 93 | RenderAsync.configuration 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /app/views/render_async/_request_jquery.js.erb: -------------------------------------------------------------------------------- 1 | if (window.jQuery) { 2 | (function($) { 3 | <% if turbolinks %> 4 | if (document.documentElement.hasAttribute("data-turbolinks-preview")) { 5 | return; 6 | } 7 | <% end %> 8 | <% if turbo %> 9 | if (document.documentElement.hasAttribute("data-turbo-preview")) { 10 | return; 11 | } 12 | <% end %> 13 | function createEvent(name, container) { 14 | var event = undefined; 15 | if (typeof(Event) === 'function') { 16 | event = new Event(name); 17 | } else { 18 | event = document.createEvent('Event'); 19 | event.initEvent(name, true, true); 20 | } 21 | event.container = container 22 | return event; 23 | } 24 | 25 | function _runAfterDocumentLoaded(callback) { 26 | if (document.readyState === 'complete' || document.readyState === 'interactive') { 27 | // Handle a case where nested partials get loaded after the document loads 28 | callback(); 29 | } else { 30 | <% if turbolinks %> 31 | $(document).one('turbolinks:load', callback); 32 | <% elsif turbo %> 33 | $(document).one('turbo:load', callback); 34 | <% else %> 35 | $(document).ready(callback); 36 | <% end %> 37 | } 38 | } 39 | 40 | function _makeRequest(currentRetryCount) { 41 | var headers = <%= headers.to_json.html_safe %>; 42 | var csrfTokenElement = document.querySelector('meta[name="csrf-token"]'); 43 | if (csrfTokenElement) 44 | headers['X-CSRF-Token'] = csrfTokenElement.content; 45 | 46 | <% if retry_count_header %> 47 | if (typeof(currentRetryCount) === 'number') { 48 | headers['<%= retry_count_header.html_safe %>'] = currentRetryCount; 49 | } 50 | <% end %> 51 | 52 | $.ajax({ 53 | url: '<%= path.html_safe %>', 54 | method: '<%= method %>', 55 | data: "<%= escape_javascript(data.to_s.html_safe) %>", 56 | headers: headers 57 | }).done(function(response) { 58 | var container = $("#<%= container_id %>"); 59 | 60 | // If user navigated away before the request completed 61 | if (!container.length) return; 62 | 63 | <% if !interval && replace_container %> 64 | container.replaceWith(response); 65 | <% else %> 66 | container.empty(); 67 | container.append(response); 68 | <% end %> 69 | 70 | var loadEvent = createEvent('render_async_load', container); 71 | document.dispatchEvent(loadEvent); 72 | 73 | <% if event_name.present? %> 74 | var event = createEvent("<%= event_name %>", container) 75 | document.dispatchEvent(event); 76 | <% end %> 77 | }).fail(function(response) { 78 | var skipErrorMessage = false; 79 | <% if retry_count > 0 %> 80 | skipErrorMessage = retry(currentRetryCount) 81 | <% end %> 82 | 83 | if (skipErrorMessage) return; 84 | 85 | var container = $("#<%= container_id %>"); 86 | if (!container.length) return; 87 | 88 | container.replaceWith("<%= error_message.try(:html_safe) %>"); 89 | 90 | var errorEvent = createEvent( 91 | "<%= error_event_name || 'render_async_error' %>", 92 | container 93 | ) 94 | errorEvent.retryCount = currentRetryCount 95 | 96 | document.dispatchEvent(errorEvent); 97 | }); 98 | }; 99 | 100 | <% if retry_count > 0 %> 101 | var _retryMakeRequest = _makeRequest 102 | 103 | <% if retry_delay %> 104 | _retryMakeRequest = function(currentRetryCount) { 105 | setTimeout(function() { 106 | _makeRequest(currentRetryCount) 107 | }, <%= retry_delay %>) 108 | } 109 | <% else %> 110 | _retryMakeRequest = _makeRequest 111 | <% end %> 112 | 113 | function retry(currentRetryCount) { 114 | if (typeof(currentRetryCount) === 'number') { 115 | if (currentRetryCount >= <%= retry_count %>) 116 | return false; 117 | 118 | _retryMakeRequest(currentRetryCount + 1); 119 | return true; 120 | } 121 | 122 | _retryMakeRequest(1); 123 | return true; 124 | } 125 | <% end %> 126 | 127 | var _renderAsyncFunction = _makeRequest; 128 | 129 | var _interval; 130 | <% if interval %> 131 | var _renderAsyncFunction = function() { 132 | // If interval is already set, return 133 | if (typeof(_interval) === 'number') return 134 | 135 | _makeRequest(); 136 | _interval = setInterval(_makeRequest, <%= interval %>); 137 | } 138 | 139 | var _clearRenderAsyncInterval = function() { 140 | if (typeof(_interval) === 'number'){ 141 | clearInterval(_interval) 142 | _interval = undefined; 143 | } 144 | } 145 | 146 | function _setUpControlEvents() { 147 | var container = $("#<%= container_id %>"); 148 | 149 | // Register a stop polling event on the container 150 | $(container).on('async-stop', _clearRenderAsyncInterval) 151 | 152 | // Register a start polling event on the container 153 | $(container).on('async-start', _renderAsyncFunction) 154 | } 155 | 156 | _runAfterDocumentLoaded(_setUpControlEvents) 157 | 158 | <% if turbolinks %> 159 | $(document).one('turbolinks:visit', _clearRenderAsyncInterval); 160 | <% end %> 161 | <% if turbo %> 162 | $(document).one('turbo:visit', _clearRenderAsyncInterval); 163 | <% end %> 164 | <% end %> 165 | 166 | <% if !replace_container %> 167 | function _setUpRefreshEvent() { 168 | var container = $("#<%= container_id %>"); 169 | 170 | $(container).on('refresh', _renderAsyncFunction) 171 | } 172 | 173 | _runAfterDocumentLoaded(_setUpRefreshEvent) 174 | <% end %> 175 | 176 | <% if toggle %> 177 | function _setUpToggle() { 178 | $(document).<%= toggle[:once] ? 'one' : 'on' %>('<%= toggle[:event] || 'click' %>', '<%= toggle[:selector] %>', function(event) { 179 | if (typeof(_interval) === 'number') { 180 | clearInterval(_interval); 181 | _interval = undefined; 182 | } else { 183 | _renderAsyncFunction(); 184 | } 185 | }); 186 | 187 | <% if toggle[:start] %> 188 | _renderAsyncFunction() 189 | <% end %> 190 | } 191 | 192 | _runAfterDocumentLoaded(_setUpToggle); 193 | <% elsif !toggle %> 194 | _runAfterDocumentLoaded(_renderAsyncFunction) 195 | <% end %> 196 | }(jQuery)); 197 | } else { 198 | console.warn("Looks like you've enabled jQuery for render_async, but jQuery is not defined on the window object"); 199 | }; 200 | -------------------------------------------------------------------------------- /app/views/render_async/_request_vanilla.js.erb: -------------------------------------------------------------------------------- 1 | (function() { 2 | <% if turbolinks %> 3 | if (document.documentElement.hasAttribute("data-turbolinks-preview")) { 4 | return; 5 | } 6 | <% end %> 7 | <% if turbo %> 8 | if (document.documentElement.hasAttribute("data-turbo-preview")) { 9 | return; 10 | } 11 | <% end %> 12 | function createEvent(name, container) { 13 | var event = undefined; 14 | if (typeof(Event) === 'function') { 15 | event = new Event(name); 16 | } else { 17 | event = document.createEvent('Event'); 18 | event.initEvent(name, true, true); 19 | } 20 | event.container = container 21 | return event; 22 | } 23 | 24 | function _runAfterDocumentLoaded(callback) { 25 | <% if turbolinks %> 26 | document.addEventListener("turbolinks:load", function(e) { 27 | e.target.removeEventListener(e.type, arguments.callee); 28 | callback(); 29 | }); 30 | <% elsif turbo %> 31 | document.addEventListener("turbo:load", function(e) { 32 | e.target.removeEventListener(e.type, arguments.callee); 33 | callback(); 34 | }); 35 | <% else %> 36 | document.addEventListener("DOMContentLoaded", callback); 37 | <% end %> 38 | } 39 | 40 | function _makeRequest(currentRetryCount) { 41 | var request = new XMLHttpRequest(); 42 | var asyncRequest = true; 43 | var SUCCESS = 200; 44 | var ERROR = 400; 45 | 46 | request.open('<%= method %>', '<%= path.html_safe %>', asyncRequest); 47 | 48 | var headers = <%= headers.to_json.html_safe %>; 49 | var csrfTokenElement = document.querySelector('meta[name="csrf-token"]'); 50 | if (csrfTokenElement) 51 | headers['X-CSRF-Token'] = csrfTokenElement.content; 52 | 53 | <% if retry_count_header %> 54 | if (typeof(currentRetryCount) === 'number') { 55 | headers['<%= retry_count_header.html_safe %>'] = currentRetryCount; 56 | } 57 | <% end %> 58 | 59 | Object.keys(headers).map(function(key) { 60 | request.setRequestHeader(key, headers[key]); 61 | }); 62 | 63 | request.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 64 | 65 | request.onreadystatechange = function() { 66 | if (request.readyState === 4) { 67 | if (request.status >= SUCCESS && request.status < ERROR) { 68 | var container = document.getElementById('<%= container_id %>'); 69 | 70 | // If user navigated away before the request completed 71 | if (!container) return; 72 | 73 | <% if !interval && replace_container %> 74 | container.outerHTML = request.response; 75 | <% else %> 76 | container.innerHTML = request.response; 77 | <% end %> 78 | 79 | var loadEvent = createEvent('render_async_load', container); 80 | document.dispatchEvent(loadEvent); 81 | 82 | <% if event_name.present? %> 83 | var event = createEvent('<%= event_name %>', container); 84 | document.dispatchEvent(event); 85 | <% end %> 86 | } else { 87 | var skipErrorMessage = false; 88 | <% if retry_count > 0 %> 89 | skipErrorMessage = retry(currentRetryCount) 90 | <% end %> 91 | 92 | if (skipErrorMessage) return; 93 | 94 | var container = document.getElementById('<%= container_id %>'); 95 | if (!container) return; 96 | 97 | container.outerHTML = '<%= error_message.try(:html_safe) %>'; 98 | 99 | var errorEvent = createEvent( 100 | "<%= error_event_name || 'render_async_error' %>", 101 | container 102 | ); 103 | errorEvent.retryCount = currentRetryCount 104 | 105 | document.dispatchEvent(errorEvent); 106 | } 107 | } 108 | }; 109 | 110 | var body = "<%= escape_javascript(data.to_s.html_safe) %>"; 111 | request.send(body); 112 | }; 113 | 114 | <% if retry_count > 0 %> 115 | 116 | <% if retry_delay %> 117 | var _retryMakeRequest = function(currentRetryCount) { 118 | setTimeout(function() { 119 | _makeRequest(currentRetryCount) 120 | }, <%= retry_delay %>) 121 | } 122 | <% else %> 123 | var _retryMakeRequest = _makeRequest 124 | <% end %> 125 | 126 | function retry(currentRetryCount) { 127 | if (typeof(currentRetryCount) === 'number') { 128 | if (currentRetryCount >= <%= retry_count %>) 129 | return false; 130 | 131 | _retryMakeRequest(currentRetryCount + 1); 132 | return true; 133 | } 134 | 135 | _retryMakeRequest(1); 136 | return true; 137 | } 138 | <% end %> 139 | 140 | var _renderAsyncFunction = _makeRequest; 141 | 142 | var _interval; 143 | <% if interval %> 144 | var _renderAsyncFunction = function() { 145 | // If interval is already set, return 146 | if (typeof(_interval) === 'number') return 147 | 148 | _makeRequest(); 149 | _interval = setInterval(_makeRequest, <%= interval %>); 150 | } 151 | 152 | var _clearRenderAsyncInterval = function() { 153 | if (typeof(_interval) === 'number'){ 154 | clearInterval(_interval) 155 | _interval = undefined; 156 | } 157 | } 158 | 159 | function _setUpControlEvents() { 160 | var container = document.getElementById('<%= container_id %>'); 161 | 162 | // Register a polling stop event on the container 163 | container.addEventListener("async-stop", _clearRenderAsyncInterval) 164 | 165 | // Register a start polling event on the container 166 | container.addEventListener("async-start", _renderAsyncFunction) 167 | } 168 | 169 | _runAfterDocumentLoaded(_setUpControlEvents) 170 | 171 | <% if turbolinks %> 172 | document.addEventListener("turbolinks:visit", _clearRenderAsyncInterval) 173 | <% end %> 174 | <% if turbo %> 175 | document.addEventListener("turbo:visit", _clearRenderAsyncInterval) 176 | <% end %> 177 | <% end %> 178 | 179 | <% if !replace_container %> 180 | function _setUpRefreshEvent() { 181 | var container = document.getElementById('<%= container_id %>'); 182 | 183 | container.addEventListener('refresh', _renderAsyncFunction) 184 | } 185 | 186 | _runAfterDocumentLoaded(_setUpRefreshEvent) 187 | <% end %> 188 | 189 | <% if toggle %> 190 | function _setUpToggle() { 191 | var selectors = document.querySelectorAll('<%= toggle[:selector] %>'); 192 | var handler = function(event) { 193 | if (typeof(_interval) === 'number') { 194 | clearInterval(_interval); 195 | _interval = undefined; 196 | } else { 197 | _renderAsyncFunction(); 198 | } 199 | <% if toggle[:once] %> 200 | this.removeEventListener(event.type, handler); 201 | <% end %> 202 | }; 203 | 204 | <% if toggle[:start] %> 205 | _renderAsyncFunction() 206 | <% end %> 207 | 208 | for (var i = 0; i < selectors.length; ++i) { 209 | selectors[i].addEventListener('<%= toggle[:event] || 'click' %>', handler) 210 | } 211 | } 212 | 213 | _runAfterDocumentLoaded(_setUpToggle); 214 | <% elsif !toggle %> 215 | _runAfterDocumentLoaded(_renderAsyncFunction); 216 | <% end %> 217 | })(); 218 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "render_async", 3 | "projectOwner": "renderedtext", 4 | "files": [ 5 | "README.md" 6 | ], 7 | "imageSize": 100, 8 | "commit": true, 9 | "contributors": [ 10 | { 11 | "login": "nikolalsvk", 12 | "name": "Nikola Đuza", 13 | "avatar_url": "https://avatars2.githubusercontent.com/u/3028124?v=4", 14 | "profile": "https://nikolalsvk.github.io", 15 | "contributions": [ 16 | "question", 17 | "code", 18 | "doc", 19 | "review" 20 | ] 21 | }, 22 | { 23 | "login": "colinxfleming", 24 | "name": "Colin", 25 | "avatar_url": "https://avatars0.githubusercontent.com/u/3866868?v=4", 26 | "profile": "http://www.colinxfleming.com", 27 | "contributions": [ 28 | "code", 29 | "doc", 30 | "example" 31 | ] 32 | }, 33 | { 34 | "login": "kaspergrubbe", 35 | "name": "Kasper Grubbe", 36 | "avatar_url": "https://avatars2.githubusercontent.com/u/334273?v=4", 37 | "profile": "http://kaspergrubbe.com", 38 | "contributions": [ 39 | "code" 40 | ] 41 | }, 42 | { 43 | "login": "sairam", 44 | "name": "Sai Ram Kunala", 45 | "avatar_url": "https://avatars2.githubusercontent.com/u/163584?v=4", 46 | "profile": "https://sairam.xyz/", 47 | "contributions": [ 48 | "doc" 49 | ] 50 | }, 51 | { 52 | "login": "nightsurge", 53 | "name": "Josh Arnold", 54 | "avatar_url": "https://avatars2.githubusercontent.com/u/3065882?v=4", 55 | "profile": "https://github.com/nightsurge", 56 | "contributions": [ 57 | "code", 58 | "doc" 59 | ] 60 | }, 61 | { 62 | "login": "SaladFork", 63 | "name": "Elad Shahar", 64 | "avatar_url": "https://avatars3.githubusercontent.com/u/107798?v=4", 65 | "profile": "https://eladshahar.com", 66 | "contributions": [ 67 | "code", 68 | "example" 69 | ] 70 | }, 71 | { 72 | "login": "sasharevzin", 73 | "name": "Sasha", 74 | "avatar_url": "https://avatars3.githubusercontent.com/u/232392?v=4", 75 | "profile": "http://www.revzin.co.il", 76 | "contributions": [ 77 | "code", 78 | "doc" 79 | ] 80 | }, 81 | { 82 | "login": "elsurudo", 83 | "name": "Ernest Surudo", 84 | "avatar_url": "https://avatars3.githubusercontent.com/u/50223?v=4", 85 | "profile": "http://elsurudo.com", 86 | "contributions": [ 87 | "code" 88 | ] 89 | }, 90 | { 91 | "login": "krainboltgreene", 92 | "name": "Kurtis Rainbolt-Greene", 93 | "avatar_url": "https://avatars1.githubusercontent.com/u/334809?v=4", 94 | "profile": "https://kurtis.rainbolt-greene.online", 95 | "contributions": [ 96 | "code" 97 | ] 98 | }, 99 | { 100 | "login": "schneems", 101 | "name": "Richard Schneeman", 102 | "avatar_url": "https://avatars2.githubusercontent.com/u/59744?v=4", 103 | "profile": "https://www.schneems.com", 104 | "contributions": [ 105 | "doc" 106 | ] 107 | }, 108 | { 109 | "login": "richardvenneman", 110 | "name": "Richard Venneman", 111 | "avatar_url": "https://avatars1.githubusercontent.com/u/75705?v=4", 112 | "profile": "https://www.cityspotters.com", 113 | "contributions": [ 114 | "doc" 115 | ] 116 | }, 117 | { 118 | "login": "filipewl", 119 | "name": "Filipe W. Lima", 120 | "avatar_url": "https://avatars3.githubusercontent.com/u/381395?v=4", 121 | "profile": "https://github.com/filipewl", 122 | "contributions": [ 123 | "doc" 124 | ] 125 | }, 126 | { 127 | "login": "eclemens", 128 | "name": "Jesús Eduardo Clemens Chong", 129 | "avatar_url": "https://avatars0.githubusercontent.com/u/3135638?v=4", 130 | "profile": "https://github.com/eclemens", 131 | "contributions": [ 132 | "code" 133 | ] 134 | }, 135 | { 136 | "login": "reneklacan", 137 | "name": "René Klačan", 138 | "avatar_url": "https://avatars3.githubusercontent.com/u/1935686?v=4", 139 | "profile": "https://github.com/reneklacan", 140 | "contributions": [ 141 | "code", 142 | "doc" 143 | ] 144 | }, 145 | { 146 | "login": "gil27", 147 | "name": "Gil Gomes", 148 | "avatar_url": "https://avatars1.githubusercontent.com/u/1313442?v=4", 149 | "profile": "http://gilgomes.com.br", 150 | "contributions": [ 151 | "doc" 152 | ] 153 | }, 154 | { 155 | "login": "ThanhKhoaIT", 156 | "name": "Khoa Nguyen", 157 | "avatar_url": "https://avatars0.githubusercontent.com/u/6081795?v=4", 158 | "profile": "https://github.com/ThanhKhoaIT", 159 | "contributions": [ 160 | "code", 161 | "doc" 162 | ] 163 | }, 164 | { 165 | "login": "preetsethi", 166 | "name": "Preet Sethi", 167 | "avatar_url": "https://avatars2.githubusercontent.com/u/8645918?v=4", 168 | "profile": "https://www.linkedin.com/in/preetsethila/", 169 | "contributions": [ 170 | "code" 171 | ] 172 | }, 173 | { 174 | "login": "fffx", 175 | "name": "fangxing", 176 | "avatar_url": "https://avatars3.githubusercontent.com/u/11586335?v=4", 177 | "profile": "https://github.com/fffx", 178 | "contributions": [ 179 | "code" 180 | ] 181 | }, 182 | { 183 | "login": "lipsumar", 184 | "name": "Emmanuel Pire", 185 | "avatar_url": "https://avatars3.githubusercontent.com/u/1191418?v=4", 186 | "profile": "http://blog.lipsumarium.com", 187 | "contributions": [ 188 | "code", 189 | "doc" 190 | ] 191 | }, 192 | { 193 | "login": "maximgeerinck", 194 | "name": "Maxim Geerinck", 195 | "avatar_url": "https://avatars1.githubusercontent.com/u/615509?v=4", 196 | "profile": "https://github.com/maximgeerinck", 197 | "contributions": [ 198 | "code" 199 | ] 200 | }, 201 | { 202 | "login": "vanboom", 203 | "name": "Don", 204 | "avatar_url": "https://avatars1.githubusercontent.com/u/251706?v=4", 205 | "profile": "https://github.com/vanboom", 206 | "contributions": [ 207 | "code" 208 | ] 209 | }, 210 | { 211 | "login": "villu164", 212 | "name": "villu164", 213 | "avatar_url": "https://avatars0.githubusercontent.com/u/998682?v=4", 214 | "profile": "https://github.com/villu164", 215 | "contributions": [ 216 | "doc" 217 | ] 218 | }, 219 | { 220 | "login": "Mbuckley0", 221 | "name": "Mitchell Buckley", 222 | "avatar_url": "https://avatars.githubusercontent.com/u/11203679?v=4", 223 | "profile": "https://github.com/Mbuckley0", 224 | "contributions": [ 225 | "code", 226 | "doc" 227 | ] 228 | }, 229 | { 230 | "login": "yhirano55", 231 | "name": "yhirano55", 232 | "avatar_url": "https://avatars.githubusercontent.com/u/15371677?v=4", 233 | "profile": "https://github.com/yhirano55", 234 | "contributions": [ 235 | "code", 236 | "doc" 237 | ] 238 | } 239 | ], 240 | "repoType": "github" 241 | } 242 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 2.1.11 (2021/10/11) 2 | 3 | * [#148](https://github.com/renderedtext/render_async/pull/148): Polish retry count header feature - [@nikolalsvk](https://github.com/nikolalsvk). 4 | * [#147](https://github.com/renderedtext/render_async/pull/147): Add support for retry count header - [@reneklacan](https://github.com/reneklacan). 5 | 6 | ### 2.1.10 (2021/03/21) 7 | 8 | * [#146](https://github.com/renderedtext/render_async/pull/146): Add comment when we check if container is present - [@nikolalsvk](https://github.com/nikolalsvk). 9 | * [#145](https://github.com/renderedtext/render_async/pull/145): Wrap html_options for turbolinks - [@yhirano55](https://github.com/yhirano55). 10 | * [#144](https://github.com/renderedtext/render_async/pull/144): Avoid TypeError if container has already disappear - [@yhirano55](https://github.com/yhirano55). 11 | 12 | ### 2.1.9 (2021/02/23) 13 | 14 | * [#142](https://github.com/renderedtext/render_async/pull/142): Update Turbolinks docs - [@nikolalsvk](https://github.com/nikolalsvk). 15 | * [#141](https://github.com/renderedtext/render_async/pull/141): Add Support for Turbo - [@MBuckley0](https://github.com/Mbuckley0). 16 | * [#139](https://github.com/renderedtext/render_async/pull/139): Fix readme configuration-options anchor - [@richardvenneman](https://github.com/richardvenneman). 17 | * [#138](https://github.com/renderedtext/render_async/pull/138): Add Rails 6 base app as a fixture - [@nikolalsvk](https://github.com/nikolalsvk). 18 | * [#137](https://github.com/renderedtext/render_async/pull/137): Rename config - [@nikolalsvk](https://github.com/nikolalsvk). 19 | 20 | ### 2.1.8 (2020/10/24) 21 | 22 | * [#134](https://github.com/renderedtext/render_async/pull/134): Add config option for setting nonce - [@nikolalsvk](https://github.com/nikolalsvk). 23 | * [#132](https://github.com/renderedtext/render_async/pull/132): Refresh render_async with an event - [@nikolalsvk](https://github.com/nikolalsvk). 24 | * [#131](https://github.com/renderedtext/render_async/pull/131): Start to poll on page load with toggle - [@nikolalsvk](https://github.com/nikolalsvk). 25 | * [#130](https://github.com/renderedtext/render_async/pull/130): Set up control events after document loaded - [@nikolalsvk](https://github.com/nikolalsvk). 26 | * [#127](https://github.com/renderedtext/render_async/pull/127): Update README.md, to reflect correct turbolinks configuration value - [@villu164](https://github.com/villu164). 27 | 28 | ### 2.1.7 (2020/8/1) 29 | 30 | * [#125](https://github.com/renderedtext/render_async/pull/125): Implement retry after some time feature - [@nikolalsvk](https://github.com/nikolalsvk). 31 | * [#124](https://github.com/renderedtext/render_async/pull/124): Add more info on how to control polling - [@nikolalsvk](https://github.com/nikolalsvk). 32 | * [#123](https://github.com/renderedtext/render_async/pull/123): Simplify calling of start and stop event when interval is defined - [@nikolalsvk](https://github.com/nikolalsvk). 33 | * [#119](https://github.com/renderedtext/render_async/pull/119): Add polling control start/stop events - [@vanboom](https://github.com/vanboom). 34 | * [#120](https://github.com/renderedtext/render_async/pull/120): Fine tune custom content_for feature - [@nikolalsvk](https://github.com/nikolalsvk). 35 | * [#117](https://github.com/renderedtext/render_async/pull/117): Allow a custom content_for name - [@vanboom](https://github.com/vanboom). 36 | 37 | ### 2.1.6 (2020/5/9) 38 | 39 | * [#114](https://github.com/renderedtext/render_async/pull/114): Call render_async logic if document state is ready or interactive - [@nikolalsvk](https://github.com/nikolalsvk). 40 | * [#113](https://github.com/renderedtext/render_async/pull/113): Remove interval after Turbolinks visit event - [@nikolalsvk](https://github.com/nikolalsvk). 41 | * [#112](https://github.com/renderedtext/render_async/pull/112): Add X-Requested-With header in Vanilla JS - [@nikolalsvk](https://github.com/nikolalsvk). 42 | * [#110](https://github.com/renderedtext/render_async/pull/110): Remove preventDefault, and load toggle in stream - [@vanboom](https://github.com/vanboom). 43 | 44 | ### 2.1.5 (2020/3/22) 45 | 46 | * [#105](https://github.com/renderedtext/render_async/pull/105): Load toggle listeners after page loads - [@nikolalsvk](https://github.com/nikolalsvk). 47 | * [#104](https://github.com/renderedtext/render_async/pull/104): Attach container to dispatched events - [@nikolalsvk](https://github.com/nikolalsvk). 48 | * [#103](https://github.com/renderedtext/render_async/pull/103): Add generic "load" and "error" events - [@lipsumar](https://github.com/lipsumar). 49 | * [#98](https://github.com/renderedtext/render_async/pull/98): Bump nonce pattern in the README to follow Rails CSP standard - [@colinxfleming](https://github.com/colinxfleming). 50 | 51 | ### 2.1.4 (2019/11/11) 52 | 53 | * [#96](https://github.com/renderedtext/render_async/pull/96): Add once option to remove event once it's triggered #94 - [@fffx](https://github.com/fffx). 54 | 55 | ### 2.1.3 (2019/9/24) 56 | 57 | * [#95](https://github.com/renderedtext/render_async/pull/95): Use double quotes for displaying error message - [@nikolalsvk](https://github.com/nikolalsvk). 58 | * [#93](https://github.com/renderedtext/render_async/pull/93): Fix: "Uncaught ReferenceError: \_interval" #92 - [@fffx](https://github.com/fffx). 59 | 60 | ### 2.1.2 (2019/8/17) 61 | 62 | * [#89](https://github.com/renderedtext/render_async/pull/89): Bump version to 2.1.2 - [@nikolalsvk](https://github.com/nikolalsvk). 63 | * [#82](https://github.com/renderedtext/render_async/pull/88): When toggle true, do not fire `_renderAsyncFunc` on `turbolinks:load` - [@ThanhKhoaIT](https://github.com/ThanhKhoaIT). 64 | 65 | ### 2.1.1 (2019/8/17) 66 | 67 | * [#85](https://github.com/renderedtext/render_async/pull/85): Rename main JS function and support toggle feature with other features - [@nikolalsvk](https://github.com/nikolalsvk). 68 | * [#82](https://github.com/renderedtext/render_async/pull/82): Add toggle selector and event to render - [@ThanhKhoaIT](https://github.com/ThanhKhoaIT). 69 | * DEPRECATION WARNING - html_options is now a hash that you pass to render_async instead of an argument. If you passed for example a nonce: '21312aas...', you will need to pass 70 | html_options: { nonce: '21312aas...' } 71 | 72 | ### 2.1.0 (2019/5/6) 73 | 74 | * [#77](https://github.com/renderedtext/render_async/pull/77): Call render_async logic every N seconds - [@nikolalsvk](https://github.com/nikolalsvk). 75 | * [#76](https://github.com/renderedtext/render_async/pull/76): Retry render_async on failure - [@nikolalsvk](https://github.com/nikolalsvk). 76 | 77 | ### 2.0.2 (2019/1/4) 78 | 79 | * [#74](https://github.com/renderedtext/render_async/pull/74): Remove bundler as a dependency - [@nikolalsvk](https://github.com/nikolalsvk). 80 | 81 | ### 2.0.1 (2018/12/10) 82 | 83 | * [#69](https://github.com/renderedtext/render_async/pull/69): Adding CHANGELOG.md - [@gil27](https://github.com/gil27). 84 | * [#66](https://github.com/renderedtext/render_async/pull/66): Adding support for Turbolinks 5+ - [@eclemens](https://github.com/eclemens). 85 | * [#65](https://github.com/renderedtext/render_async/pull/65): Invalid jQuery Promise method name - [@eclemens](https://github.com/eclemens). 86 | 87 | ### 2.0.0 (2018/10/06) 88 | 89 | * [#64](https://github.com/renderedtext/render_async/pull/64): Document new features - [@nikolalsvk](https://github.com/nikolalsvk). 90 | * [#63](https://github.com/renderedtext/render_async/pull/63): Configure gem - [@nikolalsvk](https://github.com/nikolalsvk). 91 | * [#62](https://github.com/renderedtext/render_async/pull/62): Handle request errorss - [@nikolalsvk](https://github.com/nikolalsvk). 92 | * [#61](https://github.com/renderedtext/render_async/pull/61): Add option to pass in container_class and container_id - [@nikolalsvk](https://github.com/nikolalsvk). 93 | * [#60](https://github.com/renderedtext/render_async/pull/60): Support event creation in IE - [@nikolalsvk](https://github.com/nikolalsvk). 94 | 95 | ### 1.3.0 (2018/10/06) 96 | 97 | * [#58](https://github.com/renderedtext/render_async/pull/58): Tune different method requests - [@nikolalsvk](https://github.com/nikolalsvk). 98 | * [#52](https://github.com/renderedtext/render_async/pull/52): Configurable AJAX method, headers and body - [@reneklacan](https://github.com/reneklacan). 99 | * [#54](https://github.com/renderedtext/render_async/pull/54): Add recipe for using with respond_to/format.js - [@filipewl](https://github.com/filipewl). 100 | * [#50](https://github.com/renderedtext/render_async/pull/50): Add Turbolinks usage note- [@richardvenneman](https://github.com/richardvenneman). 101 | 102 | ### 1.2.0 (2018/01/25) 103 | 104 | * [#49](https://github.com/renderedtext/render_async/pull/49): Pass in html_element_name - [@nikolalsvk](https://github.com/nikolalsvk). 105 | * [#48](https://github.com/renderedtext/render_async/pull/48): Move JS code to a partial - [@nikolalsvk](https://github.com/nikolalsvk). 106 | * [#47](https://github.com/renderedtext/render_async/pull/47): Trigger request when document is ready - [@nikolalsvk](https://github.com/nikolalsvk). 107 | * [#36](https://github.com/renderedtext/render_async/pull/36): Update README with instructions for nested async - [@SaladFork](https://github.com/SaladFork). 108 | * [#41](https://github.com/renderedtext/render_async/pull/41): Only bother when the dom is actually ready to be changed - [@krainboltgreene](https://github.com/nikolalsvk). 109 | * [#43](https://github.com/renderedtext/render_async/pull/43): [ci skip] Get more Open Source Helpers - [@schneems](https://github.com/schneems). 110 | * [#42](https://github.com/renderedtext/render_async/pull/42): Integration tests - [@nikolalsvk](https://github.com/nikolalsvk). 111 | 112 | ### 1.1.3 (2017/11/23) 113 | 114 | * [#40](https://github.com/renderedtext/render_async/pull/40): Replace render_async's container with the response data - [@nikolalsvk](https://github.com/nikolalsvk). 115 | 116 | ### 1.1.2 (2017/11/17) 117 | 118 | * [#35](https://github.com/renderedtext/render_async/pull/35): Fix async without jQuery - [@SaladFork](https://github.com/SaladFork). 119 | * [#33](https://github.com/renderedtext/render_async/pull/33): Don't html-escape the path when outputting JS - [@elsurudo](https://github.com/elsurudo). 120 | * [#31](https://github.com/renderedtext/render_async/pull/31): Allow `render_async_cache` to take a placeholder - [@elsurudo](https://github.com/elsurudo). 121 | 122 | ### 1.1.1 (2017/10/14) 123 | 124 | * [#29](https://github.com/renderedtext/render_async/pull/29): Use jQuery if available - [@nikolalsvk](https://github.com/nikolalsvk). 125 | 126 | ### 1.1.0 (2017/10/14) 127 | 128 | * [Fix event name explanation](https://github.com/renderedtext/render_async/commit/bd1ebb7011be6868dce9da76c5db9ca1133ec71d) - [@nikolalsvk](https://github.com/nikolalsvk). 129 | * [#22](https://github.com/renderedtext/render_async/pull/22): Dispatch JS Event when AJAX is finished- [@nikolalsvk](https://github.com/nikolalsvk). 130 | 131 | ### 1.0.1 (2017/10/14) 132 | 133 | * [#28](https://github.com/renderedtext/render_async/pull/28): Make vanilla JS more readable - [@nikolalsvk](https://github.com/nikolalsvk). 134 | * [#25](https://github.com/renderedtext/render_async/pull/25): Convert to vanilla js - [@sasharevzin](https://github.com/sasharevzin). 135 | * [#27](https://github.com/renderedtext/render_async/pull/27): Add turbolinks recipe - [@colinxfleming](https://github.com/colinxfleming). 136 | * [#23](https://github.com/renderedtext/render_async/pull/23): Test placeholder - [@nikolalsvk](https://github.com/nikolalsvk). 137 | * [#21](https://github.com/renderedtext/render_async/pull/21): Add specs for render_async_cache - [@nikolalsvk](https://github.com/nikolalsvk). 138 | 139 | ### 1.0.0 (2017/09/21) 140 | 141 | * [#20](https://github.com/renderedtext/render_async/pull/20): Rename block to placeholder - [@nikolalsvk](https://github.com/nikolalsvk). 142 | 143 | ### 0.4.1 (2017/09/07) 144 | 145 | * [#19](https://github.com/renderedtext/render_async/pull/19): Fix replace with - [@nikolalsvk](https://github.com/nikolalsvk). 146 | 147 | ### 0.4.0 (2017/09/07) 148 | 149 | * [#18](https://github.com/renderedtext/render_async/pull/18): Use replaceWith instead of html - [@nikolalsvk](https://github.com/nikolalsvk). 150 | 151 | ### 0.3.3 (2017/09/04) 152 | 153 | * [#17](https://github.com/renderedtext/render_async/pull/17): Prepare and test caching - [@nikolalsvk](https://github.com/nikolalsvk). 154 | * [#16](https://github.com/renderedtext/render_async/pull/16): Improvements and fixes for cached view - [@nightsurge](https://github.com/nightsurge). 155 | * [#15](https://github.com/renderedtext/render_async/pull/15): Add all contributors - [@nikolalsvk](https://github.com/nikolalsvk). 156 | * [#14](https://github.com/renderedtext/render_async/pull/14): Add view caching support - [@nightsurge](https://github.com/nightsurge). 157 | 158 | ### 0.2.3 (2017/07/11) 159 | 160 | * [#10](https://github.com/renderedtext/render_async/pull/10): Adjust to use javascript_tag and take html_options - [@colinxfleming](https://github.com/colinxfleming). 161 | 162 | ### 0.1.3 (2017/06/28) 163 | 164 | * [#9](https://github.com/renderedtext/render_async/pull/9): Use commit history from kaspergrubbe/render_async - [@nikolalsvk](https://github.com/nikolalsvk). 165 | * [#7](https://github.com/renderedtext/render_async/pull/7): Update README badges - [@nikolalsvk](https://github.com/nikolalsvk). 166 | * [#5](https://github.com/renderedtext/render_async/pull/5): Update README.md - [@nikolalsvk](https://github.com/nikolalsvk). 167 | * [#4](https://github.com/renderedtext/render_async/pull/4): Update installation info - [@nikolalsvk](https://github.com/nikolalsvk). 168 | * [#3](https://github.com/renderedtext/render_async/pull/3): Allow push to rubygems.org - [@nikolalsvk](https://github.com/nikolalsvk). 169 | * [#2](https://github.com/renderedtext/render_async/pull/2): Remove railtie and engine files - [@nikolalsvk](https://github.com/nikolalsvk). 170 | * [#1](https://github.com/renderedtext/render_async/pull/1): Use commit history from kaspergrubbe/render_async - [@nikolalsvk](https://github.com/nikolalsvk). 171 | 172 | ### 0.0.2 (2013/08/23) 173 | 174 | * It is now safe to call the method render_async instead of render_asynk (Billetto namespace issue) - [@kaspergrubbe](https://github.com/kaspergrubbe) 175 | 176 | ### 0.0.1 (2013/08/23) 177 | 178 | * Add a webpage description to the Gemspec - [@kaspergrubbe](https://github.com/kaspergrubbe) 179 | -------------------------------------------------------------------------------- /spec/render_async/view_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "json" 3 | 4 | describe RenderAsync::ViewHelper do 5 | let(:helper) { Class.new { extend RenderAsync::ViewHelper } } 6 | 7 | describe "#render_async_cache_key" do 8 | it "returns cache key" do 9 | expect(helper.render_async_cache_key("users")).to eq("render_async_users") 10 | end 11 | end 12 | 13 | describe "#render_async_cache" do 14 | let(:cached_view) { double("CachedView", 15 | html_safe: "
2 |
3 |
4 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
Sorry, users loading went wrong :(
' %> 553 | ``` 554 | 555 | - `error_event_name` 556 | 557 | calling `render_async` with `error_event_name` will dispatch event in the case 558 | of an error with your AJAX call. 559 | ```erb 560 | <%= render_asyc users_path, error_event_name: 'users-error-event' %> 561 | ``` 562 | 563 | You can then catch the event in your code with: 564 | ```js 565 | document.addEventListener('users-error-event', function() { 566 | // I'm on it 567 | }) 568 | ``` 569 | 570 | ### Caching 571 | 572 | `render_async` can utilize view fragment caching to avoid extra AJAX calls. 573 | 574 | In your views (e.g. `app/views/comments/show.html.erb`): 575 | ```erb 576 | # note 'render_async_cache' instead of standard 'render_async' 577 | <%= render_async_cache comment_stats_path %> 578 | ``` 579 | 580 | Then, in the partial (e.g. `app/views/comments/_comment_stats.html.erb`): 581 | ```erb 582 | <% cache render_async_cache_key(request.path), skip_digest: true do %> 583 |