Last active
April 1, 2025 08:42
-
-
Save wpeasy/41eb1cd54726fe624e2d89edf5d567ae to your computer and use it in GitHub Desktop.
Bricks Ajax Loader
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/***** | |
* | |
* USAGE | |
* On any element to use as a trigger, add the following attributes: | |
* - data-ajax-fetch-bricks | |
* - data-post-id : (int) Post ID | |
* - data-ajax-container : CSS selector for the root container | |
* - data-html-selector : Child selector inside the container to render the HTML | |
* Optional (children of the container): | |
* - data-title-selector : Selector for post.title | |
* - data-excerpt-selector : Selector for post.excerpt | |
* - data-date-selector : Selector for post.date | |
* - data-modified-selector : Selector for post.modified | |
* - data-author-selector : Selector for post.author | |
* Optional (grouping): | |
* - data-load-first=".parent-class" : Triggers the first ajax element inside each matching parent container | |
*/ | |
(() => { | |
const triggerSelector = '[data-ajax-fetch-bricks]'; | |
const styleId = 'wpe-bricks-dynamic-style'; | |
const maxFetchTime = 5; // seconds | |
const wpe_get_by_ajax = ({ postId, containerSelector, htmlSelector, metaSelectors = {} }) => { | |
const { nonce, wpRestNonce } = window.bricksData || {}; | |
if (!wpRestNonce) { | |
console.error("Nonce not found in window.bricksData"); | |
return; | |
} | |
const endpoint = "/wp-json/wpe_bricks_ajax/v1/wpe_render_element"; | |
const headers = { | |
"Content-Type": "application/json", | |
"X-WP-Nonce": wpRestNonce, | |
}; | |
const body = { | |
postId, | |
nonce, | |
element: htmlSelector, | |
}; | |
const container = document.querySelector(containerSelector); | |
if (!container) { | |
console.warn(`Container "${containerSelector}" not found`); | |
return; | |
} | |
container.classList.add("fetching"); | |
const controller = new AbortController(); | |
const timeout = setTimeout(() => { | |
controller.abort(); | |
console.warn(`Bricks fetch aborted after ${maxFetchTime}s for postId ${postId}`); | |
}, maxFetchTime * 1000); | |
fetch(endpoint, { | |
method: "POST", | |
headers, | |
body: JSON.stringify(body), | |
signal: controller.signal, | |
}) | |
.then((response) => { | |
if (!response.ok) { | |
throw new Error(`Request failed with status ${response.status}`); | |
} | |
return response.json(); | |
}) | |
.then(({ html, styles, post }) => { | |
const htmlTarget = container.querySelector(htmlSelector); | |
if (htmlTarget) { | |
const fragment = document.createRange().createContextualFragment(html); | |
htmlTarget.innerHTML = ""; | |
htmlTarget.appendChild(fragment); | |
} else { | |
console.warn(`HTML target "${htmlSelector}" not found in container`); | |
} | |
if (post && typeof post === "object") { | |
const { title, excerpt, date, modified, author } = post; | |
if (metaSelectors.title) { | |
const el = container.querySelector(metaSelectors.title); | |
if (el) el.textContent = title; | |
} | |
if (metaSelectors.excerpt) { | |
const el = container.querySelector(metaSelectors.excerpt); | |
if (el) el.textContent = excerpt; | |
} | |
if (metaSelectors.date) { | |
const el = container.querySelector(metaSelectors.date); | |
if (el) el.textContent = date; | |
} | |
if (metaSelectors.modified) { | |
const el = container.querySelector(metaSelectors.modified); | |
if (el) el.textContent = modified; | |
} | |
if (metaSelectors.author) { | |
const el = container.querySelector(metaSelectors.author); | |
if (el) el.textContent = author; | |
} | |
} | |
if (styles) { | |
const parser = new DOMParser(); | |
const doc = parser.parseFromString(styles, "text/html"); | |
const originalStyle = doc.querySelector("style"); | |
const cssContent = originalStyle?.textContent; | |
if (cssContent) { | |
const existingStyle = document.getElementById(styleId); | |
if (existingStyle) existingStyle.remove(); | |
const styleTag = document.createElement("style"); | |
styleTag.id = styleId; | |
styleTag.textContent = cssContent; | |
document.body.appendChild(styleTag); | |
} | |
} | |
}) | |
.catch((error) => { | |
if (error.name === 'AbortError') { | |
console.warn("Fetch aborted due to timeout."); | |
} else { | |
console.error("Error rendering Bricks element:", error); | |
} | |
}) | |
.finally(() => { | |
clearTimeout(timeout); | |
container.classList.remove("fetching"); | |
}); | |
}; | |
const extractSelectors = (el) => ({ | |
container: el.dataset.ajaxContainer, | |
html: el.dataset.htmlSelector, | |
meta: { | |
title: el.dataset.titleSelector, | |
excerpt: el.dataset.excerptSelector, | |
date: el.dataset.dateSelector, | |
modified: el.dataset.modifiedSelector, | |
author: el.dataset.authorSelector, | |
} | |
}); | |
document.addEventListener("click", (event) => { | |
let el = event.target; | |
while (el && el !== document) { | |
if (el.matches(triggerSelector)) break; | |
el = el.parentElement; | |
} | |
if (el && el.matches(triggerSelector)) { | |
event.preventDefault(); | |
const postId = parseInt(el.dataset.postId || "1", 10); | |
const { container, html, meta } = extractSelectors(el); | |
wpe_get_by_ajax({ postId, containerSelector: container, htmlSelector: html, metaSelectors: meta }); | |
} | |
}); | |
document.addEventListener("DOMContentLoaded", () => { | |
const triggers = document.querySelectorAll(`${triggerSelector}[data-load-first]`); | |
const processedParents = new Set(); | |
triggers.forEach((trigger) => { | |
const groupSelector = trigger.getAttribute("data-load-first"); | |
if (!groupSelector) return; | |
const parentGroups = document.querySelectorAll(groupSelector); | |
parentGroups.forEach((parent) => { | |
if (processedParents.has(parent)) return; | |
const firstChildTrigger = parent.querySelector(triggerSelector); | |
if (firstChildTrigger) { | |
const postId = parseInt(firstChildTrigger.dataset.postId || "1", 10); | |
const { container, html, meta } = extractSelectors(firstChildTrigger); | |
wpe_get_by_ajax({ postId, containerSelector: container, htmlSelector: html, metaSelectors: meta }); | |
processedParents.add(parent); | |
} | |
}); | |
}); | |
}); | |
})(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class AB_BricksAPI | |
{ | |
const API_NAMESPACE = "wpe_bricks_ajax/v1"; | |
static function init() | |
{ | |
add_action("rest_api_init", [ | |
__CLASS__, | |
"rest_api_init_custom_endpoints", | |
]); | |
} | |
static function rest_api_init_custom_endpoints() | |
{ | |
register_rest_route(self::API_NAMESPACE, "wpe_render_element", [ | |
"methods" => "POST", | |
"callback" => [__CLASS__, "render_content"], | |
"permission_callback" => [ | |
__CLASS__, | |
"render_element_permissions_check", | |
], | |
]); | |
} | |
static function render_content($request) | |
{ | |
$data = $request->get_json_params(); | |
$post_id = absint($data["postId"]); | |
$post = get_post($post_id); | |
if (!$post || $post->post_status !== "publish") { | |
return new \WP_Error( | |
"bricks_api_unpublished", | |
__("Post not found or not published."), | |
["status" => 404] | |
); | |
} | |
$post_type = get_post_type($post_id); | |
$bricks_data = get_post_meta($post_id, BRICKS_DB_PAGE_CONTENT, true); | |
$is_bricks = is_array($bricks_data) && !empty($bricks_data); | |
$render_mode = $is_bricks ? "bricks" : "wordpress"; | |
$post_data = [ | |
"ID" => $post->ID, | |
"title" => get_the_title($post), | |
"slug" => $post->post_name, | |
"excerpt" => get_the_excerpt($post), | |
"date" => get_the_date("", $post), | |
"modified" => get_the_modified_date("", $post), | |
"permalink" => get_permalink($post), | |
"author" => get_the_author_meta("display_name", $post->post_author), | |
]; | |
if (!$is_bricks) { | |
return [ | |
"html" => apply_filters("the_content", $post->post_content), | |
"styles" => "", | |
"render_mode" => $render_mode, | |
"post_type" => $post_type, | |
"post" => $post_data, | |
]; | |
} | |
\Bricks\Assets::$inline_css["content"] = ""; | |
\Bricks\Assets::$css_looping_elements = []; | |
\Bricks\Assets::generate_css_from_elements($bricks_data, "content"); | |
$inline_css = \Bricks\Assets::$inline_css["content"]; | |
$inline_css .= \Bricks\Assets::generate_global_classes(); | |
$inline_css .= \Bricks\Assets::$inline_css["global_classes"]; | |
$css_output = "<style>{$inline_css}</style>"; | |
return [ | |
"html" => \Bricks\Frontend::render_data($bricks_data), | |
"styles" => $css_output, | |
"render_mode" => $render_mode, | |
"post_type" => $post_type, | |
"post" => $post_data, | |
]; | |
} | |
static function render_element_permissions_check($request) | |
{ | |
$data = $request->get_json_params(); | |
if ( | |
empty($data["postId"]) || | |
empty($data["element"]) || | |
empty($data["nonce"]) | |
) { | |
return new \WP_Error( | |
"bricks_api_missing", | |
__("Missing parameters"), | |
["status" => 400] | |
); | |
} | |
return true; | |
} | |
} | |
// Make sure we have Bricks enabled before initialising */ | |
add_action('after_setup_theme', function(){ | |
if(defined('BRICKS_VERSION')){ | |
AB_BricksAPI::init(); | |
} | |
}); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment