Should you have kids?

May 23rd, 2018

I saw this article about the decline in babies on the Washington Post website, to which I have a subscription and wrote this comment:

A topic close to my heart. We loved having our son and daughter in the eighties. Kids forced us to cut back a little on rock climbing, but we still found ways to get out. Kids love to play outside! My wife did licensed home daycare during the 9 years they were young and not in school. Her daycare earned enough to pay our mortgage payment each month, and she was able to be their parent full time while I worked swing.

When they were both in school, she was able to return to her old job (COTA at a school), and that gave her the same summers off as the kids. This allowed her to be home when they got out of school. We never put our kids in day care. We also had two sets of grandparents within easy driving distance who would watch the kids on weekends to give us a break.

My blue collar income was never very much…but we lived frugally. I see so many young people now spending vast sums of money on really dumb stuff…like motorhomes, boats, hotels and exotic fly away vacations. We still car camp in little backpacking tents, as do our kids…it is possible to live cheaply…and make kids work.

They both got after school jobs in high school (dry cleaners, barista, planter nursery) which helped when they needed to buy their own transportation. They kept those jobs all the way through college. It was impossible for us to save money for college while raising two kids, paying our mortgage and keeping 3 old cars running.

Amazingly, our local community college had a scholarship program. It was called the “First Generation Grant”. If neither parent had a 4 year college degree, you could apply, and they granted about a dozen a year. We were surprised when they both qualified and were awarded the grants. They both work as nurses now and have money to burn, and zero college loans.

I don’t know if they will have kids…I know they kind of want them…but they are having so much fun they don’t want to slow down yet. I’m hoping they have them. My parents made sacrifices to have me and my siblings  I’d like to return the favor to the next generation. Kids are a bucket list thing….and besides, what could be more natural?

Old friends

May 13th, 2018

Saw Ted today. We met in the 6th grade and have stayed friends ever since. Not counting my cousins, he is my oldest friend. He brought his daughter and we had a classic adventure getting lost at Point Defiance. We walked down to Owens Beach, then West along the water until we ran out of time. We saw a trail heading up the steep bank and started up. The trail petered out and we were lost. Not really lost…though that depends on how you define it. But it got interesting as we picked our way though the forest until a road showed up.

We are going to get together in a couple weeks to jam. We started playing together in high school when 4 of us started a garage band. I was the harp player and Ted played base. We had a rhythm, lead guitarist and a drummer. The drummer had a real job so he was in charge of renting the rehearsal garage. I remember pedaling my bicycle to band practice. We only played a few gigs, but it was lots of fun playing and practicing.

Later Ted and Bob formed a much better band that won a competition in Seattle.  They toured Eastern Europe on a fully funded one month concert tour. We all thought they were on their way to something big…but I think they broke up not long after that. We continued jamming for a decade after high school, but then drifted apart as old friends do. There were a few meetings over the decades that followed but they got farther and farther apart.  Anyway it was awesome to see my old buddy again and I hope we play some live music soon.

I stumbled across a Tedx talk online about a guy who says social media is bad for your career. This runs counter to current thinking, but his speech was so compelling I decided to take a break from Facebook and Instagram for a while. It’s only been two days but I like it already.

His main point was that social media is distracting and keeps you from doing what he calls: “Deep Work”. That is defined as long periods, like 4 or more hours where you are completely focused on one task. For a programmer it might be writing complex code. For a writer it would mean writing non-stop for hours with no distraction. For me as an artist, it means painting steadily for hours without any distraction…at all.

He also argues that a commonly held social media theory is a myth. The theory is that being on social media widens your networking audience and makes you more employable. He feels that getting off social media will make us much more marketable because we will be able to do deep work. And that deep work is so rare that it will overcome our lack of social media presence and bring employers. It makes sense. Multi tasking is a dog that won’t hunt. Deep work is something I’ve always loved. Running a printing press was all about deep work. We’d commonly work 12 hours or longer. Climbing is all about deep work and total focus. I never take my cell phone climbing, and I am happier for it.

Speaking of climbing, V. and I spent 3 days at Squish and discovered a new crag called the Papoose. I’d been there with Bud Miller decades ago but never went back after a runnout scare, until last weekend. There are two lovely 5.9 splitters at the base that we both on-sighted. They were both exceptional climbs and I’m so glad we decided to go exploring on a new crag.

Indian Creek 2018

March 28th, 2018

We left Friday at 5PM. Arrived Saturday at Sand Creek Campground in Moab at 4PM. Easily got a site in the huge campground for $15 a night. Bought 3 nights planning on a Tuesday AM departure.

Hiked in light snow at Arches Sunday. Monday I painted Delicate Arch twice, morning and afternoon light. We’d planned on hiking the first half of the trip, then climbing the last few days. But the weather looked to be turning bad Thursday Friday. I was worried that if we hiked until Tuesday, we might only get one day of climbing on Wednesday, then the rain would come in. I texted Chad that he might want to come down a day early. Monday evening Chad showed up and we drove down to Indian Creek Tuesday morning where we did Generic Crack, Binou’s Crack and Chocolate Corner before camping at Super Bowl.

Wednesday we hiked up to Supercrack buttress, which is the same parking lot as Donnelly and did Twin Cracks 5.8+, Incredible Hand Crack 5.10 (12 yellows, 2 reds and a blue) and No Name (left of Twin Cracks) 5.10. Chad did a heck of a job getting half way up No Name before running out of steam. He has smaller hands, so it was miles of steeple jams, which, as he said: “Two moves takes everything I have.”

I was fresh from belaying, so I took over the lead and hung dog my way to the anchors. We used to call that yo yoing back in the day. It took 11 blues, 3 reds and maybe 8 yellows? Everything we did needs just one 60 to get off, as the anchors seem to be bolted right at 30 meters. I suspect the routes went in before 70’s became popular.Generic may be the exception at a little longer…but I’m not sure.

I think one would need at least 2 weeks down there to build up the muscle power to climb those cleanly. Or maybe it is something I need to do before going down. The guys I saw who were sending those hand cracks cleanly all had arms twice as thick as mine. At the very least I need to hit the pull ups hard, and make a couple trips up to Vertical World and run laps on the hand crack. Hand power is ultra critical down there.

Thursday we needed a rest day so we climbed the easier South Six Shooter. The approach was thought provokingly dangerous, and I backed off the 5.6 mantel move. I had a ton of rope drag, the wind was howling, and it didn’t feel like a rest day move. Plus the if you blew the mantel move you would very likely break something as  the gear was below your feet, and there were ledges to hit. Getting injured on top of  a mountain with a one hour approach is just plain stupid.

Chad owed me a finish from No Name, so he stepped up to the plate and sent the mantle nicely. Because it was a rest day, I also had trouble with a rounded 5.7 crack lower down on the second pitch. I hung 3 times. I think I was just tired. The creek is burly.

The summit views were lovely…climbing mountains is satisfying. We did the rap in two raps with our 60. One short one down to the chains 50 feet below the summit, and another 30 meter rap to the ground, passing the ugly slung block station.

Alex ran up while we were climbing but got there too late to join us, so he walked Sue down to the car. She was getting cold waiting. When we got down to the car I was ready to get serious about my rest day. But Alex was there…and I didn’t want to say no. When he offered to carry the pig up to the crag I caved and abandoned any thoughts of a rest day.  We hiked up and Alex led Supercrack. It took all 11 blues, plus 6 yellows. I would have brought a 5 for the last move to the chains, had I led it. It was Alex’s first creek lead, so he hung a lot but got up.

I hung twice on follow. The jams were great hands for my huge mitts. Plus I was wearing Ocuns over tape to be fatter. I’m not sure I will ever lead it though because the 10C start crack is super awkward. It starts good, then flares to a rounded #5 with a block in the way. You might be able to get a half inch cam in the block. Other than that I’m sure I could hang dog my way up the main crack. It’s a spectacular crack. Love to get some photos from either the anchors or the tower off right. It did have anchors for safely taking pictures of your leader.

Thursday night it poured, which matched the forecast from Monday. We drove up to Moab where I painted Double Arch while Sue did another Arches hike to Landscape Arch and beyond. And then the epic drive home. Left at 6PM, got home at 2PM Saturday.

Newspaper Rock

Newspaper Rock

 

North Six Shooter

North Six Shooter

 

South Six Shooter, Chad about to take over the lead at the mantle.

South Six Shooter, Chad about to take over the lead at the mantle.

 

 

North Six Shooter, from summit of South Six Shooter.

North Six Shooter, from summit of South Six Shooter.

 

Summit of South Six Shooter.

Summit of South Six Shooter.

 

Summit of South Six Shooter.

Summit of South Six Shooter

 

Petroglyph on first pitch of South Six Shooter.

Petroglyph on first pitch of South Six Shooter.

 

Incredible Hand Crack, all yellows

Incredible Hand Crack, all yellows

 

Incredible Hand Crack, all yellows. I found a rest!

Incredible Hand Crack, all yellows. I found a rest!

 

Chad on No Name crack

Chad on No Name crack

 

Generic Crack

Generic Crack

 

Binou's crack, got it clean this year

Binou’s crack, got it clean this year

 

Painting Delicate Arch, building on a blue monotone base

Painting Delicate Arch, building on a blue monotone base

 

Climbing season begins

March 15th, 2018

I’m putting together some borrowed gear for climbing. It’s a big rack.  I’ve been playing a lot of ping pong for exercise. I’m a decent defensive player, able to dig for balls that appear to be hopeless. I have a couple pretty good serves, and can occasionally get in a good slam, though it’s not reliable yet.

So when I heard there was a ping pong tournament at the local climbing gym I got all excited. The prize was a $180 pass for 10 visits. Most young people are too busy playing video games and don’t pursue ball sports. And if they are athletic, I figured they’d focus on climbing, at the expense of ball sports.

The first young guy I played had a little talent but clearly didn’t spend a lot of time at pong. Then I sat out and watched 3 games with people who I thought I could beat…or at least equal.

When my turn came again I was matched with a 23 year old guy who held his paddle with the penhold grip. If that wasn’t scary enough, the way he was bouncing on his feet like a boxer sealed the deal. From the very first time his ball came over the net I knew I was in serious trouble. Every ball had a wicked spin. They’d float over the net headed south, but after the bounce they’d head east, or west, or any direction but the original vector.

I lost 8 to 11 and went home in a bad mood. I mean, really, what was I thinking? I’m 64 and I went to tournament in  a climbing gym full of people in their 20’s. It was inevitable that I would loose. Pick any activity, and no matter how good you are, there is always going to be someone better. Add in the factor of age, and it just happens more often.

While I was stewing in my bad mood I got to thinking about all the things at which I am mediocre. Here is my list. I have mediocre skill at the following:

  • rock climbing
  • backcountry skiing
  • downhill skiing
  • ping pong
  • guitar
  • chromatic harmonica
  • landscape, still-life, seascape painting
  • photography
  • operating an industrial sewing machine
  • operating a printing press
  • programming JavaScript
  • filmmaking
  • studio lighting
  • electrical wiring

And here is my list of things at which I  consider myself to be talented:

  • parenting…kids turned out great!
  • blues harp
  • teaching ( I was awarded tenure…until my program closed)
  • HTML5, CSS3, JavaScript Animation
  • Photoshop and Illustrator
  • WordPress
  • portrait painting
  • life planning (our house and cars are paid off)
  • marriage…though Sue might disagree?
  • inventing, designing and trouble shooting…I make things
  • social skills, I can get along with anyone.

After a couple days of thinking about it I came around to the realization that being a jack of all trades, master of none means I am quite good at one thing that isn’t on either list:

I am really good at having fun! The fact that I am mediocre at an activity doesn’t stop me from enjoying it. That long list of things at which I am mediocre represents a lot of time…a lifetime really…of trying different things…and focusing in on the fun ones.

I’ll be working three days a week again in April. That will be fun to get a pay check again. I sort of miss those. In late June I’ll be off again for the summer…which might be really sweet. I plan to keep studying JavaScript. I decided to give up Python…I think that was a wrong turn. I realized there was much more to learn in JavaScript so I got a new book called Eloquent Javascript that looks fun.

 

Skied up Mount St. Helens

March 12th, 2018

Clint, Craig and I skied up Mt. St. Helens yesterday.  I ate steak and potatoes the night before but it wasn’t enough. At the halfway point we could see figures on the summit, but my endurance was very low. I could skin up about 100 feet, then I’d have to pause for a while to catch my breath, and repeat. The distances between pauses got shorter the higher I got.

I apologized to Clint for going so slow, and he said he wasn’t in any hurry, that I should take my time. My son and Craig are both superb skiers, while I am mediocre at best. I was the oldest guy on the mountain by at least 20 years. Most of them were between 25 and 35. Most of the 100 people we saw were on snowshoes or on foot. It’s weird that only about 1 in 5 were on skis. Skis made the trip down a lot faster, and in spots the skiing was excellent, feeling like  the steep section of a blue run at Crystal i.e. the Forest Queen chair lift. The skiing down Worm Flow couloir was fairly easy in spring corn, though it had some cement snow in places.

Down lower on the main hiking route there were tons of post hole tracks which made for choppy skiing. I was so tired by then (10 mile round trip, 4500 feet gain) that all I could do was shaky snow plow turns. We were racing the light, as getting caught in the dark on icy snow was not an attractive option.

 

# # Example file for parsing and processing JSON # import urllib import json def printResults(data): # Use the json module to load the string data into a dictionary theJSON = json.loads(data) # now we can access the contents of the JSON like any other Python object if "title" in theJSON["metadata"]: print(theJSON["metadata"]["title"]) # output the number of events, plus the magnitude and each event name count = theJSON["metadata"]["count"] print (str(count) + " events recorded") # for each event, print the place where it occurred for i in theJSON["features"]: print(i["properties"]["place"]) print("-----------------\n")#this runs after the for loops is done # print the events that only have a magnitude greater than 4 for i in theJSON["features"]: if i["properties"]["mag"] >= 4.0: print "%2.1f" % i["properties"]["mag"], i["properties"]["place"] print("-----------------\n")#this runs after the for loops is done # print only the events where at least 1 person reported feeling something print("Events that were felt: ") for i in theJSON["features"]: feltReports = i["properties"]["felt"] if feltReports != None: if feltReports > 0: print "%2.1f" % i["properties"]["mag"], i["properties"]["place"], " reported " + str(feltReports) + " times" def main(): # define a variable to hold the source URL # In this case we'll use the free data feed from the USGS # This feed lists all earthquakes for the last day larger than Mag 2.5 urlData = "http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_day.geojson" # Open the URL and read the data webUrl = urllib.urlopen(urlData) print ("result code: " + str(webUrl.getcode()))#returns: 200 = true, it got data if (webUrl.getcode() == 200): data = webUrl.read() printResults(data) else: print("Received error, cannot parse results") if __name__ == "__main__": main()

Me and my son Clint

Summit shot, me, Craig & Clint

Summit shot, me, Craig & Clint

 

St. Helens crater

St. Helens crater

 

Clint and Craig lunch break, Mt. Adams in the background

Clint and Craig lunch break, Mt. Adams in the background

I’ve been studying Python. My Django studies got weird because Django runs in Python and I’d never done Python. Sometimes I feel like I’m trying to learn Japanese, French, Russian and Spanish all at the same time.

This was my latest exercise. This is the Python code for pulling JSON data from the USGS Earthquake website. It formats the data and then spits it back out as a list of recent earthquakes with information about strength, location and whether it was felt.

# 
# Example file for parsing and processing JSON
#
import urllib 
import json

def printResults(data):
  # Use the json module to load the string data into a dictionary
  theJSON = json.loads(data)
  
  # now we can access the contents of the JSON like any other Python object
  if "title" in theJSON["metadata"]:
    print(theJSON["metadata"]["title"])
    
  
  # output the number of events, plus the magnitude and each event name  
  count = theJSON["metadata"]["count"]
  print (str(count) + " events recorded")

  # for each event, print the place where it occurred
  for i in theJSON["features"]:
    print(i["properties"]["place"])
  print("-----------------\n")#this runs after the for loops is done

  # print the events that only have a magnitude greater than 4
  for i in theJSON["features"]:
    if i["properties"]["mag"] >= 4.0:
      print "%2.1f" % i["properties"]["mag"], i["properties"]["place"]
  print("-----------------\n")#this runs after the for loops is done   
  
  # print only the events where at least 1 person reported feeling something
  print("Events that were felt: ")
  for i in theJSON["features"]:
    feltReports = i["properties"]["felt"]
    if feltReports != None:
      if feltReports > 0:
        print "%2.1f" % i["properties"]["mag"], i["properties"]["place"], " reported " + str(feltReports) + " times"
  
def main():
  # define a variable to hold the source URL
  # In this case we'll use the free data feed from the USGS
  # This feed lists all earthquakes for the last day larger than Mag 2.5
  urlData = "http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_day.geojson"

  # Open the URL and read the data
  webUrl = urllib.urlopen(urlData)
  print ("result code: " + str(webUrl.getcode()))#returns: 200 = true, it got data
  if (webUrl.getcode() == 200):
    data = webUrl.read()
    printResults(data)
  else:
    print("Received error, cannot parse results")

if __name__ == "__main__":
  main()

 

Recent Songs

March 7th, 2018

I’ve been playing guitar and or harmonica about an hour a day recently. Along with regular exercise, it helps me stay balanced. Music is a wonderful gift I got from my parents, who were also both amateur musicians. Dad sang into his 90’s, and mom played the piano.

Here are my recent songs. Finding the  sheet music and chords for any given song is a challenge. The two websites linked here seem to be the current best choice. Both of them allow you to transpose the key. Transposing is essential if you are a beginner, as I am on the harp. I need as few flats and sharps as possible. Plus the key needs to fit my voice and the guitar chords need to be easy. It’s a lot to ask, but those websites make it happen. And for $5.50, the sheet music is nicely priced and instantly downloadable.

Once I get the chords, I copy them into Word and massage them so they are correct, and so they fit on one page. Here are 4 of my most recent songs.

 

bobby-mcgee

bobby-mcgee

 

save-best4-last

save-best4-last

 

candle-in-wind

candle-in-wind

 

my-father-chords

my-father-chords

 

Sitting on the dock of the bay

Sitting on the dock of the bay

Skiing and climbing in one weekend

March 7th, 2018

Saturday Sue, Lisa and I skinned up to Pebble Creek above Panorama Pt. Then we dropped into the bowl above the top of Mazama Ridge, where a cloud rose up to meet us just as we got to the best snow. We skied blindly for half an hour, trusting in our previous trips there to know we weren’t skiing off cliffs. That was nerve wracking. Lisa is the best skier and was boldly leading out ahead. She’d do a few turns into the white out, holler up that it was ok and we’d follow.

Eventually we came out of the cloud but by then we were in the flats on top of Mazama. Another half hour brought us to Clints snowcave. He, Craig, Tony and Mike had skied up with overnight gear and were well into a 4 man snow palace.

It was getting on toward 4 PM by then and we couldn’t stay long as the car was several miles down and away in deep cement snow. It was a lovely day with the family, but we were very glad to get to the car. Sue and I were exhausted but Lisa could have probably done it all again.

I got up early Sunday and drove to Vantage with Vitaliy, Christine and Julia. We met Vladi and a few other gym friends there. I got up Air Guitar and Pony Keg cleanly, but had to hang on Whipsaw and Sunshine Buttress. My absence from the climbing gym has made my fingers weak. It was great hanging out with so many good climbing friends. Even Chad was there and we talked about Indian Creek.

When we got to Ellensburg there was a roadblock on I-90. The pass had shut down due to snow. It took us a total of 8 hours to drive home from Vantage, including waiting at a restaurant for an hour. We took 97 over Bluit Swauk Pass, then Stevens Pass, and 203 down through Carnation to Julias car at North Bend.

Lisa's 30th birthday. From right, me, Clint, a nurse, nurse, Lisa, Bonnie

Lisa’s 30th birthday. From right, me, Clint, a nurse, nurse, Lisa, Bonnie

 

Vitaliy, me, Christine, Julia, ?, AJ, ?, Christian

Vitaliy, me, Christine, Julia, ?, AJ, ?, Christian at Vantage

 

Lisa and Clint top of Mazama Ridge

Lisa and Clint top of Mazama Ridge

 

Looking up above Pan

Looking up above Pan

 

Lisa and Sue top of pan

 

to do app in html5

February 26th, 2018

I spent a full day trying to debug an indexedDB JavaScript lesson. It would work on my iPhone, and on codepen, but nowhere else. It wouldn’t  work at all on my MacBook, except for codepen.com. I knew my code was clean because typing errors are always the first thing I check. And the way it was behaving, I could tell it was some kind of security blockage either on the hosting servers or in the browsers.

While searching for an answer I came across another free online tutorial, and this one worked. I don’t know why it worked because it was structured much the same, though with subtle differences. Anyway I was able to combine stuff I’d learned in both tutorials to make a pretty sweet little  pure  HTML5 and vanilla JavaScript  note taking app.

Once I had it running I realized it was missing the drag and drop functionality I’ve come to expect in apps like wunderlist. After a quick google search, I found a tutorial that built a drag and drop list. Sadly, that one has a problem on mobile devices. You can drag the list items on a computer, but not on a phone. Phones have a different set of responses and the  eventListeners are set up completely different from computers.

But more searching led me to this cool tutorial page with links out to the slip.js library.

And finally I had a very nice little “todo” note taker app. It’s still not hooked up to a database…that may be coming. But it’s actually quite useable. There are 400 lines of code, not counting the slip javascript library. I prettied it up with some style sheets, and tried to make it mobile friendly.

It’s tricky to embed it in WordPress. There are conflicts with classes and ID’s. So instead, here is a direct link on my server.. It remembers your list items as long as you don’t clear your browsers cache. It does not sync with your computer though… You need wunderlist for that.

Just for practice, I’ve also posted it up on codepen so that you can tinker with it, if you should so desire.

There are two js files that run it. This is the one that hooks it up to the indexedDB database.

var todoDB = (function(){
  var tDB = {};
  var datastore = null;

  //todo: add methods for interacting with db here
  //open connection to the datastore

  tDB.open = function(callback){
    //database version
    var version = 1;

    //open connection to db
    var request = indexedDB.open('todos', version);

    //handle upgrades to datastore
    request.onupgradeneeded = function(e) {
      var db = e.target.result;

      e.target.transaction.onerror = tDB.onerror;

      //delete old datastore
      if(db.objectStoreNames.contains('todo')) {
        db.deleteObjectStore('todo');
      }

      //create a new datastore
      var store = db.createObjectStore('todo', {
        keyPath: 'timestamp'
      });

    };//end request.onupgradeneeded function

    //handle success datastore access
    request.onsuccess = function(e) {
      //get a reference to the DB
      datastore = e.target.result;

      //execute callback
      callback();
    };
    //handlerrors opening datastore
    request.onerror = tDB.onerror;

  };//end tDB.open = function

  //fetch all the todo items in the datastore
  tDB.fetchTodos = function(callback) {
    var db = datastore;
    var transaction = db.transaction(['todo'], 'readwrite');
    var objStore = transaction.objectStore('todo');

    var keyRange = IDBKeyRange.lowerBound(0);
    var cursorRequest = objStore.openCursor(keyRange);

    var todos = [];

    transaction.oncomplete = function(e) {
      //execute callback
      callback(todos);
    };

    cursorRequest.onsuccess = function(e){
      var result = e.target.result;

      if(!!result == false) {
        return;
      }

      todos.push(result.value);

      result.continue();
    };

    cursorRequest.onerror = tDB.onerror;
  };

  /**
  * create new todo item
  */
  tDB.createTodo = function(text, callback){
    // get reference to db
    var db = datastore;

    //intiate new transaction
    var transaction = db.transaction(['todo'], 'readwrite');

    //get the datastore
    var objStore = transaction.objectStore('todo');

    //create a timestamp for todo item
    var timestamp = new Date().getTime();

    //create an object for the todo itemsv
    var todo = {
      'text': text,
      'timestamp': timestamp
    };

    //create the datastore cursorRequest
    var request = objStore.put(todo);

    //handle the success put
    request.onsuccess = function(e) {
      //execute callback
      callback(todo);
    };

    //handle handlerrors
    request.onerror = tDB.onerror;
  };//end tDB.createTodo function

  /**
  * delete a todo item
  */
  tDB.deleteTodo = function(id, callback){
    var db = datastore;
    var transaction = db.transaction(['todo'], 'readwrite');
    var objStore = transaction.objectStore('todo');

    var request = objStore.delete(id);

    request.onsuccess = function(e) {
      callback();
    }
    //writes errors to the console
    request.onerror = function(e){
      console.log(e);
    }
  };//end tDB.deleteTodo function
  //export the tDB object
  return tDB;
}());

And here is the one where I did quite a bit of customization, you can see the drag and drop “slip” references at the bottom.

window.onload = function(){
  //todo: app code goes here
  //display todo items by passing in the refreshTodos function as a parameter?
  todoDB.open(refreshTodos);

  //get references to html elements
  var newTodoForm = document.getElementById('new-todo-form');
  var newTodoInput = document.getElementById('new-todo');
  var submitBtn = document.querySelector('.submitBtn');
  //my form submitter addEventListener
  newTodoForm.addEventListener('submit', triggerForm);

  submitBtn.addEventListener('click', triggerForm);

  //handle the form sumissions
function triggerForm() {
    //get text
    var text = newTodoInput.value;

    //check tomake sure test is not blank or just spaces
    if(text.replace(/ /g,'') != ''){
      //create the todo item
      todoDB.createTodo(text, function(todo) {
        refreshTodos();
      });
    }

    //reset input field
    newTodoInput.value = '';

    //don't send the form
    return false;
  };//end triggerForm function

  //update the list of todo items.
  function refreshTodos(){
    todoDB.fetchTodos(function(todos){
      var todoList = document.getElementById('todo-items');
      todoList.innerHTML = '';

      for(var i = 0; i < todos.length; i++){
        //read the todo items backwards-most recent first
        var todo = todos[(todos.length - 1 - i)];

        var li = document.createElement('li');
        li.id = 'todo-' + todo.timestamp;
        //li.setAttribute('draggable', 'true');
        var checkbox = document.createElement('button');
        checkbox.textContent = 'X';
        checkbox.className = 'todo-checkbox';//""
        checkbox.setAttribute('data-id', todo.timestamp);//""



        var span = document.createElement('span');
        span.innerHTML = todo.text;

        li.appendChild(span);
        li.appendChild(checkbox);
        todoList.appendChild(li);

        //set up listener checkbox
        checkbox.addEventListener('click', function(e){
          var id = parseInt(e.target.getAttribute('data-id'));

          todoDB.deleteTodo(id, refreshTodos);
        });
      }//end for i < todos.length
//my non mobile dragger function used to be here.

    });//end todoDB.fetchTodos
    newTodoInput.focus();

  }//end function refreshTodos
//begin slippery functions
//slideThem = <ul id="todo-items">
var slideThem = document.querySelector('#todo-items');
new Slip(slideThem);//apply slip library to <ul>
slideThem.addEventListener('slip:reorder', function(e){
  e.target.parentNode.insertBefore(e.target, e.detail.insertBefore);
});
//end slipper function
};//end window.onload

The style sheet got fairly long  because…well…I’m sort of artsy and I wanted it to be pretty.

* {
  -moz-box-sizing: border-box;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
}

body, html {
  padding: 0;
  margin: 0;
}

body {

  color: #545454;
  background: #f7f7f7;
}
#page-wrapper {
    font: 2em Verdana, Helvetica, sans-serif;
  width: 100%;
  max-width: 750px;
  margin: 0.2em auto;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,0.2);
  border-radius: 12px;
}
#page-wrapper footer {
border-top: 2px solid #0088cc;
padding: 0.5em;
background-color: rgb(207, 240, 245);
border-bottom-left-radius: 12px;
border-bottom-right-radius: 12px;
}
#page-wrapper footer h3 {
  font-size: 1.2em;
  color: #61bfee;
  margin:0;
  padding: 0;
  text-align: center;
}
#new-todo-form {
  padding: 0.5em;
  background: #0088cc;
  border-top-left-radius: 12px;
  border-top-right-radius: 12px;
}

#new-todo {
  width: 100%;
  padding: 0.5em;
  font-size: 1em;
  border-radius: 3px;
  border: 0;
}

#todo-items {
  list-style: none;
  padding: 0.3em 0.3em;
  margin: 0;
}

#todo-items li {
  margin: 0.5em 0 0 0;
  padding: 0;
  background-color: rgb(246, 232, 161);
  border: 1px solid rgb(153, 129, 67);
  display: flex;
  justify-content: space-between;
/* cursor: move; */
}
#todo-items li.dragElem {
  opacity: 0.6;
}
#todo-items li.over {
  border-top: 4px solid red;
}
#todo-items li span {
  margin: 0.7em 0.2em;

}
#page-wrapper button:hover {
  background-color: #0088cc;
  color: white;
}
.submitBtn {
      color: white;
      font-size: 1em;
      padding: 0.5em;
      margin: 0.5em 0 0 0;
      background-color: rgb(255, 162, 4);
}

.todo-checkbox {
  color: white;
  background-color: rgb(213, 119, 21);
  font-size: 1.2em;
  padding: 0.5em;
}

 

HTML5 To Do shopping list app

February 22nd, 2018

I’ve been using both Evernote and Wunderlist for years. I’ve built a ToDo list before in jQuery that came close to Wunderlist. It had draggable list items, and delete item checkboxes. But when studying JavaScript, jQuery is considered cheating.

So I was pleased to see this one show up in my latest online lesson. I prettied it up with some style sheets. I’m waiting until later in my studies to figure out how to connect it to a database, hopefully with something like Node.js. For now though, it just works in the browser session memory. It’s cool to make useful things that actually work. This one embedded in WordPress has some extra padding related to WordPress styles, but the direct link outside of WordPress works as intended.

Shopping list


You can also access it here as a direct link outside of WordPress.

Custom HTML5 video player controls

February 22nd, 2018

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);
// }