Module:TextPageTabs
From Bodhicitta
Documentation for this module may be created at Module:TextPageTabs/doc
local p = {}
function p.main(frame)
local pArgs = frame:getParent().args
local qPage = pArgs.page
local subPage = pArgs.queryPagename
-- Build first tab content with intro and featured vid (loads immediately)
local firstTab = 'Introduction = <div class="row mx-0">'
if pArgs.FeaturedYoutubeID then
firstTab = firstTab .. '<div class="col-md-8 pl-md-0">' .. pArgs.introduction .. '</div>'
local featureVid = '<div class="col-md-4 pr-md-0">' .. frame:expandTemplate{
title = 'YouTube',
args = {
id = pArgs.FeaturedYoutubeID,
caption = pArgs.FeaturedVidCaption
}
} .. '</div>'
firstTab = firstTab .. featureVid
else
firstTab = firstTab .. '<div class="col-12">' .. pArgs.introduction .. '</div>'
end
firstTab = firstTab .. '</div>'
-- Build tab content, starting with first tab
local tabContent = { firstTab }
-- Create placeholder tabs with loading indicators
-- Each tab gets a unique ID and data attributes for AJAX loading
local tabs = {
{name = 'Recensions', id = 'recensions'},
{name = 'Translations', id = 'translations'},
{name = 'Commentaries', id = 'commentaries'},
{name = 'Teachings', id = 'teachings'},
{name = 'Media', id = 'media'},
{name = 'Related scholarship', id = 'scholarship'},
{name = 'Other content', id = 'other'}
}
for _, tab in ipairs(tabs) do
local tabHtml = tab.name .. ' = <div class="ajax-tab-content" ' ..
'data-tab-type="' .. tab.id .. '" ' ..
'data-page="' .. mw.text.encode(qPage) .. '" ' ..
'data-subpage="' .. mw.text.encode(subPage) .. '" ' ..
'data-loaded="false">' ..
'<div class="text-center p-4">' ..
'<div class="spinner-border text-primary" role="status">' ..
'<span class="sr-only">Loading...</span>' ..
'</div>' ..
'<div class="mt-2">Loading ' .. tab.name:lower() .. '...</div>' ..
'</div></div>'
table.insert(tabContent, tabHtml)
end
-- Check for extra tabs added manually
local extraTabs = ''
if pArgs.ExtraTabs then
extraTabs = frame:preprocess(pArgs.ExtraTabs)
end
-- Add the JavaScript for lazy loading
local script = [=[
<script>
(function() {
// Function to load tab content
function loadTabContent(tabElement) {
var $tab = $(tabElement);
if ($tab.data('loaded') === 'true' || $tab.data('loading') === 'true') {
return;
}
$tab.data('loading', 'true');
var tabType = $tab.data('tab-type');
var page = $tab.data('page');
var subpage = $tab.data('subpage');
// Make API call to load the tab content
$.ajax({
url: mw.util.wikiScript('api'),
data: {
action: 'parse',
format: 'json',
text: '{{#invoke:TextPageTabsAjax|' + tabType + '|page=' + page + '|subpage=' + subpage + '}}',
contentmodel: 'wikitext',
disablelimitreport: true,
wrapoutputclass: ''
},
success: function(data) {
if (data.parse && data.parse.text) {
$tab.html(data.parse.text['*']);
$tab.data('loaded', 'true');
} else {
$tab.html('<div class="alert alert-warning">Failed to load content.</div>');
}
},
error: function() {
$tab.html('<div class="alert alert-danger">Error loading content. Please refresh the page.</div>');
},
complete: function() {
$tab.data('loading', 'false');
}
});
}
// Load first visible tab immediately after page load
$(document).ready(function() {
setTimeout(function() {
// Find the first non-introduction tab that's visible
$('.tabber__panel:visible .ajax-tab-content').first().each(function() {
loadTabContent(this);
});
}, 100);
});
// Load tabs when clicked
$(document).on('click', '.tabber__tab', function() {
setTimeout(function() {
$('.tabber__panel:visible .ajax-tab-content').each(function() {
loadTabContent(this);
});
}, 50);
});
// Also handle when tabs become visible through other means
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
$(mutation.target).find('.ajax-tab-content').each(function() {
if ($(this).is(':visible') && $(this).data('loaded') !== 'true') {
loadTabContent(this);
}
});
}
});
});
// Observe all tabber panels for visibility changes
$('.tabber__panel').each(function() {
observer.observe(this, { attributes: true });
});
})();
</script>]=]
-- Add CSS for spinner (if not already in your site CSS)
local styles = [=[
<style>
.spinner-border {
display: inline-block;
width: 2rem;
height: 2rem;
vertical-align: text-bottom;
border: .25em solid currentColor;
border-right-color: transparent;
border-radius: 50%;
animation: spinner-border .75s linear infinite;
}
@keyframes spinner-border {
to { transform: rotate(360deg); }
}
</style>]=]
-- Return tabber with scripts
if #tabContent > 0 or extraTabs then
return frame:preprocess('{{#tag:tabber|' .. table.concat(tabContent, '{{!}}-{{!}}') .. '}}') ..
extraTabs .. script .. styles
else
return 'No related content found.'
end
end
return p