Browse Source

Rework catalog sort/filter.

pull/55/head
Ned Zimmerman 7 years ago
parent
commit
3d1344cb72
No known key found for this signature in database
GPG Key ID: FF56334A013120CA
  1. 365
      assets/scripts/routes/catalog.js
  2. 1
      assets/styles/aldine.scss
  3. 106
      assets/styles/layouts/_catalog.scss
  4. 8
      dist/mix-manifest.json
  5. 14673
      dist/scripts/aldine.js
  6. 10356
      dist/scripts/customizer.js
  7. 3114
      dist/styles/aldine.css
  8. 302
      dist/styles/editor.css
  9. 1
      functions.php
  10. 3
      header.php
  11. 13
      inc/filters/namespace.php
  12. 72
      partials/content-page-catalog.php

365
assets/scripts/routes/catalog.js

@ -1,114 +1,285 @@
const jQueryBridget = require( 'jquery-bridget' );
const Isotope = require( 'isotope-layout' );
const jQueryBridget = require('jquery-bridget');
const Isotope = require('isotope-layout');
export default {
init() {
// JavaScript to be fired on the catalog page
jQuery( $ => {
jQueryBridget( 'isotope', Isotope, $ );
let $grid = $( '.books' );
$grid.isotope( {
(function() {
// Get all the <h2> headings
const headings = document.querySelectorAll('fieldset h2');
Array.prototype.forEach.call(headings, heading => {
// Give each <h3> a toggle button child
heading.innerHTML = `
<button type="button" aria-expanded="false">
${heading.textContent}
<svg aria-hidden="true" focusable="false" class="arrow" width="13" height="8" viewBox="0 0 13 8" xmlns="http://www.w3.org/2000/svg"><path d="M6.255 8L0 0h12.51z" fill="currentColor" fill-rule="evenodd"></path></svg>
</button>
`;
// Function to create a node list
// of the content between this <h2> and the next
const getContent = elem => {
let elems = [];
while (
elem.nextElementSibling &&
elem.nextElementSibling.tagName !== 'H2'
) {
elems.push(elem.nextElementSibling);
elem = elem.nextElementSibling;
}
// Delete the old versions of the content nodes
elems.forEach(node => {
node.parentNode.removeChild(node);
});
return elems;
};
// Assign the contents to be expanded/collapsed (array)
let contents = getContent(heading);
// Create a wrapper element for `contents` and hide it
let wrapper = document.createElement('div');
wrapper.hidden = true;
// Add each element of `contents` to `wrapper`
contents.forEach(node => {
wrapper.appendChild(node);
});
// Add the wrapped content back into the DOM
// after the heading
heading.parentNode.insertBefore(wrapper, heading.nextElementSibling);
// Assign the button
let btn = heading.querySelector('button');
btn.onclick = () => {
// Cast the state as a boolean
let expanded = btn.getAttribute('aria-expanded') === 'true' || false;
// Switch the state
btn.setAttribute('aria-expanded', !expanded);
// Switch the content's visibility
wrapper.hidden = expanded;
};
});
})();
(function() {
// Get all the <h3> headings
const headings = document.querySelectorAll('fieldset h3');
Array.prototype.forEach.call(headings, heading => {
// Give each <h3> a toggle button child
heading.innerHTML = `
<button type="button" aria-expanded="false">
${heading.textContent}
<svg class="arrow" width="13" height="8" viewBox="0 0 13 8" xmlns="http://www.w3.org/2000/svg"><path d="M6.255 8L0 0h12.51z" fill="currentColor" fill-rule="evenodd"></path></svg>
</button>
`;
// Function to create a node list
// of the content between this <h2> and the next
const getContent = elem => {
let elems = [];
while (
elem.nextElementSibling &&
elem.nextElementSibling.tagName !== 'H3'
) {
elems.push(elem.nextElementSibling);
elem = elem.nextElementSibling;
}
// Delete the old versions of the content nodes
elems.forEach(node => {
node.parentNode.removeChild(node);
});
return elems;
};
// Assign the contents to be expanded/collapsed (array)
let contents = getContent(heading);
// Create a wrapper element for `contents` and hide it
let wrapper = document.createElement('div');
wrapper.hidden = true;
// Add each element of `contents` to `wrapper`
contents.forEach(node => {
wrapper.appendChild(node);
});
// Add the wrapped content back into the DOM
// after the heading
heading.parentNode.insertBefore(wrapper, heading.nextElementSibling);
// Assign the button
let btn = heading.querySelector('button');
btn.onclick = () => {
// Cast the state as a boolean
let expanded = btn.getAttribute('aria-expanded') === 'true' || false;
// Switch the state
btn.setAttribute('aria-expanded', !expanded);
// Switch the content's visibility
wrapper.hidden = expanded;
};
});
})();
jQuery($ => {
jQueryBridget('isotope', Isotope, $);
let $grid = $('.books');
$grid.isotope({
itemSelector: '.book',
getSortData: {
title: '.title a',
getSortData: {
title: '.book__title a',
subject: '[data-subject]',
latest: '[data-date-published]',
latest: '[data-date-published]',
},
sortAscending: {
title: true,
subject: true,
latest: false,
title: true,
subject: false,
latest: false,
},
} );
$( '.filters > a' ).click( e => {
e.preventDefault();
$( '.filters' ).toggleClass( 'is-active' );
$( '.filter-groups > div' ).removeClass( 'is-active' );
} );
$( '.filter-groups .subjects > a' ).click( e => {
e.preventDefault();
let id = $( e.currentTarget ).attr( 'href' );
$( `.filter-groups .subjects:not(${id})` ).removeClass( 'is-active' );
$( `.filter-groups ${id}` ).toggleClass( 'is-active' );
} );
$( '.licenses > a' ).click( e => {
e.preventDefault();
let id = $( e.currentTarget ).attr( 'href' );
$( id ).toggleClass( 'is-active' );
} );
$( '.subjects .filter-list a' ).click( e => {
e.preventDefault();
if ( $( e.currentTarget ).hasClass( 'is-active' ) ) {
$( '.subjects .filter-list a' ).removeClass( 'is-active' );
$( '.subjects' ).removeClass( 'has-active-child' );
} else {
$( '.subjects .filter-list a' ).removeClass( 'is-active' );
$( e.currentTarget ).addClass( 'is-active' );
$( '.subjects' ).removeClass( 'has-active-child' );
$( e.currentTarget )
.parent()
.parent()
.parent( '.subjects' )
.addClass( 'has-active-child' );
}
let subjectValue = $( '.subjects .filter-list a.is-active' ).attr(
'data-filter'
);
let licenseValue = $( '.licenses .filter-list a.is-active' ).attr(
'data-filter'
);
if ( typeof licenseValue === 'undefined' ) {
licenseValue = '';
} else {
licenseValue = `[data-license="${licenseValue}"]`;
});
let licenses = document.querySelector('.license-filters');
let subjects = document.querySelector('.subject-filters');
let sorts = document.querySelector('.sorts');
licenses.addEventListener('click', function(event) {
if (event.target.type !== 'radio') {
return;
}
if ( typeof subjectValue === 'undefined' ) {
subjectValue = '';
} else {
subjectValue = `[data-subject="${subjectValue}"]`;
let license = '';
let subject = '';
if (subjects.querySelector('input[type="radio"]:checked').value) {
subject = `[data-subject="${
subjects.querySelector('input[type="radio"]:checked').value
}"]`;
}
$grid.isotope( { filter: `${subjectValue}${licenseValue}` } );
} );
$( '.licenses .filter-list a' ).click( e => {
e.preventDefault();
if ( $( e.currentTarget ).hasClass( 'is-active' ) ) {
$( '.licenses .filter-list a' ).removeClass( 'is-active' );
$( '.licenses' ).removeClass( 'has-active-child' );
} else {
$( '.licenses .filter-list a' ).removeClass( 'is-active' );
$( e.currentTarget ).addClass( 'is-active' );
$( '.licenses' ).addClass( 'has-active-child' );
license = `[data-license="${event.target.value}"]`;
alert(`[data-license="${event.target.value}"]${subject}`);
$grid.isotope({
filter: `[data-license="${event.target.value}"]${subject}`,
});
});
subjects.addEventListener('click', function(event) {
if (event.target.type !== 'radio') {
return;
}
let subjectValue = $( '.subjects .filter-list a.is-active' ).attr(
'data-filter'
);
let licenseValue = $( '.licenses .filter-list a.is-active' ).attr(
'data-filter'
);
if ( typeof licenseValue === 'undefined' ) {
licenseValue = '';
} else {
licenseValue = `[data-license="${licenseValue}"]`;
let license = '';
let subject = '';
if (licenses.querySelector('input[type="radio"]:checked').value) {
license = `[data-license="${
licenses.querySelector('input[type="radio"]:checked').value
}"]`;
}
if ( typeof subjectValue === 'undefined' ) {
subjectValue = '';
} else {
subjectValue = `[data-subject="${subjectValue}"]`;
subject = `[data-subject="${event.target.value}"]`;
alert(`${license}[data-subject="${event.target.value}"]`);
$grid.isotope({
filter: `${license}${subject}`,
});
});
sorts.addEventListener('click', function(event) {
if (event.target.type !== 'radio') {
return;
}
$grid.isotope( { filter: `${subjectValue}${licenseValue}` } );
} );
$( '.sort > a' ).click( e => {
e.preventDefault();
$( '.sort' ).toggleClass( 'is-active' );
} );
$( '.sorts a' ).click( e => {
e.preventDefault();
let sortBy = $( e.currentTarget ).attr( 'data-sort' );
$( '.sorts a' ).removeClass( 'is-active' );
$( e.currentTarget ).addClass( 'is-active' );
$grid.isotope( { sortBy: sortBy } );
} );
} );
$grid.isotope({ sortBy: event.target.value });
});
// $('.filters > a').click(e => {
// e.preventDefault();
// $('.filters').toggleClass('is-active');
// $('.filter-groups > div').removeClass('is-active');
// });
// $('.filter-groups .subjects > a').click(e => {
// e.preventDefault();
// let id = $(e.currentTarget).attr('href');
// $(`.filter-groups .subjects:not(${id})`).removeClass('is-active');
// $(`.filter-groups ${id}`).toggleClass('is-active');
// });
// $('.licenses > a').click(e => {
// e.preventDefault();
// let id = $(e.currentTarget).attr('href');
// $(id).toggleClass('is-active');
// });
// $('.subjects .filter-list a').click(e => {
// e.preventDefault();
// if ($(e.currentTarget).hasClass('is-active')) {
// $('.subjects .filter-list a').removeClass('is-active');
// $('.subjects').removeClass('has-active-child');
// } else {
// $('.subjects .filter-list a').removeClass('is-active');
// $(e.currentTarget).addClass('is-active');
// $('.subjects').removeClass('has-active-child');
// $(e.currentTarget)
// .parent()
// .parent()
// .parent('.subjects')
// .addClass('has-active-child');
// }
// let subjectValue = $('.subjects .filter-list a.is-active').attr(
// 'data-filter'
// );
// let licenseValue = $('.licenses .filter-list a.is-active').attr(
// 'data-filter'
// );
// if (typeof licenseValue === 'undefined') {
// licenseValue = '';
// } else {
// licenseValue = `[data-license="${licenseValue}"]`;
// }
// if (typeof subjectValue === 'undefined') {
// subjectValue = '';
// } else {
// subjectValue = `[data-subject="${subjectValue}"]`;
// }
// $grid.isotope({ filter: `${subjectValue}${licenseValue}` });
// });
// $('.licenses .filter-list a').click(e => {
// e.preventDefault();
// if ($(e.currentTarget).hasClass('is-active')) {
// $('.licenses .filter-list a').removeClass('is-active');
// $('.licenses').removeClass('has-active-child');
// } else {
// $('.licenses .filter-list a').removeClass('is-active');
// $(e.currentTarget).addClass('is-active');
// $('.licenses').addClass('has-active-child');
// }
// let subjectValue = $('.subjects .filter-list a.is-active').attr(
// 'data-filter'
// );
// let licenseValue = $('.licenses .filter-list a.is-active').attr(
// 'data-filter'
// );
// if (typeof licenseValue === 'undefined') {
// licenseValue = '';
// } else {
// licenseValue = `[data-license="${licenseValue}"]`;
// }
// if (typeof subjectValue === 'undefined') {
// subjectValue = '';
// } else {
// subjectValue = `[data-subject="${subjectValue}"]`;
// }
// $grid.isotope({ filter: `${subjectValue}${licenseValue}` });
// });
// $('.sort > a').click(e => {
// e.preventDefault();
// $('.sort').toggleClass('is-active');
// });
// $('.sorts a').click(e => {
// e.preventDefault();
// let sortBy = $(e.currentTarget).attr('data-sort');
// $('.sorts a').removeClass('is-active');
// $(e.currentTarget).addClass('is-active');
// $grid.isotope({ sortBy: sortBy });
// });
});
},
finalize() {},
};

1
assets/styles/aldine.scss

@ -15,3 +15,4 @@
@import "layouts/header";
@import "layouts/front-page";
@import "layouts/pages";
@import "layouts/catalog";

106
assets/styles/layouts/_catalog.scss

@ -0,0 +1,106 @@
fieldset {
border-top: solid 2px var(--accent);
font-family: $font-family-sans-serif;
h2,
h3 {
margin-bottom: 0;
font-size: 1rem;
font-weight: bold;
text-align: left;
text-transform: none;
&:before {
display: none;
}
button {
display: flex;
flex-direction: row;
justify-content: space-between;
all: inherit;
width: 100%;
padding: 1rem 1.1875rem;
margin: 0;
border-top: 0;
svg {
display: block;
float: right;
margin-top: 0.5rem;
}
&:hover,
&:focus {
color: var(--brand);
background: var(--bg-body);
}
&:active {
box-shadow: none;
}
&[aria-expanded="true"] {
color: var(--brand);
svg {
transform: rotate(180deg);
}
}
}
}
h2 {
button[aria-expanded="true"] {
border-bottom: solid 2px var(--accent);
background: #fafdff;
}
}
[type="radio"] {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
border: 0 !important;
overflow: hidden !important;
clip: rect(1px, 1px, 1px, 1px);
}
[type="radio"] + label {
cursor: pointer;
display: block;
padding: 1rem 1.1875rem;
margin-bottom: 0;
svg {
display: none;
}
}
[type="radio"]:focus {
label {
cursor: pointer;
display: block;
}
}
[type="radio"]:checked + label {
color: var(--brand);
font-weight: bold;
svg {
display: block;
float: right;
margin-top: 0.5rem;
width: 1rem;
height: 1rem;
fill: transparent;
}
}
&:last-of-type {
border-bottom: solid 2px var(--accent);
margin-bottom: 1rem;
}
}

8
dist/mix-manifest.json vendored

@ -1,6 +1,6 @@
{
"/scripts/aldine.js": "/scripts/aldine.js?id=c8641d03dfb38dc75e0d",
"/styles/aldine.css": "/styles/aldine.css?id=7fca925f82ab0be23d54",
"/styles/editor.css": "/styles/editor.css?id=10e3f4b144847aa8d75e",
"/scripts/customizer.js": "/scripts/customizer.js?id=f1f1f4225cba4c1b35f2"
"/scripts/aldine.js": "/scripts/aldine.js",
"/styles/aldine.css": "/styles/aldine.css",
"/styles/editor.css": "/styles/editor.css",
"/scripts/customizer.js": "/scripts/customizer.js"
}

14673
dist/scripts/aldine.js vendored

File diff suppressed because one or more lines are too long

10356
dist/scripts/customizer.js vendored

File diff suppressed because one or more lines are too long

3114
dist/styles/aldine.css vendored

File diff suppressed because one or more lines are too long

302
dist/styles/editor.css vendored

File diff suppressed because one or more lines are too long

1
functions.php

@ -48,6 +48,7 @@ add_filter( 'mce_buttons', '\\Aldine\\Filters\\add_style_select' );
add_filter( 'tiny_mce_before_init', '\\Aldine\\Filters\\add_blocks' );
add_filter( 'body_class', '\\Aldine\\Filters\\body_classes' );
add_filter( 'excerpt_more', '\\Aldine\\Filters\\excerpt_more' );
add_filter( 'query_vars', '\\Aldine\\Filters\\register_query_vars' );
add_filter( 'wp_nav_menu_items', '\\Aldine\\Filters\\adjust_menu', 10, 2 );
add_action( 'widgets_init', '\\Aldine\\Actions\\widgets_init' );
add_action( 'wp_enqueue_scripts', '\\Aldine\\Actions\\enqueue_assets' );

3
header.php

@ -35,6 +35,9 @@
<symbol id="twitter" fill="currentColor" viewbox="0 0 512 512"><path d="M161 433c193 0 299-161 299-300v-14c20-15 38-34 52-55-19 9-40 14-60 17 22-13 38-34 46-59-21 13-43 21-67 26-32-35-84-44-126-21s-64 71-53 117c-84-4-163-44-216-110C8 82 22 144 68 175c-17 0-33-5-48-13v1c0 50 36 94 85 104-16 4-32 5-48 2 14 43 54 72 98 73-37 30-83 46-130 45-8 0-17 0-25-1 48 31 104 47 161 47"/></symbol>
<symbol id="arrow-right" fill="currentColor" viewBox="0 0 512 512"><path d="M291 32c0 6 3 12 7 17l133 135H23c-13 0-23 11-23 24s10 24 23 24h408L298 367c-4 4-7 10-7 16s3 12 7 17c10 9 24 9 33 0l173-176c9-9 9-23 0-33L331 15c-9-10-23-10-33 0-4 4-7 10-7 17"/></symbol>
<symbol id="arrow-left" fill="currentColor" viewBox="0 0 512 512"><path d="M220 45c0 6-3 12-7 17L79 200h410c12 0 23 11 23 25 0 13-11 24-23 24H79l134 138c4 5 7 11 7 17 0 7-3 13-7 18-10 9-24 9-33 0L7 242c-9-10-9-25 0-34L180 26c9-9 23-9 33 0 4 5 7 11 7 19"/></symbol>
<symbol id="checkmark" viewBox="0 0 20 16">
<polyline stroke="currentColor" stroke-width="3" transform="translate(10.063477, 4.717773) rotate(-45.000000) translate(-10.063477, -4.717773) " points="2.06347656 0.717773438 2.06347656 8.71777344 18.0634766 8.71777344"></polyline>
</symbol>
</defs>
</svg>
<div id="page" class="site">

13
inc/filters/namespace.php

@ -38,6 +38,19 @@ function body_classes( array $classes ) {
return array_filter( $classes );
}
/**
* Add custom query vars for catalog.
*
* @param array $vars The array of available query variables.
*
* @link https://codex.wordpress.org/Plugin_API/Filter_Reference/query_vars
*/
function register_query_vars( $vars ) {
$vars[] = 'license';
$vars[] = 'subject';
return $vars;
}
/**
* Customize excerpt.
*

72
partials/content-page-catalog.php

@ -28,44 +28,48 @@ $subject_groups = ( defined( 'PB_PLUGIN_VERSION' ) ) ? \Pressbooks\Metadata\get_
<?php get_template_part( 'partials/page', 'header' ); ?>
<section class="network-catalog">
<div class="controls">
<div class="search">
<h2><a href="#search"><?php _e( 'Search by titles or keyword', 'pressbooks-aldine' ); ?> <svg class="arrow" width="13" height="8" viewBox="0 0 13 8" xmlns="http://www.w3.org/2000/svg"><path d="M6.255 8L0 0h12.51z" fill="#b01109" fill-rule="evenodd"/></svg></a></h2>
</div>
<div class="filters">
<a href="#filter"><?php _e( 'Filter by', 'pressbooks-aldine' ); ?> <svg class="arrow" width="13" height="8" viewBox="0 0 13 8" xmlns="http://www.w3.org/2000/svg"><path d="M6.255 8L0 0h12.51z" fill="#b01109" fill-rule="evenodd"/></svg></a>
<div id="filter" class="filter-groups">
<?php foreach ( $subject_groups as $key => $val ) : ?>
<div class="<?php echo $key; ?> subjects" id="<?php echo $key; ?>">
<a href="#<?php echo $key; ?>"><?php echo $val['label']; ?> <svg class="arrow" width="13" height="8" viewBox="0 0 13 8" xmlns="http://www.w3.org/2000/svg"><path d="M6.255 8L0 0h12.51z" fill="#b01109" fill-rule="evenodd"/></svg></a>
<ul class="filter-list">
<form role="form" class="filter-sort" method="get">
<input type="hidden" name="paged" value="<?php echo $current_page; ?>" />
<fieldset class="license-filters">
<h2><?php _e( 'Filter by License', 'pressbooks-aldine' ); ?></h2>
<input type="radio" name="license" id="all-licenses" value="" <?php checked( $license, '' ); ?>>
<label for="all-licenses"><?php _e( 'All Licenses', 'pressbooks-aldine' ); ?> <svg class="checked"><use xlink:href="#checkmark" /></svg></label>
<?php foreach ( $licenses as $key => $value ) : ?>
<input type="radio" name="license" id="<?php echo $key; ?>" value="<?php echo $key; ?>" <?php checked( $license, $key ); ?>>
<label for="<?php echo $key; ?>"><?php echo $value; ?> <svg class="checked"><use xlink:href="#checkmark" /></svg></label>
<?php endforeach; ?>
</fieldset>
<fieldset class="subject-filters">
<h2><?php _e( 'Filter by Subject', 'pressbooks-aldine' ); ?></h2>
<input type="radio" name="subject" id="all-subjects" value="" <?php checked( $subject, '' ); ?>>
<label for="all-subjects"><?php _e( 'All Subjects', 'pressbooks-aldine' ); ?> <svg class="checked"><use xlink:href="#checkmark" /></svg></label>
<div class="subject-groups">
<?php foreach ( $subject_groups as $key => $val ) : ?>
<h3><?php echo $val['label']; ?></h3>
<?php foreach ( $val['children'] as $k => $v ) :
if ( strlen( $k ) === 2 ) : ?>
<li><a data-filter="{{ $k }}"><?php echo $v; ?><span class="close">&times;</span></a></li>
<input type="radio" name="subject" id="<?php echo $k; ?>" value="<?php echo $k; ?>" <?php checked( $subject, $k ); ?>>
<label for="<?php echo $k; ?>"><?php echo $v; ?> <svg class="checked"><use xlink:href="#checkmark" /></svg></label>
<?php endif; ?>
<?php endforeach; ?>
</ul>
</div>
<?php endforeach; ?>
</div>
<div class="licenses" id="licenses">
<a href="#licenses"><?php _e( 'Licenses', 'pressbooks-aldine' ); ?><svg class="arrow" width="13" height="8" viewBox="0 0 13 8" xmlns="http://www.w3.org/2000/svg"><path d="M6.255 8L0 0h12.51z" fill="#b01109" fill-rule="evenodd"/></svg></a>
<ul class="filter-list">
<?php foreach ( $licenses as $key => $value ) : ?>
<li><a data-filter="<?php echo $key; ?>"><?php echo $value; ?><span class="close">&times;</span></a></li>
<?php endforeach; ?>
</ul>
</div>
</div>
<div class="sort">
<a href="#sort"><?php _e( 'Sort by', 'pressbooks-aldine' ); ?> <svg class="arrow" width="13" height="8" viewBox="0 0 13 8" xmlns="http://www.w3.org/2000/svg"><path d="M6.255 8L0 0h12.51z" fill="#b01109" fill-rule="evenodd"/></svg></a>
<ul id="sort" class="sorts">
<li><a data-sort="title" href="<?php echo "/catalog/page/$current_page/?orderby=title"; ?>"><?php _e( 'Title', 'pressbooks-aldine' ); ?></a></li>
<li><a data-sort="subject" href="<?php echo "/catalog/page/$current_page/?orderby=subject"; ?>"><?php _e( 'Subject', 'pressbooks-aldine' ); ?></a></li>
<li><a data-sort="latest" href="<?php echo "/catalog/page/$current_page/?orderby=latest"; ?>"><?php _e( 'Latest', 'pressbooks-aldine' ); ?></a></li>
</ul>
</div>
</div>
<?php endforeach; ?>
</div>
</fieldset>
<fieldset class="sorts">
<h2><?php _e( 'Sort by', 'pressbooks-aldine' ); ?></h2>
<?php
$sorts = [
'title' => __( 'Title', 'pressbooks-aldine' ),
'subject' => __( 'Subject', 'pressbooks-aldine' ),
'latest' => __( 'Latest', 'pressbooks-aldine' ),
];
foreach ( $sorts as $key => $value ) { ?>
<input type="radio" name="orderby" id="<?php echo $key ?>" value="<?php echo $key ?>" <?php checked( $orderby, $key ); ?>>
<label for="<?php echo $key ?>"><?php echo $value; ?> <svg class="checked"><use xlink:href="#checkmark" /></svg></label>
<?php } ?>
</fieldset>
<button type="submit"><?php _e( 'Submit', 'pressbooks-aldine' ); ?></button>
</form>
<ul class="books">
<?php foreach ( $catalog_data['books'] as $book ) :
include( locate_template( 'partials/book.php' ) );

Loading…
Cancel
Save