Sliders as Inputs

Origin Story

There once was a worksheet that was meant to be used in a face-to-face scenario. The goal was indicate where you fell on a spectrum across a number cultural orientation of measures. The challenge was to transform that into something digital that could then be part of a larger conversations.

Watch the video above to get an idea of what the experience is like or this will make even less sense.

The Sliders

I like sliders as interface elements for things like these.1 You can see the HTML below that builds them or check out the Codepen for more CSS etc. That’s all pretty straight forward.

<div class="slider-container">
<div class="slide-label left">Direct Communication</div>
<div class="slide-label right">Indirect Communication</div>
<input id="directness" class="slider" max="100" min="0" type="range" value="50" />
</div>

I did have to add the input tag to the KSES allowed list to keep WordPress from stripping it out. To do that I added the following to our KSES modifier file.

$allowedposttags["input"] = array(
 "type" => array(),
 "range" => array(),
 "min" => array(),
 "max" => array(),
 "value" => array(),
 "id" => array(),
 "class" => array(),
);

Getting the Values

Now I needed some javascript to look at these sliders and record the values as the sliders were . . . slid in various directions. This little bit gets our values once we loop through the sliders that exist. Simple.

function sliderAmount(slider){
	 sliderValue = slider.value;
	return sliderValue;
}

Gravity Forms Integration

I did this via Gravity Forms because it’s fast and I’m overly comfortable with it. This is where things get a bit weird. We’re using a typical Gravity Forms to post scenario. Nothing odd there but I ended up building a shortcode to let me build the post content which is not as typical.

The javascript below is looking for our form and then writing various variables to the fields (which are hidden by CSS). The shortcode ends up looking like [dih-graph scores=”50,50,50,50,50″] with the 50s being replaced by whatever scores are recorded.

if (document.getElementById('gform_wrapper_6'))//change for prod
{
	let values = [50,50,50,50,50];
	let sliders = Array.from(document.getElementsByClassName('slider'));
	let field = document.getElementById('input_6_4');
	let title = document.getElementById('input_6_1');	
	sliders.forEach(function(slider, index) {
		slider.oninput = function (){				
				values[index] = sliderAmount(slider);
				field.value = '[dih-graph scores="'+values.join(',')+'"]';
				title.value = values.join('/');
		}

	})
}

The Shortcode

The shortcode is pretty ugly but it works. It loops through the data provided and generates the necessary HTML.

function dih_grapher_sc( $atts ) {
	$a = shortcode_atts( array(
		'scores' => '1,2,3,4,5',
	), $atts );
    $graphs = '';
    $lefts = ['Direct Communication','Monochronic','Low Power Distance','Individualism','Task Focus'];
    $rights = ['Indirect Communication','Polychronic','High Power Distance','Collectivism','Relationship Focus'];
    $scores = explode(",", $a['scores']);
    foreach ($scores as $key => $score) {
	   $graphs .= graph_builder($score, $lefts[$key], $rights[$key]);
	}
	
	return $graphs;
}
add_shortcode( 'dih-graph', 'dih_grapher_sc' );

function graph_builder($score, $left, $right){
	return '<div class="slider-container">
<div class="slide-label left">'.$left.'</div>
<div class="slide-label right">'.$right.'</div>
<p><input type="range" min="0" max="100" value="'.$score.'" class="slider" id="directness">
</p></div>';

}

Now because Gravity Forms kept insisting on running filters I didn’t want on the content, I just stuck the data in a custom post field and then appended it to the post via a filter. That seems weird now that I write it but whatever.

function filterPostForTBL($content){
    global $post;	
	if (get_post_meta( $post->ID, 'tbl_score', true )){
	  return $content . get_post_meta( $post->ID, 'tbl_score', true ) . get_post_meta($post->ID, 'discussion-prompt', true);
	} else {
		return $content;
	}
}

add_filter( 'the_content', 'filterPostForTBL');

Scroll to Entries

With our entries showing up on the same page as the form, I wanted to make sure that the person was redirected back to where they’d see other people’s entries. I did this by adding an id to the confirmation message and adding a bit of javascript to scroll to that ID.


function scrollToThanks(){
	console.log('scroll ran')
	if(document.getElementById("thanks")){
		console.log('scroll found thanks')
	  let thanks = document.getElementById("thanks");
	  let topThanks = thanks.offsetTop;
	  window.scrollTo({
		  top: topThanks,
		  left: 0,
		  behavior: 'smooth'
		});
	}
}

var callback = function(){
  // Handler when the DOM is fully loaded
  scrollToThanks();
};

if (
    document.readyState === "complete" ||
    (document.readyState !== "loading" && !document.documentElement.doScroll)
) {
  callback();
} else {
  document.addEventListener("DOMContentLoaded", callback);
}

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.

Starting to Think Through a Mapping Theme with ACF

We’re working with Dr. Nicole Turner on a mapping site that will accompany her upcoming book. There’s a lot of specifics there which we’re considering while trying to walk the fine line where what we make is also something we’ll be able to use with other people down the road. We want to generalize but not too much. I’m sketching out some early thinking here as a way to document it personally and to share it with Jeff (who’s thinking through the javascript side of things).1

Thinking About the Data

It seems that any mapping project would have three basic data types– People, Locations, and Events. People because humans are usually important in these scenarios. Locations being important in mapping and events for things that have limited duration. Matt described it well as the Who, When, and Where.

Those types could be associated with each other in multiple ways. A person might be associated with various locations and various events. Events might involve various people in various places. If I think too hard I’ll make this more complex.

The Custom Post Types & ACF Structure

I feel relatively good about those three main types so breaking down the details of what those big boxes should contain was the next consideration.

People

  • First Name
  • Middle Name
  • Last Name
  • Title
  • Description/Bio
  • Birth Event*
  • Death Event*
  • Events*
  • Locations*
  • Categories
  • Featured Image

Locations

  • Title
  • Latitude
  • Longitude
  • Description
  • Street Address
  • Events*
  • People*
  • Categories
  • Featured Image

Events

  • Title
  • Start
  • End
  • Description
  • Location*
  • People*
  • Categories
  • Featured Image

The items designated with the * are tied in via the ACF relationship field. This was something new to me but was super easy to set up. I turned on the ACF to Rest API plugin in addition to making sure these custom post types would appear in the WP REST API ‘show_in_rest’ => true.

Now I’ve got a decent interface for backend editing that makes adding connections between all these things as easy as clicking. We’ll refine things more going forward but it confirms some solid options that we can build on.
Animation showing association of one post with another.

The Data

With relationships we can either return the ID of the associated post or we can return the whole post object. Now at the REST endpoints (wp-json/wp/v2/event?_embed) for the various post types we’ll get JSON like this. I think this is going to be very nice for some of the related navigation construction.
JSON data structure showing the associated ACF post data.


1 I started to write him an email but it go very long and emails aren’t friendly to some of the formatting so I figured why not write a blog post.