@bytesnz

Jack Farley, Computer Engineer

  • home
  • about
  • 12/25/2020, 12:13:48 AM

    A new Litter Survey

    softwarelitterlitter-survey

    In a culmination of litter surveys and litter picks, linked data() and data exploration, and remoteStorage and ActivityPub, I have created a web-based litter pick/survey app that I hope will allow federated citizen science.

  • 12/13/2020, 6:00:00 PM

    Hoe Stream Trash

    litterpi-trash-camlitter-pick

    My latest litter pick target was Hoe Stream and the White Rose Lane Local Nature Reserve. Here's how it went.

  • 11/24/2020, 6:00:00 PM

    Release the Beast

    softwaregitlabcirelease

    I just created a Gitlab CI job to create a release with information from a CHANGELOG.md file for some of my projects. Here's how I did it.

  • 9/30/2020, 9:00:00 PM

    Gitlab CI Caching

    gitlabcontinuous integration

    I noticed something strange happening during build process during a multi-tasking bug fix. Turns out I was using Gitlab CI's caching incorrectly. I should have been using artifacts. Here's what I saw.

  • 9/21/2020, 6:00:00 PM

    Birthday Trash

    litterpi-trash-cam

    As a birthday treat, I took the day off work to try out my electronerised litter picker. Here's how it went.

  • 9/13/2020, 12:12:13 AM

    Pi Trash Cam

    raspberry pielectronicslitterpi-trash-cam

    In preparation for a day of litter picking, I finally got round to a project idea - attaching a camera to a litter picker to record it all. Here's what I did.

  • 8/25/2020, 9:29:58 PM

    yarn add --dev webdriverio

    first-draftsoftwaretestinggitlab

    I finally started implementing UI testing on first-draft using WebdriverIO. While writing tests was easy, getting the tests running was a little more difficult. Here is how I did it.

  • 8/16/2020, 7:04:44 PM

    Hooray! My new blog is live! Based on Sapper, using MongoDB and eventually ActivityPub and ActivityStreams, it will be my federated posting hub to the world.

    softwareblog
  • 8/9/2020, 5:40:49 PM

    Removing EXIF Data from Photos

    exifphotos

    Creating this new blog, I wanted to make sure there was no metadata data leaking personal information. Here's how I removed all the metadata tags except the ones I wanted from my photos.

  • 8/9/2020, 10:21:41 AM

    tmux List and Reattach

    tmuxbashsoftware

    Using tmux for your terminal multiplexer but want an easy to reattach to a session? Here's a small bash script to do it.

  • 8/8/2020, 2:58:48 PM

    Selectable Shell Examples

    markdownsyntaxcss

    Here's how to help your readers save time by making your post's shell commands easy to select and copy - with a simple CSS property.

  • 8/5/2020, 6:48:38 PM

    Be Dates You

    mongodbdocker

    Making my new blog, I didn't initially set the published dates to be native dates in the database. Here what I did to change them ...and do all the upgrades I needed.

  • 7/8/2020, 4:00:00 PM

    Testing vue components

    softwarevuetestingjavascript

    I recently needed to test that some Vue components were creating the correct HTML. To do this, I decided to create snapshots of Object representations of the rendered HTML.

  • 2/20/2019, 12:00:00 AM

    No More Numbers

    javascripthtml5software

    HTML5 number inputs aren't useful, but tel inputs, have all the power

  • 12/21/2018, 12:00:00 AM

    A Hacker "Hacked" Me

    securityemailgrep

    I decided to look into the extortion emails I have been getting and wrote a small script to extract the bitcoin addresses that have been used.

  • 12/7/2018, 12:00:00 AM

    Mouse Surgery

    pledge to not upgradeelectronics

    As part of my pledge not to upgrade, I decided to repair two of my failing mice instead of replacing them with a brand new model (as tempting as it was). Here's what I did.

  • 10/29/2018, 7:00:00 PM

    Danger Danger, Highly Reactive

    vuereactivegraphingdata
  • 9/11/2018, 7:00:00 AM

    Switching to SSR

    reactssrreact-loader
  • 8/10/2018, 4:00:00 PM

    Clean Docker Registry

    nodedockerconsolejavascript
  • 2/1/2018, 12:00:00 AM

    Testing on the Filesystem

    testingjavascript
  • 1/26/2018, 12:00:00 AM

    Tag You Are

    javascripttags
  • 1/25/2018, 9:00:00 PM

    NaN Got Me

    rubber duck savejavascriptnodejsjson
  • 4/28/2017, 10:25:34 AM

    All our app's tabs are belong to us

    angularjavascriptlocalstoragerxjssessionstorage
  • 2/20/2016, 2:33:19 AM

    Developing NPM modules

    nodejsnpm
  • 2/20/2016, 1:48:13 AM

    Nfa + Nfb or N(f+fa+fb)

    arrayfunctionjavascriptjsperf
  • 10/31/2015, 3:36:29 PM

    Photo Layout

    arrangercssgallery hierarchyhtmljavascriptphotostinymcewordpresssoftware
  • 10/31/2015, 2:33:49 PM

    Browning Pass HideAway Web Site

    cssgallery hierarchyhtmlwordpress
  • 10/17/2015, 4:04:57 PM

    Think Mobile

    mobileweb
  • 10/13/2015, 9:30:59 AM

    Pledge to Refuse and Not Buy Bottled Water

    environmentpledges
  • 2/25/2015, 7:42:33 AM

    Run PHP run!

    maximum execution timephptrial and error
  • 11/26/2014, 6:35:32 AM

    Gallery Hierarchy

    galleryhierarchyphotos
  • 10/3/2014, 7:22:04 AM

    Vim and functions

    functionnavigationtagsvim
  • 9/26/2014, 3:18:53 AM

    I'm making hierarchies

    hierarchical datahierarchyphp
  • 9/4/2014, 1:45:18 PM

    Google Sheets fun

    googlegoogle scriptgoogle sheetsjavascriptsheets

    So I am currently having to tinker with Google Sheets for some volunteer work I am doing. The organisation is currently using Google Sheets for management of volunteers etc (argh!). As an interim measure (will move to a database) to help make the sheets a little easier to use, I have created some scripts to do some fancy tricks.

    There are a few very cool things about Google Docs (not mentioning trying to use Docs over a bad link), one of which is Google Script (documentation is available here). The scripts are written in Javascript and can be run either at certain times or on certain events, including onEdit and onOpen.

    Below are the functions that I created.

    This is a function to send out email alerts if a date on a row is 7, 3 days away, the current day, or the date has passe. It will email the alert to email addresses specified in a cell range.

    function reminderAlert() {
      // Get Volunteer form and sheet
      var spreadsheet = SpreadsheetApp.openById('xxxxx');
      var sheet = spreadsheet.getSheetByName('Volunteer Summary'); 
    
      var recipients = removeBlanks(spreadsheet.getRangeByName('alert_emails_followup').getValues());
    
      var followupDates = spreadsheet.getRangeByName('volunteer_followup_date').getValues();
      var names = spreadsheet.getRangeByName('volunteer_name').getValues();
      var emails = spreadsheet.getRangeByName('volunteer_email').getValues();
    
      Logger.log('Checking Followup dates');
      // Go through (skipping the title row)
      for(var i = 1; i < names.length; i++) {
        if (names[i] != '') {
          if (followupDates[i] != '') {
            var when;
            var today = new Date().valueOf();
            var due = new Date(followupDates[i].toString()).valueOf();
            var count = due - today;
            count = Math.floor(count / 1000 / 60 / 60 / 24);
            if (count == 7 || count == 3 || count == 0 || count < 0) {
              if (count < 0) {
                when = 'was due ' + (count * -1) + ' days ago';
              } else if (count == 0) {
                when = 'is due today';
              } else {
                when = 'is due in ' + count + ' days';
              }
    
              var subject = 'NGO Taxi: Follow up with ' + names[i] + ' ' + when;
              var message = 'Hello,\n\nThe follow up with ' + names[i] + ' ' + when + '.\n' +
                'Their details are:\n' +
                  'Name: ' + names[i] + '\n' +
                    'Email: ' + emails[i] + '\n' +
                      'Comments:\n' + getComments(names[i], 5) + '\n\n' +
                      'For details, see: ' + spreadsheet.getUrl() + '\n' +
                        '[This is an automated message, do not reply.]';
    
              Logger.log('Sending email about ' + names[i]);
              MailApp.sendEmail(recipients, subject, message, { noReply: true });
            }
          }
        }
      }
    }

    This function gets all the comments (stored in a comments sheet) for a particular volunteer.

    function getComments(name, limit) {
      var c, comment = {}, commentDates = [];
      var spreadsheet = SpreadsheetApp.openById('xxxxx');
    
      var dates = spreadsheet.getRangeByName('volunteer_comment_dates').getValues();
      var names = spreadsheet.getRangeByName('volunteer_comment_names').getValues();
      var comments = spreadsheet.getRangeByName('volunteer_comments').getValues();
    
      for(var i = 1; i < names.length; i++) {
        if (names[i] == name) {
          var date = new Date(dates[i]).valueOf();
          comment[date] = (dates[i] + ': ' + comments[i]);
          commentDates.push(date);
        }
      }
    
      if (commentDates) {
        commentDates.sort(function (a, b) {
          if (a > b)
            return -1;
          if (a < b)
            return 1;
          // a must be equal to b
          return 0;
        });
        if (!limit) {
          limit = commentDates.length;
        }
        var commentParts = [];
        for (c = 0; c < limit; c++) {
          commentParts.push(comment[commentDates[c]]);
        }
        return commentParts.join('\n');
      } else {
        return '';
      }
    }

    This is a function to automatically insert a date into a cell of the active row if the row is modified. The date will be deleted if the rest of the row is cleared. You can specify what sheets/columns dates will be added to.

    function insertDate(sheet) {
      var dates = [
        {
           sheet: 'Comments', // Name of the sheet
           column: [2, 3], // Columns to check for content
           minRow: 4, // The start row (to ignore headings
           dateColumn: 1 // The column that the date should be inserted into
        },
        {
          sheet: 'Volunteer Summary',
          column: 6,
          minRow: 2,
          dateColumn: 7,
          baseDateColumn: 6, // Instead of adding the current date, use the value in this column
          addDays: 4, // Add this many days to the date
          overwrite: true, // Overwrite any values that are in there
          overwriteIfNewer: true // Only overwrite if is a newer date
        }
      ];
    
      var d, aCell, dCell, c;
      for (d in dates) {
        if (sheet.getName() == dates[d]['sheet']) {
          aCell = sheet.getActiveCell();
          var col = dates[d]['column'];
          if (col instanceof Array) {
          } else {
            col = [col];
          }
          for (c in col) {
            Logger.log('checking if col ' + col[c] + '(' + aCell.getColumn() + ')');
            Logger.log(dates[d]['minRow'] + ' >= ' + aCell.getRow());
            Logger.log('Offset will be ' + (col[c] - aCell.getColumn()));
            if (aCell.getColumn() == col[c]
                && (dates[d]['minRow'] ? (aCell.getRow() >= dates[d]['minRow']) : true)) {
              var value;
              // Check for and set value based on baseDate column
              if (dates[d]['baseDateColumn']) {
                var bDCell = aCell.offset(0, (dates[d]['baseDateColumn'] - aCell.getColumn()));
                value = new Date(bDCell.getValue());
                if (value && dates[d]['addDays']) {
                  value.setDate(value.getDate() + dates[d]['addDays']);
                }
              }
              // Set value to current date if we don't have one
              if (!value) {
                value = new Date();
              }
              // Current cell to change
              var dCell = aCell.offset(0, (dates[d]['dateColumn'] - aCell.getColumn()));
                  Logger.log('Current value is :' + dCell.getValue());
              if (aCell.getValue() != '' && (dates[d]['overwrite'] || dCell.getValue() == '')) {
                Logger.log('setting value');
                dCell.setValue(value);
                break;
              } else if (aCell.getValue() == '' && dCell.getValue() != '') {
                Logger.log('setting value');
                dCell.setValue('');
                break;
              }
            }
          }
        }
      }
    }

    This function hides rows of a sheet based on the value of a column in the row. When the value is changed, the function will hide the rows that aren't associated with that value (the value is in a specified column. It will also hide column that it looks for the value in. When a new row is started, the selected value will automatically be inserted into the specified column.

    function valueSpecificSheet(sheetObj) {
      var sheets = [
        {
          sheet: 'Comments', // The name of the sheet to monitor
          valueCell: [3, 2], // The cell to get the value of [col, row]
          column: 2, // The column number to look for the value (and to hide etc)
          startRow: 4,
          lastColumn: 3,
        },
        {
          sheet: 'Activity Tracker', // The name of the sheet to monitor
          valueCell: [7, 2], // The cell to get the value of [col, row]
          column: 1, // The column number to look for the value (and to hide etc)
          startRow: 4,
          lastColumn: 8,
        },
      ];
    
      var d, aCell, dCell, c, sheet, value;
      for (s in sheets) {
        sheet = sheets[s];
        if (sheetObj.getName() == sheet.sheet) {
          var lastRow = (sheet.lastRow ? sheet.lastRow : sheetObj.getLastRow());
          aCell = sheetObj.getActiveCell();
          var selectedValue = sheetObj.getRange(sheet.valueCell[1], sheet.valueCell[0]).getValue();
          if (aCell.getRow() == sheet.valueCell[1] && aCell.getColumn() == sheet.valueCell[0]) {
            // Check if the edited cell is the value Cell, if not check for new row
            // Unhide everything
            sheetObj.showRows(1, lastRow);
            if (selectedValue) {
              sheetObj.hideColumns(sheet.column);
    
              // Go through rows and hide the rows with a values not equal to selected value
              for (i = sheet['startRow']; i <= lastRow; i++) {
                // Check value
                value = sheetObj.getRange(i, sheet.column).getValue()
                if (value != '') { // Ignore empty row
                } else if (selectedValue != value) { // Hide
                  sheetObj.hideRows(i)
                } 
              }
            } else {
              sheetObj.showColumns(sheet.column);
            }
          } else if (selectedValue != '') {
            // Check if there are values in the row, if so make sure the value in column is correct
            var lastColumn = (sheet.lastColumn ? sheet.lastColumn : sheetObj.getLastColumn());
            var dCell = sheetObj.getRange(aCell.getRow(), sheet.column);
            var haveValue = false;
            for (i = 1; i <= lastColumn; i++) {
              if (i == sheet.column) {
                continue;
              }
              if (sheetObj.getRange(aCell.getRow(), i).getValue() != '') {
                haveValue = true;
                break;
              }
            }
    
            if (haveValue) {
              if (dCell.getValue() != selectedValue) {
                dCell.setValue(selectedValue);
              }
            } else {
              if (dCell.getValue() != '') {
                dCell.setValue('');
              }
            }
          }
        }
      }
    }

    This is just a simple function to remove blank rows from a array of rows.

    function removeBlanks(values) {
      var v = [];
      for (var i = 0; i < values.length; i++) {
        if (values[i] != '') {
          v.push(values[i]);
        }
      }
      return v;
    }
    Close
  • 8/13/2014, 3:14:22 AM

    Travel Photos

    photo managementphotostravel photos
  • 8/13/2014, 3:02:22 AM

    Image managment scripts

    photo managementphotos
  • 7/31/2014, 3:43:45 PM

    MeldCE logo

    csshtml5javascriptsvg
  • 4/26/2014, 6:34:09 AM

    MoltenDB

    databasemoltendbmongodbnodejs
  • 10/29/2013, 12:09:07 AM

    The Expensive Side

    house
  • 9/22/2013, 7:00:08 PM

    abcde

    music
  • 7/16/2013, 8:26:10 PM

    Energizer Power Plus Rechargeable Batteries

    gadgets
  • 3/4/2013, 6:27:40 PM

    USAR FOGTeX

    fogtex
  • 2/21/2013, 9:09:54 PM

    Personal Gear Bag

    rescuegear
  • 2/18/2013, 11:10:34 PM

    Geocaching Stamp

    stampgeocaching
  • 4/2/2007, 3:50:02 PM

    Call Record Presenter

    parserpythonsqliteweb
  • 9/25/2004, 9:48:10 AM

    Kimi Ora School

    csshtml
  • 6/25/2004, 8:53:53 AM

    All About Catering Web Site

    csshtml
  • 7/26/2003, 7:52:11 AM

    Quantum Accounting Web Site

    csshtml
Copyright BytesNZ 2021. Powered by Sapper
[matrix]