WordPress and ACF as an HTML generator for Canvas Catalog

I’ve written before about trying to figure out some decent ways to customize Canvas Catalog. It’s not a straightforward path. I put together a mix of HTML and CSS on top of the basic bootstrap stuff that Catalog comes with. Unfortunately, that meant I was a choke point in creating new course listings. While we’re not cranking out tons of course, I didn’t like it.

I decided I’d build an ACF pattern in WordPress that would then let you just copy and paste the HTML into Catalog. Then minor changes could be made in Catalog and, in a worse case scenario, you could go edit the WP post and repaste it into Catalog.

You can see how it works in the video above.

To make this, I looked at an example course listing in view source and just roughed out all the HTML and text that would be consistent as a template literal in javascript. Then I broke down the variable elements as ACF fields. Nothing fancy here, mainly text boxes, an image, a video URL, and a couple repeater fields for lists of learning objectives and stuff like that.

//a long template literal which probably makes some people sad but I don't mind it
destination.innerHTML = `
  			${exciteHTML}
  			<div id="c-holder">
  				<div id="c-left">
  				<h2>Course Overview</h2>
  					<p>${courseOverview}</p>
  					<ul>
  						${courseFeatureHMTL}
  					</ul>
  				<h2>This course is for you if . . . </h2>
  						${forYouHTML}
  				</div><!--end left-->
  				<div id="c-right">
  					<div class="video-responsive">
  						<iframe style="border: 1px solid #464646;" src="${videoURL}&autoplay=false&offerviewer=false&showtitle=false&showbrand=true&captions=false&interactivity=none" width="720" length="405"></iframe>
  					</div>
  						<div class="outcomes">
  							<h2>What you will learn</h2>
  							${outcomeHTML}
  						</div>
  					</div>
  				</div><!--end right-->
  				<div id="bio-block" class="full bio">
  					${bioImgHTML}
  					<h2>About the instructor</h2>
  					${bio}
  				</div><!--end bio-->
  				<div id="content-block" class="full">
  					<h2>Contact us</h2>
  					${contact}
  				</div><!--end contact block-->
  				<div id="mailing-bock" class="full">
					<h2>Join our mailing list</h2>
					<p>Not interested in this course, but want to be notified about future options?</p>
					<a id="mailing" href="#">Sign up!</a>
					<div id="myModal" class="modal">
						<div class="modal-content"><span id="closeModal" class="close">×</span>
						<div id="m-form"><iframe id="mailing-div" src="https://learn.middlebury.edu/l/74172/2023-01-30/2bxzz54" width="100%" height="850px"></iframe></div>
						</div>
					</div>
				</div>
  				<!--end contact us-->
  				<p><a id="faq-button" href="#" data-faqid="${faqID}">Questions?</a></p>
					<div id="faq-block">
						<div id="faq-holder" class="modal">
						<div id="faq-modal" class="modal-content"></div>
				</div>
  			</div>


  `;

ACF lets you create custom post types now. So I made one called course listings. I can also make all the ACF data visible in the WP JSON API. So I did that as well. I did have one issue here. Initially, even though I set the image field to return the image URL, only the image ID was showing in the JSON. Now I could do another fetch, but I didn’t want to. There was also some hassle with the rich text fields returning text that was formatted for javascript rather than as HTML. I could deal with that by writing a function like the one below (which I did), but the whole thing irritated me into actually searching for an answer.

function makeParagraphs(data){
	let paragraphHTML = '';
	const paragraphs = data.split('\r\n\r\n');
	paragraphs.forEach((paragraph) => {
		paragraphHTML += `<p>${paragraph}</p>`
	})
	return paragraphHTML;
}

Turns out ACF has a little parameter you can add to the JSON endpoint to get ACF formatted data. I don’t know how new this is. I think I used to have to write custom PHP functions to get this previously or I could have been doing a lot of work for no reasons. Either way, using acf_format=standard got me exactly what I wanted and I could ditch the extra fetch and my little paragraph maker functions. You can see the difference in the data returned by these two URLs.

https://experiments.middcreate.net/faqs/wp-json/wp/v2/course-listing?id=60
https://experiments.middcreate.net/faqs/wp-json/wp/v2/course-listing?id=60&acf_format=standard

Now life was easy and I just built a little HTML page with enough of the CSS to get a good representation for what the course listing would look like.

Rather than making a whole theme for this, I just made a tiny plugin that filters the content of just the Course Listing custom post type to insert an iframe. It uses the post ID in the URL to load the correct data.

/* Add iframe just to course listings */
function dlinq_cc_add_iframe ( $content ) {
   global $post;
   $post_id = $post->ID;
    if ( is_singular('course-listing') ) {
        return $content . '<iframe width="100%" height="2000px" src="https://experiments.middcreate.net/extras/catalog/?id='.$post_id.'"></iframe>';
    }

    return $content;
}
add_filter( 'the_content', 'dlinq_cc_add_iframe');

Then we’ve got the copy button which is similar to the copy table HTML button I used in the Detox Bias tool. It grabs just the block of HTML we need and avoids all the other stuff. I also had to write a little cleaner to keep from getting the FAQ HTML which is pulled in automatically by some other javascript on the Catalog site. Eventually, I’ll make a better confirmation pattern and get rid of alert, but it works for now.

/*HTML COPY*/
const copyButton = document.querySelector('#html-copy');
copyButton.addEventListener("click", copyAllHTML);

function copyAllHTML(){
	const content = document.querySelector('#holder');
	document.querySelector('#faq-modal').innerHTML = '';//erase FAQ HTML . . . so it can come via JSON
	// Create a temporary element to hold the HTML
	  const tempElement = document.createElement("textarea");
	  tempElement.value = content.outerHTML;

	  // Append the element to the body (hidden)
	  document.body.appendChild(tempElement);

	  // Select the content
	  tempElement.select();

	  // Copy the content
	  document.execCommand("copy");

	  // Remove the temporary element
	  document.body.removeChild(tempElement);

	alert('copied it')
}

Now you can go to Canvas Catalog, enter HTML view and paste this in.

I end up with mixed feelings about stuff like this. It is a shadow tool that allows for complexity in an enterprise tool. Extra stuff to maintain, extra stuff for people to know. If I leave and it breaks, likely it will stay broken. Maybe more likely, if a few people leave, it will be forgotten and someone will build something else from scratch. With some effort, I could probably make something more integrated into Canvas Catalog. That will make for better integration but a higher technical debt. I’m not sure we’ll keep using Catalog for long enough to justify that. Maybe we need to reconsider our course description patterns and make it something that does fit better into Catalog. No right answers and I don’t know if those kinds of issues can be fixed.

In the meantime, I try to enjoy making something that works and that uses a couple tricks I learned in other places. That feels economical in certain ways. I incrementally improve my programming skills and experience. Maybe I make someone else happy because they can do what they want without waiting for me.

Leave a Reply