Custom HTML5 video player controls

Posted by on February 22nd, 2018  •  0 Comments  •  Full Article

We had an inch of snow today. I took a brisk walk around the block on the sunny, but slippery sidewalks before getting back to work programming.

Yesterday our exercise was to remove the default html5 video player controls, then substitute a handbuilt controller. As an added challenge, they asked if we could program in a clickable playhead. They gave us a couple clues, but thinking it through was a  brain squeezer. There was a 190 pixel wide area where the progress bar walked across as the video played.

Somehow that area needed to become clickable. When clicked, the video should play at that percentage. Meaning, if you click half way across the progress bar area, the video should jump to the halfway point.

I can’t post the video here in WordPress without a lot of trouble from width properties. So instead I’m linking to it here as an external page. But the code is shown below. The magic in the  clickable progress bar is on line 157: function jumpTo(e){…}

My solution isn’t a true draggable playhead, but it does at least navigate, and displays a nice red bar when clicked.

var media = document.querySelector('video');
var controls = document.querySelector('.controls');

var play = document.querySelector('.play');
var stop = document.querySelector('.stop');
var rwd = document.querySelector('.rwd');
var fwd = document.querySelector('.fwd');

var timerWrapper = document.querySelector('.timer');
var timer = document.querySelector('.timer span');
var timerBar = document.querySelector('.timer div');
var intervalRwd;
var intervalFwd;
//removes the default html5 controls
media.removeAttribute('controls');
controls.style.visibility = 'visible';

play.addEventListener('click', playPauseMedia);

function playPauseMedia(){

  /*below fixes a problem that occurs if they try to stop
  while fast forward or fast reverse is running*/
  rwd.classList.remove('active');
  fwd.classList.remove('active');
  clearInterval(intervalRwd);
  clearInterval(intervalFwd);

  if(media.paused) {
    play.setAttribute('data-icon', 'u');
    media.play();
  } else {
    play.setAttribute('data-icon','P');
    media.pause();
  }
}//end function playPauseMedia

stop.addEventListener('click', stopMedia);
media.addEventListener('ended', stopMedia);

function stopMedia() {
  media.pause();
  media.currentTime = 0;
  play.setAttribute('data-icon','P');

  /*below fixes a problem that occurs if they try to stop
  while fast forward or fast reverse is running*/
  rwd.classList.remove('active');
  fwd.classList.remove('active');
  clearInterval(intervalRwd);
  clearInterval(intervalFwd);
}

rwd.addEventListener('click', mediaBackward);
fwd.addEventListener('click', mediaForward);

function mediaBackward(){
  clearInterval(intervalFwd);
  fwd.classList.remove('active');

  if(rwd.classList.contains('active')){
    rwd.classList.remove('active');
    clearInterval(intervalRwd);
    media.play();
  } else {
    rwd.classList.add('active');
    media.pause();

    /*
    If it hasn't yet been set, we add the active
    class to the rwd button using classList.add(),
    pause the video using HTMLMediaElement.pause(),
    then set the intervalRwd variable to equal
    a setInterval() call. When invoked, setInterval()
    creates an active interval, meaning that
    it runs the function given as the first
    parameter every x milliseconds,
    where x is the value of the 2nd parameter.
    So here we are running the windBackward()
    function every 200 milliseconds — we'll
    use this function to wind the video backwards
    constantly. To stop a setInterval() running,
    you have to call clearInterval(),
    giving it the identifying name of the
    interval to clear, which in this case
    is the variable name intervalRwd
    */
    intervalRwd = setInterval(windBackward, 200);
  }
}//end function mediaBackward

function mediaForward(){
  clearInterval(intervalRwd);
  rwd.classList.remove('active');

  if(fwd.classList.contains('active')){
    fwd.classList.remove('active');
    clearInterval(intervalFwd);
    media.play();
  } else {
    fwd.classList.add('active');
    media.pause();
    intervalFwd = setInterval(windForward, 200);//setInterval() takes two parameters: a function & milliseconds
  }
}//end function mediaForward

function windBackward(){
  if(media.currentTime <= 3){
    //rwd.classList.remove('active');
    //clearInterval(intervalRwd);
    stopMedia();
  } else {
    media.currentTime -= 3;
  }
}//end function windBackward

function windForward(){
  if(media.currentTime >= media.duration - 3){
    //fwd.classList.remove('active');
    //clearInterval(intervalFwd);
    stopMedia();
  } else {
    media.currentTime += 3;
  }
}//end function windBackward

media.addEventListener('timeupdate', setTime);

function setTime(){
  var minutes = Math.floor(media.currentTime / 60);//floor rounds down to nearest integer, if 90 seconds, returns: 1
  var seconds = Math.floor(media.currentTime - minutes * 60);//if 90 seconds, returns: 30
  var minuteValue;
  var secondValue;

  if(minutes < 10){
    minuteValue = '0' + minutes;
  } else {
    minuteValue = minutes;
  }

  if (seconds < 10){
    secondValue = '0' + seconds;
  } else {
    secondValue = seconds;
  }

  var mediaTime = minuteValue + ':' + secondValue;
  timer.textContent = mediaTime;

  //any elements width is found via: element.clientWidth
  var barLength = timerWrapper.clientWidth * (media.currentTime/media.duration);
  timerBar.style.width = barLength + 'px';
}//end function setTime()

timerWrapper.addEventListener('click', jumpTo)
var durationPercent;
function jumpTo(e){
  durationPercent = Math.floor((e.x - 273) / 2);
  media.currentTime = durationPercent * (media.duration / 100);
  timerBar.classList.add('playHead');
  console.log('xMouse: ' + e.x) + ', ' +  console.log('yMouse: ' + e.y);
}

// timerWrapper.onclick = function(e){
//   console.log('xMouse: ' + e.x) + ', ' +  console.log('yMouse: ' + e.y);
// }

 

Rotate in 3D with JavaScript

Posted by on February 21st, 2018  •  0 Comments  •  Full Article

I’m taking online classes in Javascript for a few months while I’m between contracts. I learned how to animate a cube in 3 dimensions today. It uses a library called three.js. As with Jquery libraries, we plug them in (three.js) to make things easier.

I wrote these 50 lines of code that makes it work. It’s a fun lesson, but the full credit needs to go developer.mozilla.org. A lot of that stuff makes no sense to me. I gotta be honest…some of these lessons are so far above my head I wonder sometimes if I’m totally wasting my time learning this stuff. I mean these people are geniuses, probably with masters degrees in computer science. I can write their code and make the lesson work…but full understanding would only come with doing it full time.

var scene = new THREE.Scene();

/*1. field of view in degree; 2. aspect ratio; 3. Minimum render distance; 4. Max render distance
*/
//takes 4 arguments                       1                     2                     3     4
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

/*set the camera's position to be 5 distance
 units out of the Z axis, which, like in CSS,
 is out of the screen towards you, the viewer.*/
camera.position.z = 5;

var renderer = new THREE.WebGLRenderer();//creates a <canvas>
renderer.setSize(window.innerWidth, window.innerHeight);

document.body.appendChild(renderer.domElement);//appends <canvas>

var cube;
var loader = new THREE.TextureLoader();

loader.load(
  'metal003.png',
  function (texture) {
    texture.wrapS = THREE.RepeatWrapping;
    texture.wrapT = THREE.RepeatWrapping;
    texture.repeat.set(2,2);

    var geometry = new THREE.BoxGeometry(2.4, 2.4, 2.4);
    var material = new THREE.MeshLambertMaterial( { map: texture, shading: THREE.FlatShading } );
    cube = new THREE.Mesh(geometry, material);
    scene.add(cube);
    draw();
  }//end function (texture)
);//end loader.load

var light = new THREE.AmbientLight('rgb(255, 255, 255)');//soft white
scene.add(light);

var spotLight = new THREE.SpotLight('rgb(255, 255, 255)');
spotLight.position.set( 100, 1000, 1000 );
spotLight.castShadow = true;
scene.add(spotLight);

function draw(){
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
  renderer.render(scene, camera);
  requestAnimationFrame(draw);//self loop at 60fps call
}

 

In other news, I removed my woocomerce store and shopping cart. I had zero sales from it, and it was always worrisome that I would have a purchase while I wasn’t paying attention…which could lead to an unhappy customer. I’m thinking I might try etsy at some point as some artists are selling there.

Selling my paintings is very low on my priority list right now. I think of myself as being in college full-time. I’m hitting the books every weekday, 9 to 5. The only unusual thing is that I’m not enrolled anywhere. I’m doing self study. This stuff is all free online…no need to pay a teacher. Hmmm, that might be a contributing factor to why I’m not working this quarter as a teacher. Go figure.

Christmas at Joshua Tree 2017

Posted by on February 18th, 2018  •  0 Comments  •  Full Article

I can’t spare the time to write the story of my trip this year. But it was awesome, perhaps the best jtree trip in 9 years. Good climbing trips are all about finding a good partner.

Clouds high above Vitaliy on Sexy Grandma

Clouds high above Vitaliy on Sexy Grandma

~

Vitaliy

Vitaliy on Papa Woolsey

~

me on bambi and godzilla

me on bambi and godzilla

~

Me leading Fisticuffs, 10b

Me leading Fisticuffs, 10b

~

Vitaliy following Fisticuffs

Vitaliy following Fisticuffs

~

Night photography

~

Micah following The Watershute 10B

~

Vitaliy on Orphan

~

Craig following The Watershute

~

Me leading The Watershute. First bolt is 20 feet up. It’s a scary lead but the stemming feels secure to get there.

~

Vitaliy following my hangdog lead of Heart of Darkness, 5.11

~

Rapping off Strawberry Jam outhouse rock

~

Me and some awesome friends

~

New Years eve

~

New Years eve

~

~

~

~

~

~

Doubles Ping Pong

Posted by on February 16th, 2018  •  0 Comments  •  Full Article

ping pong Alex brother

I grew up with a ping pong table in dad’s basement. Three years ago Marty and I played pong on a climbing trip at Squamish and fell in love with the game again. We were having as much fun playing pong in the evenings as we did climbing during the day. We would trash talk each other’s climbing abilities with promises of revenge at the table after dinner.

And then the battles for superiority, man that game is fun! When I returned from that climbing trip I built a table from some scrap plywood and have it in our spare bedroom. Sue and I play when we can. This is a picture of a  friend who came over to play. So when I heard that our new community center had ping pong twice a week I decided it would be nice break from studying and a good way to get some exercise.

When I walked in I was a little alarmed to see people my age, and older. One woman was 73, and one of the guys has a stiff back.  But I try to never judge a book by it’s cover and was soon batting the ball around. They specialize in doubles in this gym, which I have rarely played. That first day, I ran over my partner going for a ball and knocked her to the ground. Everyone looked shocked, as if I’d committed a horrible sin. I felt terrible and apologized of course, though in my sports: climbing, skiing, hiking,  windsurfing, volleyball, falls are normal.

She got up slowly and kept playing, saying she was well padded. But I could see my first day with these people had got off to a bad start. Despite being seniors, several of them are a lot better than me. They have wicked serves and slams that rarely miss. Some of them are really good!

In doubles, you only hit every other ball. So if your partner is serving, you stand back. As soon as  the ball goes over the net, the table is yours. You hit the returning ball and dash out of the way. Your partner hits the next one while you stand behind them…or at least out of their way. Then it all repeats.

Often, because you are behind your partner when they are hitting the ball, you have no idea what has happened. Your partner hits, then circles around behind you while you assess the situation: where is the ball, and where is it headed…assuming the other side has managed to hit it back. It requires intense concentration to be in the right place to return the ball, while simultaneously avoiding colliding  with your partner, who is hovering nearby, getting ready to return to play.

I’d liken doubles table tennis (it’s official name) to a dance. When it’s done well for several rallies there is a beauty that builds up…a sense that magic is happening and we all try to keep it going for as long as possible. I know it all sounds a bit stupid, batting a little white plastic ball around a table. But after a couple hours I’m sweating and wishing I’d worn shorts and a tank top, in February.

Bouncing ball with collision detection

Posted by on February 14th, 2018  •  0 Comments  •  Full Article

Self Portrait 1989 charcoal and white conte crayon

The drawing above is a self portrait done so long ago I can’t even remember the year. I have dark hair, so it was probably during my twenties or thirties.

I used to program games in flash that had rockets flying in space. They had motors that fired, directional thrusters, variable velocity, collision detection, gravity, friction and bounce. But flash is dead…or at least it doesn’t work on phones, and I like to make things that work on phones.

Now I’ve just finished another excellent lesson at https://developer.mozilla.org It was an applied application of OOP as it is used in JavaScript. It didn’t make a rocket, but it did make a bunch of balls that change colors when they collide. I added in a drop list that lets you change the number of balls. I got it to show up here in wordpress via the shortcoder plugin.

Here is the code if you are curious: https://codepen.io/markhwebster/pen/VQzyJM

Programmed a working calendar for journal

Posted by on February 12th, 2018  •  0 Comments  •  Full Article

On February 3 I posted the working example from a calendar lesson I had learned in JavaScript. All it did was generate months of the year with the correct number of days, depending on which month you selected. It was dumb but it got me thinking.

As I moved forward in my JavaScript studies I was mulling over a way to make use of that calendar. To the right of this story you will see two calendars. The top one is a free WordPress plugin. I didn’t build it, I just installed the plugin.  It lets you switch between journal entries going back to 2011 when I first started using WordPress. All the post entry links are automatically populated from the WordPress database.

But I was keeping an online journal long before they invented WordPress. I have been writing since late 1997,  a year after I got my first computer. There was a story that year in the Seattle times about a woman who was keeping an online journal. She started it in ’97, and it inspired me to do the same. But as my journal grew it was difficult to build a navigation system back through the entries. I’ve struggled with a number of solutions, as you will see if you prowl around.

The one I used the longest was a flash calendar that I built from scratch. But even I refuse to download flash anymore. So if I was on a smartphone,  I couldn’t navigate my own journal until just recently.

My solution? Build a real working calendar in Javascript and embed it on every page , or at least the pages starting at September 2001, which is when I changed the interface drastically. To get it to work I had to create a JavaScript array for every year, about 10 of them.

As you can see to the right, I also embedded that new JavaScript calendar right here in WordPress below the plug in calendar. I got it to appear by using a WordPress plugin called shortcoder. The shortcoder plugin presents itself as a box in which you can paste html, css and js. Then it gives you a short little snippet of code, called a shortcode. That shortcode references the longer code. I pasted the shortcode snippet into a standard text widget, and dropped the widget into my WordPress sidebar.

You can see it all working here. It’s not search engine friendly yet…I have to think about that. I’ve linked to the contents (hand built index of the journal) page. It has links out to most of the good writing, perhaps that will negate the fact that a dynamically generated calendar can’t be spidered by google.

I’m fairly happy with this solution. It’s not perfect, I think Jessamyn does a better job of archiving, but at least mine was a fun way to practice my JavaScript.

Here is the array for the year 2000. Note that I didn’t start writing until March of 1999, or, at least that is the first named file: three99.html

/**************
begin 1999
****************/

//makes an array of the names of the html files for the year.
var calendar1999 = ['three99', 'four99', 'five99', 'six99', 'seven99','eight99', 'nine99', 'ten99', 'eleven99', 'twelve99'];

function createMonths1999(){
  //captures the <ul id="joynal1999">
  var joynal1999 = document.getElementById('joynal1999');//
  for(let i = 0; i < calendar1999.length; i++){

    //makes virtual <li></li> but doesn't use it in DOM yet
    var listItem = document.createElement("li");

    //makes virtual <a></a> but doesn't use it in DOM yet
    var anchorTag = document.createElement("a");

    /*makes a snippet of text containing a number based on loop iteration
    first one will be 0+4 = 4 which is April of 99...later found entries in March, and changed it to 3*/
    var monthNumber = document.createTextNode(i+3);

    //<a>4</a>//inserts 4 into virtual anchor tag
    anchorTag.appendChild(monthNumber);


    //adds the href property to the virtual element <a href="four99.html">4</a>
    anchorTag.setAttribute('href', calendar1999[i] + '.html');

    // <li><a href="four99.html">4</a></li>
    listItem.appendChild(anchorTag);

    //inserts the virtual "li" with clickable month into <ul> dom element so it can be seen
    joynal1999.appendChild(listItem);
  }//end month maker for loop
}//end createMonths99 function

Next I modified the simpler calendar function from a week ago so that it dynamically generated the months for the selected year, complete with clickable links for each month. But there was a problem. If you selected say, April of 2004, when you got there, the calendar did not display. You had to choose a year again. This was bad usability. I wanted the calendar to always display all the months of the year you were reading, no matter which month you were reading. But that meant getting the pages to talk to each other. The technical term is: passing data between html pages. I had a vague idea that it might be doable with cookies, but I’ve never baked cookies.

Google to the rescue! I discovered there is a way to store and pass variables between pages that have JavaScript. It’s called localStorage. You can write to it, and pull data from it. As long as they are on a long session on your website, you can make your html pages pass data from page to page.

The first line below listens for you to make a choice from the drop list. If you do ‘change’ it, the script fires off the ‘createYear‘ function.

That function puts the year you chose into a variable called ‘choice‘. At the same time, it places that year into session memory via the localStorage.setItem command. This is like registering at a hotel and telling the concierge that you would like the Seattle Times news paper each time you walk in. He remembers your preference during your session at the hotel…all week long.

As you surf from month to month, the concierge (localStorage data) remembers your year preference and keeps that year’s calendar visible

Next I do a couple checks to see whether this is your second journal month choice, or if you just arrived and haven’t made any previous choices. If the latter, I present the first years calendar, which is 1999.

Further down I have a switch-case conditional that fires off the correct createMonths function for the year you selected.

select.addEventListener('change', createYear);

function createYear(){
  if(select.value) {  //does data exist?
      var choice = select.value;  //ie: 2001
      localStorage.setItem('storageName',choice);// hold on to year choice like a cookie but in virtual session

     //empties out all <ul> from <div id="ulHolder">

      ulHolder.innerHTML = '';
  } else if (localStorage.getItem){// we found from session memory they've chosen a year on a previous page
      ulHolder.innerHTML = '';
      choice = localStorage.getItem('storageName');
  } else {// this is only true on first visit to joynal, new session
    ulHolder.innerHTML = '';
     choice = '1999';
  }
  // create ul
  var makeUl = document.createElement("ul");

  // <ul id="joynal1999"></ul>
  makeUl.setAttribute('id','joynal' + choice);
  makeUl.setAttribute('class', 'calendar')

  ulHolder.appendChild(makeUl);


/*
They drop the select drop list and make
a 'choice' of a year. We capture the year in the
'choice' variable.
So they 'switched' it and
we are examining the variable 'choice'
to see what we should do based on it's value
*/
  switch (choice) {
//in 'case' 'choice' is = to 1999
    case '1999':
    createMonths1999();
    h1.textContent = '1999';
    break;

    case '2000':
    createMonths2000();
    h1.textContent = '2000';
    break;

    case '2001':
    createMonths2001();
    h1.textContent = '2001';
    break;

    case '2002':
    createMonths2002();
    h1.textContent = '2002';
    break;

    case '2003':
    createMonths2003();
    h1.textContent = '2003';
    break;

    case '2004':
    createMonths2004();
    h1.textContent = '2004';
    break;

    case '2005':
    createMonths2005();
    h1.textContent = '2005';
    break;

    case '2006':
    createMonths2006();
    h1.textContent = '2006';
    break;

    case '2007':
    createMonths2007();
    h1.textContent = '2007';
    break;

    case '2008':
    createMonths2008();
    h1.textContent = '2008';
    break;

    case '2009':
    createMonths2009();
    h1.textContent = '2009';
    break;

    case '2010':
    createMonths2010();
    h1.textContent = '2010';
    break;

  }//end switch(choice) case


}//end function createYear()

In other news, Sue and I skied at Paradise yesterday. This was only her second trip of the year. She is still babying her rotator cuff surgery. It was a nice one inch covering of powder over a hard base. It wasn’t  grabby, and was lovely skiing, not counting the water runnels, which were as bad as moguls in spots. I went back up for another run, but the fog had settled in, masking the earlier bright blue sky. I turned around before Pan, and finished out the day.

Today I bought a $15 bouquet but discovered I’ve forgotten how to draw. Jeez! I’ve been so lost in programming I’ve not only forgotten how to climb, I can’t draw either. How come I can’t be good at everything? It would be so handy. Perhaps when I get a full time job again and get settled in I can get back to my hobbies. I miss them.

To keep from going crazy with all this studying I’ve been exercising every day and playing a fair amount of ping pong. Last week I played at the local community center with a bunch of seniors. We played doubles, which involves a very intricate dance around the end of the table. You have to hit the ball, but only every other time. This means you hit the ball, then run backwards to get out of the way so your partner can hit it…and repeat. It was so much fun I went to another public game out on 76th.

This was a beast of a different color. They were people of all ages, and they were serious, with a capital S! They literally wiped the floor with me. I lost.every.single.game. That’s nine games in a row, but who is counting? I walked out after an hour. I mean, what was the point? I love the game, but I don’t have the time to develop that kind of skill. I am already neglecting too many hobbies, don’t need to add ping pong to the list. I won’t stop playing, but I will avoid playing with people who live and breath the game.

OOP studies and a sold painting

Posted by on February 8th, 2018  •  0 Comments  •  Full Article

Crooked River Smith Rocks

Crooked River Smith Rocks SOLD

I spent today working through my first Object Oriented Programming exercise in JavaScript. Actually, I spent 2 hours of the day framing a painting for a climbing friend. We met at Jtree, and she came to the house and fell in love with this painting. She picked it up today, and I went back to programming. The code below will only work if it’s in  a script tag embedded in the <body> element. Then you go to the developer tool console of the browser. Finally, you type person1.bio() and press enter.

It will trigger an alert box that says: “Bob Wayno is 35 years old. He likes music, knitting, skiing, climbing, painting, inventing, and sewing. Shoe size is: 15 and their favorite color is orange.” OOP programming is a stepping stone towards some of the higher end languages I plan to pursue, such as React, AngularJS and Node.js.

If this sounds like a bunch of geek speak move on to the next blog entry. I posted it here because I want to be able to search for it later, and read the code with syntax highlighting. I’m building up some code libraries of my own to refer back to.

//constructor class function names are *Capitalized*

/* When an object instance is created from a class, the classes
constructor function is run to create the instance. This is
called instantiation. The object instance is
instantiated from the class. In console type:
person1.bio()
*/
//"function Person" is an class (object template)!
function Person(first, last, age, gender, interests, shoeSize, favColor) {
  this.name = {
    'first' : first,
    'last'  : last
  };
  /*the word age on The right side  = what ever string comes
   in from the object instance via the console.person1.bio() command */
  this.age = age;
  this.gender = gender;// gender = Person[3]
  this.interests = interests;

  /*the shoeSize in the parameters listed above Person[5] gets
  replaced with whatever comes in from
  when the object instance  gets used (instantiated)*/
  this.shoeSize = shoeSize;
  this.favColor = favColor;

  this.bio = function() {
    var pronoun;
    var hobbies = '';
    if (this.gender === 'male'){
      pronoun = 'He';
    } else if (this.gender === 'female'){
      pronoun = 'She';
    } else {
      pronoun = 'This person';
    }
    for (let i = 0; i < interests.length; i++)
    {
      if(i === interests.length-1){
        hobbies += 'and ' + interests[i];
      } else {
      hobbies += interests[i] + ', ';
      }
    }
      var myString = this.name.first + ' ' + this.name.last
      + ' is ' + this.age + ' years old. '
      + pronoun + ' likes ' + hobbies + '. Shoe size is: '
      + shoeSize + ' and their favorite color is '
      + favColor + '.';
      alert(myString);
  };//end bio function
  this.greeting = function(){
    alert('Hi! I\'m ' + this.name.first + '.');
  };
}
// person1 is an object instance created  from the Person class
var person1 = new Person(
  'Bob', 'Wayno', 35, 'male',
  [
    'music',
    'knitting', 'skiing', 'climbing', 'painting', 'inventing',
    'sewing'
  ], 15, 'orange'
);//end new Person
  if (person1[3] === 'male'){
      person1[3] = 'He';

} else if (person1[3] === 'female'){
           person1[3] = 'She';
  } else {
  person1[3] = 'This person';
  }//end if gender === ?

 

Vantage and an image gallery

Posted by on February 6th, 2018  •  0 Comments  •  Full Article

I studied Sunday so I could play hooky with Christine on Monday at Vantage. Chris was super strong and led Sinsemilla 10c and George and Martha cleanly.  I was very rusty after 6 weeks of not climbing. I can’t afford the climbing gym so my arms have become weak. As I said after lowering down from hanging on Pony Keg: “That was not the climber who came back from Joshua Tree on January 2!”

Chris: “I know, I wanted to meet that guy.”

Me: “He is gone, and I wonder if he ever really existed. Perhaps that was just a fairytale from some bizarre alternate universe.”

Such is my life as a 64 year old rock climber. It’s a sport for young people. Trying to do it at my advanced age of decrepitude requires a lot more work than it does for young people. I do the work, and happily, but it requires climbing every weekend, or 5 days straight, whichever comes first.

So now I’m back to my JavaScript studies at this super cool free website:

https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Image_gallery

I’ve been using Evernote to take notes of the stuff I’m learning. But Evernote doesn’t have syntax highlighting. It’s going to clog up this blog with a bunch of code…but I suspect this blog never gets read by anyone but me anyway. Probably I screwed up the search engine functionality or something. Not that I care enough to sort it out. I have bigger priorities, like learning high end JavaScript.

So here is my latest “homework”. I built a barebones image gallery switcher. It’s too boring to show the working example in an iframe. But this is the code that makes it work, which is the fun part for me. On my main website, I use a awesome image gallery function called Photoswipe. You can see it here.

Whoever built that had the job that I want.

Meanwhile, back on the farm, this is the code for the one I built in my lesson. They gave me the images, html and JavaScript. But they only gave me clues as to how to write the script. I’m fairly happy I figured it all out. Baby steps…

Here is the html:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">

    <title>Image gallery</title>

    <link rel="stylesheet" href="style.css">
    
  </head>

  <body>
    <h1>Image gallery example</h1>

    <div class="full-img">
      <img class="displayed-img" src="images/pic1.jpg">
      <div class="overlay"></div>
      <button class="dark">Darken</button>
    </div>

    <div class="thumb-bar">


    </div>
    <script src="main.js"></script>
  </body>
</html>

And here is the script that I wrote:

// the big images in the gallery
var displayedImage = document.querySelector('.displayed-img');

//the div that contains the dynamically generated thumbs
var thumbBar = document.querySelector('.thumb-bar');

btn = document.querySelector('button');

//a dumb transparent overlay
var overlay = document.querySelector('.overlay');

/* Looping through images in the image folder*/
var allImages = ['pic1', 'pic2', 'pic3', 'pic4', 'pic5'];

for(let i = 0; i < allImages.length; i++){

  //makes a virtual <img > tag
  var newImage = document.createElement('img');

  //changes to <img src="images/pic1.jpg">
  newImage.setAttribute('src', 'images/' + allImages[i] + '.jpg');

  /* below line changes vritual image  class to
  <img src="images/pic1.jpg" class="pic1">
  Have to add 1 to i because in first loop i = 0, but the first
  item in the array, which I'm trying to access, is pic1  */
  newImage.classList.add('pic' + (i+1));

  //adds the new <img ...> to dom at bottom inside of div.thumbBar
  thumbBar.appendChild(newImage);

  /*thumbClass = pic1. Now that I have image on the displayedImage
  I need to grab it's class so I can talk to it*/
    var thumbClass = newImage.getAttribute('class');

    // finds the new <img ...> based on the class added in the loop
    var thumbReady = document.querySelector('.' + thumbClass);

    //makes the new <img ...> be clickable and fires showBig
    thumbReady.addEventListener('click', showBig);


  function showBig(){
    //changes big image to match thumbnail clicked
    //changes path of big image to images/pic1.jpg
    displayedImage.setAttribute('src', 'images/' + allImages[i] + '.jpg');
  }
}//end for loop
/* Wiring up the Darken/Lighten button, not important*/
btn.addEventListener('click', darkLight);
function darkLight(){
  if(btn.getAttribute('class') === 'dark'){
    btn.setAttribute('class','light');
    btn.textContent = 'Lighten';
    overlay.style.backgroundColor = 'rgba(0,0,0,0.5)';
  } else {
    btn.setAttribute('class','dark');
    btn.textContent = 'Darken';
    overlay.style.backgroundColor = 'rgba(0,0,0,0)';
  }
}

 

Programming in JavaScript

Posted by on February 3rd, 2018  •  0 Comments  •  Full Article

I’ve been studying JavaScript full time for a month. Sounds boring, but it’s something that should help make me a more valuable programmer. I take lots of breaks as they say it’s not healthy to stare at a computer for too long. I run downstairs for more coffee, walk out to the shed to do pull ups, do my 50 daily sit ups, or pedal my schwinn airdyne for a lunch break.

I like the way JavaScript blends my existing web knowledge with dynamic functionality. I was working on a calendar lesson that dynamically populates a calendar with the correct number of days in a month. I didn’t like the example in the lesson because they had 4 days in a week, which was just dumb.

I’ve been keeping an eye on the latest trends and remembered that css grids could make a nice calendar. Here it is, with the code down below. I also added in a random color generator for the days. Click a day, and the background color of that list item will change.

The relevant html:

    <select id="theCalendar">
      <option value="">--choose--</option>
      <option value="january">January</option>
      <option value="february">February</option>
      <option value="march">March</option>
      <option value="april">April</option>
      <option value="may">May</option>
      <option value="june">June</option>
      <option value="july">July</option>
      <option value="august">August</option>
      <option value="september">September</option>
      <option value="october">October</option>
      <option value="november">November</option>
      <option value="december">December</option>

    </select>

 

The important css:

    
     #calendar {
      display: grid;
      grid-template-columns: 14.08% 14.08% 14.08% 14.08% 14.08% 14.08% 14.08%;
      grid-template-rows: 44px 44px 44px 44px 44px;
      grid-row-gap: 1px;
      grid-column-gap: 1px;
      background-color: rgb(180, 181, 181);
      margin: 0;
      padding: 0.2em;
      /* width: 370px; */
    }
    #calendar li {
      width: auto;
      height: auto;
      background-color: white;
      display: flex;
      align-items: center;/*vertical*/
      justify-content: center;/*horeeeeezontal*/
      margin: 0;
      padding: 0;
    }

 

The JavaScript:

var select = document.querySelector('select');
var h1 = document.querySelector('h1');

//begin random number generator
function random(number) {

  // the plus 1 keeps the number 1 or higher
  return Math.floor(Math.random()*(number+1));
}
 function bgChange(){
   // ie: rgb(200, 156, 50)
  return 'rgb(' + random(255) + ', ' + random(255) + ', ' + random(255) + ')';
}

var calendar = document.getElementById('calendar');



select.addEventListener('change', createCalendar);
var days = 0;
function createCalendar(){
  var choice = select.value;
  calendar.innerHTML = '';
  if(choice === 'january' || choice === 'march' ||
  choice === 'may' || choice === 'july' ||
  choice === 'august' || choice === 'october' ||
   choice === 'december' ) {
    days = 31;
  } else if (choice === 'april' || choice === 'june' ||
   choice === 'september' || choice === 'november'){
    days = 30;
  } else if (choice === 'february'){
    days = 28;
  } else {
    days = 0;
  }
  for(let i = 1; i < days+1; i++){//darn start at zero problem fixed by adding 1

  //makes virtual <li></li> but doesn't use it in DOM yet
    var listItem = document.createElement("li");

    //makes a virtual text element from current value of i, but doesn't use it in DOM yet
    var whichDay = document.createTextNode(i);

    // appends or inserts day of month  "i" in virtual <li>i</li>
    listItem.appendChild(whichDay);

    //inserts the virtual "li" with day of month into <ul> dom element so it can be seen
    calendar.appendChild(listItem);
  }


  //console.log(days);
  //console.log(choice);
  switch (choice) {
    case 'january':
      h1.textContent ='January';
      break;
    case 'february':
      h1.textContent ='February';
      break;
    case 'march':
      h1.textContent ='March';
      break;
    case 'april':
      h1.textContent ='April';
      break;
    case 'may':
      h1.textContent ='May';
      break;
    case 'june':
      h1.textContent ='June';
      break;
    case 'july':
      h1.textContent ='July';
      break;
    case 'august':
      h1.textContent ='August';
      break;
    case 'september':
      h1.textContent ='September';
      break;
    case 'october':
      h1.textContent ='October';
      break;
    case 'november':
      h1.textContent ='November';
      break;
    case 'december':
      h1.textContent ='December';
      break;
    default:
      h1.textContent ='make a choice';
  }
  // rgb(200, 156, 50) begin random color clicker function on <li>


   //cycles thru all the <li> and makes an array
  var lis = document.querySelectorAll('li');

  //loops thru all the <li> and sets up onclick
  for (let i = 0; i < lis.length; i++){
    lis[i].onclick = function(e) {
      /*'e' basically means 'this'. So they click a calendar day,
      the <li>, via this/e feels it. To the <li> it adds style="background-color: rgb();"
      and uses an rgb value of bgChange(), which is a function that
      returns rgb(200, 156, 50), or random values of those
      */
      e.target.style.backgroundColor = bgChange();
    }

  }

}//end createCalendar function

 

Made a duet and a game

Posted by on January 26th, 2018  •  0 Comments  •  Full Article

I learned some filmmaking skills 3 years ago and I like to dust the rust off every now and then. I enjoy the technology and the tools of the trade: DSLR cameras, lenses, lights, clapper boards and double system sound recording.  Editing is also a lot of fun. Making something beautiful that didn’t exist before appeals to the creative side of my personality. Premier is a fine piece of software. I like the challenge of perfectly syncing multiple tracks, and then playing director in the multi-cam window.

In the video below, I set up lights, tripods and cameras, white balanced everything, recruited talent (my wife), performed and recorded 4 video tracks, 2 audio files. Then blended them all together.

The challenging part was syncing the second shoot with the first shoot. They happened a half hour apart, so there was no way to sync with a  clapper board sound. The earpiece I’m wearing is playing the audio from the first take, while I’m recording the second take. I’d never done this before but I think it’s called dubbing. I had to sync the two takes together by looking at the audio waves in Premiere, while listening to the two takes playing simultaneously as layers in the sequence. There were 4 video layers and 6 audio layers. Four of the audio layers would later be deleted as they were from the cameras. But the two Tascam DR-40 digital audio recorder files had to match perfectly with their video files. Long story short, I was able to get the harmonica sound close, then nudge the audio file left and right one frame at a time until it synced, using the keyboard shortcut of Alt + left or right arrow keys.

My other challenge was setting up the multi-cam sequence. It’s too complicated to explain here, but once it’s set up and running, editing  multiple cameras into one final cut is a breeze. A common problem you will face if you try this is that multi-cam window likes to have the audio follow the video. Meaning, if you click camera 3, the sound switches to the audio from camera 3. In double system sound, this doesn’t work, because you delete all the camera audio tracks after syncing, they are low quality.

Instead, you bring in audio from your digital sound recorder, in my case, a Tascam DR-40, which had feed from two real microphones seen in the video below. So I had 4 video tracks, and two audio tracks. When I went to multi-cam window, it would only play the top audio track, which was the guitar and ukulele. It ignored the harp audio track. Even with ‘audio follows video’ unchecked. My solution was to copy the synced audio track from the main layered sequence and paste it into the multi-cam sequence in  a new audio layer. So now I had one video layer and two audio layers, plus I had all 4 videos displaying at the same time, in little thumbnails. All I had to do then was play the video, and click thumbnails in realtime, director style. If you’ve never seen it, go to youtube and search for premiere multi-cam window. It’s awesome.

I also like to listen to music the old fashioned way, by making it myself or with friends. I come from a long line of amateur musicians. Making music was a tradition anytime our family got together for as long as I can remember.  Grandma used to teach piano, but when she became blind in her nineties she still wanted to participate in our sing alongs. Me and my cousin would sit on either side of her. My cousin would sing, and I would play songs on my harmonica, the one in this video. Actually, that 1967 Hohner harmonica got old, so I recently replaced it with a Suzuki SCX-48 Chromatic in the key of C. It is a lovely instrument, but you can be the judge.

 

My Javascript studies are going well. I learned how to build a number guessing game here:

https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/A_first_splash

I’ve got it embedded in an iframe, but if that doesn’t work, here is a direct link. Hint: if you turn on developer tools, I console.log out the random number…so you can cheat.

The tutorial was fun, but as I was working it occurred to me that it could form the backbone of an animated hangman game. I don’t play computer games, life seems to short. But I wanted this game to work, and I wanted it to be pretty.  A lot of what I wanted in the game was functionality that I didn’t know how to program. I found the answers with simple google searches. It is 600 lines of code, including the html, css and JavaScript. I wrote every.single.line.

Here is the game on codepen.

While I was programming the game, I fell in love with a new free code editor called Atom. I like it better than both brackets and sublime. I need to get back to studying Javascript. But if you are curious how I built my hangman game, here is the Javascript code:

  document.querySelector('.dropmenu a').addEventListener('click', toggleMenu); 
  
  function toggleMenu(){
    //e.preventDefault;//fixes safari ios bug
    document.querySelector('.leveltwo').classList.toggle('fold');
    //document.querySelector('.boxMinus').style.visibility = 'visible';
    document.querySelector('.boxMinus').classList.toggle('boxHide');
    document.querySelector('.boxPlus').classList.toggle('boxHide');
  }



//create variables from svg id elements
  var skyBlue = document.getElementById('skyBlue');
  var niteSky = document.getElementById('niteSky');

  var base1 = document.getElementById('base1');
  var base2 = document.getElementById('base2');
  var base3 = document.getElementById('base3');
  var base4 = document.getElementById('base4');
  var base5 = document.getElementById('base5');//mans face
  var base6 = document.getElementById('base6');//mans spine
  var base7 = document.getElementById('base7');
  var base8 = document.getElementById('base8');
  var base9 = document.getElementById('base9');
  var base10 = document.getElementById('base10');
  var man = document.getElementById('man');
//group of the entire hanging man
  var happy = document.getElementById('happy');// hidden group 
  /*
make an array from the svg parts variables so they 
can be called numerically via hangman[0], hangman[1], etc
   hangman[0] = base1 ; hangman[9] = base10 ; hangman.length = 10
*/
  var hangman = [
base1, base2, base3, base4, base5, base6, base7, base8, base9, base10
];
  //console.log(hangman);
  /*******************
    below = create variables from each keyPadButton
  *****************/
  var k1 = document.getElementById('k1');
  var k2 = document.getElementById('k2');
  var k3 = document.getElementById('k3');
  var k4 = document.getElementById('k4');
  var k5 = document.getElementById('k5');
  var k6 = document.getElementById('k6');
  var k7 = document.getElementById('k7');
  var k8 = document.getElementById('k8');
  var k9 = document.getElementById('k9');
  var k0 = document.getElementById('k0');
  var backUp = document.getElementById('backUp');
  /***** begin number pad function ******/
  var keyPad = [k1, k2, k3, k4, k5, k6, k7, k8, k9, k0]
//make keyPad array from keyPad buttons
/************* 
create eventListeners for each keyPad button that fire 
the keyPadPress function 
**************/
  for (var kp = 0; kp < keyPad.length; kp++){
    keyPad[kp].addEventListener('click', keyPadPress);
  }
  /**************
capture number on button and put it into guessField
******/
  function keyPadPress(mw) {
   //console.log('button pressed');
    mw.preventDefault();
   guessField.innerHTML += this.textContent;
/*
permits multiple digit numbers using = what it what plus what was added
*/
   var guessFieldLength = guessField.innerHTML.length;
   /*
console.log('total characters in guess field is: ' + guessFieldLength);
*/
  }
  backUp.addEventListener('click', backUpCursor);
  function backUpCursor(bw){
    //console.log ('backup cursor function fired');tag.innerText.length
    bw.preventDefault();
    let totalNumbers = guessField.innerText.length;
    /*
console.log('length of guess field during backup  is ' + totalNumbers);
*/
 guessField.innerHTML = guessField.innerHTML.substring(0,totalNumbers-1);
//strips off last number
  }
/**********end number pad function***********/
  var randomNumber = Math.floor(Math.random() * 100) +1;

  var guesses = document.querySelector('.guesses');
  var guessesRemaining = document.querySelector('.guessesRemaining');
  var lastResult = document.querySelector('.lastResult');
  var lowOrHi = document.querySelector('.lowOrHi');
  var theArrowUp= document.querySelector('.theArrowUp');
  var theArrowDown= document.querySelector('.theArrowDown');
  var guessSubmitButton = document.querySelector('#guessSubmitButton');
//the submit button
  //var guessField = document.querySelector('.guessField');
//the div  where they enter guesses
  //console.log(hangman[9]);//reads base10
  var guessCount = 1;
    //console.log("guessCount before game is = " + guessCount);
  var resetButton;//creates empty variable
  var guessesRemainingCount = 11;

  //button eventListeners:
  guessSubmitButton.addEventListener('click', checkGuess);
//if they click submit button, run the checkGuess()
  //document eventListeners:
  window.addEventListener('keydown', enterKey);
//has to be keydown!!!!!!! 
//https://www.kirupa.com/html5/keyboard_events_in_javascript.htm
  function enterKey(e){
    e.preventDefault();
      if (e.which === 13) { 
    // if they pressed a key...which one? 13 is enter key
          checkGuess();//fire the checkGuess function
         //console.log("enter key fired checkguess");
        }
  }


  function checkGuess(){
    var userGuess = Number(guessField.innerText);
//captures what ever they guess into userGuess, makes sure it's a number
    if (guessCount === 1){
/*is it their first guess? this is true at the point 
they click it first time*/
      guesses.textContent = 'Guesses: ';
//this text only gets written once.
    }
    guesses.textContent += userGuess + ', ';
//add latest userGuess to list of guesses.
    //console.log('non enter key fired checkguess')
    if (userGuess === randomNumber) {//they nailed it!
      lastResult.textContent = 'Correct!';
      lastResult.style.backgroundColor = 'green';
      for(let i = 0; i < hangman.length; i++ ) {//loops 10 times
        hangman[i].style.visibility = "hidden";//hides each
      }
      skyBlue.style.visibility = 'visible';
      happy.style.visibility = 'visible';

      happy.classList.add('happyBounce');
      //lowOrHi.textContent = '';//empty out Previous 'you are too low!'
      setGameOver();
    } else if (guessCount === 11) {
//they played it 11 times...we counted each time
      lastResult.textContent = 'GAME OVER! ' + guessCount + ' guesses.' ;
      man.classList.add('manBounce');
      guessesRemaining.textContent = '0 guesses left.';
      setGameOver();
    } else {//****************** they guessed wrong
      lastResult.textContent = 'Wrong!';
      lastResult.style.backgroundColor = 'red';
      lowOrHi.style.backgroundColor = 'rgb(2, 120, 157)';
      if (userGuess < randomNumber) {//guess was low

        lowOrHi.textContent = 'Too Low';

      } else if (userGuess > randomNumber){// guess was high


        lowOrHi.textContent = 'Too High';
      }
      /*
console.log("guessCount during game 
before incrementing = " + (guessCount));
     */
      guessesRemainingCount--;//incrementing down remaining guesses
      guessesRemaining.textContent =  'Guesses left: ' + guessesRemainingCount;
      guessCount++;//incrementing up the times they guessed

      hangman[guessCount-2].style.visibility = 'visible';
//turns on hangman artwork parts incrementally
      //console.log(hangman[guessCount-2]);
      guessField.innerHTML = '';
      //guessField.focus();//returns blinking cursor to guessField
    }//end if(userGuess ===)
  }


  console.log(randomNumber);


  function setGameOver(){
//gray out functionality so they can't keep playing
    //guessSubmitButton.removeEventListener('click', checkGuess);
    guessField.style.visibility = 'hidden';
    document.querySelector('.guessSubmitButton a').style.visibility = 'hidden';
    //guessField.disabled = true;
    /******** below may not workvc!******/
//console.log ('set game over is running');
    //guessSubmit.disabled = true;
    resetButton = document.createElement('button');
    resetButton.textContent = 'Start new game';
    //resetButton.classList.add('guessSubmit');
    document.querySelector('.guessSubmitButton').appendChild(resetButton);
    resetButton.classList.add('playAgain');
    resetButton.addEventListener('click', resetGame);
  }

  function resetGame(){//put game back to starting condition
    guessCount = 1;
    guessesRemainingCount = 11;
    if(man.classList.contains('manBounce')){
/*
check to see if man is bouncing, if so  remove the manBounce 
class.Note, he may not be bouncing if they won before 10 guesses
*/
      man.classList.remove('manBounce');
    }
    if(happy.classList.contains('happyBounce')){
      happy.classList.remove('happyBounce');
    }

    for(let i = 0; i < hangman.length; i++ ) {//loops 10 times
      hangman[i].style.visibility = 'hidden';//hides each
    }
    happy.style.visibility = 'hidden';
    var resetParas = document.querySelectorAll('.resultParas p');
//creates an array from the 3 <p>s

    for (var i = 0; i < resetParas.length ; i++){
//for f3 loops, go thru th p's [i] and empty them
      resetParas[i].textContent = '';
    }

    resetButton.parentNode.removeChild(resetButton);
//son tells the parent to remove the son, himself
    skyBlue.style.visibility = 'hidden';
    guessField.style.visibility = 'visible';
      guessSubmitButton.style.visibility = 'visible';
    //guessSubmitButton.addEventListener('click', checkGuess);
    guessField.innerHTML = '';
    //guessField.focus();
    lowOrHi.style.backgroundColor = 'rgba(116, 118, 119, 0)';
//make it invisible
    lastResult.style.backgroundColor = 'rgba(255, 255, 255, 0)';
    randomNumber = Math.floor(Math.random() * 100) +1;
    console.log('New reset random number: ' + randomNumber);
  }