GithubHelp home page GithubHelp logo

renderedtext / render_async Goto Github PK

View Code? Open in Web Editor NEW
1.1K 20.0 74.0 417 KB

render_async lets you include pages asynchronously with AJAX

Home Page: https://rubygems.org/gems/render_async/

License: MIT License

Ruby 63.39% HTML 3.48% Shell 1.33% JavaScript 31.80%
rails rails5 gem ajax async semaphore-open-source hacktoberfest

render_async's Introduction

render_async

๐Ÿ‘‹ Welcome to render_async

Let's make your Rails pages fast again ๐ŸŽ


Donate Downloads All contributors Gem Version
Discord Server Build Status Code Climate Maintainablity Test Coverage License Help Contribute to Open Source

render_async is here to make your pages show faster to users.

Pages become faster seamlessly by rendering partials to your views.

Partials render asynchronously and let users see your page faster than using regular rendering.

It works with Rails and its tools out of the box.

โœจ A quick overview of how render_async does its magic:

  1. user visits a page
  2. render_async makes an AJAX request on the controller action
  3. controller renders a partial
  4. partial renders in the place where you put render_async view helper

JavaScript is injected straight into <%= content_for :render_async %> so you choose where to put it.

๐Ÿ“ฃ P.S. Join our Discord channel for help and discussion, and let's make render_async even better!

๐Ÿ“ฆ Installation

Add this line to your application's Gemfile:

gem 'render_async'

And then execute:

$ bundle install

๐Ÿ”จ Usage

  1. Include render_async view helper somewhere in your views (e.g. app/views/comments/show.html.erb):

    <%= render_async comment_stats_path %>
  2. Then create a route for it config/routes.rb:

    get :comment_stats, controller: :comments
  3. Fill in the logic in your controller (e.g. app/controllers/comments_controller.rb):

    def comment_stats
      @stats = Comment.get_stats
    
      render partial: "comment_stats"
    end
  4. Create a partial that will render (e.g. app/views/comments/_comment_stats.html.erb):

    <div class="col-md-6">
      <%= @stats %>
    </div>
  5. Add content_for in your base view file in the body part (e.g. app/views/layouts/application.html.erb):

    <%= content_for :render_async %>

๐Ÿ› ๏ธ Advanced usage

Advanced usage includes information on different options, such as:

Passing in a container ID

render_async renders an element that gets replaced with the content of your request response. In order to have more control over the element that renders first (before the request), you can set the ID of that element.

To set ID of the container element, you can do the following:

<%= render_async users_path, container_id: 'users-container' %>

Rendered code in the view:

<div id="users-container">
</div>

...

Passing in a container class name

render_async renders an element that gets replaced with the content of your request response. If you want to style that element, you can set the class name on it.

<%= render_async users_path, container_class: 'users-container-class' %>

Rendered code in the view:

<div id="render_async_18b8a6cd161499117471" class="users-container-class">
</div>

...

Passing in HTML options

render_async can accept html_options as a hash. html_options is an optional hash that gets passed to a Rails' javascript_tag, to drop HTML tags into the script element.

Example of utilizing html_options with a nonce:

<%= render_async users_path, html_options: { nonce: true } %>

Rendered code in the view:

<script nonce="2x012CYGxKgM8qAApxRHxA==">
//<![CDATA[
  ...
//]]>
</script>

...

<div id="render_async_18b8a6cd161499117471" class="">
</div>

๐Ÿ’ก You can enable nonce to be set everywhere by using configuration option render_async provides.

Passing in an HTML element name

render_async can take in an HTML element name, allowing you to control what type of container gets rendered. This can be useful when you're using render_async inside a table and you need it to render a tr element before your request gets loaded, so your content doesn't get pushed out of the table.

Example of using HTML element name:

<%= render_async users_path, html_element_name: 'tr' %>

Rendered code in the view:

<tr id="render_async_04229e7abe1507987376">
</tr>
...

Passing in a placeholder

render_async can be called with a block that will act as a placeholder before your AJAX call finishes.

Example of passing in a block:

<%= render_async users_path do %>
  <h1>Users are loading...</h1>
<% end %>

Rendered code in the view:

<div id="render_async_14d7ac165d1505993721">
  <h1>Users are loading...</h1>
</div>

<script>
//<![CDATA[
  ...
//]]>
</script>

After AJAX is finished, placeholder will be replaced with the request's response.

Passing in an event name

render_async can receive :event_name option which will emit JavaScript event after it's done with fetching and rendering request content to HTML.

This can be useful to have if you want to add some JavaScript functionality after your partial is loaded through render_async.

You can also access the associated container (DOM node) in the event object that gets emitted.

Example of passing it to render_async:

<%= render_async users_path, event_name: "users-loaded" %>

Rendered code in view:

<div id="render_async_04229e7abe1507987376">
</div>

<script>
//<![CDATA[
  ...
    document.dispatchEvent(new Event("users-loaded"));
  ...
//]]>
</script>

Then, in your JavaScript code, you could do something like this:

document.addEventListener("users-loaded", function(event) {
  console.log("Users have loaded!", event.container); // Access the container which loaded the users
});

๐Ÿ’ก Dispatching events is also supported for older browsers that don't support Event constructor.

Using default events

render_async will fire the event render_async_load when an async partial has loaded and rendered on the page.

In case there is an error, the event render_async_error will fire instead.

This event will fire for all render_async partials on the page. For every event, the associated container (DOM node) will be passed along.

This can be useful to apply JavaScript to content loaded after the page is ready.

Example of using events:

// Vanilla javascript
document.addEventListener('render_async_load', function(event) {
  console.log('Async partial loaded in this container:', event.container);
});
document.addEventListener('render_async_error', function(event) {
  console.log('Async partial could not load in this container:', event.container);
});

// with jQuery
$(document).on('render_async_load', function(event) {
  console.log('Async partial loaded in this container:', event.container);
});
$(document).on('render_async_error', function(event) {
  console.log('Async partial could not load in this container:', event.container);
});

Refreshing the partial

render_async lets you refresh (reload) the partial by letting you dispatch the 'refresh' event on the render_async's container. An example:

<%= render_async comments_path,
                 container_id: 'refresh-me',
                 replace_container: false %>

<button id="refresh-button">Refresh comments</button>

<script>
  var button = document.getElementById('refresh-button')
  var container = document.getElementById('refresh-me');

  button.addEventListener('click', function() {
    var event = new Event('refresh');

    // Dispatch 'refresh' on the render_async container
    container.dispatchEvent(event)
  })
</script>

If you follow the example above, when you click "Refresh comments" button, render_async will trigger again and reload the comments_path.

๐Ÿ’ก Note that you need to pass replace_container: false so you can later dispatch an event on that container.

Retry on failure

render_async can retry your requests if they fail for some reason.

If you want render_async to retry a request for number of times, you can do this:

<%= render_async users_path, retry_count: 5, error_message: "Couldn't fetch it" %>

Now render_async will retry users_path for 5 times. If it succeeds in between, it will stop with dispatching requests. If it fails after 5 times, it will show an error message which you need to specify.

This can show useful when you know your requests often fail, and you don't want to refresh the whole page just to retry them.

Retry after some time

If you want to retry requests but with some delay in between the calls, you can pass a retry_delay option together with retry_count like so:

<%= render_async users_path,
                 retry_count: 5,
                 retry_delay: 2000 %>

This will make render_async wait for 2 seconds before retrying after each failure. In the end, if the request is still failing after 5th time, it will dispatch a default error event.

๐Ÿฌ If you are catching an event after an error, you can get retryCount from the event. retryCount will have the number of retries it took before the event was dispatched.

Here is an example on how to get retryCount:

<%= render_async users_path,
                 retry_count: 5,
                 retry_delay: 2000,
                 error_event_name: 'it-failed-badly' %>

<script>
  document.addEventListener('it-failed-badly', function(event) {
    console.log("Request failed after " + event.retryCount + " tries!")
  });
</script>

If you need to pass retry count to the backend, you can pass retry_count_header in render_async's options:

<%= render_async users_path,
                 retry_count: 5,
                 retry_count_header: 'Retry-Count-Current' %>

And then in controller you can read the value from request headers.

request.headers['Retry-Count-Current']&.to_i

Toggle event

You can trigger render_async loading by clicking or doing another event to a certain HTML element. You can do this by passing in a selector and an event name which will trigger render_async. If you don't specify an event name, the default event that will trigger render_async will be 'click' event. You can do this by doing the following:

<a href='#' id='comments-button'>Load comments</a>
<%= render_async comments_path, toggle: { selector: '#comments-button', event: :click } %>

This will trigger render_async to load the comments_path when you click the #comments-button element. If you want to remove an event once it's triggered, you can pass once: true in the toggle options. The once option is false (nil) by default.

You can also pass in a placeholder before the render_async is triggered. That way, the element that started render_async logic will be removed after the request has been completed. You can achieve this behaviour with something like this:

<%= render_async comments_path, toggle: { selector: '#comments-button', event: :click } do %>
  <a href='#' id='comments-button'>Load comments</a>
<% end %>

Control polling with a toggle

Also, you can mix interval and toggle features. This way, you can turn polling on, and off by clicking the "Load comments" button. In order to do this, you need to pass toggle and interval arguments to render_async call like this:

<a href='#' id='comments-button'>Load comments</a>
<%= render_async comments_path, toggle: { selector: '#comments-button', event: :click }, interval: 2000 %>

If you want render_async to render the request on load, you can pass start: true. Passing the start option inside the toggle hash will trigger render_async on page load. You can then toggle off polling by interacting with the element you specified. An example:

<a href='#' id='comments-button'>Toggle comments loading</a>
<%= render_async comments_path,
                 toggle: { selector: '#comments-button',
                           event: :click,
                           start: true },
                 interval: 2000 %>

In the example above, the comments will load as soon as the page is rendered. Then, you can stop polling for comments by clicking the "Toggle comments loading" button.

Polling

You can call render_async with interval argument. This will make render_async call specified path at the specified interval.

By doing this:

<%= render_async comments_path, interval: 5000 %>

You are telling render_async to fetch comments_path every 5 seconds.

This can be handy if you want to enable polling for a specific URL.

โš ๏ธ By passing interval to render_async, the initial container element will remain in the HTML tree and it will not be replaced with request response. You can handle how that container element is rendered and its style by passing in an HTML element name and HTML element class.

Controlled polling

You can controller render_async polling in 2 manners. First one is pretty simple, and it involves using the toggle feature. To do this, you can follow instructions in the control polling with a toggle section.

The second option is more advanced and it involves emitting events to the render_async's container element. From your code, you can emit the following events:

  • 'async-stop' - this will stop polling
  • 'async-start' - this will start polling.

๐Ÿ’ก Please note that events need to be dispatched to a render_async container.

An example of how you can do this looks like this:

<%= render_async wave_render_async_path,
                 container_id: 'controllable-interval', # set container_id so we can get it later easily
                 interval: 3000 %>

<button id='stop-polling'>Stop polling</button>
<button id='start-polling'>Start polling</button>

<script>
  var container = document.getElementById('controllable-interval')
  var stopPolling = document.getElementById('stop-polling')
  var startPolling = document.getElementById('start-polling')

  var triggerEventOnContainer = function(eventName) {
    var event = new Event(eventName);

    container.dispatchEvent(event)
  }

  stopPolling.addEventListener('click', function() {
    container.innerHTML = '<p>Polling stopped</p>'
    triggerEventOnContainer('async-stop')
  })
  startPolling.addEventListener('click', function() {
    triggerEventOnContainer('async-start')
  })
</script>

We are rendering two buttons - "Stop polling" and "Start polling". Then, we attach an event listener to catch any clicking on the buttons. When the buttons are clicked, we either stop the polling or start the polling, depending on which button a user clicks.

Handling errors

render_async lets you handle errors by allowing you to pass in error_message and error_event_name.

  • error_message

    passing an error_message will render a message if the AJAX requests fails for some reason

    <%= render_async users_path,
                     error_message: '<p>Sorry, users loading went wrong :(</p>' %>
  • error_event_name

    calling render_async with error_event_name will dispatch event in the case of an error with your AJAX call.

    <%= render_asyc users_path, error_event_name: 'users-error-event' %>

    You can then catch the event in your code with:

    document.addEventListener('users-error-event', function() {
      // I'm on it
    })

Caching

render_async can utilize view fragment caching to avoid extra AJAX calls.

In your views (e.g. app/views/comments/show.html.erb):

# note 'render_async_cache' instead of standard 'render_async'
<%= render_async_cache comment_stats_path %>

Then, in the partial (e.g. app/views/comments/_comment_stats.html.erb):

<% cache render_async_cache_key(request.path), skip_digest: true do %>
  <div class="col-md-6">
    <%= @stats %>
  </div>
<% end %>
  • The first time the page renders, it will make the AJAX call.
  • Any other times (until the cache expires), it will render from cache instantly, without making the AJAX call.
  • You can expire cache simply by passing :expires_in in your view where you cache the partial

Doing non-GET requests

By default, render_async creates AJAX GET requests for the path you provide. If you want to change this behaviour, you can pass in a method argument to render_async view helper.

<%= render_async users_path, method: 'POST' %>

You can also set body and headers of the request if you need them.

<%= render_async users_path,
                 method: 'POST',
                 data: { fresh: 'AF' },
                 headers: { 'Content-Type': 'text' } %>

Using with Turbolinks

On Turbolinks applications, you may experience caching issues when navigating away from, and then back to, a page with a render_async call on it. This will likely show up as an empty div.

If you're using Turbolinks 5 or higher, you can resolve this by setting Turbolinks configuration of render_async to true:

RenderAsync.configure do |config|
  config.turbolinks = true # Enable this option if you are using Turbolinks 5+
end

This way, you're not breaking Turbolinks flow of loading or reloading a page. It is more efficient than the next option below.

Another option: If you want, you can tell Turbolinks to reload your render_async call as follows:

<%= render_async events_path, html_options: { 'data-turbolinks-track': 'reload' } %>

This will reload the whole page with Turbolinks.

๐Ÿ’ก If Turbolinks is misbehaving in some way, make sure to put <%= content_for :render_async %> in your base view file in the <body> and not the <head>.

Using with Turbo

On Turbo applications, you may experience caching issues when navigating away from, and then back to, a page with a render_async call on it. This will likely show up as an empty div.

If you're using Turbo, you can resolve this by setting Turbo configuration of render_async to true:

RenderAsync.configure do |config|
  config.turbo = true # Enable this option if you are using Turbo
end

This way, you're not breaking Turbos flow of loading or reloading a page. It is more efficient than the next option below.

Another option: If you want, you can tell Turbo to reload your render_async call as follows:

<%= render_async events_path, html_options: { 'data-turbo-track': 'reload' } %>

This will reload the whole page with Turbo.

๐Ÿ’ก If Turbo is misbehaving in some way, make sure to put <%= content_for :render_async %> in your base view file in the <body> and not the <head>.

Using with respond_to and JS format

If you need to restrict the action to only respond to AJAX requests, you'll likely wrap it inside respond_to/format.js blocks like this:

def comment_stats
  respond_to do |format|
    format.js do
      @stats = Comment.get_stats

      render partial: "comment_stats"
    end
  end
end

When you do this, Rails will sometimes set the response's Content-Type header to text/javascript. This causes the partial not to be rendered in the HTML. This usually happens when there's browser caching.

You can get around it by specifying the content type to text/html in the render call:

render partial: "comment_stats", content_type: 'text/html'

Nested async renders

It is possible to nest async templates within other async templates. When doing so, another content_for is required to ensure the JavaScript needed to load nested templates is included.

For example:

<%# app/views/comments/show.html.erb %>

<%= render_async comment_stats_path %>
<%# app/views/comments/_comment_stats.html.erb %>

<div class="col-md-6">
  <%= @stats %>
</div>

<div class="col-md-6">
  <%= render_async comment_advanced_stats_path %>
</div>

<%= content_for :render_async %>

Customizing the content_for name

The content_for name may be customized by passing the content_for_name option to render_async. This option is especially useful when doing nested async renders to better control the location of the injected JavaScript.

For example:

<%= render_async comment_stats_path, content_for_name: :render_async_comment_stats %>

<%= content_for :render_async_comment_stats %>

Configuration options

render_async renders Vanilla JS (regular JavaScript, non-jQuery code) by default in order to fetch the request from the server.

If you want render_async to use jQuery code, you need to configure it to do so.

You can configure it by doing the following anywhere before you call render_async:

RenderAsync.configure do |config|
  config.jquery = true # This will render jQuery code, and skip Vanilla JS code. The default value is false.
  config.turbolinks = true # Enable this option if you are using Turbolinks 5+. The default value is false.
  config.turbo = true # Enable this option if you are using Turbo. The default value is false.
  config.replace_container = false # Set to false if you want to keep the placeholder div element from render_async. The default value is true.
  config.nonces = true # Set to true if you want render_async's javascript_tag always to receive nonce: true. The default value is false.
end

Also, you can do it like this:

# This will render jQuery code, and skip Vanilla JS code
RenderAsync.configuration.jquery = true

Aside from configuring whether the gem relies on jQuery or VanillaJS, you can configure other options:

  • turbolinks option - If you are using Turbolinks 5+, you should enable this option since it supports Turbolinks way of loading data. The default value for this option is false.
  • turbo option - If you are using Turbo, you should enable this option since it supports Turbo way of loading data. The default value for this option is false.
  • replace_container option - If you want render_async to replace its container with the request response, turn this on. You can turn this on globally for all render_async calls, but if you use this option in a specific render_async call, it will override the global configuration. The default value is true.
  • nonces - If you need to pass in nonce: true to the javascript_tag in your application, it might make sense for you to turn this on globally for all render_async calls. To read more about nonces, check out Rails' official guide on security. The default value is false.

โš’๏ธ Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment. To run integration tests, use bin/integration-tests. For more information, check out CONTRIBUTING file, please.

Got any questions or comments about development (or anything else)? Join render_async's Discord channel and let's make render_async even better!

๐Ÿ™ Contributing

Check out CONTRIBUTING file, please.

Got any issues or difficulties? Join render_async's Discord channel and let's make render_async even better!

๐Ÿ“ License

The gem is available as open source under the terms of the MIT License.

Contributors

Thanks goes to these wonderful people (emoji key):


Nikola ฤuza

๐Ÿ’ฌ ๐Ÿ’ป ๐Ÿ“– ๐Ÿ‘€

Colin

๐Ÿ’ป ๐Ÿ“– ๐Ÿ’ก

Kasper Grubbe

๐Ÿ’ป

Sai Ram Kunala

๐Ÿ“–

Josh Arnold

๐Ÿ’ป ๐Ÿ“–

Elad Shahar

๐Ÿ’ป ๐Ÿ’ก

Sasha

๐Ÿ’ป ๐Ÿ“–

Ernest Surudo

๐Ÿ’ป

Kurtis Rainbolt-Greene

๐Ÿ’ป

Richard Schneeman

๐Ÿ“–

Richard Venneman

๐Ÿ“–

Filipe W. Lima

๐Ÿ“–

Jesรบs Eduardo Clemens Chong

๐Ÿ’ป

Renรฉ Klaฤan

๐Ÿ’ป ๐Ÿ“–

Gil Gomes

๐Ÿ“–

Khoa Nguyen

๐Ÿ’ป ๐Ÿ“–

Preet Sethi

๐Ÿ’ป

fangxing

๐Ÿ’ป

Emmanuel Pire

๐Ÿ’ป ๐Ÿ“–

Maxim Geerinck

๐Ÿ’ป

Don

๐Ÿ’ป

villu164

๐Ÿ“–

Mitchell Buckley

๐Ÿ’ป ๐Ÿ“–

yhirano55

๐Ÿ’ป ๐Ÿ“–

This project follows the all-contributors specification. Contributions of any kind welcome!

render_async's People

Contributors

colinxfleming avatar eclemens avatar elsurudo avatar fffx avatar filipewl avatar gil27 avatar kaspergrubbe avatar krainboltgreene avatar lipsumar avatar maximgeerinck avatar nikolalsvk avatar preetsethi avatar reneklacan avatar richardvenneman avatar sairam avatar saladfork avatar sasharevzin avatar schneems avatar surgevelocityllc avatar thanhkhoait avatar villu164 avatar yhirano55 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

render_async's Issues

2.1.5 breaks manual trigger of click event

With 2.1.4 we could do this on the toggle selector element:

  $(document).ready(function(){
    $("#async-trigger").trigger('click');
  });

With 2.1.5 this does not work. Running the ...trigger('click') in the console works, but the timing of this during the page load sequence is no longer working in 2.1.5.

I have not determined why yet, and will comment if I can determine what is happening in more detail.

Events are getting registered before DOM load

Please excuse me, if I am trying this whole thing wrongly.

I have followed the documentation, and followed all the steps under Usage correctly, and can see the render_async working. But when I try to use toggle event, click action is not working.

Implemented toggle as below:

<a href="#" id='detail-button'>Detail</a>

<%= render_async gift_cards_cart_transactions_path, toggle: { selector: '#detail-button', event: :click } %>

Can see the code generated as below:

 <script>
//<![CDATA[

      (function() {

  var _makeRequest = function(currentRetryCount) {
    var request = new XMLHttpRequest();
    var asyncRequest = true;
    var SUCCESS = 200;
    var ERROR = 400;

    request.open('GET', '/cart/transactions/gift_cards', asyncRequest);

    var headers = {};
    var csrfTokenElement = document.querySelector('meta[name="csrf-token"]')
    if (csrfTokenElement)
      headers['X-CSRF-Token'] = csrfTokenElement.content

    Object.keys(headers).map(function(key) {
      request.setRequestHeader(key, headers[key]);
    });

    request.onreadystatechange = function() {
      if (request.readyState === 4) {
        if (request.status >= SUCCESS && request.status < ERROR) {
          var container = document.getElementById('render_async_3d086c93961567805960');
          container.outerHTML = request.response;

        } else {
          var skipErrorMessage = false;

          if (skipErrorMessage) return;

          var container = document.getElementById('render_async_3d086c93961567805960');
          container.outerHTML = '';

        }
      }
    };

    var body = "";
    request.send(body);
  };


  var _renderAsyncFunction = _makeRequest;


  var selectors = document.querySelectorAll('#detail-button');

  [...selectors].forEach(function(selector) {
    selector.addEventListener('click', function(event) {
      event.preventDefault();
      if (_interval) {
        clearInterval(_interval);
        _interval = undefined;
      } else {
        _renderAsyncFunction();
      }
    })
  });

})();


//]]>
</script>

But the issue happening is, this piece of code is running before DOM load, which technically mean the element with id detail-button is still available on the DOM, and so no event listener is being added to it.

Is there a way to get this script executed after DOM is ready?

Thanks in advance.

render_async in table

I have a partial view with root element <tr>
But the gem is wrapping this partial view with a <div>

<table>
  <tbody>
     <div>
        <tr> ... </tr>
    </div>
  </tbody>
</table> 

Which eventually will cause the partial view to be rendered outside of the table 
(Let's say it's invalid HTML)

Idea: only fire the AJAX request on scroll (Lazy Partials)

I'm sorry if this gem already does this, but I couldn't find it in the documentation.

The idea is: go one step further in optimizing the page load, and have lazy partials, just like we have lazy images.

Using the Intersection Observer API (https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API), we can fire the AJAX request for the partial only if the user scrolls down and gets near the partial placeholder.

Imagine a blog posts comment section. If we use something like <%= render_async comment_stats_path, lazy: true %>, the javascript could use the IntersectionObserver API to only fire that request some X pixels before the user reaches the partial.

What do you guys think? :)

Retry/failure event

So we have a large amount of data we want from the server, so we do this:

  • Get the total count of the data
  • Create "pages"
  • render_async_cache for each page
  • View cache the partial based on the async cache key: cache(render_async_cache_key) { ... }
  • View cache based on the collection cache(records)
  • View cache the individual cache(record)

So sometimes it takes a long time to load a an async partial and those requests timeout, but we can safely retry those requests and each time they'll get smaller.

I would love to have an option for render_async*() that tells the javascript to retry on failure (infinitely, or n times with backoff). Thoughts?

Render on click?

I use render_async widely in my application, so I can get initial page load to be very fast, and then load each sub-component individually.

I'd also like to be able to load new page sub-components asynchronously when a user, for example, clicks on a button.

In the past, I've tried the rails-ujs method of link_to with remote: true, but this requires extra JavaScript code -- not as seamless or easy as render_async.

Any recommendations?

Manual retry based on JS event

Use case:

  1. Render a data table on a view using render async.
  2. Allow the user to edit a row in the table.
  3. Trigger a refresh of the table using the render_async plumbing. (Refreshing only the edited row is not feasible because the table contains totals and other calculated data)

I am thinking that we could register an event handler on the container to receive the async-retry event and re-run the original request. After reviewing the code, I see a potential breaking change that I think would be necessary to make this happen.

Around line 48 of _request_jquery.js.erb

      <% if interval %>
          container.empty();
          container.append(response);
        <% else %>
          container.replaceWith(response);
        <% end %>

Replacing the container in the non-interval path does not allow us to retain the container for future event handling. Instead of replacing the container, empty and append would keep the container intact.

This change would potentially break some view code where users expect the container div to disappear when the view is rendered.

I have implemented a possible solution on my fork, branch: immediate_setup. It required the addition of a configuration option for keep_container to keep the existing behavior intact and control whether or not the container would remain for further event handling. I welcome some advice or other options to implement this functionality in a cleaner way. Thanks.

Retry render_async after some time

If request fails and you want to retry it, you should be able to wait for N seconds before firing another request. You can already retry on failure, but it will emit another request right away.

Inspired by this comment #46 (comment)

If you would like to see this request, please ๐Ÿ‘ this issue.

Render_async on header

Hi!

I am trying to load a partial in the header and it doesn't load. If I put the code in the view, it does.

Header partial:
<%= render_async notifications.notifications_count_path %>

Controller (notifications engine, maybe this is not ok):

def notifications_count @notif_count = Notification.unread_count(current_user) render partial: 'layouts/notifications_count' end
Inside my routes:

Notifications::Engine.routes.draw do get :notifications_count, controller: :notifications end

Am I doing something wrong?

Thanks in advance!

Feature idea: Render async with ActiveJob & ActionCable

Rendering with ActiveJob & ActionCable would allow for long performing background jobs to push view content when finished.

Rough Flow:

  1. Render async called in view, which also creates ActionCable client subscription
  2. Controller action immediately queues background job for processing
  3. When job completes, content is broadcasted to client
  4. Client inserts content into page from the subscription success callback

Use Case:
We have some long running queries in our admin interface around reporting. Since this is internal, it's not that big of a deal that a response takes 10 seconds, especially since it's only used a few times a day. We'd rather focus dev optimization effort on the customer facing part of the application.

However, this request can cause timeouts for the rest of the application, especially if requests are simultaneous. Pushing the processing to the background and rendering when ready would allow it be truly non-blocking for other requests.

Uncaught ReferenceError: _interval is not defined when render_aync with Selector

I use render_async with:

render_async some_path,  toggle: { selector: "#sync_load_trigger" }

when I click sync_load_trigger button I got

(index):11015 Uncaught ReferenceError: _interval is not defined
    at HTMLLIElement.<anonymous> ((index):11015)

render_async version: 2.1.2

My workaround is define a global variable _interval before render_async

 <script type="text/javascript">
       _interval = null
 </script>

Toggle event just refreshing the page

Hi there,

I'm using the toggle events as listed in your docs and when I click the 'Details' button, the page refreshes. Even if I add "onclick='return false;'" nothing happens at all, the server action isn't called.

<%= render_async comments_path, toggle: { selector: '#detail-button', event: :click } do %>
  <a href='#' id='detail-button'>Load More</a>
<% end %>

Any advice or tips?

Feature idea: Always pass nonce if config is set

hi @nikolalsvk ! It looks like render_async is increasingly starting to take initializer configs, like config.turbolinks = true or config.jquery = true. I kinda like that pattern a lot, as the turbolinks option is removing a lot of redundant stuff from my codebase, and I appreciate that!

A similar thing I am doing (to literally every render_async call) is html_options: { nonce: true } because I need to get the javascript past my CSP. So I was wondering: Do you have any interest in adding a 'turn on nonces by default' as an option in RenderAsync.configuration, or do you think it would be overkill? Would probably be something like (spitballing):

RenderAsync.configure do |config|
  config.nonces = true
end

# _render_async.html.erb
...
javascript_tag html_options.merge({nonce: RenderAsync.configuration.nonces}) do
  ...
end

and that would basically do the same thing as what you can currently accomplish with:

render_async users_path, html_options: { nonce: true }

(The other thing I kinda thought about was a default_html_options config, which would merge into html_options but I think I like the specific option better.)

More than happy to take a stab at this work if it sounds good or interesting? I kinda think it would be a nice 'turn on extra security feature by default' kinda deal. Let me know what you think, or if I can clarify anything.

Event dispatch does not work on IE11

Creating new events using new Event("...") does not work in IE11 ("Object doesn't support this action").

I fixed this "problem" in my app by overriding the render_async partial, and using jQuery to trigger the event (ie. $(document).trigger("<%= event_name %>");). There are other workarounds, however these are already deprecated in newer browsers.

I don't think there is anything to be done in this gem, as IE11 is already pretty old. Just leaving this here if somebody runs into this problem.

Add examples for rendering non-GET requests

There's a hint in the 1.3.0 release but it's not enough, it should be added to the README too.

Something along these lines, but with an explanation:

<%= render_async some_post_path,
                 method: 'POST',
                 data: { fresh: 'AF' },
                 headers: { 'Content-Type': 'text' } %>

Stop polling based on the response

First of all, thanks for the work on the lib :)

I have the following situation: I'm running a script in the background and want to poll for the logs. The problem I see with the current polling approach is that it never stops, only if some user-initiated event happens. But that's not a good experience in this case. Ideally, the polling should stop when the server returns that the script is done running.

Is it possible to solve it somehow?

Duplication of injected JS for nested async templates

I have a tabbed UI where a CREATE action can add tabs. The tabs are dynamically loaded using render_async when the user clicks on the tab label. Consider something like this...

targetprocess-screen-capture (6)

Each tab is loaded asynchronously, so the view layout contains:

= content_for(:render_async)

However, because the CREATE function can add a new tab to the UI, the asynchronous view partial also contains:

= content_for(:render_async)

This is causing duplication of the injected Javscript when the view is rendered because it is being injected at the layout level and within the partial for each tab.

Possible solution: what if the render_async method accepted an option to specify the content tag? This would provide control over where the injected Javascript would go. Of course, the option would default to :render_async for backward compatibility. I will experiment with this on my fork and offer a PR if I can get something working.

alternative caching strategy

just a suggestion that might be added to the documentation.

i'm using render async with this pattern: i'm rendering the existing partials by adding paths and methods that just render the existing partials, while making use of the existing request caching mechanisms. the key advantage is that it's not taking cache memory on the server but it's using the client side (or in between) HTTP caches. for full correctness, fragment caching is complimentary in the general case, but in general the HTTP caching should not be forgotten and can bring extra 'relief' for the server.

  def _details
    authorize @node, :show?
    if (stale?(@node)) # makes the browser cache subsequent requests
      readonly = (current_user == nil) || (current_user.can_write? == false)
      render partial: 'details', locals: {node: @node, node_structure: @node_structure, readonly: readonly}
    end
  end

Render multiple partials in one html view

Hi;
I started to use your gem to render ads in my application. I have multiple ads in the page, so I used render_async multiple times However, only one of them shows up at the end. Is this a limitation or is there something I am missing out?
Thanks

Add readme note about use with turbolinks?

hey @nikolalsvk , spent some time a few weeks ago on getting a render_async div to work cleanly with turbolinks, which wound up requiring a somewhat specific incantation. It seems like it might be a pretty common use case given that turbolinks is part of the rails standard, so I thought I might pay it forward and add a recipe for it to the readme, but wanted to check with you first to make sure that was cool.

If you'd rather not feel free to close this issue, otherwise let me know! ๐Ÿ’ฏ

no-jQuery is replacing all of parent element's HTML

In _render_async.html.erb for the no-jQuery condition, all of the HTML for a container's parent is being replaced. I'm assuming it should only be replacing the container itself.

The line in question is: container.parentNode.innerHTML = request.response;

Please add a CHANGELOG

3 releases in 1 day. What happened?
Too many commits to review -> I won't upgrade right now.

This project needs a changelog.

Start polling immediately option when toggle is specified

Suggestion: provide an option to start polling immediately even when a toggle is provided. The current behavior is to poll immediately when a toggle is not present, but wait for the toggle when the toggle option is used.

This would support the use case where we want to start polling immediately on page load but provide the user the capability to toggle the polling OFF. Thanks for your consideration.

Bug: Using with haml

Hi.

I'm working in a project that uses haml, isntead of erb.
When I added render_async to the project, I started getting this error undefined method 'haml_tag'.

I looked at the render_async code, and didn't find anything that might cause that conflict.
You can check the error here https://github.com/jonatasrancan/bug-render-async

Just start the app and try to access the root page, the error will show up.
Then remove render_async, from the gem file, and the app will work normally.

Any idea on why this is happening?

Sk-spinner css class

It's not documented how this works and I'm not seeing the spinner on my page. How do you import the css for sk-spinner sk-spinner-bounce ?

Event driven reloads.

So I am looking to achieve to what I can best describe as an "event driven reload".

Example Use-case:

A list of students is rendered on page load and a button is presented to the end-user that will allow them to refresh that list.

--

I do see that there's toggle functionality available, however, the event selector seems to gate-keep the initial render.

Is there currently any path for rendering on page load and leveraging events to trigger subsequent renders?

Proposal: Allow nonces?

hey, was wondering -- we at DCAF Engineering are interested in using this, but have a pretty strict content security policy that prohibits running inline js without whitelisting it with a sha or a nonce. We was mulling over submitting a PR to this that would take a nonce value as an argument, but wanted to check and make sure this was something you all were interested in first.

If you are, our team can whip something up real quick, if that sounds good to you?

Start and stop polling

Add options to be able to start polling. Let's say you want to start polling after the user clicks a button and you emit an event "render-async-start-polling", it would be cool to do that.

Also, have render_async stop polling when you emit a certain event, e.g. "stop-polling-now".

Idea rose from this comment #67 (comment)

Question: What exactly is returned if interval is used?

The readme says that the HTML can be handled by passing in an HTML element name and HTML element class, but this doesn't explain that is being returned...

Is the content of the rendered partial being returned, and do we need to handle an event?

Add CHANGELOG.md

Include CHANGELOG.md file in the repo where users can easily go through changes. But also, keep the Releases tab up-to-date.

Form doesn't seem to work in partial

Hey all, love this gem!

I'm getting a confusing issue for one of my uses though. In my partial, I'm loading a table of data and in each row, I have a form_tag. This works nicely in my old, regular partial but when I load it async, using this gem, the form fields don't render and the submit button doesn't trigger any action when pressed.

Here's an example of my code:

# CONTROLLER ACTIONS:
  def new
    @role = MemberRole.find(params[:member_role_id])
  end

  def api_available_projects
    @integration_project = IntegrationProject.new(project_params)
    @repos = ListIntegrationProject.new(current_user.get_data).call
  end

    render partial: "api_available_projects"
  end

 # View new.html.erb
<%= render_async api_available_projects_path(member_role_id: params[:member_role_id]) %>

# View api_available_projects.html.erb
<div class="scrolly">
  <table class="table table-responsive table-bordered table-dark">
    <tr>
      <th>Name</th>
      <th>Access Level</th>
      <th>Actions</th>
    <tr>
      <% @repos.each do |repo| %>
 
            <tr>
              <%= form_tag integration_projects_path(repo: repo), :method => :post do %>
                <td><%= repo[:name] %></td>
                <td><%= select_tag 'access_level', options_for_select(Integration.access_types) %></td>
                <td><%= submit_tag %></td>
              <% end %>
            </tr>
  
      <% end %>
  </table>
</div>

preventDefault() in toggle code interferes with other UI functionality

I have a tabbed interface where I would like to kick off the render async when the user selects a tab. The tab system is built using a radio and HTML/CSS only.

I can attach render_async toggle selector to the ID of the radio button that controls the tab switching, but preventDefault() is keeping the default radio behavior from happening.

I am proposing that the preventDefault() be removed from _request_jquery.js.erb:105. So far in my app I have not found any negative ramifications of doing so.

Thanks for all you guys do on this gem.

Can not stop polling after page navigation changes.

Hi,
Is there a way to stop the polling once user navigates to a different page other than the one in which a 'render_async' partial is rendered.

Another problem case I found is as under:

  • I have my main page M1 in which I am rendering a partial P1 through render_async with a set interval of 30 seconds.
  • The problem I found is that, when M1 is navigated by clicking on some link, in that case the partial P1's part does not gets shown at all (on first load) and instead the html part shown for asynchronous waiting time is getting shown until 30 seconds is passed and a refresh occurs.
  • And when I am already on page M1 and hit a refresh (Ctrl.+ F5), in this case the partial P1 part gets shown properly after page gets loaded.

Can anyone help with above problems please.

Thanks.

Passing data example does not work

From your readme:

<%= render_async users_path,
                 method: 'POST',
                 data: { fresh: 'AF' },
                 headers: { 'Content-Type': 'text' } %>

When I tried this, I get:

=> <ActionController::Parameters {"{:fresh"=>">"AF"}", "controller"=>"pages", "action"=>"sidebar_ads"} permitted: false>

Async content node weirdly dissapearing

Hello there!

I've started using this gem for creating a dashboard and ran into a strange issue. The container is being removed somehow, instead of having its content replaced with what comes from the AJAX's response. Take a look:

brokz70rum

My structure is pretty straightforward:

# view.html.erb
<%= render_async foo_path do %>
  Loading number of logins...
<% end %>
# _partial.html.erb
<h3>Number of logins:</h3> <%= @logins_count %>

Weirdly enough, I had it working just fine after a few tries. Then as I continued with other changes, it stopped working again. I even reverted to a commit I thought I had it working and it just didn't work.
ยฏ\_(ใƒ„)_/ยฏ

I can't pinpoint exactly what could be going wrong, any guess?

Thanks for the gem! I first got to know it by the Semaphore's article and knew I would be useful any time I were to work with async loading partials with Rails. :)

Nested async templates - JS not firing/loading

The parent async templates are working great (and love the gem, btw)!

Started to have the need for nested async templates and followed the instructions to the T, including the <%= content_for :render_async %> inside the parent async template. JS inspection confirms that the nested asyncs are generated but it's not firing the URL.

Can't help but wonder if it's because the JS is dynamically added by the parent and then not firing because it has been added after page load?

Any insights are greatly appreciated.

Idea: only make request if element is visible

Use case:

Utilizing responsive principles, different elements of my page are hidden/shown depending on the screen size. As render_async works now, my page fetches all the elements at page load time, even the ones that are currently not being shown (perhaps they are only shown on small screens, for example). Obviously this is not ideal for performance reasons.

At least for the jQuery case, we could use the elem.is(':visible') pseudo-selector (as per https://stackoverflow.com/questions/178325/how-do-i-check-if-an-element-is-hidden-in-jquery) to determine whether the element is visible, and only fetch the async partial if it is (not sure about the non-jQuery case).

What if the user loads the page full-screen, and then shrinks the window? Perhaps there is a way to handle this case as well, but I haven't done the research.

Just putting this here as a feeler for now. What do you think?

How can I update my view with jquery interval?

I want to constantly update my partial rendering for showing latest feed after 5 minutes interval. How can I do as using jquery getscript with the specified URL only works in the background and partial on the front side never gets updated?

Thanks

Toggle & Turbolinks - turbolinks:load event forces _renderAsyncFunction to fire regardless of toggle

Hey there!

First and foremost, thanks for all the work with render_async, we use it everyday in production!

I was the guy using the toggle feature prior to your release. After updating to 2.1.1, I noticed some strange behavior: when turbolinks is enabled, the _renderAsyncFunction will fire regardless of the toggle configuration.

I believe this little section of code is responsible

  <% if turbolinks %>
  document.addEventListener("turbolinks:load", function (e) {
    e.target.removeEventListener(e.type, arguments.callee);
    _renderAsyncFunction();
  });
  <% elsif !toggle %>
  document.addEventListener("DOMContentLoaded", _renderAsyncFunction);
  <% end %>

I'm sending you a quick PR, let me know if you want me to change anything.

If this isn't the intended behavior, could you please elaborate on your thinking?

Thanks!

edit:

Realized I can probably clarify and describe our use case a bit more.

Assume the following render_async call:

<%= render_async performance_stats_path(campaign), toggle: { selector: "#load-campaign-#{campaign.id}-stats", event: 'click'}, interval: 60000 do %>
  <a id="<%= "load-campaign-#{campaign.id}-stats" %>" class="btn btn-default btn-xs">
    Load Statistics <i class="fa fa-line-chart ld ld-surprise"></i>
  </a>
<% end %>

Basically we render a button as a placeholder and the button contains the toggle selector. With turbolinks off, everything functions as expected: when you click the button, render_async works its magic and loads the partial, replacing the button with some awesome stats for our customers.

When turbolinks config is enabled, the partial is loaded on turbolinks:load regardless of any toggle configuration. It took me a while to actually root this out because I use all of the following:

  1. rails fragment caching
  2. turbolinks (which has its own caching mechanism)
  3. render_async (with caching, toggle, and interval used in different situations)

and I did not want to file an issue prematurely.

We're running my branch in staging now and everything is functioning as expected. We even have a render_async call with caching, toggle, and interval + turbolinks, and it's working well on our initial evaluation.

Once again, thanks!

Idea: Jquery or no-jQuery, depending on config

There is no point rendering both the jQ, and non-jQ JS, when in reality only one will be used. IMO it would make sense to simply make this a config var on the server-side. Default should be vanilla JS, I suppose?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.