Canvas-ish WordPress Template

Origin Story

The request came in on Monday for a WordPress theme that looked like the Canvas LMS. That’s not a very dramatic origin but it’s what I’ve got. You can check out the current iteration of the theme here or get it on github here.

What does Canvas look like?

I’m really just imitating the pages so I went to check out a demo page. It looks something like this.
Canvas page screenshot. Menu on the far left. Content on the right. Sub-navigation below the content.

In my head, I break it down into rectangles and start thinking of how to get the data into those boxes. The rough sketch below is how I conceptualized it. Most of the content is pretty straight forward as well.
The previous canvas lms screenshot but with an overlay of green rectangles indicating different website areas. Each area is labeled with the content that will go there.

The Work

For this type of quick work I knew that the understrap theme would get me most of the way there. It has a left sidebar template and it’s in bootstrap so no drama there. I didn’t strip this to the bones but that option remains open if we want to make this an even more streamlined theme. The two things I was less sure about were how I wanted to populate the left navigation bar and how I wanted to build out the next/previous buttons at the bottom of the page.

Left Sidebar Menu

I altered the sidebar-left.php file to read like this.

if (is_active_sidebar('left-sidebar' ))	{
	  dynamic_sidebar( 'left-sidebar' ); 
	} else {
	   if ( current_user_can('editor') || current_user_can('administrator') ){
	   	 echo '<div class="alert alert-info" role="alert" aria-atomic="true">Put a Menu in the left sidebar as a widget to further customize.</div>';//alert about using the menu to do fancier stuff
	   }
	  echo '<ul id="default-menu" aria-role"navigation">';
	  $args = array(
        'depth'        => 0,
         'title_li'     => '',
    );
      wp_list_pages($args);
	  echo '</ul>';	
	}

If I haven’t assigned a menu using the widget>left sidebar interaction, it will list all the pages but if I’m logged-in and can edit it will also give me a helpful little notification saying that I could customize that if I wanted to.
A small blue box notifying people they can set the menu through the widget interface for the left sidebar.

Now that we have links populating our sidebar, I considered a couple ways of doing the next buttons. You can tie them to menu order and do custom nav walkers etc. but that seemed like a lot of hassle given we basically had the order we wanted right here in the sidebar. This is one of those times when I copy a chunk of the HTML I need and throw that in codepen to use while I figure out the needed javascript.

To do this I figured I needed to know which page I was on and where that page sat in the order of links in the sidebar already. I’d also need to know when I was at the front or end of that list to hide the required buttons if a previous or next didn’t exist.

I could use the following to get the name of the page.

const currentPage = document.querySelectorAll('h1')[0].innerHTML;//gets first element that is an H1 and returns the inner HTML

I can use something similar to get all the li elements from our sidebar menu.

const navList = document.querySelectorAll('#default-menu li');get the li elements inside the element with the id default-menu

Now we know what page we’re on and we’ve got all the links in what amounts to an array. When the page title is the same as the name in the loop of menu items, we can move backwards or forwards in the array and get that information to set the URLs in our previous/next buttons. If there’s nothing there we’ll just hide the buttons.

function buildNav(navList, currentPage){
//add the index variable below lets me know where in the array the loop is at
  navList.forEach((list, index) => {
    if (list.childNodes[0].innerHTML == currentPage){
      if (index-1 > -1){
       let prevLink = navList[(index-1)].childNodes[0].href;  
         setNavUrl('prev-btn',prevLink)
      } else {
        hideEmptyNav('prev-btn')
      }
       if (index+1 < navList.length){
        let nextLink = navList[(index+1)].childNodes[0].href;
          setNavUrl('next-btn',nextLink)
       } else {
          hideEmptyNav('next-btn')
       }
    }
  })
}

function setNavUrl(id,url){
 const nav = document.getElementById(id);
 nav.href = url; 
}

function hideEmptyNav(id){
  const nav = document.getElementById(id);
  nav.classList.add('hidden')
}

The HTML that holds the navigation is like so.

  <div id="sub-nav-footer">
    <a href="" id="prev-btn" class="sub-nav-btn">Previous</a>
    <a href="" id="next-btn" class="sub-nav-btn">Next</a>
 </div>

You can see the whole codepen here.

See the Pen
sub nav js
by Tom (@twwoodward)
on CodePen.

In the end we get something like this. While not an exact match to Canvas it’s a decent parallel and in a pretty short turn around.
WordPress theme that looks very much like the Canvas LMS interface.

Learn Dash Gradebook Customization

Origin Story

We had a group get in touch with us who were fairly far down the road using LearnDash for a project. I recalled seeing it in the past but had never used it myself. There were a couple things they wanted it to do that they were having trouble with and so they reached out to us. Joining in on a project late in the game is never much fun. It is best to just go with the flow rather than thinking dark thoughts around any/all of the choices made prior to your arrival. That is the path I am on. Here are choices made. Proceed to find a path forward.

As I write this I also realize how much I’ve already forgotten about the wandering path this took. One more reason to keep the blog posts rolling.

The Issues

There were three major elements they wanted to work/work differently.

  1. Etherpad integration – integrate etherpad creation and sharing with the group function in LearnDash. Jeff handled this and got it working well.
  2. Quiz results/Group integration – the goal here was to show the choices made by all individuals in the same group on the completion of the quiz. I did this but am still not in the mood to write about it.
  3. Proctor Grading/Commenting – build a minimalist interface to let proctors evaluate particular assignments for the LearnDash assigned group(s)1 they supervise.

I’ll focus on this third piece in this post but some of the other stuff might get referenced. If nothing else, I hope it helps someone else wandering around in this stuff.

Where is this data?

Turns out LearnDash documentation is not what I’d hoped. So I opted to start browsing the database directly. I’m not a fan of browsing in terminal so I used SequelPro. Even with GUI help, it’s not necessarily evident where things live. Database tables are titled fairly obtuse things like wp_wp_pro_quiz_statistic_ref.2

Things started of messy. Initially there was an another group plugin active which resulted in me finding where that plugin stored group data instead of where LearnDash stored its data. So another reminder to myself to make sure I fully understand what people have done prior to starting.

After some more sifting around, I found the data for quizzes. It’s in a couple of related tables that are fairly well labeled (wp_wp_pro_quiz_statistic, wp_wp_pro_quiz_question). I won’t get into those details here but it’s nice to have an early success so you don’t fall into deep despair.

I kept looking around and never really found any good places to find gradebook or group data. That compelled me to try some other approaches. I went into the regular WordPress backend and went to the gradebook. I looked around, looked at the URL (…/wp-admin/admin.php?page=learndash-gradebook) but it wasn’t until I clicked into a student to edit the user’s grades that I saw what I needed in the URL.

…/wp-admin/admin.php?page=learndash-gradebook-user-grades&gradebook=785&user=3&return=gradebook&referrer=%2FVCU%2Fwp-admin%2Fadmin.php%3Fpage%3Dlearndash-gradebook#ld-gb-gradebook-anchor

In that tangled mess, you’ll see the number 785. If I go to wp_posts and look it up in the database I see that it has the same title as the gradebook we’re using. Good. post_type is equal to gradebook. Super. But there’s not a ton more data there.

My next stop is wp_postmeta. I’m again looking for matches where post_id = 785. I’ve got some hits.
MySql table screenshot showing a variety of metafields populated.

The field ld_gb_components seems to be where our data is held. It starts with a:12:{i:0;a:7:{s:6:”weight”;s:1:”6″;s:2:”id . . . which is serialized data. I can maybe_userialize and dump that somewhere or I can use a site like unserialize to more easily read what this stuff says. Turns out it’s useful but not really what I want or need.

A bit more fooling around and I move to the wp_users and then the wp_usermeta tables. Here we finally hit pay dirt. We have a meta_key which is ld_gb_manual_grades_785_9 and in that is another serialized chunk of data but we also have other numbers like ld_gb_manual_grades_785_3 which unserialize like so.

While I’m in here I also find the LearnDash group membership (learndash_group_users_779) and group leadership information (learndash_group_leaders_779).

So now we know where stuff lives and we know how it’s written. I end up writing emails to people like this.

Just further documenting where stuff lives . . . (this is less obvious) but it looks like controlling the gradebook through alternate means is possible.

see function add_manual_grade( $grade ) in admin/class-ld-gb-adminpage-user-grades.php

gradebook titles/additional info . . .
in table post_meta
post_id = 785
gradebook titles/components are in ld_gb_components

actual grades
in table wp_usermeta
ld_gb_manual_grades_785_(ITEM ID)

data looks like a:1:{i:0;a:4:{s:5:”score”;d:82;s:4:”name”;s:6:”asdasd”;s:6:”status”;s:0:””;s:9:”component”;s:1:”1″;}}

Why did they write the data like that?

I do not know.

It seems really strange to me to have variables in the meta_key name that are then repeated in the meta_value. It makes it a bit of a hassle to query for as well.3

I can say I ended up writing this to deal with some of the quiz stuff.

function alt_ipd_join_stats_tables_join($user_ids, $quiz_id){
	$quiz_id = (int)$quiz_id;
	global $wpdb;
	$results = $wpdb->get_results( "SELECT wp_wp_pro_quiz_statistic_ref.statistic_ref_id, wp_wp_pro_quiz_statistic_ref.quiz_id, wp_wp_pro_quiz_statistic_ref.user_id, wp_wp_pro_quiz_statistic.statistic_ref_id, wp_wp_pro_quiz_statistic.answer_data AS answer_choice, wp_wp_pro_quiz_statistic.question_id, wp_wp_pro_quiz_statistic.correct_count, wp_wp_pro_quiz_question.title, wp_wp_pro_quiz_question.question, wp_wp_pro_quiz_question.answer_data FROM wp_wp_pro_quiz_statistic_ref INNER JOIN wp_wp_pro_quiz_statistic ON wp_wp_pro_quiz_statistic_ref.statistic_ref_id = wp_wp_pro_quiz_statistic.statistic_ref_id JOIN wp_wp_pro_quiz_question ON wp_wp_pro_quiz_question.id = wp_wp_pro_quiz_statistic.question_id WHERE (wp_wp_pro_quiz_statistic_ref.quiz_id =" . $quiz_id . " AND wp_wp_pro_quiz_statistic_ref.user_id IN (" . $user_ids . ")) ORDER BY question_id ASC");
	return $results;
}

What it is

A brief interaction with the learn dash gradebook. It is a lot of colored boxes and popup windows.

This is the default gradebook interaction for manual grade entry. It’s a bit intimidating for unskilled people and doesn’t hide people that the proctors shouldn’t be grading. So our goal is to build something simple that will let them choose on a scale of 0 to 3 and insert basic comments. The interface will only show the students they supervise and will translate their 0 to 3 ratings into the corresponding 100 point scale that goes into the gradebook.

We end up with something like this. Dropdowns restrict the entry to the four choices. A comment field exists (but has been left to the other group to style). Very simple and hopefully very easy to use.
A screenshot of a grid showing a list of students with a list of assignments across the top. Dropdown elements for grades and comments fields are there for each student/assignment.

Making Stuff Do Stuff

At this point I’m just going to do a bunch of stuff to get the right data in a place. Then I’m going to enable the right people to manipulate that data. I did some very, very wrong things with key value pairs in PHP that made my life harder but my life still went on and it all works. I’ll detail how it all works below but the audience for this is even smaller than my normal content.4

The Proctor View

I added this as a shortcode that will take the ID of the current user and display the right students (assuming they have any). That allows us to stick the shortcode on a page somewhere and point all the proctors at the same URL. It simplifies making directions etc.


function ipe_proctor_view(){
	$html = '';
	$group_members = alt_ipe_get_group_members_leader();
	$proctor_scores = [];
	$gradebook_contents = get_post_meta(785,'ld_gb_components', true);//all the gradebook info - associated w post ID which you can find via https://ipecase.org/VCU/wp-admin/edit.php?post_type=gradebook
	$gradebook = maybe_unserialize($gradebook_contents);
	$proctor_assignments = [];
	foreach ($gradebook as $key => $assignment) {
		$assignment_name = strtolower($assignment['name']);//make it lower case for match below
		if (strpos($assignment_name, 'proctor') !== false ){
			array_push($proctor_assignments, array($assignment['name'] => $assignment['id']));
		}
	}
	$html = '<div class="proctor-grades"><div class="empty-cell assignment-title assignment-cell"></div>';
	foreach ($proctor_assignments as $key => $assignment) {
		$html .= '<div class="column assignment-title">' . key($assignment) . '</div>';
	}
	foreach ($group_members as $key => $member) {
		if (isset($group_members[$key-1]['group'])){
			$check = $group_members[$key-1]['group'];
		} else {
			$check = 'foo';
		}
		if ($member['group'] !=  $check && $key != 0 ){
			$html .= '<div class="cover"> <h2>'. $member['group'] .'</h2></div>';
		}
		$html .= '<div class="proctor-assignment-cell proctor-student-name">'. key($member) . '</div>';
		$user_id = $member[key($member)];
		foreach ($proctor_assignments as $key => $assignment) {
			$assignment_id = $assignment[key($assignment)];
			$score = return_assignment_score($user_id, $assignment_id);
			$comment = return_assignment_comment($user_id, $assignment_id);     
			$html .= '<div class="proctor-assignment assignment-cell">' . selected_proctor_score($score, $user_id, $assignment_id, $comment) . '</div>';
		}

	}
	//grades are in wp_usermeta at patters like ld_gb_manual_grades_785_1 (785 being the gradebook) and 1 being the item
	//print("<pre>".print_r($proctor_assignments,true)."</pre>");	
	//print("<pre>".print_r($group_members,true)."</pre>");	
	return $html;
}
add_shortcode( 'proctor', 'ipe_proctor_view' );

You can also see in functions like the one below how having variables in the metafield names led to some weird query patterns.

//GET GRP ID FROM LEARN DASH GROUPS which is in the metadata for the logged in user
function alt_ipe_get_group_members_leader(){
	global $user;
	$user_id = get_current_user_id();//get logged in user
	$user = get_user_meta($user_id);	//get user ID
	$all_users = [];
	foreach($user as $key => $value){//cycle through metadata looking for learndash partial match
	  $i = substr($key,0,24);
	 
	  if("learndash_group_leaders_" == substr($key,0,24)){ //such a mess to do partial match	   		
	   		$users = alt_ipd_get_group_users($value[0]);//get other users who have this metadata field	
	   		$group_id =  $value[0];
	   		//print("<pre>".print_r(alt_ipd_users_for_proctor_view($users),true)."</pre>");	
	   		foreach ($users as $key => $student) {
				$name =  $student->display_name;
				array_push($all_users, array($name =>$student->ID, 'group'=>$group_id));
			}
		  }
		}	
		//print("<pre>".print_r($all_users,true)."</pre>");	
	 return $all_users;//get user ids with matching groups
	}

Building our proctor score dropdown was kind of neat. It entwines with a chunk of javascript to set the variables that are referenced on change.

function selected_proctor_score($score, $user_id, $assignment_id, $assignment_comment){
	$scores = [
			'unscored'=>'unscored',
			'0 - unsatisfactory' => 50, 
			'1 - needs improvement' => 75, 
			'2 - satisfactory' => 85, 
			'3 - excellent' => 100];
	$html = '<select name="proctor-grade" data-user="'.$user_id.'" data-assignment="'.$assignment_id.'" data-comment="'.$assignment_comment.'">' ;
		foreach ($scores as $key => $value) {
			if ($value == $score ){
				$selected = 'selected="selected"';
			} else {
				$selected = '';
			}
			$html .= '<option value="'. $value .'"' . $selected . '>' . $key . '</option>';
			//  <option value="100">3 - excellent</option>
		}
	$html .= '</select>';
	$html .= '<input class="assignment-comment" type="text" name="comment" id="comment-' . $user_id . '" value="' . $assignment_comment . '">';
	return $html;
}

function updateProctorScores(){
	console.log(this.value);
	console.log(this.dataset.user);
	console.log(this.dataset.assignment);
	console.log(this.dataset.comment);
	var score = this.value;
	var assignment_id = this.dataset.assignment;
	var user_id = this.dataset.user;
	var assignment_comment = this.dataset.comment;
	jQuery.ajax({
		url : proctor_score.ajax_url,
		type : 'post',
		data : {
			action : 'update_proctor_grades',
			user_id : user_id,
			assignment_score : score,
			assignment_id : assignment_id,
			assignment_comment : assignment_comment,
		},
		success : function( response ) {
			alert('update success')
		}
	});
}


jQuery( 'select' ).change( updateProctorScores)

let commentBoxes = document.querySelectorAll('input')
commentBoxes.forEach(function(commentBox){
	commentBox.addEventListener('input', function(evt){
    console.log(this.value)
    console.log(this.parentNode.childNodes[0].setAttribute('data-comment', this.value))
  })
})


And finally the ajax stuff that knits it together.


add_action( 'wp_ajax_update_proctor_grades', 'update_proctor_grades' );

function update_proctor_grades(){
	$user_id = $_POST['user_id'];
	$assignment_id =  $_POST['assignment_id'];
	$score =  $_POST['assignment_score'];
	$comment = $_POST['assignment_comment'];
	$db_score = array();
	array_push($db_score, array('score'=>$score, 'name'=>$comment, 'status'=>'', 'component'=>1));
	$serialized = $db_score; //it appears that it's serializing it without me
		if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { 
	 	 update_user_meta($user_id, 'ld_gb_manual_grades_785_' . $assignment_id, $serialized);
	 	}
	 	die();
}

This was my scratch pad for some of the layout and javascript interactions. I didn’t end up putting some of the CSS stuff in there but it’s kind of interesting to see that you could do that easily.

See the Pen
proctor layout
by Tom (@twwoodward)
on CodePen.


1 That ‘s’ was a late addition and required some reworking of the code so I write it with a degree of emphasis.

2 I also suspect that the doubling of the wp_ prefix is because of a mistake in how some stuff is written which didn’t make me feel too optimistic.

3 BUT I AM FOCUSING ON THE THINGS I CAN CONTROL! My sub-footnote is that I actually don’t care. I am like water but people like emotion in posts and I am nothing if not an audience pleaser.

4 I believe we have achieved negative numbers here.

Learn Dash Gradebook Customization

Origin Story

We had a group get in touch with us who were fairly far down the road using LearnDash for a project. I recalled seeing it in the past but had never used it myself. There were a couple things they wanted it to do that they were having trouble with and so they reached out to us. Joining in on a project late in the game is never much fun. It is best to just go with the flow rather than thinking dark thoughts around any/all of the choices made prior to your arrival. That is the path I am on. Here are choices made. Proceed to find a path forward.

As I write this I also realize how much I’ve already forgotten about the wandering path this took. One more reason to keep the blog posts rolling.

The Issues

There were three major elements they wanted to work/work differently.

  1. Etherpad integration – integrate etherpad creation and sharing with the group function in LearnDash. Jeff handled this and got it working well.
  2. Quiz results/Group integration – the goal here was to show the choices made by all individuals in the same group on the completion of the quiz. I did this but am still not in the mood to write about it.
  3. Proctor Grading/Commenting – build a minimalist interface to let proctors evaluate particular assignments for the LearnDash assigned group(s)1 they supervise.

I’ll focus on this third piece in this post but some of the other stuff might get referenced. If nothing else, I hope it helps someone else wandering around in this stuff.

Where is this data?

Turns out LearnDash documentation is not what I’d hoped. So I opted to start browsing the database directly. I’m not a fan of browsing in terminal so I used SequelPro. Even with GUI help, it’s not necessarily evident where things live. Database tables are titled fairly obtuse things like wp_wp_pro_quiz_statistic_ref.2

Things started of messy. Initially there was an another group plugin active which resulted in me finding where that plugin stored group data instead of where LearnDash stored its data. So another reminder to myself to make sure I fully understand what people have done prior to starting.

After some more sifting around, I found the data for quizzes. It’s in a couple of related tables that are fairly well labeled (wp_wp_pro_quiz_statistic, wp_wp_pro_quiz_question). I won’t get into those details here but it’s nice to have an early success so you don’t fall into deep despair.

I kept looking around and never really found any good places to find gradebook or group data. That compelled me to try some other approaches. I went into the regular WordPress backend and went to the gradebook. I looked around, looked at the URL (…/wp-admin/admin.php?page=learndash-gradebook) but it wasn’t until I clicked into a student to edit the user’s grades that I saw what I needed in the URL.

…/wp-admin/admin.php?page=learndash-gradebook-user-grades&gradebook=785&user=3&return=gradebook&referrer=%2FVCU%2Fwp-admin%2Fadmin.php%3Fpage%3Dlearndash-gradebook#ld-gb-gradebook-anchor

In that tangled mess, you’ll see the number 785. If I go to wp_posts and look it up in the database I see that it has the same title as the gradebook we’re using. Good. post_type is equal to gradebook. Super. But there’s not a ton more data there.

My next stop is wp_postmeta. I’m again looking for matches where post_id = 785. I’ve got some hits.
MySql table screenshot showing a variety of metafields populated.

The field ld_gb_components seems to be where our data is held. It starts with a:12:{i:0;a:7:{s:6:”weight”;s:1:”6″;s:2:”id . . . which is serialized data. I can maybe_userialize and dump that somewhere or I can use a site like unserialize to more easily read what this stuff says. Turns out it’s useful but not really what I want or need.

A bit more fooling around and I move to the wp_users and then the wp_usermeta tables. Here we finally hit pay dirt. We have a meta_key which is ld_gb_manual_grades_785_9 and in that is another serialized chunk of data but we also have other numbers like ld_gb_manual_grades_785_3 which unserialize like so.

While I’m in here I also find the LearnDash group membership (learndash_group_users_779) and group leadership information (learndash_group_leaders_779).

So now we know where stuff lives and we know how it’s written. I end up writing emails to people like this.

Just further documenting where stuff lives . . . (this is less obvious) but it looks like controlling the gradebook through alternate means is possible.

see function add_manual_grade( $grade ) in admin/class-ld-gb-adminpage-user-grades.php

gradebook titles/additional info . . .
in table post_meta
post_id = 785
gradebook titles/components are in ld_gb_components

actual grades
in table wp_usermeta
ld_gb_manual_grades_785_(ITEM ID)

data looks like a:1:{i:0;a:4:{s:5:”score”;d:82;s:4:”name”;s:6:”asdasd”;s:6:”status”;s:0:””;s:9:”component”;s:1:”1″;}}

Why did they write the data like that?

I do not know.

It seems really strange to me to have variables in the meta_key name that are then repeated in the meta_value. It makes it a bit of a hassle to query for as well.3

I can say I ended up writing this to deal with some of the quiz stuff.

function alt_ipd_join_stats_tables_join($user_ids, $quiz_id){
	$quiz_id = (int)$quiz_id;
	global $wpdb;
	$results = $wpdb->get_results( "SELECT wp_wp_pro_quiz_statistic_ref.statistic_ref_id, wp_wp_pro_quiz_statistic_ref.quiz_id, wp_wp_pro_quiz_statistic_ref.user_id, wp_wp_pro_quiz_statistic.statistic_ref_id, wp_wp_pro_quiz_statistic.answer_data AS answer_choice, wp_wp_pro_quiz_statistic.question_id, wp_wp_pro_quiz_statistic.correct_count, wp_wp_pro_quiz_question.title, wp_wp_pro_quiz_question.question, wp_wp_pro_quiz_question.answer_data FROM wp_wp_pro_quiz_statistic_ref INNER JOIN wp_wp_pro_quiz_statistic ON wp_wp_pro_quiz_statistic_ref.statistic_ref_id = wp_wp_pro_quiz_statistic.statistic_ref_id JOIN wp_wp_pro_quiz_question ON wp_wp_pro_quiz_question.id = wp_wp_pro_quiz_statistic.question_id WHERE (wp_wp_pro_quiz_statistic_ref.quiz_id =" . $quiz_id . " AND wp_wp_pro_quiz_statistic_ref.user_id IN (" . $user_ids . ")) ORDER BY question_id ASC");
	return $results;
}

What it is

A brief interaction with the learn dash gradebook. It is a lot of colored boxes and popup windows.

This is the default gradebook interaction for manual grade entry. It’s a bit intimidating for unskilled people and doesn’t hide people that the proctors shouldn’t be grading. So our goal is to build something simple that will let them choose on a scale of 0 to 3 and insert basic comments. The interface will only show the students they supervise and will translate their 0 to 3 ratings into the corresponding 100 point scale that goes into the gradebook.

We end up with something like this. Dropdowns restrict the entry to the four choices. A comment field exists (but has been left to the other group to style). Very simple and hopefully very easy to use.
A screenshot of a grid showing a list of students with a list of assignments across the top. Dropdown elements for grades and comments fields are there for each student/assignment.

Making Stuff Do Stuff

At this point I’m just going to do a bunch of stuff to get the right data in a place. Then I’m going to enable the right people to manipulate that data. I did some very, very wrong things with key value pairs in PHP that made my life harder but my life still went on and it all works. I’ll detail how it all works below but the audience for this is even smaller than my normal content.4

The Proctor View

I added this as a shortcode that will take the ID of the current user and display the right students (assuming they have any). That allows us to stick the shortcode on a page somewhere and point all the proctors at the same URL. It simplifies making directions etc.


function ipe_proctor_view(){
	$html = '';
	$group_members = alt_ipe_get_group_members_leader();
	$proctor_scores = [];
	$gradebook_contents = get_post_meta(785,'ld_gb_components', true);//all the gradebook info - associated w post ID which you can find via https://ipecase.org/VCU/wp-admin/edit.php?post_type=gradebook
	$gradebook = maybe_unserialize($gradebook_contents);
	$proctor_assignments = [];
	foreach ($gradebook as $key => $assignment) {
		$assignment_name = strtolower($assignment['name']);//make it lower case for match below
		if (strpos($assignment_name, 'proctor') !== false ){
			array_push($proctor_assignments, array($assignment['name'] => $assignment['id']));
		}
	}
	$html = '<div class="proctor-grades"><div class="empty-cell assignment-title assignment-cell"></div>';
	foreach ($proctor_assignments as $key => $assignment) {
		$html .= '<div class="column assignment-title">' . key($assignment) . '</div>';
	}
	foreach ($group_members as $key => $member) {
		if (isset($group_members[$key-1]['group'])){
			$check = $group_members[$key-1]['group'];
		} else {
			$check = 'foo';
		}
		if ($member['group'] !=  $check && $key != 0 ){
			$html .= '<div class="cover"> <h2>'. $member['group'] .'</h2></div>';
		}
		$html .= '<div class="proctor-assignment-cell proctor-student-name">'. key($member) . '</div>';
		$user_id = $member[key($member)];
		foreach ($proctor_assignments as $key => $assignment) {
			$assignment_id = $assignment[key($assignment)];
			$score = return_assignment_score($user_id, $assignment_id);
			$comment = return_assignment_comment($user_id, $assignment_id);     
			$html .= '<div class="proctor-assignment assignment-cell">' . selected_proctor_score($score, $user_id, $assignment_id, $comment) . '</div>';
		}

	}
	//grades are in wp_usermeta at patters like ld_gb_manual_grades_785_1 (785 being the gradebook) and 1 being the item
	//print("<pre>".print_r($proctor_assignments,true)."</pre>");	
	//print("<pre>".print_r($group_members,true)."</pre>");	
	return $html;
}
add_shortcode( 'proctor', 'ipe_proctor_view' );

You can also see in functions like the one below how having variables in the metafield names led to some weird query patterns.

//GET GRP ID FROM LEARN DASH GROUPS which is in the metadata for the logged in user
function alt_ipe_get_group_members_leader(){
	global $user;
	$user_id = get_current_user_id();//get logged in user
	$user = get_user_meta($user_id);	//get user ID
	$all_users = [];
	foreach($user as $key => $value){//cycle through metadata looking for learndash partial match
	  $i = substr($key,0,24);
	 
	  if("learndash_group_leaders_" == substr($key,0,24)){ //such a mess to do partial match	   		
	   		$users = alt_ipd_get_group_users($value[0]);//get other users who have this metadata field	
	   		$group_id =  $value[0];
	   		//print("<pre>".print_r(alt_ipd_users_for_proctor_view($users),true)."</pre>");	
	   		foreach ($users as $key => $student) {
				$name =  $student->display_name;
				array_push($all_users, array($name =>$student->ID, 'group'=>$group_id));
			}
		  }
		}	
		//print("<pre>".print_r($all_users,true)."</pre>");	
	 return $all_users;//get user ids with matching groups
	}

Building our proctor score dropdown was kind of neat. It entwines with a chunk of javascript to set the variables that are referenced on change.

function selected_proctor_score($score, $user_id, $assignment_id, $assignment_comment){
	$scores = [
			'unscored'=>'unscored',
			'0 - unsatisfactory' => 50, 
			'1 - needs improvement' => 75, 
			'2 - satisfactory' => 85, 
			'3 - excellent' => 100];
	$html = '<select name="proctor-grade" data-user="'.$user_id.'" data-assignment="'.$assignment_id.'" data-comment="'.$assignment_comment.'">' ;
		foreach ($scores as $key => $value) {
			if ($value == $score ){
				$selected = 'selected="selected"';
			} else {
				$selected = '';
			}
			$html .= '<option value="'. $value .'"' . $selected . '>' . $key . '</option>';
			//  <option value="100">3 - excellent</option>
		}
	$html .= '</select>';
	$html .= '<input class="assignment-comment" type="text" name="comment" id="comment-' . $user_id . '" value="' . $assignment_comment . '">';
	return $html;
}

function updateProctorScores(){
	console.log(this.value);
	console.log(this.dataset.user);
	console.log(this.dataset.assignment);
	console.log(this.dataset.comment);
	var score = this.value;
	var assignment_id = this.dataset.assignment;
	var user_id = this.dataset.user;
	var assignment_comment = this.dataset.comment;
	jQuery.ajax({
		url : proctor_score.ajax_url,
		type : 'post',
		data : {
			action : 'update_proctor_grades',
			user_id : user_id,
			assignment_score : score,
			assignment_id : assignment_id,
			assignment_comment : assignment_comment,
		},
		success : function( response ) {
			alert('update success')
		}
	});
}


jQuery( 'select' ).change( updateProctorScores)

let commentBoxes = document.querySelectorAll('input')
commentBoxes.forEach(function(commentBox){
	commentBox.addEventListener('input', function(evt){
    console.log(this.value)
    console.log(this.parentNode.childNodes[0].setAttribute('data-comment', this.value))
  })
})


And finally the ajax stuff that knits it together.


add_action( 'wp_ajax_update_proctor_grades', 'update_proctor_grades' );

function update_proctor_grades(){
	$user_id = $_POST['user_id'];
	$assignment_id =  $_POST['assignment_id'];
	$score =  $_POST['assignment_score'];
	$comment = $_POST['assignment_comment'];
	$db_score = array();
	array_push($db_score, array('score'=>$score, 'name'=>$comment, 'status'=>'', 'component'=>1));
	$serialized = $db_score; //it appears that it's serializing it without me
		if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { 
	 	 update_user_meta($user_id, 'ld_gb_manual_grades_785_' . $assignment_id, $serialized);
	 	}
	 	die();
}

This was my scratch pad for some of the layout and javascript interactions. I didn’t end up putting some of the CSS stuff in there but it’s kind of interesting to see that you could do that easily.

See the Pen
proctor layout
by Tom (@twwoodward)
on CodePen.


1 That ‘s’ was a late addition and required some reworking of the code so I write it with a degree of emphasis.

2 I also suspect that the doubling of the wp_ prefix is because of a mistake in how some stuff is written which didn’t make me feel too optimistic.

3 BUT I AM FOCUSING ON THE THINGS I CAN CONTROL! My sub-footnote is that I actually don’t care. I am like water but people like emotion in posts and I am nothing if not an audience pleaser.

4 I believe we have achieved negative numbers here.

Seeking Answers: Can a Narrative Tie a Course Together?

This idea has been bouncing in my head for way too long, it’s time to get some help chasing down an answer. And I would be fine if it’s not the one I seek.

I have been contacted by a quiet, off the beaten track, but legit organization, –call them the Campbell Consortium– interested in the application of story approaches in higher education. It might be better explained in a short video.

Joe Murphey at Kenyon College has stepped forward with interest as well, and we are planning a three day workshop series in June to explore the question; hopefully we can interest some good curiosity and willingness to follow this line of questioning.

A recap is in a post following my conversation last summer with Bonni Stachowiak on Episode 218 of the Teaching in Higher Education podcast.

The question the Campbell people have (and I myself) is, what would it take to apply a storytelling approach in courses outside ones about storytelling. So I need some people to probe for an answer, even if it’s “not very possible”.

Having to explain this should compel me to write some kind of short paper / long blog post about it— this has been sitting long on the To Do shelf. What might it mean to have a narrative tie a course together?

It’s not about turning an entire course into a story, it’s more about establishing some kind of back story, or even genre/metaphor that the work students do in a class can tie into, so that it’s more than just a series of assignments (likely disposable). It’s like creating a some kind of analogue of plot continuity in film. It’s something that creates a backdrop or scene for what happens in a course. It’s making something propel the course with story methods of an arc, of building suspense, of creating something that makes you want to know what happens next.

Maybe it’s just my madness.

But here is my short list of possible examples, mostly drawn from storytelling/narrative focused classes.

  • It starts for me with the DS106 Summer of Oblivion masterminded by Jim Groom at the University of Mary Washington (UMW), spinning the class as a web version of the movie Video Drone. The course itself became a story that evolved as it went. As Andy Rush noted, “No one shaves their head in an LMS”. I remember being a skeptical of the claim that a course needed a narrative (as you can see, I changed later)
  • The following Fall Michael Branson Smith picked up with a version for York College of a DS106 built around Journey to the Center of the Internet and a mysterious Dr Olivier (some blog posts are around).
  • In the summer of 2012, I co-taught a UMW summer course version of DS106 with Martha Burtis where we built it around a cheerful summer camp called Camp MacGuffin, where there was something slightly sinister behind the scenes. Our joke was that the first thing we did in planning was design our t-shirts. We had a series of videos done as counselors in front of green screen images of camp. In the end, everything blew up in Minecraft.
  • I taught an online version of DS106 in Spring 2013 (for UMW) where there was a loose theme that, as an online teacher, I was stuck in a black and white 1960s TV show. I taught on online version in Spring 2014 for graduate students in the George Mason University Instructional Design and Technology Program (IDT). — because the students were all working in corporate firm, the theme was DS106 Goes to Work. I had a weekly video series playing out a story of the skeptical “corporate guy” (played by me) visiting the media creative guy (played by me). These were all rather low production value videos filmed at my house on iPads and laptops, edited in iMovie.
  • The 2013 summer version of DS106 led by Jim Groom started a series of versions where there was a genre for the course, so assignments were aimed at, but did not specifically have to, use that genre. That one on 2013 was around the stories of the Twilight Zone, or in this case, the DS106 Zone. Later came ones themed around The Wire (jim Groom and Paul Bond, UMW Fall 2014), Noir 106 (Jim Groom, Paul Bond, Martha Burtis, UMW Spring 2015), Tales from DS106 UMW (a comic theme co-taught by Paul Bond and Jim Groom, Fall 2015), a planned Western 106 I had hoped to co teach with Bill Genereux in 2016, eventually run as an open course), UMW Fall 2016 “The Cover Page” (Kris Shaffer), and UMW Fall 2016 “Mission 106” (Paul Bond). There’s likely more I am missing!
  • Perhaps a peak for me in playing out this concept was during a 2014-2015 Fellowship at Thompson Rivers University. Working with Brian Lamb, we ran a DS106 inspired (meaning there was blog syndication, media creation, a daily challenge) thing we called “The You Show”, an open online course aimed at TRU Faculty. I pitched this idea of it being built around a “TV Show” hosted by two technically inept hosts (played by Brian and me), who were mocked by the two “tech guys in the back” (also played by Brian and me). We had visitors each week who played guest roles (like D’Arcy Norman, Nancy White, and Tannis Morgan). As each episode progressed, the inept hosts got less inept, the logo grew refined, and the video production got better– so that by the end episode, the “tech guys” in the back were not even needed. This plot evolved as we went along; each week I would sketch out some rough notes to Brian, we’d pick a location, and mostly improv the action in one take. It was maybe an hour to film and another 2-3 hours to edit in iMovie. I started doing more with multiple cameras, assisted often by Brian’s son who would shoot extra footage on a second iPhone.
  • I brought a fair bit of this You show approach to the Networked Narratives courses (you might call it a cousin of DS106) I have co-taught with Mia Zamora. The first year, I had suggested to Mia we create some mystery in our weekly intro videos, and after a few episodes, this folded into a theme where are videos were being “hacked” by some outside entities (who later we brought into our Mirror World space where the hackers turned out to be friendly). The videos were not even part of the course content, just something we put out there to build a sense of mystery around the course. We did more of these types of videos in the next two years, more as a means to build out the “spine” approach to course planning, not really as content. Because we were in different places, these were “filmed” in Google Hangouts on Air, and lightly edited in iMovie.

But again, these are all examples for course about media making and storytelling. The challenge is to now see what it would take to find a narrative thread in other kinds of courses. And without it being a significant overhead. Maybe I do it because I am obsessive and like being a bit off center ;-) The theme need not be one of mystery or darkness either, it just ought to be something that makes assignments and activities focus on something other than completing them.

This is what I hope we can get going during our workshop at Kenyon College, plus the extra factor that I will be beaming in via video.

Is this a mad idea? What suggestions do you have? Should Kenyon just run away from me?


Featured Image: Added screenshots of my blogpost on the Teaching in Higher Ed Podcast and the ds106 site to Arnold Air Force Base image of Coherent Anti-Stokes Raman Spectroscopy system placed into the public domain as the work of a US Government agency.

In the SPLOT Footnotes it says 1. you can add your own features

I’d say a good tool is one you do not have to wait for MasterCraftCompanyInc to make a feature for you, when there are pathways to hone it yourself.

This follows up somewhat to my ode to WordPress as HyperCard re-incarnated, but also just what happens when I get an unexpected idea from reading someone’s blog posts.

A week or so ago while idling through items in my RSS feeds (apparently this is a waning mode of being informed, but I cannot climb on that train) , I was catching up John Stewart’s week in review post, mainly curious what he was working on. I saw him reference using the Easy Footnotes plugin on a WordPress site.

Maybe it’s because I am thinking about using the TRU Writer SPLOT for the NetNarr Final project reports but it rang a bell that footnotes would be something some people might want in a site that collects short papers.

I decided to give it a play, but what occurred to me is that adding this functionality does not call on any work by me to add to the template. But you can see a quick example of how it looks when published at http://splot.ca/writer/2019/618

It’s a matter of installing and activating the Easy Footnotes plugin.

Using it when writing, is a matter of starting at the point you want the footnote attached, adding an [efn_note] shortcode then the text that you want to appear in the footnote, and closing it with the ending [/efn_note] shortcode.

The text as editing showing locations of the footnote shortcode in the article published.
A variety of footnotes added, some with hyperlinks in them for the sample published at http://splot.ca/writer/2019/618

A great thing about this plugin is that it manages all the ordering and linking for the footnotes.

If it’s one thing I’d like, and maybe will tinker with, it’s that it’s a bit tedious to remember to add the footnotes shortcode. It’s possible (maybe) to write a plugin that could add an editor button that a user could click, it pops a dialog box for the footnote content, and it inserts it with the proper shortcode wrappers.

That’s for another day.

On publishing, I thought the footnotes where placed a bit close to the text and could use a smaller size, so this was added to the site via the WordPress customer as Custom CSS:

ol.easy-footnotes-wrapper {
    margin-top: 8em;
    font-size: 0.8em;
    border-top: 1px #ddd solid;
    padding-top: 1em;
}

And because this SPLOT has Customizer features to modify the prompts for the form fields, I can even add my site specific instructions for using footnotes:

Shows the editing interface to modify the prompt on the left and how it changes what is displayed to a user in the form

The openness here is that all of this is doable without me making any changes to the theme. Just plugins and use the affordances present in WordPress.

Put a footnote on that.


Featured Image: Added some icons to image by jacqueline macou from Pixabay

Weekly Web Harvest for 2019-04-21

Gravity Forms Notification to Google Spreadsheet

The idea that data can flow to different places for different purposes is one of the key concepts I want people to believe in. Different technologies and different interfaces have different affordances depending on what you’re trying to do.

In this case, we’ve built some online training for students. As part of that training they need to sign off indicating they read various rules and safety advice. We’re using Gravity Forms to collect that information. We’re going to set a special notification email that’s easier to parse in addition to the regular email that gets sent out (that one is oriented towards student confirmation and alerting the individual faculty).

Gravity Forms Notification

We’re just going to put the student email and faculty email in the subject line with a space between them. I did some fancier stuff early but went back to this when I realized what we were doing just wasn’t complex enough to justify extra drama. I set the from name to Health Hub Logger so it’d be easier to write the filter in GMail.

Notifications in Gravity Forms are pretty straight forward but you can find out more on their site.

Screenshot of the Gravity Forms notification interface indicating that the students and faculty email are in the subject line of the email via variables.

GMail Filter

Screenshot of gmail filter interface showing that I want these emails marked as read and labeled as "HealthHub".
I then setup a filter in GMail so that I could be confident that the Google Script could find these emails and that I would not really see them. I search for anything from the Health Hub Logger name and make it as read and label it with the “HealthHub” tag.

Google Script

Now comes the Google Script. I open a spreadsheet. Go to Tools>Script Editor and put in the following. After that, I set the trigger in the Google Script editor interface to run every 15 minutes.

function healthHubLogger() {
  
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheets()[0];
  
 var today = new Date();
 var dd = today.getDate()-1;
 var mm = today.getMonth()+1; //January is 0 DO NOT FORGET THIS
 var yyyy = today.getFullYear();
 var yesterday = yyyy + '/' + mm + '/' + dd;
  
  var query = "after:" + yesterday  + " label:HealthHub";// not necessary to restrict date really but I figure it's faster -- note the HealthHub label
  
  var threads = GmailApp.search(query);
  
  var allSubjects = sheet.getRange("C:C" + sheet.getLastRow()).getValues();
  var flatSubjects = allSubjects.map(function(row) {return row[0]});
  
  for (var i = 0; i < threads.length; i++) {
    var messages = threads[i].getMessages();
    //Logger.log(messages);    
    for (var m = 0; m < messages.length; m++) {
       var healthLog = [];
     
      var from = messages[m].getFrom();
      var to = messages[m].getTo();
      var time = messages[m].getDate();
      var subject = messages[m].getSubject();
      var student = subjectSplitter(subject,0);
      var faculty = subjectSplitter(subject,1);
      var mId = messages[m].getId();
      
      var mYear = time.getFullYear();
      var mMonth = time.getMonth()+1;
      var mDay = time.getDate();
      var messageDate = mYear + '/' + mMonth + '/' + mDay;
      if(flatSubjects.indexOf(subject) < 0 ) { 
        healthLog.push(from);
        healthLog.push(time);
        healthLog.push(subject);
        healthLog.push(student);
        healthLog.push(faculty);
        healthLog.push('https://mail.google.com/mail/u/0/#inbox/'+mId);
        sheet.appendRow(healthLog);      
     }
    }

  }          

}


//split subject line
function subjectSplitter(subject,num){
  var emails = subject.split(" ");
  return emails[num];
}

Now all that I need to do is share the spreadsheet with the program administrators and they have an easy way to see what’s what without having to go into WordPress or get any additional accounts.

Gravity Forms to Graph Interaction

I’ve long wanted to build more interactive digital content after being inspired by Brett Victor’s explorable explanations. I’m now finally coming to the place where my ability to build things is starting to match my desire to create them.

This particular piece of interactive content was inspired by the NYT’s You Draw It Obama article. We did a direct imitation with carbon sequestration a while ago but I liked the broader concept of guessing at something and then being presented with data. In this case we’re trying to get people to think a little harder about the good things they got out of life just by being born.

Screenshot of the submission pattern for the confronting your privilege form. Buttons from -10 to 10 are horizontally across the top and a grid of yes/no questions are below.

Form Tweaking

First I wanted to tweak the gravity form layout. Given our first question was a placement on a scale of -10 to 10, that content needed to be arranged horizontally. This is Gravity Forms and the radio button input type. So I copied the HTML into a codepen to play with it. This is the result.

See the Pen
gform layout
by Tom (@twwoodward)
on CodePen.

Results Display

Another pattern I find myself repeating is roughing out the data display portion in Codepen. I can then figure out how I need Gravity Forms/WordPress to pass the data live but it’s easier to build out the experience a bit without worrying about getting live data. Isolate your variables and gradually add complexity.

In the codepen below you can see that scores are set manually for the demo. The function goes through each score and gets the div with the same ID. It also increments the data-count +1 each time it cycles through. That allows me to set an inline CSS style for border-top to be the data-count * 8.5 (or whatever looks good). Once we have enough entries we can tone this down but prest-o change-o a bar chart using nothing but thick borders.

let scores = ['pos-1', 'pos-1', 'pos-3','neg-2','pos-1', 'pos-1', 'pos-3','neg-2', 'neg-10','neg-10','neg-10','neg-10','neg-10','neg-10','neg-10','neg-10','pos-7']

countThem(scores)
function countThem(scores){
  scores.forEach(function(score){
    let bubble = document.getElementById(score);
    console.log(bubble)
    let count = parseInt(bubble.getAttribute('data-count'),10);
    count = count+1;
    bubble.setAttribute('data-count', count);
    bubble.setAttribute('style', 'border-top:' + (count*8.5) + 'px solid #00b3be;');
  })
}
<div class="bubble-holder" id="bubble-zone">
    <div class="bubble" id="neg-10" data-count="0">-10</div>
    <div class="bubble" id="neg-9" data-count="0">-9</div>
    <div class="bubble" id="neg-8" data-count="0">-8</div>
    <div class="bubble" id="neg-7" data-count="0">-7</div>
    <div class="bubble" id="neg-6" data-count="1">-6</div>
    <div class="bubble" id="neg-5" data-count="0">-5</div>
    <div class="bubble" id="neg-4" data-count="0">-4</div>
    <div class="bubble" id="neg-3" data-count="2">-3</div>
    <div class="bubble" id="neg-2" data-count="1">-2</div>
    <div class="bubble" id="neg-1" data-count="11">-1</div>
    <div class="bubble" id="zero-0" data-count="1">0</div>
    <div class="bubble" id="pos-1" data-count="3">1</div>
    <div class="bubble" id="pos-2" data-count="0">2</div>
    <div class="bubble" id="pos-3" data-count="3">3</div>
    <div class="bubble" id="pos-4" data-count="0">4</div>
    <div class="bubble" id="pos-5" data-count="5" >5</div>
    <div class="bubble" id="pos-6" data-count="0">6</div>
    <div class="bubble" id="pos-7" data-count="0">7</div>
    <div class="bubble" id="pos-8" data-count="0">8</div>
    <div class="bubble" id="pos-9" data-count="0">9</div>
    <div class="bubble" id="pos-10" data-count="0">10</div>
  </div>

See the Pen
bubblish chart exploration
by Tom (@twwoodward)
on CodePen.

WordPress/Gravity Forms Land

Now to get the live data and integrate it all. This function gets the Gravity Form entries and creates the data structure we need to pass on to the javascript.


function gform_stepper($entry, $form){
   $search_criteria = array(
    'status'        => 'active',    
);

  $sorting         = array();
  $paging          = array( 'offset' => 0, 'page_size' => 100 );//getting most recent 100 right now
  $total_count     = 0;

  $entries = GFAPI::get_entries(4, $search_criteria, $sorting, $paging, $total_count );
  $html = '';
  $total_scores = [];
  $total_guesses = [];
    foreach ($entries as $entry) {
      if (intval($entry['gsurvey_score'])>0){
        $pre = 'pos-';//add pos if greater than 0
      }
      if (intval($entry['gsurvey_score'])<0){
        $pre = 'neg';//add neg if less than 0
      } 
      if (intval($entry['gsurvey_score']) === 0) {
        $pre = 'zero-';
      }
      array_push($total_scores,$pre . $entry['gsurvey_score']);
      $guess = array_push($total_guesses, $entry[3]);
    }    
      $gform_scores = array(          
           'scores' => $total_scores,
       );
     wp_localize_script('main-course', 'gformScores', $gform_scores); //sends data to script as variable     
}

let scores = gformScores.scores;//THIS IS THE VARIABLE PASSED via localize
countThem(scores)
function countThem(scores){

  scores.forEach(function(score){
    let bubble = document.getElementById(score);
    console.log(bubble)
    let count = parseInt(bubble.getAttribute('data-count'),10);
    count = count+1;
    bubble.setAttribute('data-count', count);
    bubble.setAttribute('style', 'border-top:' + (Math.ceil(count*3.5)) + 'px solid rgba(0, 179, 190,1);');
  })
}

The result is a form that you can submit and get results like you see below. Just the beginning of things but a step in the right direction I think.

After form submission the data is graphed and displayed. Boxes show your guess vs your actual score and combined scores of previous participants.

Gravity Forms to Graph Interaction

I’ve long wanted to build more interactive digital content after being inspired by Brett Victor’s explorable explanations. I’m now finally coming to the place where my ability to build things is starting to match my desire to create them.

This particular piece of interactive content was inspired by the NYT’s You Draw It Obama article. We did a direct imitation with carbon sequestration a while ago but I liked the broader concept of guessing at something and then being presented with data. In this case we’re trying to get people to think a little harder about the good things they got out of life just by being born.

Screenshot of the submission pattern for the confronting your privilege form. Buttons from -10 to 10 are horizontally across the top and a grid of yes/no questions are below.

Form Tweaking

First I wanted to tweak the gravity form layout. Given our first question was a placement on a scale of -10 to 10, that content needed to be arranged horizontally. This is Gravity Forms and the radio button input type. So I copied the HTML into a codepen to play with it. This is the result.

See the Pen
gform layout
by Tom (@twwoodward)
on CodePen.

Results Display

Another pattern I find myself repeating is roughing out the data display portion in Codepen. I can then figure out how I need Gravity Forms/WordPress to pass the data live but it’s easier to build out the experience a bit without worrying about getting live data. Isolate your variables and gradually add complexity.

In the codepen below you can see that scores are set manually for the demo. The function goes through each score and gets the div with the same ID. It also increments the data-count +1 each time it cycles through. That allows me to set an inline CSS style for border-top to be the data-count * 8.5 (or whatever looks good). Once we have enough entries we can tone this down but prest-o change-o a bar chart using nothing but thick borders.

let scores = ['pos-1', 'pos-1', 'pos-3','neg-2','pos-1', 'pos-1', 'pos-3','neg-2', 'neg-10','neg-10','neg-10','neg-10','neg-10','neg-10','neg-10','neg-10','pos-7']

countThem(scores)
function countThem(scores){
  scores.forEach(function(score){
    let bubble = document.getElementById(score);
    console.log(bubble)
    let count = parseInt(bubble.getAttribute('data-count'),10);
    count = count+1;
    bubble.setAttribute('data-count', count);
    bubble.setAttribute('style', 'border-top:' + (count*8.5) + 'px solid #00b3be;');
  })
}
<div class="bubble-holder" id="bubble-zone">
    <div class="bubble" id="neg-10" data-count="0">-10</div>
    <div class="bubble" id="neg-9" data-count="0">-9</div>
    <div class="bubble" id="neg-8" data-count="0">-8</div>
    <div class="bubble" id="neg-7" data-count="0">-7</div>
    <div class="bubble" id="neg-6" data-count="1">-6</div>
    <div class="bubble" id="neg-5" data-count="0">-5</div>
    <div class="bubble" id="neg-4" data-count="0">-4</div>
    <div class="bubble" id="neg-3" data-count="2">-3</div>
    <div class="bubble" id="neg-2" data-count="1">-2</div>
    <div class="bubble" id="neg-1" data-count="11">-1</div>
    <div class="bubble" id="zero-0" data-count="1">0</div>
    <div class="bubble" id="pos-1" data-count="3">1</div>
    <div class="bubble" id="pos-2" data-count="0">2</div>
    <div class="bubble" id="pos-3" data-count="3">3</div>
    <div class="bubble" id="pos-4" data-count="0">4</div>
    <div class="bubble" id="pos-5" data-count="5" >5</div>
    <div class="bubble" id="pos-6" data-count="0">6</div>
    <div class="bubble" id="pos-7" data-count="0">7</div>
    <div class="bubble" id="pos-8" data-count="0">8</div>
    <div class="bubble" id="pos-9" data-count="0">9</div>
    <div class="bubble" id="pos-10" data-count="0">10</div>
  </div>

See the Pen
bubblish chart exploration
by Tom (@twwoodward)
on CodePen.

WordPress/Gravity Forms Land

Now to get the live data and integrate it all. This function gets the Gravity Form entries and creates the data structure we need to pass on to the javascript.


function gform_stepper($entry, $form){
   $search_criteria = array(
    'status'        => 'active',    
);

  $sorting         = array();
  $paging          = array( 'offset' => 0, 'page_size' => 100 );//getting most recent 100 right now
  $total_count     = 0;

  $entries = GFAPI::get_entries(4, $search_criteria, $sorting, $paging, $total_count );
  $html = '';
  $total_scores = [];
  $total_guesses = [];
    foreach ($entries as $entry) {
      if (intval($entry['gsurvey_score'])>0){
        $pre = 'pos-';//add pos if greater than 0
      }
      if (intval($entry['gsurvey_score'])<0){
        $pre = 'neg';//add neg if less than 0
      } 
      if (intval($entry['gsurvey_score']) === 0) {
        $pre = 'zero-';
      }
      array_push($total_scores,$pre . $entry['gsurvey_score']);
      $guess = array_push($total_guesses, $entry[3]);
    }    
      $gform_scores = array(          
           'scores' => $total_scores,
       );
     wp_localize_script('main-course', 'gformScores', $gform_scores); //sends data to script as variable     
}

let scores = gformScores.scores;//THIS IS THE VARIABLE PASSED via localize
countThem(scores)
function countThem(scores){

  scores.forEach(function(score){
    let bubble = document.getElementById(score);
    console.log(bubble)
    let count = parseInt(bubble.getAttribute('data-count'),10);
    count = count+1;
    bubble.setAttribute('data-count', count);
    bubble.setAttribute('style', 'border-top:' + (Math.ceil(count*3.5)) + 'px solid rgba(0, 179, 190,1);');
  })
}

The result is a form that you can submit and get results like you see below. Just the beginning of things but a step in the right direction I think.

After form submission the data is graphed and displayed. Boxes show your guess vs your actual score and combined scores of previous participants.

H4 Widget Headers – Incorrect Nesting Fix

Nothing fancy here but I’m documenting it for the benefit of beginners or people who don’t do this at all.

It seems most of the widgets in WordPress automatically give you h4 headers when you add them to the sidebar. That seems to go against the idea of nested and orderly headers that WCAG accessibility wants. Granted, I’m not an expert and reading their documentation seems more painful than it should be. I do know that the SiteImprove checker flags it on the site I’m working on so I turned to javascript for a fix.

What I need to do is take the HTML below and remove the H4 element and replace it with a div. I add a class so it’s easier for me to repeat the style that the H4 element had.

<div id="tag_cloud-2" class="widget widget_tag_cloud">
    <h4 class="widgettitle">Tags</h4>
    <div class="tagcloud">
<!--bunch of stuff removed for simplicity's sake-->
    </div>

As is typical for me, I did it on one element first and then I move to see if I can make a more generalizable function.

if (document.getElementById("tag_cloud-2")) {
   let tags = document.getElementById("tag_cloud-2");//gets the existing widget chunk
   let div = document.createElement("div");//makes the new div
   div.innerHTML = "Tags";//sets the inner HTML of the div to tags
   div.classList.add("widgettitle");//adds the class
   tags.replaceChild(div, tags.querySelector('h4'));//gets the first h4 element of the widget div and replaces it with the div we just made
 }

Now I need to repeat this for the other 3 or 4 widgets. I could just copy it over and replace it but I’m really only dealing with two variables so making a decent function makes sense. I need to select the specific ID of the widget and I need specific replacement text. That leads to a function like this.

function replaceWidgetTitles(id,text){
  if (document.getElementById(id)) {
    let widget = document.getElementById(id);
    let newTitle = document.createElement("div");
    newTitle.innerHTML = text;
    newTitle.classList.add("widgettitle");
    widget.replaceChild(newTitle, widget.querySelector('h4'));
  }
}


replaceWidgetTitles('tag_cloud-2','Tags')
replaceWidgetTitles('recent-posts-2','Recent Posts')
replaceWidgetTitles('wpp-2','Popular Posts')
replaceWidgetTitles('archives_calendar-2','Archives')

See the Pen
replace tags h4 with div
by Tom (@twwoodward)
on CodePen.