Skip to content

Instantly share code, notes, and snippets.

@andynu
Created September 4, 2024 16:19
Show Gist options
  • Save andynu/b4b0f320aea7b1eee19ae7feafa69aa4 to your computer and use it in GitHub Desktop.
Save andynu/b4b0f320aea7b1eee19ae7feafa69aa4 to your computer and use it in GitHub Desktop.
Ajax-frames - Like Turbo Frames (from rails), but you have to specify the target frame for every link.
/* global $ */
/**
* ajax-frames v1.1.0
* pre-requisites: jquery
* compatible with: rails-ujs
*
* Exports:
* - ajax_frames_init(container) - Automatically runs on document ready for the entire document.
* - load_ajax_frames_multi(url)
*
* Example haml:
*
* -# Lazy load a frame
* %div.ajax-frame{src: 'http://lazy-contents.com'}
*
* -# Remote form
* #box_editor.ajax-frame
* = form_for @box, html: { id: 'box_form', data: { "ajax-frame": "box_editor", remote: true} } do |f|
*
* -# Remote link
* = link_to "Edit", edit_location_path(box_idx), data: { "ajax-frame": "box_editor", remote: true }
*
* Example Javascript:
*
* // Binding additional javascript events
* $(document).on('ajax-frame:success', (event, data) => {
* const $container = $(event.target);
* init_semantic_hooks($container);
* });
*
* // Re-binding ajax-frames after a non-ajax-frames event
* $.ajax({
* url: window.location.href,
* success: (data) => {
* $('#overview').replaceWith($(data).find('#overview'));
* ajax_frames_init('#overview');
* }
* });
*/
(() => {
$(function() {
console.log("Initializing ajax-frames");
return ajax_frames_init(document);
});
window.ajax_frames_init = function(container) {
bind_ajax_frame_success(container, '.ajax-frame[src]');
bind_ajax_frame_success(container, 'form[data-ajax-frame][data-remote="true"]');
// bind_ajax_frame_success(container, 'form[data-ajax-frame][data-local="false"]')
bind_ajax_frame_success(container, 'button[data-ajax-frame]');
bind_ajax_frame_success(container, 'a[data-ajax-frame][data-remote="true"]');
// bind_ajax_frame_success(container, 'a[data-ajax-frame][data-local="false"]')
load_lazy_ajax_frames(container);
};
// Do the get of the lazy frames.
//
// <div id='myframe' class='ajax-frame' src='http://lazy-contents.com'></div>
//
// Will load the src and look for a div with the same id as this frame. and
// replace this div with that one.
//
function load_lazy_ajax_frames(container) {
$(container).find(".ajax-frame[src]").each(function() {
if (this===container) { return; }
const $this = $(this);
const id = $this.attr('id');
const src = $this.attr('src');
console.log(`load_lazy_ajax_frames #${id}`);
return $.get(src, (response, status, xhr) => {
console.log(`ajax get success, trigger ajax:success on #${id}`);
return $this.trigger('ajax:success', {detail: [response, status, xhr]});
});
});
}
/**
* Loads multiple AJAX frames from a given URL.
*
* @param {string} url - The URL from which to load the AJAX frames.
*
* This function sends a GET request to the provided URL and expects a response containing HTML with elements that have the class 'ajax-frame'.
* For each 'ajax-frame' element found in the response, the function:
* - Extracts the 'ajax-frame' data attribute to determine the frame ID.
* - Replaces the current frame with the same ID in the document with the new frame from the response.
* - Binds the 'ajax:success' event to forms, buttons, and anchor elements within the new frame.
* - Triggers the 'ajax-frame:success' event on the new frame.
*
* Note: This function does not handle errors in the AJAX request. It assumes that the request will always succeed and that the response will always contain valid HTML with the expected 'ajax-frame' elements.
*/
window.load_ajax_frames_multi = function(url) {
$.get(url, (response, status, xhr) => {
console.log("load_ajax_frames_multi success");
for (var frame of Array.from($(response).find('.ajax-frame'))) {
console.log(frame);
var $el = $(frame);
var frame_id = $el.data('ajax-frame');
if (frame_id === undefined) {
frame_id = $el.attr('id');
}
var frame_sel = '#' + frame_id;
console.log(`replace ${frame_sel}`);
$(frame_sel).replaceWith(frame);
//ajax_frames_init(frame_id) # This would be non-terminating recursive. Known limitation.
var container = $(frame_id);
bind_ajax_frame_success(container, 'form[data-ajax-frame][data-remote="true"]');
// bind_ajax_frame_success(container, 'form[data-ajax-frame][data-local="false"]')
bind_ajax_frame_success(container, 'button[data-ajax-frame]');
bind_ajax_frame_success(container, 'a[data-ajax-frame][data-remote="true"]');
// bind_ajax_frame_success(container, 'a[data-ajax-frame][data-local="false"]')
$(frame_sel).trigger('ajax-frame:success');
}
});
}
/**
* Handles the binding of 'ajax:success' event and manipulates the result DOM for all AJAX frames.
* This includes:
* - Lazy loaded frames
* - Forms with 'local: false'
* - Buttons with 'remote: true'
* - Links with 'remote: true' and 'method: post'
*
* @param {Object} container - The DOM element that serves as the container for the elements to be initialized.
* @param {string} selector - The selector to find the elements within the container to bind the 'ajax:success' event.
*/
function bind_ajax_frame_success(container, selector) {
console.log(`Binding container: ${container} ${selector}`);
$(container).find(selector).each(function() {
if (this===container) { return; }
const $el = $(this);
let frame_id = $el.data('ajax-frame');
if (frame_id === undefined) {
frame_id = $el.attr('id');
}
const frame_sel = '#' + frame_id;
console.log(`Binding element ${$el[0].nodeName} ${frame_sel}`);
$el.on('ajax:success', function(...args) {
// the last argument has the .details attribute
// rails-ujs params: event (event has .details)
// jquery.trigger params: event, extraParams (extraParams has .details)
//
//[event] = args # rails-ujs
//[event, extraParams] = args # jquery.trigger
//[_, _, _, event] = args
//[jq_event, response, status, event] = args
//event = args[args.length - 1]
let response, status;
const eventidx = args.findIndex(e => e && e.detail !== undefined);
let event = args[eventidx];
if (event === undefined) {
let jq_event;
[jq_event, response, status, event] = args;
} else {
let xhr;
[response, status, xhr] = event.detail;
}
console.log(`ajax_frame_success ${$el[0].nodeName} replacement ${frame_sel}`);
const new_div = $(response).find(frame_sel);
const cur_div = $(frame_sel);
$(frame_sel).replaceWith(new_div);
ajax_frames_init(frame_sel);
$(frame_sel).trigger('ajax-frame:success');
});
$el.on('ajax:send', (...args) => $(frame_sel).trigger('ajax-frame:send'));
$el.on('ajax:complete', (...args) => $(frame_sel).trigger('ajax-frame:complete'));
$el.on('ajax:error', (...args) => $(frame_sel).trigger('ajax-frame:error'));
});
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment