Recent Work – End of June Edition

Greetings dear reader. I write to you again out of the desperate hope that writing this cements things I’ve learned better in my own head and maybe gives a person out there something that saves them some time or suffering or maybe just provides a kernel of an idea that they can improve on. That’s pretty much the blogging philosophy that’s kept me erratically writing for the last 130 years.1 It’s strange that I have to look back at YouTube, my email, my calendar, and the other posts I’ve written to figure out what I’ve done over the last couple of weeks. It all blurs and fades so fast.2

In any case, on with the show . . .

FotoFika Continues

The goal here was to let very busy reviewers review individual student artist cards. We already had the reviewer names. We were assigning certain people certain students. Why not make that all a direct link? It turns out you can use the normal Google iframe embed code and the pre-populate form fields together. You end up with something like this example. I made a little js button to switch things.

See the Pen
google form pre-filled iframe
by Tom (@twwoodward)
on CodePen.

For our needs though we’re going to build a page anyway, might as well populate it with URL parameters. We’ll build our URL in Google Sheets because that’s where our data is.
=””&X44&”&reviewer=”&B44&” “&C44&”&title=”&E44&”&affiliation=”&F44
That grabs our variables into a url string. We can then turn that string into a functional link with something like =iferror(HYPERLINK(Y44), “Not ready yet”) this and it even says “not ready yet” if the conditions haven’t been met. Could that have all been done in one giant function? Sure but I can always hide a column and I like to take things in steps because I’m simple minded.

Now to consume our URL, something like this was my original plan but I should have done it with a ternary operator. Both work, the ternary is just cleaner if you understand the pattern.3

 const urlParams = new URLSearchParams(queryString);     
 if (urlParams.get('reviewer')){
          var reviewer = urlParams.get('reviewer')
        } else {
          var reviewer = ''
         if (urlParams.get('title')){
          var title = urlParams.get('title')
        }else {
          var title = ''
        if (urlParams.get('affiliation')){
          var affiliation = urlParams.get('affiliation')
        }else {
          var affiliation = ''

So now I can use those variables to fill in the form fields.

function makeForm(element, reviewer, title, affiliation){
  let name = element.gsx$yournameasyouwouldlikeittoappear.$t;
  let reviewerFilled = (reviewer != '') ? '&entry.347538168='+reviewer : ''
  let titleFilled =  (title != '') ? '&entry.232749916='+title : ''
  let affiliationFilled = (affiliation != '') ? '&entry.938826356='+affiliation : ''
  let form = document.querySelector('iframe');
  form.src = ''+name+reviewerFilled+titleFilled+affiliationFilled;

Given those are the “interesting” bits I’m sure you want to see a video of it in action!

Early Engineers

This is for a k12/School of Engineering partnership and I’m actually pretty (bordering on really) happy with the way it’s coming together. Granted, I have participated in way more than my share of curriculum resource sharing sites in my life so it’s not like I haven’t thought about this before, built this before, lamented on this before. It’s the same old, same old. How can we get the maximum information teachers will want into the minimum space? How will we provide the right balance of friendly visuals while still getting down to business?

It has happened pretty fast given all the other stuff going on. We had a working prototype up 3 hours after I told them I couldn’t do it until the second week in July. I’m not sure what that says about anything but it’s what happened. Underpromise and over deliver? Always be closing? Anyway, I’ve been banging away at it when I had boring zoom meetings or when I needed break from other things and it’s grown pretty nicely.

Early Engineers website screenshot.
The lesson plan page has a bunch of ACF fields to help structure the writing and a number of taxonomic structures to help people find the content. It auto-embeds a google folder for resource sharing etc. etc.

I went the FacetWP route for the search. As such I made a bunch of custom taxonomies because it made that easier. Here’s one. The part I want to remember is that removing a custom taxonomy from the sidebar in the editor is done in the $args via ‘meta_box_cb’ => false. That’s nice when you have it as ACF taxonomy field and don’t want people getting confused about where to click.

// Register Custom Taxonomy
function ee_science_tags() {

	$labels = array(
		'name'                       => _x( 'Science Themes', 'Taxonomy General Name', 'text_domain' ),
		'singular_name'              => _x( 'Science Theme', 'Taxonomy Singular Name', 'text_domain' ),
		'menu_name'                  => __( 'Science Themes', 'text_domain' ),
		'all_items'                  => __( 'All Items', 'text_domain' ),
		'parent_item'                => __( 'Parent Item', 'text_domain' ),
		'parent_item_colon'          => __( 'Parent Item:', 'text_domain' ),
		'new_item_name'              => __( 'New Item Name', 'text_domain' ),
		'add_new_item'               => __( 'Add New Item', 'text_domain' ),
		'edit_item'                  => __( 'Edit Item', 'text_domain' ),
		'update_item'                => __( 'Update Item', 'text_domain' ),
		'view_item'                  => __( 'View Item', 'text_domain' ),
		'separate_items_with_commas' => __( 'Separate items with commas', 'text_domain' ),
		'add_or_remove_items'        => __( 'Add or remove items', 'text_domain' ),
		'choose_from_most_used'      => __( 'Choose from the most used', 'text_domain' ),
		'popular_items'              => __( 'Popular Items', 'text_domain' ),
		'search_items'               => __( 'Search Items', 'text_domain' ),
		'not_found'                  => __( 'Not Found', 'text_domain' ),
		'no_terms'                   => __( 'No items', 'text_domain' ),
		'items_list'                 => __( 'Items list', 'text_domain' ),
		'items_list_navigation'      => __( 'Items list navigation', 'text_domain' ),
	$args = array(
		'labels'                     => $labels,
		'hierarchical'               => true,
		'public'                     => true,
		'show_ui'                    => true,
		'show_admin_column'          => true,
		'show_in_nav_menus'          => true,
		'show_tagcloud'              => true,
		'meta_box_cb'                => false,
	register_taxonomy( 'science_tags', array( 'post', 'lesson' ), $args );

add_action( 'init', 'ee_science_tags', 0 );

The search page is going to need some visual cleanup but functionally, I like how it’s working.
Facet WP search page for early engineering site.

I had a couple of ACF repeater fields for time and resource elements. I thought it’d be nice to show and search by the total time and total resource items needed. That led to doing these update functions. Simple but handy. Now I have custom fields that will update on post update and they’re readily available for FacetWP use.

function total_time($id){   
    $total_time = 0;
    if (have_rows('time_needed',$id)){
        $needs = get_field('time_needed');
       foreach ($needs as $need ) {
            $time = intval($need["time"]);  
            $total_time = $total_time + $time;
         return $total_time;

function ee_update_total_time( $post_id ) {
    $total = total_time($post_id);
    update_post_meta( $post_id, 'total_time_count', $total );

add_action( 'save_post', 'ee_update_total_time' );

function ee_update_total_resources( $post_id ) {
    $total = count(get_field('materials', $post_id));
    update_post_meta( $post_id, 'total_resource_count', $total );

add_action( 'save_post', 'ee_update_total_resources' );

Multi-Camera View Switcher

We’re working with Craft and Materials on options if their courses have to go fully or partially online again. It’s everything from woodworking to glass blowing. Some of them we can do directly, some will really require some creative thinking. One of the things we are pushing is a frontloading of lab/space/tool work. Get people in the spaces while you can.

One aspect of that is trying to create videos that do a better job getting students ready to work. When you think about how you observe a process when it’s live, your eyes are shifting focus. Big picture view. Peering in close for details. Up on your toes to get a better view downward. Watching it again later you might need to shift those patterns to get stuff you missed because of your focus. Repeated watching helps build the larger understanding and the student being in control of when they shift those views is important and hard to replicate in a single stream video. The editor is in control there. We want the student in control. We want them able to move their view and rewatch portions from different angles based on what they need to see.

This proof of concept attempts to address that. We took 4 synchronous shots that Molly already had lying around to build this quick proof of concept demo. Imagine it’s an overhead shot of a potter’s wheel, a side shot, a front shot, and a close up on the hands.4

See this video demo of the demo video.

This ties all the controls together so play/pause effects all the videos at once.

let play = document.getElementById('play')

let pause = document.getElementById('pause')

function playVideo(){
  var videos = document.querySelectorAll('video');
  videos.forEach(function(v) {

function pauseVideo(){
  var videos = document.querySelectorAll('video');
  videos.forEach(function(v) {

Now if we end up finding people want to use this I’ll get fancier in a variety of ways (slow motion, stuff like that) and I’ll have to get the part where all the videos load fully working. You can see an aspect of that below. It’s from a Stackoverflow post of course. It is also how I mute all but one audio track. There might be some interesting things to think about regarding switching audio to the selected track on each view change. I’ve been thinking about that. There are scenarios where muting or lowering/raising the selected video volume could be really interesting.

var promise = new Promise(function(resolve) {
  var loaded = 0;

  videos.forEach(function(v,index) {
    if(index != 0){
      v.muted = true;//mute those others
    v.addEventListener('loadedmetadata', function() {

      if (loaded === videos.length) {

I forgot that I like this video switcher piece. It felt almost elegant.

function switchSource(e){
  let mainVid = document.querySelector('.central.sideview')
  let sideVid = document.getElementById(
  mainVid.classList = 'sideview'
  sideVid.classList = 'central sideview'

Pressbook Export Paths

We have our own Pressbooks install. I was trying to get Part 3: Pressbooks Dependencies straight. It’s running on CentOS Linux server on Reclaim Hosting. I know this because I spent a truly sad amount of time in server land. Don’t get me wrong. Servers could be very cool and interesting but I must make choices and I have chosen not to spend my time in server land learning their customs, memorizing their sea shanties. As a result, I get into a server and I feel stupid and frustrated. I have to recall that Centos uses yum rather than apt-get like ol’ Ubuntu. The brew stuff is for my Mac . . . and linux? And how different are linux and unix? You can see how all this stuff might make someone feel a bit frustrated even if, in isolation, these things are easy and make sense.

The problem I had was that I was thinking that I’d get the path to these dependencies via pwd. I tried many other things as well. I eventually5 wrote a help ticket to dear old Reclaim hoping they’d take mercy on me. And they did. Chris Blankenship was kind enough to point me to the which command. Like most things, if you have the vocabulary you can find the answers but I was using the wrong words (both in the terminal and in my google searches).

1 14 or 15 years you say? Well. Time is an illusion and I’m pretty sure it’s been at least 100.

2 Yet my emotional memory of 8 continuous hours of zoom meetings persists like a festering wound.

3 Coding is like “professional” language in that some of it obscures things to prevent people from understanding and some of it makes things clearer and it’s often hard for me to tell which is which most of the time. Sometimes I don’t see the advantage until I hit a particular scenario.

4 Or something like that. I’m not a potter. I’d know more about spoon carving which is something we really did talk about but it sounded too ridiculous for the example.

5 I do not like to ask for help.

Comments on this post

  1. carpetbomberz said on June 26, 2020 at 12:24 pm

    It does feel like a 100 years to me, at least since March 20, 2020.

Leave a Reply

Trackbacks and Pingbacks on this post

No trackbacks.

TrackBack URL