YouTube Full Screen – Javascript & CSS

I’m sticking this js/css combo in a number of the WordPress themes we’re building.

It’s two pieces, a javascript piece that finds the YouTube videos and wraps them in a div and a CSS Piece that makes the videos full size for the enclosing div. It stops overflows from getting ugly and expands small embeds (via oembed or embed code).

I think it’s handy and I keep having to go to codepen to find it. So now it’s here.

See the Pen
full size youtube/vimeo
by Tom (@twwoodward)
on CodePen.

SPLOTbox With More Configurable Media Variety

As alluded to last week, some expansions have come to the SPLOTbox media collector theme for WordPress.

It made sense to add support for image-type media, making the SPLOTbox perhaps a bit broader than TRU Collector. This was relatively easy, so a SPLOTbox site can accept uploads of images (JPG, PNG, GIF), or add them by direct link to a URL, but also through autoembed of flickr.

And built in support is baked in for MixCloud (audio), Giphy (you know what they offer, right?), plus Slideshare and Speakerdeck (presentations).

All of these are in play, and you can see examples on the demo site at http://splot.ca/box

And this all works if you want a Big Box of All Kinds of Media. But I thought there might be use cases where a site should just accept certain types of media, say for curating videos, or maybe just audio.

So as of now (or 30 minutes ago), the theme options includes checkboxes to designate the media sites that are supported via URL:

Options screen for Media supported Bu URL, checked are flickr, giphy, mixcloud, soundcloud, TED Talk video, vimeo, and YouTube.
You can now choose which media sites are supported by SPLOTbox.

These are then reflected on the front end share form as the list of sites supported (and if URLs for unsupported ones are entered, you see an error message).

Changing the theme options lists only the ones checked as supported (note Metacafe appears because this site runs an extra experimental plugin to extend support to more sites, this remains to be published as a plugin as it is pretty crude now).

If you update a site, there is a small chance on first load none will be checked, I’ve yet to isolate the use cases where this happens. This just means you have to check the ones you want to enable.

There also have been simplications on many of the SPLOTs to reduce the reliance on code in page templates for generating a link to a random item. I was able to pull that out into the general code (this is part of a secret SPLOT project I will hint at below) (that’s for any human that might be reading this) (hi human).

What I had done on many of my themes is to have a WordPress page template named page-random.php that was merely code.

// set arguments for WP_Query on published posts to get 1 at random
$args = array(
    'post_type' => $ptype,
    'post_status' => 'publish',
    'posts_per_page' => 1,
    'orderby' => 'rand'
);

// It's time! Go someplace random
$my_random_post = new WP_Query ( $args );

while ( $my_random_post->have_posts () ) {
  $my_random_post->the_post ();
  
  // redirect to the random post
  wp_redirect ( get_permalink () );
  exit;
}

This means to generate a URL for random content at a site located at http://somesite.fuzzy/, a Page is created with a URL slug of random and then http://somesite.fuzzy/random does the redirect work.

But this is all done in theme code now! Magic? Nah. The first part is registering a query parameter that the site can accept, like ?random=1

// -----  add allowable url parameters
add_filter('query_vars', 'splotbox_queryvars' );

function splotbox_queryvars( $qvars ) {
	$qvars[] = 'random'; // flag for random generator
	return $qvars;
}

Then we need to add a rewrite rule for /random to be picked in a URL:

// -----  rewrite rules for licensed pretty urls
add_action('init', 'splotbox_rewrite_rules', 10, 0); 
      
function splotbox_rewrite_rules() {
	// let's go random
	add_rewrite_rule('random/?$', 'index.php?random=1', 'top');	
}

And now we can handle the logic that was in the template in the theme functions:

add_action('template_redirect','splotbox_random_template');

 function splotbox_random_template() {
   if ( get_query_var('random') == 1 ) {
		 // set arguments for WP_Query on published posts to get 1 at random
		$args = array(
			'post_type' => 'post',
			'post_status' => 'publish',
			'posts_per_page' => 1,
			'orderby' => 'rand'
		);

		// It's time! Go someplace random
		$my_random_post = new WP_Query ( $args );

		while ( $my_random_post->have_posts () ) {
		  $my_random_post->the_post ();
  
		  // redirect to the random post
		  wp_redirect ( get_permalink () );
		  exit;
		}  
   }
 }

Why is this a big deal? Well it’s not. But I am working through what might be a major leap for SPLOTkind… it means pulling all of the functionality out of a theme template… and into a yet to be fully fleshed out plugin. It means a major amount of re-coding the SPLOT logic, but if it works, it could mean the SPLOT functionality could be independent of theme.

That’s a lot of maybes for now, but so far the work is progressing well.

This new version of SPLOTbox is been tested on a few of my sites, but I could certainly stand for anyone out there to give it a go on your own site – get the latest version at https://github.com/cogdog/splotbox

Maybe you will see colored muffin tins.


Featured Image: Modified by adding SPLOTbox theme image to Muffin Tin Monday – Sweets & Treats lunch flickr photo by anotherlunch.com shared under a Creative Commons (BY) license

More Ways to Plug Into SPLOTbox

In a media making workshop I ran last week (overdue for the blog queue), I found it both reassuring to see how participants took to sharing media created in a TRU Collector SPLOT powered site and also saw some shortcomes that led to some useful code rabbit holing this weekend.

On issue was when we moved on to audio activities, I had no place ready for them to share their work (SoundCloud was suggested, but we had wariness on creating new accounts, and as a colleague noted, they seem to be going out of business for the last 4 years).

In a quick thought, I just sent them to a demo site for the SPLOTbox theme. This is a SPLOT WordPress theme that creates a site where audio and video content can be shared by link (for ones natively embeddable in WordPress) or uploads of audio files (video is problematic for the web player and for file size).

It reminded me that there seemed no reason that a media jukebox could not also include images. So I tinkered to add the ability to add images (JPG, PNG, GIF) as uploads or by direct links to images on the web as URLs.

But while in there, I added as well a feature to include flickr photos by URL to the page that contains them. And while looking at the list of content WordPress can natively embed by just a URL, I noticed that they added Speaker Deck for presentations (l have stopped using Slideshare as it’s heavily encrusted with LinkedIn paw prints).

(Oddly enough they still do not list giphy as being supported by embed via URL but I have been doing it for a while).

This means rather than fishing for an embed code in Speaker Deck, I can put a plain text URL on a new line in WordPress, like https://speakerdeck.com/cogdog/i-deal-in-stories and have it automatically embedded (hmm see some funky truncation in my theme, sigh)

Cough.

So I have added both Slideshare (why not) and Speaker Deck to the sites supported in SPLOTbox, as well as Mixcloud for music.

I’m still testing and cleaning up, but you can see it in action on the test SPLOT at http://splot.ca/box the last few items are a bunch of tests.

Currently the theme supports media as URLs from Adobe Spark Pages/Videos, Flickr, Giphy, Internet Archive, Mixcloud, Slideshare, Soundcloud, Speaker Deck, TED Talks, Vimeo, YouTube plus uploads or links to audio and image media of types .mp3 .m4a .ogg .jpg .png .gif.

This offers more plugs into a SPLOTbox site, but on a drive today, mulling this over, I am thinking it would be even better for a site owner to decided how many of these to offer- e.g. if I was building a site of just YouTube curated content, or only things from the Internet Archive, I could set these as theme options.

That is likely coming soon.

One more new feature is the SPLOTbox theme adds a search form to the top navigation (suggestion) by Daniel Villar-Onrubia):

Finding cats in a SPLOT? SPLOTbox theme will soon have an integrated search box on the menu bar

These features need a bit more testing, but hope to update the repo site soon. Give the new media features a try at http://splot.ca/box


Featured image: by Wolfgang Eckert from Pixabay

ANTH 101 – A Deeper Dive

Michael Wesch - Pop!Tech 2009 - Camden, ME

It’s been a good while since I had the pleasure of working with Mike Wesch and Ryan Klataske on ANTH 101. I revisited the course recently to write a letter of support for an award submission for online courses. I am posting an extended version of that letter below because I think it paints a path with online courses that is rarely followed but is, nonetheless, replicable and worth considering.

Bigger Picture?

I see ANTH101 as a path forward that makes me hopeful in an online space that seems increasingly depressing.1 You have two races currently in online learning. There is a race to be the cheapest and easiest place to enroll.2 This article on Liberty “University” paints that picture pretty well. This world will be like the fast food industry in many ways. How uniform can we make things? How automated? What’s the least we can pay the fewest humans? The only path to profit will be through ridiculous scale. There will be very little difference between these providers. They will use LMS products that are very similar while following very similar online course rubrics and probably (poorly) paying many of the same adjunct/itinerant online course faculty. Additional sadness will occur when the same OPM is creating content, marketing etc. for multiple universities for the same courses and programs.3 There will be increasing overlap as course, LMS, and textbook become one thing you buy (as a student or an institution). The differences will grow ever smaller. Scale will need to increase ever more.4

Then there is the prestige battle. That’s obviously trickier as restricting access to the Harvards of the world is what makes them prestigious.5 The media here will likely be high dollar. They’ll do TED-Talkish-Dineyfied-Edutainment™. Read what colleges are paying to develop these courses. It is insane. They’ll likely try to sell off badges and certificates to try to get at the scale they need to make it profitable without becoming less exclusive. You see that happening with Coursera and Ed X MOOCs. Exclusivity for the masses! Do not worry. The brand will be protected. Order will be preserved. We’ll resell these courses to other Universities.6 US News & World Reports will still tell us what the best education is.7

With all that at least partially vented, let’s look at what ANTH 101 does differently.

ANTH 101

The core message of ANTH 101 is that you have to “live your way to a new way of thinking.” In life, and especially in online courses, it’s easy to do what has been done before. It’s easy to take the safe road. Textbook publishers and LMS vendors have built the content and tools that expeditiously serve the established patterns. Those goals have been pretty low–counting discussion board responses and automatically grading quizzes as students plod along well-trod paths. Standardized tools and standardized content result in standardized experiences. ANTH 101 breaks those expectations and sets a new path for possibilities with patterns and tools that are broadly applicable, replicable, and focused on engaging students.

ANTH 101 doesn’t just take on the most difficult aspects of online learning; it actually turns these difficulties into features that improve the entire experience. It is the ability to take seemingly opposing forces and unite them in a way that is more powerful than the two parts that makes ANTH 101 a course worthy of admiration and emulation.

The idea that an online course needs to live in a single online space is a popular myth. ANTH 101 leverages a variety of tools and platforms to make a cohesive experience that is powerful and compelling because these different tools do different things well. The efficacy with which these things are blended together with a large and diverse student body (not to mention an external faculty) is proof that this can be done and it can be done well.

The main site (anth101.com) acts as the central hub for a number of tools and platforms. This site is built in WordPress which enables Professor Wesch and others to create, edit, and publish multimedia content without a high degree of technical overhead while still maintaining the ability to create a website with impressive aesthetic polish. The course doesn’t feel like a mundane LMS course–it feels and behaves like the kind of website students would visit without the coercion of grades.

A large portion of student content creation is done in Instagram. Using this tool helps deal with a number of significant problems. In a class with large numbers of authors creating multimedia content based on due dates you get large but short pulses of resource demands. Those peaks in demand are followed by longer valleys where you don’t need nearly that much server power. It becomes more difficult to predict the level of resources you’ll need when you open the course to the public. This drives up expenses and increases technical overhead. By passing the media creation and storage to Instagram, ANTH 101 can be hosted on a very low-cost server. Additionally, Instagram is a tool and community that most of the students are familiar with already. This lowers support needs with the additional advantage of being able to use Instagram’s support team when technical issues do arise. Using a familiar tool that is associated with a student’s life outside education also helps reinforce the theme that this experience is about making changes in how you live and perceive things outside of a single course. ANTH 101 is about the real world and it uses the tools you use in that world.

One of the more important features of the ANTH 101 is the attention to detail. One place that’s especially obvious is in the course is how the work has been framed. They are not assignments but challenges. Shifting the language from the outset helps change how students perceive the work they’re doing for the course. Assignments are things forced on you by outside actors. Challenges are heroic things you take up of your own volition. Each challenge is designed to cover particular learning objectives as one would expect, but the challenge is also meant to create media that other students would be interested in experiencing. These challenges are the kind of content that invite curiosity and investigation. As such, student work becomes an integral part of the course.  Previous exemplary student responses to these challenges are highlighted to help set high expectations. Students actively read and comment on each other’s content increasing their own exposure to a variety of anthropological concepts while also binding them together more deeply as a community. The fact that ANTH 101 students continue to participate and communicate long after the course is over is one of the strongest possible indicators of the success of this course.

ANTH 101 is a large-enrollment class with hundreds of students taking the course at Kansas State each semester both online and in person. Additionally, the course is open to faculty and students at other universities.  Large numbers of students from multiple universities taking a general education class results in extraordinary diversity. Students come to this class with a variety of life experiences, widely varying educational backgrounds, and vastly different reasons for taking the course. That diversity is incorporated into the course as an affordance rather than an obstacle. Student perspectives change because they are exposed to and interact with so many differing viewpoints.

While ANTH 101 is a large community it uses hashtags in Instagram to create communities at various scales so that all participants can find connections and inspiration. There is the main course tag #anth101 which lets students and faculty see any challenge from across the entire community. This scale may not be optimal for community building but the sheer volume of content helps creates energy and enthusiasm. Students see and participate in a community beyond their individual course and see perspectives that are different than their own. There are also challenge-specific hashtags like #anth101challenge3 which focus the content to that one challenge. Seeing students from all over the world doing the same challenge changes how students perceive their own work and helps reinforce thematic elements of the course. Smaller communities led by teaching assistants or outside faculty have their own hashtags like #anth204hedges. This allows for the creation of smaller more tightly knit communities. On a pragmatic level, the combination of challenge hashtags and community hashtags makes it easy for teaching assistants to stay on top of student participation.

The role of content in this course is powerful and innovative in a number of ways. The videos that Professor Wesch creates establish his social presence while also teaching the content. There is a continuous juxtaposition of his life with the larger frame of anthropology. The tandem progression of getting to know your professor and getting to know anthropology is a powerful narrative.  The student work follows this pattern. Students get to know themselves and their environment while gaining larger anthropological understanding. Students also get to see their professor as a person who is not only actively participating in the challenges he asks of them but who is also engaging in the same risks and adventures. Group cohesion is continually reinforced when students see and comment on one another’s work throughout the class.

This content also embodies the principles of the class as Wesch captures his own mistakes, documents his fears, and “lives his way into a different way of thinking.” These adventures are often very physical–like when he learns to do a handstand or to play an instrument–but are captured and reflected on digitally. Once again, we have two seeming opposites–the digital and the physical–being used to create something greater than the two parts.

ANTH 101 provides media in a myriad of formats. The course content exists online in a mixture of text, audio, and video. It’s also accompanied by a digital textbook that can be accessed for free or printed out for a small fee. An interactive digital textbook is available through Top Hat which includes self-grading quizzes and interactive elements.

There is no question that Professor Wesch is a talented creator of all kinds of multimedia. That requires a great deal of skill and commitment. But Professor Wesch also does a remarkable job of using publicly available media to reinforce conceptual messages and support students technically. For instance, in Challenge One – Talking to Strangers, media from popular culture is leveraged in a number of important ways. First, the challenge itself has been a popular challenge on social media. There are strong examples out there and it’s likely a number of students have seen them. That helps set a higher bar for what the students are going to produce while making the concept more approachable because of its familiarity. Because the concept is popular, Wesch can then choose from a variety of sophisticated media that has already been created to provide conceptual and technical support for students. You can see that clearly in the conceptual framing given to this challenge in the Humans of New York video and in the Big Talk video. Five Tips for Better Street Photography hits the other end of the spectrum by providing technical advice for this type of photography. Students get the advantage of powerful and engaging media featuring perspectives from outside academia while Wesch is able to incorporate sophisticated multimedia from subject matter experts without incurring additional cost. This blending of course content and “real world” content is a vital aspect of the course.

What ANTH 101 does is create an experience that leverages the digital to do more in the physical. It embodies the values of the discipline of anthropology in the challenges and the tools that comprise the course. The faculty of ANTH 101 live the experience with their students. Most impressive of all, the course continues to evolve. It would have been easy to stop when the course was successful. That didn’t happen. The course itself follows the model and continues to “live itself” into something better, something larger, and more powerful with each semester.

The Biggest Picture?

Given this was a letter of support, I didn’t get into concerns with Instagram8 and data privacy. That is something I worry about but one I worry about with Blackboard and Google and everything else we use with students.9 I don’t know how we deal with scale or create certain levels of technological sophistication without investing time/people/cash in a way that doesn’t seem to be happening these days. We seem to have let this lie for a long time and we are now so reliant on “free” services I don’t know what the path back looks like.

I also wonder if Mike could create something like this without such a blend of skills and interests. I don’t think you can hand off the creation/consideration of media and tool(s) to someone else and create a course like this. That’s just not how it works. If I’m right that would mean we need faculty who are more involved with the media and tools of online in addition to their discipline. They don’t have to be programmers but we can’t opt them out of the process the way many people are encouraging for online courses. The venn diagram of knowledge that is teaching, tools, and discipline should overlap significantly for all the people involved. That’s not an easy or popular argument. Those people aren’t cheap or easy to replace.

Long term, I also wonder about the University itself. If we’re teaching online courses and the students are not interacting in substantive and intentional ways with other students in other courses (within and outside their particular program) then what purpose is the larger University construct serving? Why wouldn’t you cut out the University as a middleman that adds little value while increasing costs? It would seem a short step for OPMs to begin getting accredited. From there the idea of gathering super star faculty that they could pay like tutors in South Korea seems equally plausible.

The window for higher ed to do some good things is closing. The path to a future does not lie in creating more of what already exists. The path to a successful restaurant does not lie in reselling pre-packaged food and hoping branding wins the day. We have lots of talented people but if we’re not aspiring to things worth their effort why would they stick around? If online higher education can’t make being part of a large community an exciting and beautiful thing, why bother with the overhead?


1 I will be your faithful Mumen Rider on this journey but only if you’re watching the original series with subtitles.

2 Now with 24hr a day rolling admission and a free phone shaped like a graduation hat! Operators, naturally, are standing by.

3 Online professional masters degrees are so hot right now.

via GIPHY

4 Do our current visa issues with China impact online education? New markets must be found! Can we educate pets online?

5 9 million people applied . . . we didn’t let anyone in. How exclusive is that?

6Many of these investors, of course, hope to make money on MOOCs, most likely not by selling the courses directly to students, but by renting them to small schools that then pair the outsourced lectures with proctored tests and, in some cases, group discussions and extra assignments.

7 Which is worse though, the rater or the desperate ratees?

8 I’m also frustrated with their API changes which broke a really nice aspect of the course. We have no recourse there.

9 What information does the school’s internet provider have?

Super Secret Journal Posts

Origin Story

Let’s say someone wanted people to write down very personal, very sensitive thoughts about privilege, bias etc. They’ve turned down Google Docs as an option because they heard that people can see that. They want this even if you’ve said that writing down secrets at all is not a good idea and that writing them anywhere digital, let alone the Internet, is a very bad idea.

So with that said, I think this is a pretty decent way to write stuff that’s only visible to the author and to the administrator of the site. It creates a custom post type called journal and then shuts off every thing that I think allows public access. Take note of the comments below and if you see anything I’m missing let me know.

 $args = array(
    'label' => __( 'journal', 'textdomain' ),
    'description' => __( '', 'textdomain' ),
    'labels' => $labels,
    'menu_icon' => '',
    'supports' => array('title', 'editor', 'revisions', 'author', 'custom-fields', 'thumbnail',),
    'taxonomies' => array(),
    'public' => false,//become super secret 
    'show_ui' => true,
    'show_in_menu' => true,
    'menu_position' => 5,
    'show_in_admin_bar' => false,
    'show_in_nav_menus' => true,
    'can_export' => false,//no exports
    'has_archive' => false,//no archive just in case
    'hierarchical' => false,
    'exclude_from_search' => true,
    'show_in_rest' => false,//not in JSON
    'publicly_queryable' => false,//can't query it
    'capability_type' => 'post',
    'menu_icon' => 'dashicons-lock',//make a lock icon
  );
  register_post_type( 'journal', $args );

I also threw this in here in case it somehow did get public somewhere. It filters content when it’s a journal post type and makes sure you’re the owner or admin. If you turn on some of the public stuff, this would still keep things pretty private.

function secure_the_journal($content) {
  // assuming you have created a page/post entitled 'debug'
  if ($GLOBALS['post']->post_type == 'journal') {
  	$current_user = get_current_user_id();
  	$author = $GLOBALS['post']->post_author;
   	if ($current_user === $author || current_user_can('administrator')){
	   	return $content;
	   } else {
	   	return '<h2>This content is private.</h2> <p>You will need to be the content owner and <a href="'.wp_login_url().'">logged in</a> to access it.</p>';
	   }
  }
  // otherwise returns the database content
  return $content;
}

add_filter( 'the_content', 'secure_the_journal' );

Building an ACF-Based Grade Book Plugin in 30 Minutes or Less

Origin Story

Kathy asked me if I had any grade book plugins that they could use with a faculty development course. The goal was to show faculty where they were in the course. I knew I did not wish to use Learn Dash for any number of reasons– grade book module costs extra, grade book module is super awkward, learn dash requires a fresh multisite install etc. I also had WPLMS from a few years ago when I think Jon asked to try it out. This felt pretty bloated for what we wanted and it wasn’t entirely obvious if it had a grade book in any case. The WordPress plugin repository shows three plugins and the most up to date has not been updated in 3 years.

Not a cheerful landscape. I wondered what it would take to build an ACF-based grade book relying mostly on the repeater field and simple shortcode for displaying the grades to the user. I wasn’t looking to do math or anything. I just wanted a pretty simple interface and a way for users to see their information while administrators could see all the content. That does require people to have accounts and be logged in. A bit of hassle but there is no free lunch.1 This grade book isn’t adding up things or doing weird weighting etc. It’s just a simple way to log people + assignments + scores.2 It is now known as progress book and it lives on github.

The ACF Side

We can do just about everything I can think of with one repeater field as the parent. The sub-fields will be the participant’s name, the assignment name, and the score on a scale of 1 to 100. Since we’re requiring participants to be member of the site, the participant field will be a relationship field tied to the site users. That makes the list of students automatic and makes for an easy way to recognize them when we want to show them their progress. The assignment field is a select field that has the assignments pre-populated. You’ll have to go into ACF to add assignments which isn’t optimal but the only field that lets you add choices in that interface is the checkbox field. That ends up taking up too much room if you’ve got 10 or so assignments. In this case, we should know the assignments ahead of time and even if alternations are needed, adding them via ACF is pretty straightforward. The final piece is just a slider using the range field.

That’s it. Maybe ten minutes of work with the majority of it looking at the assignments piece and trying to find a slightly nicer way to add custom values on the fly. You end up with an interface like the one below that I set to show when a page is called grade book.

One other nice piece I added was the json sync function for the plugin. It takes an extra step compared to doing this in a theme but it keeps your ACF fields nice and sync’d between development and production. This is very handy and prevents all kinds of hassle.

//ACF JSON SAVER
add_filter('acf/settings/save_json', 'my_acf_json_save_point');
 
function my_acf_json_save_point( $path ) {
    
    // update path
    $path = plugin_dir_path( __FILE__ )  . '/acf-json';
    // return
    return $path;
    
}


add_filter('acf/settings/load_json', 'my_acf_json_load_point');

function my_acf_json_load_point( $paths ) {
    
    // remove original path (optional)
    unset($paths[0]);
    
    
    // append path
    $paths[] = plugin_dir_path( __FILE__ )  . '/acf-json';
    
    
    // return
    return $paths;
    
}

Displaying Grades Shortcode

The following chunk of PHP is how we display the ACF data on the front end. It’s tied to a shortcode and builds a little table-like display and uses a tiny bit of logic to show either just your stuff if you’re not an admin or all if the stuff if you are an admin– current_user_can(‘admin’) || get_current_user_id() === get_sub_field(‘student’).

function get_progress_by_user(){
		$html = '';
		if( have_rows('progress') ):
	 	// loop through the rows of data
		$html .= '<div id="your-progress" class="progress-row"><div class="progress-title">Name</div><div class="progress-title">Assignment</div><div class="progress-title">Score</div></div>';
	    while ( have_rows('progress') ) : the_row();
	    	if (current_user_can('admin') || get_current_user_id() === get_sub_field('student')){
		    	$stu_id = get_sub_field('student');
		    	$name =  get_userdata(get_sub_field('student'))->display_name;
		    	$safe_name =  sanitize_title(get_userdata(get_sub_field('student'))->display_name);
		    	$safe_assign = sanitize_title(get_sub_field('assignment'));
		        // display a sub field value
		        $html .= '<div class="progress-row ' . $safe_name . ' ' . $safe_assign .'">';
		        $html .= '<div class="progress-student" data-student="' . $safe_name . '">' . $name . '</div>';
		        $html .= '<div class="progress-assignment" data-assign="' . $safe_assign . '">' . get_sub_field('assignment') . '</div>';
		        $html .= '<div class="progress-grade">' . get_sub_field('grade') . '</div>';
		        $html .= '</div>';
		    }

	    endwhile;

	    return $html;

	else :

	    // no rows found

	endif;

}

To make things a little easier to parse for the admins, I added a little javascript that lets you click on a participant name to hide everyone else or to click on an assignment name to hide all the other assignments.

if (document.querySelectorAll('.progress-assignment')){
	document.querySelectorAll('.progress-student').forEach(function(el){
	  el.addEventListener('click', function() {
	    hideStudents(this.dataset.student);
	  });
	});


	function hideStudents(theStudent){
		let names = document.querySelectorAll('.progress-student');
		names.forEach(function(student){
			console.log(student.dataset.student)
			console.log(theStudent)
			if (student.dataset.student != theStudent){
				student.parentNode.classList.toggle('hide-stu');
				
			}
		})
	}


	document.querySelectorAll('.progress-assignment').forEach(function(el){
	  el.addEventListener('click', function() {
	    hideAssign(this.dataset.assign);
	  });
	});


	function hideAssign(theAssign){
		let names = document.querySelectorAll('.progress-assignment');
		names.forEach(function(assign){		
			if (assign.dataset.assign != theAssign){
				assign.parentNode.classList.toggle('hide-assign');
				
			}
		})
	}
}


1 That’s actually how I measure how bad an experience will be. If they’re willing to give you free food as bait . . . expect the worst.

2 It wouldn’t take a ton of work to figure all that out but I’m not going to encourage the facade that grading requires that kind of complexity. It’s like building a custom kitchen so you can keep making instant noodles. All the fancy math in the world isn’t going to fix the fact that assessment is fundamentally broken.

Two Great WordPress Taxonomies That Link Great Together

Leave it to diabetic to use a sugary metaphor to a candy slogan.

Okay, WordPress, you have these two ways to organize posts, Tags and Categories (yes, there are more than “uncategorized”). I’m working on a new project where I am going to not have my usual freedom for custom theme coding, so I am finding all the things I can do within the box.

I have a Category taxonomy with 3 items, each with 10 sub items. But I am going to need ways to list posts also within each ones as “recommended” or “optional.” I was wondering about ways of finding posts with a category AND a specific tag.

Were I coding, doing a custom query would be a snatch.

And this little thing I found will work as a link, but not much else, but seemed like something worth noting.

So I have a category link on this here blog for WordPress cayetorized posts – https://cogdogblog.com/category/wp/ and by the time I publish this one, there will be 180 posts there.

But I can add a second query parameter via the URL to get posts categorized WordPress and tagged splot like this (41 results) https://cogdogblog.com/category/wp/?tag=splot

And when this one gets published! https://cogdogblog.com/category/wp/?tag=peanutbutter

Just by playing with URLs you can get slices of taxonomies across the two kinds WordPress gives you.


Featured Image: Image by pixel1 from Pixabay

Hider – Remove Elements via URL Parameters Plugin

Origin Story

About a week ago I posted about a theme that was designed to integrate into the LMS via iframe embeds. The goal was to strip out extra things like headers and sidebars that were confusing or unwanted. You’d still have a normal WordPress site at your regular URL but would be able to selectively embed chunks of it in the LMS in whatever way you desired.

That is the key that I don’t think I stressed enough. You could easily add CSS display:none and whittle you site down via the customizer but I wanted to be able to do that only when embedding the site (or linking to the stripped down version).

After some back and forth conversation on Twitter I decided to write a simpler version as a plugin rather than a theme so it could be used more broadly.

The plugin is here if you want to give it a shot.

The Pieces

To make things simpler I wanted all the divs to have an ID. I considered giving an ID to everything but figured in 95% of the cases the major pieces of the theme would be in divs.

That’s an amazingly easy thing to do in javascript. I could have made this even more concise but figured this would be easier to understand. The first function gets all the divs on the page and the second adds an ID if one doesn’t already exist.

function activateAllDivs(){
	allDivs = document.querySelectorAll('div')
	allDivs.forEach(function(div, index) {
	 idEverything(div, index)
	});
}

//make sure everything has an ID
function idEverything(div, index){
   if (div.id){   
    console.log(div.id);//got an id? great moving on . . . 
  } else {
    div.id = 'newId-'+index; //no id? well that's not an option
  }
}

Now we need to read the div IDs in the URL that we would like to hide. I loop through the array of IDs and set the inline CSS to diplay: none. I did that to make sure it’d override any other inline CSS after seeing that fail on a theme or two when I just added a class.

window.onload = function(){
  const urlParams = new URLSearchParams(window.location.search);
  const hide = urlParams.get('hide');//get hide parameters
  let hideArray  = hide.split(',');//make comma delimited set of IDs into array
  
  hideArray.forEach(function(el){
    document.getElementById(el).style.display = 'none' // was using add class but moved to inline for more aggression
  })
  activateAllDivs();
}

That’s all there is to it. There are a bunch of other things I half-built. I had it set so that rolling over divs changed their color so you could see better what you were planning to remove. I was going to set them to set the URL onclick events with the ID of the thing clicked. I was going to generate the embed code as well for cut/paste. I may still do those things but given how easy the Canvas API is looking, it feels like I’d need a more directly interested audience to bother doing much more. I like this idea though and it gave me a chance to play with some concepts that I am confident we’ll use in other ways.

WordPress for LMS iFrame Infiltration

Origin Story

Over the years I’ve seen a number of people embedding WordPress within the LMS using iframes. There are lots of reasons to do this but it’s not something that WordPress is really meant to do. You end up with an odd feeling as it’s a website within a website and many things feel out of place (headers, footers, sidebars). I said something on Twitter one time that I might make a theme that would behave better in those kinds of scenarios. I was able to find that tweet using Twitter’s semi-hidden advanced search. Note that Sept. 9, 2018 date. It’s been a while but here’s a super simple theme that tries to play well in the land of iframe embeds.

Show Only What You Want

As is my pattern, I figured I could hide most of the extra things through javascript and use the URL’s query string parameters1 as the way to decide what was shown.

I haven’t built out all the various parameters but for this theme, I added some javascript to look at the URL for a ‘show’ parameter. So https://baseurl.com/some-page/?show=article will show only the

tag for this theme. You can see how it pretty radically changes the way the page looks below.

Now if we embed that URL in an iframe, we’ll see the clean version. That opens up an amazing amount of flexibility.

Here’s the brute force javascript that does that. It has three parts. Part one gets the value from the URL. Part two gets all the pieces of the page via the IDs. Part three adds a class named hidden to the various components. In a future iteration, we could get much fancier and add/remove elements from an array but I figured this would be easier to understand for people looking to learn this stuff.2

        //GET THE URL VALUE - PART ONE
        const urlParams = new URLSearchParams(window.location.search);
	const show = urlParams.get('show');
        
        //GET THE PAGE COMPONENTS - PART TWO
	const headerNav = document.getElementById('wrapper-navbar');
	const rightSidebar = document.getElementById('right-sidebar');
	const footer = document.getElementById('wrapper-footer');
	const adminBar = document.getElementById('wpadminbar');
	const privacy = document.getElementById('private');
	const primary = document.getElementById('primary');

        //HIDE CERTAIN PIECES DEPENDING ON THE URL VALUE - PART THREE
	if (show === 'article') {
		headerNav.classList.add('hidden');
		rightSidebar.classList.add('hidden');
		footer.classList.add('hidden');
		adminBar.classList.add('hidden');	
		privacy.classList.add('hidden');
		primary.removeAttribute("class");
		primary.classList.add('col-md-12 content-area')
	}

iFrame Sizing

This is where things get more complex. I was hoping to avoid people having to hand-set any iframe values (height, width etc.). I’ve looked at this before and it’s messy. This time around I found a GitHub page that offered a variety of ways to solve the cross-domain iframe height problem.

The Dual Javascript Path

The method I went with the first time relied on two javascripts. This works great if your LMS doesn’t strip script tags. Our Blackboard install doesn’t strip these tags. So an embed code set something like what you see below does a great job.

<iframe frameborder='0' scrolling='no' width='100%'src='http://192.168.33.10/wordpress/minimal/for-a-title/?show=article'></iframe>
<script async src='https://rampages.us/extras/js/set-iframe-height-parent-min.js'></script>

That method gets us something nice and clean like the screenshot below in anything that doesn’t strip out script tags.
Screen Shot 2019-05-09 at 9.36.03 AM

When I moved to our Canvas test install I found the filtering there to be much more aggressive. No script tags and a variety of other things I messed with in the iframe itself were stripped- including the scrolling parameter. I tried something slightly different in this case. I knew I could get the height of the element via javascript. I could add that into the iframe element and build the embed code on the fly. If I set it as the min-height via the style element it sort of works but things get messy again on mobile views etc. I’m still think about this but given Jeff was working on a twelve minute plugin that would use the Canvas API to allow WordPress to publish directly to Canvas I may not spend a huge amount of time on this.

let main = document.getElementById('primary');
let iframeHeight = main.clientHeight;

That works and results in an iframe code like you see below. I just let the LMS editor clean it up as it desires rather than creating two different patterns.

<iframe frameborder='0' scrolling='no' width='100%' style='min-height:632px;' src='http://192.168.33.10/wordpress/minimal/for-a-title/?show=article'></iframe><script async src='https://rampages.us/extras/js/set-iframe-height-parent-min.js'></script>

Next Steps?

This was about an hour’s worth of work when I wanted a break from a variety of other projects I’d been working on. I am still kind of interested in how this might work (mainly the iframe portion) but future effort will likely depend on some sort of additional interest from someone. If I don’t see that then I’ll likely put this in my back pocket3 I’m confident a number of the concepts will come back around in the future.

Aside>
Screen Shot 2019-05-09 at 9.37.55 AM
You may note that my Blackboard HTML editor looks like an old school terminal. This was actually done in response to a faculty member with failing vision who asked if something like that was possible. I did it for a demo and it amused me so I left it on. It uses Stylebot and the following CSS>

#htmlSource {
    background-color: #000;
    color: green;
    font-size: 1.5em;
}

.contentList {
    background-color: #424242;
}

1 It’s a trail! Go see what these are if you don’t already know.

2 And it documents how I build this stuff. I start with the easier version and then work towards more complexity as needed.

3 . . . with 12 to 15 thousand other things . . . some gum and some lint.

Duplicate Featured Image Remover

Origin Story

In a number of scenarios we’ve used plugins to use the first image in the post as the featured image. That’s nice in that if people forget or are unaware of the featured image it’ll happen automatically and that fits in nicely with how various themes use featured images in various layouts. Where it doesn’t work as nicely is when the featured image is shown in a way that feels duplicative in the single post view. Something like below. I was reminded of one way to solve this after reading Alan’s post yesterday. I’m also trying to do a better job of blogging more1, commenting more, and linking back to posts.2 Who knows maybe people will start believing in RSS readers again?

A screenshot from codepen showing one example of how featured image duplication can look awkward.

Function

For this example I just grabbed the HTML from the base Understrap template and set a featured image and put it in the post body as well. The exact nature of the javascript will change depending on how the theme is setup but this is fairly generic stuff and would likely work on a variety of themes. I commented up the few lines involved so you can see what each one does.

let featured = document.querySelectorAll('img')[0];//find all the images and then select the first image (you'd change this on a theme that has a detectable header image to 1 or whatever
let content = document.getElementsByClassName('entry-content')[0];//find the div with the class entry-content (you'd change this to the class or id of your content div if your theme is different)

let firstImg = content.querySelectorAll('img')[0]; //find the first image in the div with the class entry-content (you'd change this to the class or id of the content if your theme is different)

if (featured.src === firstImg.src){ //this says if the img src of the featured image and the first content image match do the following
  //firstImg.classList.add('hidden') //option for hiding it rather than removing it assuming you have a class hidden with display:none or something similar 
  firstImg.remove(); //remove the first image 
}

Here’s the codepen with the whole thing. I’m repeating my pattern of just copying code there to make life easier for sharing examples like this.

See the Pen
wordpress featured image duplicate remover
by Tom (@twwoodward)
on CodePen.

Getting Javascript in Your Site

You have a few choices when it comes to adding javascript like this into your theme.

The easiest option is to add a plugin that lets you add javascript. Something like Header/Footer Scripts. Just make sure you save anything complex somewhere you can find it in case this gets deleted or whatever. Trust me on this one.

If you can’t or don’t want to use a plugin like that you still have some options. While you could append your code to your theme’s existing javascript, you’ll lose it on any upgrades and have to re-write it. That’s a pain. I’d suggest writing a tiny plugin. I promise it’s fairly pleasant for something like this and doing it will make you feel powerful and full of code-y goodness.

To make this work you need two things. The first is the PHP file that that says “Hi, I’m a plugin. Let me tell WP what to do.” and then loads your javascript. Secondly, you need your javascript. Here’s a link to super minimal template for a WordPress plugin that loads one javascript file.

The comments below tell you what the pieces do. The javscript will be whatever you put in the file but by default it will say “hello” in the console. I do that to make myself feel better that all is working as planned. Install it, activate it, and you’re 90% of the way there.

The PHP looks like this.

<?php 
/*
Plugin Name: SUPER UNIQUE JS PLUGIN
Plugin URI:  https://github.com/
Description: For stuff that's magical
Version:     1.0
Author:      YOU!!
Author URI:  http://YOURSITE.COM
License:     GPL2
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Domain Path: /languages
Text Domain: my-toolset

*/
defined( 'ABSPATH' ) or die( 'No script kiddies please!' );//helps keep out bad people


add_action('wp_enqueue_scripts', 'super_unique_load_scripts');//

function super_unique_load_scripts() {                           
    $deps = array('jquery');// you can use jquery with this just remember to use jQuery instead of $ (or alias it)
    $version= '1.0'; 
    $in_footer = true;//loads in footer    
    wp_enqueue_script('super-unique-main-js', plugin_dir_url( __FILE__) . 'js/super-unique-title-main.js', $deps, $version, $in_footer); 
}

1 Even just random little things like this.

2 Trackbacks can be more than just spam.