Thursday, 12 May 2022

Clearing local storage with a BookMarklet

I'm doing a fair bit with localStorage at the minute; and I need to clear it out regularly. Opening the chrome://settings can get boring real quick, so I spent a happy 5 minutes researching the subject and clocked that a Bookmarklet would work a treat; here it is Clear Local Storage, feel free to use it. Here's the JS should you need to adapt it for your purposes:

javascript:(() => {
  localStorage.clear();
  location.reload();
  console.info('Local Storage Cleared');
})();

Edit

I realised that I needed to refresh the page for the cleared cache to be evident, I've added a location.reload() to do just that.

Thursday, 28 April 2022

Unconscious Bias

We just had a work based session on unconscious bias, and while I didn't contribute, I got to thinking about my own unconscious bias. I believe my unconscious bias has been mostly burnt out; let me explain: The good and bad thing about being from Yorkshire (and being raised by my Ma) is that I grew up hating everyone! It wasn't a race, religion, ability or gender thing; it was everyone: people from other countries, counties, towns or villages; even people who lived on different streets or who lived on our street in a different house, sometimes even people in our home, and quite possibly, ourselves.

It was only once I left Yorkshire (and grew up some more) that I clocked that, on the whole, I like everyone. This realisation was firmed up by my early career and the odd personality test, which indicated I was more gregarious than curmudgeonly.

Monday, 25 April 2022

Form data to table

I've been doing a fair bit with jQuery's serialize() recently but found myself looking for a way to display the values before submitting them via an ajax() request. A friendly and straightforward method is to take advantage of console.table() to do the heavy lifting:

console.table(Object.fromEntries(new URLSearchParams($form.serialize())))

Friday, 25 March 2022

FizzBuzz, my arse

If you ever look me up on Goodreads, you'll find that I took an age to read The Fizz Buzz Fix: Secrets to Thinking Like an Experienced Software Developer.. I was primarily interested in reading about the FizzBuzz problem and found the rest exhausting (I wonder if they do it as an audio version?). Anyway, I found myself pondering it again recently and came across this version by Brandon Morelli:

for(let i=0;i<100;)console.log((++i%3?'':'fizz')+(i%5?'':'buzz')||i)

Aye, it's brilliant, but all approaches seem to end up using a For loop, and I got to thinking about alternatives. Thanks to messing about with other coding challenges, I clocked the way of generating an array, then thought about using that generated array with a forEach:

Array.from({length: 100}, (_, i) => i + 1).forEach(i => { 
  console.log((i%3 && i%5) ? `${i}` : `${i} ${(i%3 ? '' : 'Fizz') + (i%5 ? '' : 'Buzz')}`) 
})

This method has the benefit of not adding an extra space after the number for those numbers that aren't divisible by three or five. The forEach seems as fast for numbers up to 100 during testing and faster for numbers up to 1000.

The images of code are from https://chalk.ist, bloody brilliant aren't they?

Sunday, 2 January 2022

JSONDB(ish)

It's that time of year when I have a little time to piddle about, so I decided to do a little research, prompted by reading this post from Andrea Giammarchi. I've written loads about DataTables over the years, so think about grabbing data from APIs probably far too much for someone to be sane. I'm also old enough to remember that AJAX was initially designed to get XML from data sources rather than the JSON we currently, mainly, acquire and process. There's a section in my book (pp. 143-147) about the costs and benefits of both data formats.

Andrea prompted my examination of JSONH, which - in turn - pointed me to JSONDB, and then I found Devon Govett's implementation. I got to thinking about making JSON even smaller!

In an early draft, I compared the relative sizes of both the XML and JSON used as an example; I thought it'd be interesting to fiddle with the family's data I use to get to grips with the adapted CoffeeScript implementation of JSONDB. I also tweaked it to consider nested arrays of objects because why wouldn't you? Anyway, this is the data I'll be comparing:

const familyJSON = {
  family: [
    {
      title: "Dr",
      forename: "08",
      surname: "08",
      dateOfBirth: "08/08/0808",
      hairColour: "brown",
      eyeColour: "brown",
      handedness: "left",
      something: [
        {
          one: 1,
          two: 2,
          three: [1, 2]
        },
        {
          one: 2,
          two: 4,
          three: [2, 4]
        }
      ]
    },
    {
      title: "Dr",
      forename: "09",
      surname: "09",
      dateOfBirth: "09/09/0909",
      hairColour: "white",
      eyeColour: "silver",
      handedness: "ambidextrous",
      something: [
        {
          one: 3,
          two: 4,
          three: [3, 4]
        },
        {
          one: 6,
          two: 8,
          three: [6, 8]
        }
      ]
    },
    {
      title: "Dr",
      forename: "10",
      surname: "10",
      dateOfBirth: "10/10/1010",
      hairColour: null,
      eyeColour: null,      
      handedness: "ambilevous",
      something: [
        {
          one: 5,
          two: 6,
          three: [5, 6]
        },
        {
          one: 10,
          two: 12,
          three: [10, 12]
        }
      ]
    },
    {
      title: "Dr",
      forename: "11",
      surname: "11",
      dateOfBirth: "11/11/1111",
      hairColour: "brown",
      eyeColour: "brown",
      handedness: "right",
      something: [
        {
          one: 7,
          two: 8,
          three: [7, 8]
        },
        {
          one: 14,
          two: 16,
          three: [14, 16]
        }
      ]
    }
  ]
}

And this is the helper class I created:

class JSONDB {

  static isObject = o => o instanceof Object && o.constructor === Object

  static pack = records => {
    const keys = Object.keys(records[0]);
    const ret = ['JSONDB', keys.length, ...keys]
    for (let i = 0; i < records.length; i++) {
      for (const key in records[i]) {
        const record = records[i][key]
        ret.push(Array.isArray(record) && record.every(el => 
          this.isObject(el)) 
            ? this.pack(record) 
            : record)
      }
    }
    return ret;
  };

  static unpack = array => {
    if (array[0] !== 'JSONDB') { 
      return array; 
    }
    const numKeys = array[1];
    const pos = numKeys + 2;
    const keys = array.slice(2, pos);
    const ret = [];
    let cur = {};
    const iterable = array.slice(pos);
    for (let i = 0; i < iterable.length; i++) {
        let val = iterable[i];
        if(Array.isArray(val)){
          val = this.unpack(val)
        }
        if ((i > 0) && ((i % numKeys) === 0)) {
            ret.push(cur);
            cur = {};
        }
        const key = keys[i % numKeys];
        cur[key] = val;
    }
    ret.push(cur);        
    return ret;
  }

}

This example shows the saving of 339 characters.

This is the compressed data:

[
  "JSONDB",
  8,
  "title",
  "forename",
  "surname",
  "dateOfBirth",
  "hairColour",
  "eyeColour",
  "handedness",
  "something",
  "Dr",
  "08",
  "08",
  "08/08/0808",
  "brown",
  "brown",
  "left",
  [
    "JSONDB",
    3,
    "one",
    "two",
    "three",
    1,
    2,
    [
      1,
      2
    ],
    2,
    4,
    [
      2,
      4
    ]
  ],
  "Dr",
  "09",
  "09",
  "09/09/0909",
  "white",
  "silver",
  "ambidextrous",
  [
    "JSONDB",
    3,
    "one",
    "two",
    "three",
    3,
    4,
    [
      3,
      4
    ],
    6,
    8,
    [
      6,
      8
    ]
  ],
  "Dr",
  "10",
  "10",
  "10/10/1010",
  null,
  null,
  "ambilevous",
  [
    "JSONDB",
    3,
    "one",
    "two",
    "three",
    5,
    6,
    [
      5,
      6
    ],
    10,
    12,
    [
      10,
      12
    ]
  ],
  "Dr",
  "11",
  "11",
  "11/11/1111",
  "brown",
  "brown",
  "right",
  [
    "JSONDB",
    3,
    "one",
    "two",
    "three",
    7,
    8,
    [
      7,
      8
    ],
    14,
    16,
    [
      14,
      16
    ]
  ]
]

I'm wondering where I can implement this approach now; it would seem to offer significant savings in terms of the size of the payload while still returning something that will be relatively quick to convert back into JSON.

Friday, 23 July 2021

Email: border-radius and box-shadow

I love working with designers; they constantly challenge me to develop new designs to implement. They have their lovely pixel-based programs and design beautiful things, which I then convert into different formats. This conversion is often relatively easy, but recently I had to create an email with the content within a rounded container, a rounded container with a drop shadow.

Let me tell you, that was a pain! I did a fair bit of research and found excellent ways of generating rounded corners (not least my own from back in the day). AE Writer has an article on drop shadow for HTML email, which sort of confirmed my thoughts on needing to use tables. Alejandro Vargas has an article up on Medium about HTML email rounded corners. I spent a fair few hours over last weekend taking a screengrab of a container into The Gimp and playing with contrast to generate the appropriate data format for a chunk of JS to generate the appropriate nested divs within a table.

Given this table row:

<tr data-row="5"
    data-description="6->3-fe->2-fd->2-fc->1-fb->1-fd->ff">

This code:

(()=>{

  const setStyles = (element, declarations) => {
    for (const prop in declarations) {
      if(declarations.hasOwnProperty(prop)){
        const property = prop.split(/(?=[A-Z])/).join('-').toLowerCase()
        element.style[property] = declarations[prop]
      }
    }
  }



  document.querySelectorAll('tr').forEach(tr => {
    if(tr.dataset.description){
      const description = tr.dataset.description.split('->')
      let target = tr
      const td = document.createElement('td')
      td.setAttribute('align', 'center')
      setStyles(td, {
        paddingLeft: `${description[0]}px`,
        paddingRight:`${description[0]}px`,
      })
      if(!tr.dataset.main){
        setStyles(td, {
          height:'1px',
          lineHeight:'1px',
          fontSize:'1px'
        })
      }
      target.appendChild(td)
      target = td
      description.shift()
      for(let i = 0; i < description.length; i++){
        const parts = description[i].split('-')
        const div = document.createElement('div')
        setStyles(div, {
          display:'block',
          paddingLeft:'0px',
          paddingRight:'0px',
          backgroundColor:`#${parts[0].repeat(3)}`,
          width: '100% !important',
          minWidth: 'initial !important',
        })
        if(parts.length !== 1){
          setStyles(div, {
            paddingLeft: `${parts[0]}px`,
            paddingRight:`${parts[0]}px`,
            backgroundColor:`#${parts[1].repeat(3)}`,
          })
        }else{
          setStyles(div, {
            backgroundColor:`#${parts[0].repeat(3)}`,
          })
        }
        if(!tr.dataset.main){
          setStyles(div, {
            height:'1px',
            lineHeight:'1px',
            fontSize:'1px'
          })
        }
        target.appendChild(div)
        target = div
      }
    }
  })
})()

Would generate this markup:

<tr data-row="5"
    data-description="6->3-fe->2-fd->2-fc->1-fb->1-fd->ff">
  <td align="center"
      style="padding-left: 6px; padding-right: 6px; height: 1px; line-height: 1px; font-size: 1px;">
    <div style="display: block; padding-left: 3px; padding-right: 3px; background-color: rgb(254, 254, 254); height: 1px; line-height: 1px; font-size: 1px;">
      <div style="display: block; padding-left: 2px; padding-right: 2px; background-color: rgb(253, 253, 253); height: 1px; line-height: 1px; font-size: 1px;">
        <div style="display: block; padding-left: 2px; padding-right: 2px; background-color: rgb(252, 252, 252); height: 1px; line-height: 1px; font-size: 1px;">
          <div style="display: block; padding-left: 1px; padding-right: 1px; background-color: rgb(251, 251, 251); height: 1px; line-height: 1px; font-size: 1px;">
            <div style="display: block; padding-left: 1px; padding-right: 1px; background-color: rgb(253, 253, 253); height: 1px; line-height: 1px; font-size: 1px;">
              <div style="display: block; padding-left: 0px; padding-right: 0px; background-color: rgb(255, 255, 255); height: 1px; line-height: 1px; font-size: 1px;">
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </td>
</tr>

That whole manual process was boring, though, so I decided to automate the process. Especially as, knowing designers, I just knew that the box-shadow or border-radius would need to change in the future.

I knew that libraries for generating images from DOM elements were available, so I tried a couple. html2canvas wasn't quite what I was looking for, but dom-to-image worked a treat!

I decided to take an incremental approach and started by copying the dom to a png image format and placing that png within a canvas element of the same size as the element. This process is the code within the Immediately Invoked Function Expression (IIFE) at the bottom of the file. One thing to take note of is the onload function. I ran into all sorts of issues with the subsequent scripts failing until I clocked that the img wasn't loaded when I tried to manipulate it. Once we've set drawn the image atop the canvas, we add some data attributes using the getDimension function - I wouldn't've bothered with this except WebStorm kept complaining about the amount of repeated code I had.

trim, invoked at the end of the IIFE, strips out the remaining white space around the image, leaving us with an image that has only grey-scale colours surrounding it (except at the corners). It trims the rows and columns which contain only white colour values by referencing the values from getDimension. getDimension was clever and checked the values from iterating over the data from getImageData, if any value was not 255 then we had something what was not pure white. The array from getImageData should be chunked into sub-arrays of four as each lump of four values represent the RGBA value from a single pixel.

Once we have a trimmed image, we can build the values that equate with the data attribute we had in the original implementation. I created a simple class for this as a simple array wouldn't work here as I needed more than just the array of values; I needed to know which was the repeating row, so we had a placeholder for the actual content.

We chunk the data into sub-arrays of four and grab the hex colour value from each chunk. If the preceding HEX value is identical, the preceding classes incidence count is incremented; if not, it's added to the row array. If the row is not identical to the preceding row, then it's added to the rows as a Row object; if it is identical then the preceding Row has it's main value changed to true - this will be our placeholder.

We then build our table using the array of Row objects (rows) using code that is very similar to the one above but that is ever so slightly more nuanced and places a placeholder table within the main row. Nice eh? I'm quite pleased with it.

Tuesday, 13 July 2021

Rhino SFRA

Being a fan of JavaScript and now working with SFRA, colleagues told me that we're using Rhino under the hood rather than NodeJS (as I'd assumed from looking at the controllers). Further, from doing a little research, I found out that we're using Rhino 1.7R5. As such, we're a little limited in terms of the JS we can use; proper ES5.