Monday 18 December 2017

FullCalendar Anatomy

This will detail discoveries about the FullCalendar plugin.

We're primarily interested in three views: month, agendaWeek and agendaDay. Further, we're only interested in the calendar itself and not the header (div.fc-toolbar).

I've written it in Markdown so I can scroll the container.

## month ##
The `div.fc-month-view` is within the `div.fc-view-container` and consists of a table with a `thead.fc-head` which has the days of the week and a `tbody.fc-body`.
* `tbody.fc-body`
    * `tr`
        * `tr.fc-widget-content`
            * `div.fc-day-grid-container`
                * `div.fc-day-grid`
                    * `div.fc-row.fc-week` **×6**
                        * `div.fc-bg`
                            * `table`
                                * `tbody`
                                    * `tr`
                                        * `td.fc-day.fc-widget-content` **×7 plus (`fc-sun`, `fc-mon`, `fc-tue`, `fc-wed`, `fc-thu`, `fc-fri` or `fc-sat`)**
                        * `div.fc-content-skeleton`
                            * `table`
                                * `thead`
                                    * `tr`
                                        * `td.fc-day-number`  **×7 plus (`fc-sun`, `fc-mon`, `fc-tue`, `fc-wed`, `fc-thu`, `fc-fri` or `fc-sat`), this is where the numbers are.**                    
                                * `tbody`
                                    * `tr`
                                        * `td` **if these have events then `.fc-event-container`, otherwise just empty**                           

## agendaWeek ##
* `tbody.fc-body`
    * `tr`
        * `td.fc-widget-content`
            * `div.fc-day-grid`
                * `div.fc-row.fc-week.fc-widget-content`
                    * `div.fc-bg`
                        * `table`
                            * `tbody`
                                * `tr`
                                    * `td.fc-axis.fc-widget-content`
                                        * `span`
                                    * `td.fc-day.fc-widget-content` **×7 plus (`fc-sun`, `fc-mon`, `fc-tue`, `fc-wed`, `fc-thu`, `fc-fri` or `fc-sat`)**
                    * `div.fc-content-skeleton`
            * `hr.fc-divider.fc-widget-header`
            * `div.fc-time-grid-container.fc-scroller`
                * `div.fc-time-grid`
                    * `div.fc-bg`
                        * `table`
                            * `tbody`
                                * `tr`
                                    * `td.fc-axis.fc-widget-content`
                                    * `td.fc-day.fc-widget-content` **×7 plus (`fc-sun`, `fc-mon`, `fc-tue`, `fc-wed`, `fc-thu`, `fc-fri` or `fc-sat`)**
                    * `div.fc-slats`
                        * `table`
                            * `tbody`
                                * `tr`
                                    * `td.fc-axis.fc-time fc-widget-content`
                                        * `span` **Time here (repeated for every half hour during the day)** 
                                    * `td.fc-widget-content`
                                * `tr.fc-minor`
                    * `div.fc-content-skeleton`
                        * `table`
                            * `tbody`
                                * `tr`
                                    * `td.fc.axis`
                                    * `td` **×7**
                                        * `div.fc-content-col`
                                            * `div.fc-event-container.fc-helper-container`
                                            * `div.fc-event-container` **Events go here**
                                            * `div.fc-highlight-container`
                                            * `div.fc-bgevent-container`
                                            * `div.fc-business-container`

## agendaDay ##
* `tbody.fc-body`
    * `tr`
        * `td.fc-widget-content`
            * `div.fc-day-grid.fc-unselectable`
                * `div.fc-row.fc-week.fc-widget-content`
                    * `div.fc-bg`
                        * `table`
                            * `tbody`
                                * `tr`
                                    * `td.fc-axis.fc-widget-content`
                                        * `span` **all-day**
                                    * `td.fc-day.fc-widget-content` **and fc-*day***
                    * `div.fc-content-skeleton`
            * `hr.fc-divider.fc-widget-header`
            * `div.fc-scroller.fc-time-grid-container`
                * `div.fc-time-grid.fc-unselectable`
                    * `div.fc-bg`
                        * `table`
                            * `tbody`
                                * `tr`
                                    * `td.fc-axis.fc-widget-content`
                                    * `td.fc-day.fc-widget-content` **and fc-*day***
                    * `div.fc-slats`
                        * `table`
                            * `tbody`
                                * `tr`
                                    * `td.fc-axis.fc-time.fc-widget-content` **these two cells are repeated every half hour**
                                        * `span` **time here**
                                    * `td.fc-widget-content`
                    * `hr.fc-divider.fc-widget-header`
                    * `div.fc-content-skeleton`
                        * `table`
                            * `tbody`
                                * `tr`
                                    * `td.fc-axis`
                                    * `td`
                                        * `div.fc-content-col`
                                            * `div.fc-event-container.fc-helper-container`
                                            * `div.fc-event-container` **Events here**
                                                * `a.fc-time-grid-event.fc-v-event.fc-event.fc-start.fc-end.fc-draggable.fc-resizable`
                                                    * `div.fc-content`
                                                        * `div.fc-time`
                                                            * `span` **time here**
                                                        * `div.fc-title`
                                                    * `div.fc-bg`
                                            * `div.fc-highlight-container`
                                            * `div.fc-bgevent-container`
                                            * `div.fc-business-container`

Saturday 25 November 2017

Dom's Swan Song

Cross posted from Arcus Global's Blog where Nic Williams let me say goodbye. Thanks, Nic!

On the 30th of January 2010 I left my boat (I was living aboard, moored on Stourbridge common in Cambridge, at the time), unlocked and got on my bike and put my headphones on. I cycled along at a silly pace as I was listening to a cool French Punk band at the time. Getting to the Judge Business School took far too little time, and I locked my bike up and went looking for Lars. The next 45 minutes was such a fun experience I ended up emailing him afterwards and saying thanks and apologising for interrupting him – I know that that is seen as good practice in some areas, but not in nursing interviews. As I hinted, I came from a different career, full of people who practice active listening for a living and being able to talk about all the cool things I was interested in, without having to worry that I was being actively listened to, was just cool.

After a call from Denis, to check I knew what I was talking about, I was in and started in the beginning of March 2010 - encouraged to play with all the cool things I was, and still am, fascinated by. We soon won the Guardian Megas and Peter encouraged me to go up with Lars and Denis to accept the prize. One of the best nights I have ever had that was! I got to meet some fantastically bright people at work and not feel like a fraud as I could keep up my end of the conversation more often than not. I was even dragged in front of prospective clients who were so impressed they asked if I could assure them that I’d not disappear off to the Bahamas before the project was done.

We have grown such a lot since then, but I still feel valued, which is why my recent decision to leave Arcus has been so hard.

Arcus helped me more than I can ever hope to express when the worst tragedy rocked my family. Denis was a rock and let me witter on to him in the depths of my despair at ridiculous hours of the day and night – I dread to think what I was saying. That happened in the Summer and every Summer since I have had the urge to wander off. This year I have let the urge have its way, so I am leaving. It feels like leaving home. I have learnt such a lot and grown as a person and developer, but I feel its time to test myself somewhere else.

I no longer live aboard a boat but live in a house that I could not have afforded unless my efforts were rewarded at Arcus. Though being paid has always felt a little odd, as I code for fun rather more than for profit. I know I am not supposed to say that, but there’s nothing quite so much fun as being paid to do what you love. I love the fact that I am allowed, even encouraged, to disappear in my head and figure things out – though the waking up in the early hours with my head full of data structures can be disruptive. However, that is accepted at Arcus, I have popped into the office in the early hours of the morning and left in the early afternoon thanks to getting far more than eight and a half hours in and needing to rest if only to crack the next challenge.

The perks and the close-knit team make this the best place I have ever worked. The people are helpful and generous with their time and – I know I have said this before – frighteningly bright! This in itself is a challenge, but not in a bad way, rather in a way that encourages me to be brighter too.

So I will miss everyone at Arcus, and I hope that you continue to go from strength to strength. It is time for me to test myself though. Who knows, perhaps I will come close to finding a team of people who are as stunning, I do hope so.

Friday 3 November 2017

p5 Pacman.js

I've been playing with p5js on and off for a while in order to generate some resources for use with Code Club and I've made (so far) Frogger, Snake and I've helped with Pong. Now I'm working with Pacman.

The thing about Pacman though, is that his jaws open and close as he's moving along and me and maths have an interesting relationship thanks to a somewhat spotchy formal education. Anyway, p5js has an arc so I was sorted. Sorted until I clocked that by default it uses radians rather than degrees, so much head scratching followed, as well as visits to the Khan Academy. And I got it working.

But p5js knows that people have issues with radians so instead of using radians I set angleMode(DEGREES);, neat eh?

The other thing about Pacman is that he moves and moves along 4 axes so the chomping needs to go in the direction of travel. Again I was stuck with radians until I realised that degrees would work (despite them being an arbitrary measurement). So I just had to rotate the start and stop angles of the arc by 90-degree increments to get this working - except that when Pacman was going right or up he flashed when his mouth was closed, so then I had to replace the arc with a circle if the gap was zero.

This is what I came up with:

if(this.jawWidth === 0){
    ellipse(this.x, this.y, this.r * 2, this.r * 2);
}else{
    let topJaw = this.jawWidth;
    let bottomJaw = -Math.abs(this.jawWidth);
    if(this.speed.every((e, i) => e === [0,-1][i])){
        topJaw += 270;
        bottomJaw += 270;
    }
    if(this.speed.every((e, i) => e === [0,1][i])){
        topJaw += 90;
        bottomJaw += 90;
    }
    if(this.speed.every((e, i) => e === [-1,0][i])){
        topJaw += 180;
        bottomJaw += 180;
    }
    arc(
      this.x, 
      this.y, 
      this.r * 2, 
      this.r * 2, 
      topJaw, 
      bottomJaw, 
      PIE);
}

I was not overly happy with it, but it worked. But what about just rotating Pacman instead? It turns out that that is even easier but does involve pushing and popping to ensure everything works properly - and that took quite a bit of head-scratching as well (It really is no wonder I'm bald). Anyway, here it is:

push();
translate(this.x, this.y);
if(this.speed.every((e, i) => e === [0,-1][i])){
    rotate(270);
}
if(this.speed.every((e, i) => e === [0,1][i])){
    rotate(90);
}
if(this.speed.every((e, i) => e === [-1,0][i])){
    rotate(180);
}
arc(
  0, 
  0, 
  this.r * 2, 
  this.r * 2, 
  this.jawWidth, 
  -Math.abs(this.jawWidth), 
  PIE);  
pop();

It's much more elegant isn't it?

Monday 30 October 2017

Chomping through a string from the front.

I am quite keen on functional programming techniques, so I am sort of disappointed in myself today. I am disappointed because I have had to change the state of some data, but for the best of reasons considering the use case. I went so far as to write a class that did what I needed it to do without interfering with the original data, but that just felt wrong, as though I was hiding what I was doing and I am not sure if I should try to hide my guilt. On the upside, I did use recursion!

Anyway, my use case required some fragments of a string to be spat to a backend where they would then be stitched back together again. I could send the whole string in one go, but it could be arbitrarily large, so I need to chomp through it and spit each uniform chunk back to the server.

I spent ages thinking about it then, as is so often the case, I went to JSFiddle and wrote this:

const container = document.getElementById("data");
const ourString = "1234567";
const chunkSize = 2;

const sendChunk = (ourString) => {
    const chunk = ourString.substr(0, chunkSize);
    const div = document.createElement("div");
    div.textContent = `Sending: ${chunk}`;
    container.appendChild(div);
    ourString = ourString.slice(chunkSize);
    if (ourString.length) {
        sendChunk(ourString);
    } else {
        const finalDiv = document.createElement("div");
        finalDiv.textContent = "All Done!";
        container.appendChild(finalDiv);
    }
};

sendChunk(ourString);

Along with this simple bit of HTML:

<div id="data"></div>

Basically it uses substr and slice to extract and then remove a portion of the string. If the string still has a length it then repeats the process, otherwise it tells us we’re all done. Neat eh? I just wish it didn’t feel so dirty.

Wednesday 30 August 2017

p5 Frogger

We've been playing with p5.js lately in order to generate some of our own resources for Code Club. I'm sort of really keen on using JSFiddle but I'm also conscious that it's not overly kid friendly - it's not unfriendly, but it is tailored to an adult audience who know what they're doing. So I thought I'd try using trinket:

Not sure if it's playable in its embedded form but the arrow keys should move it if it is.

Needless to say, it was mostly stolen from The Coding Train but adapted and converted into ES6 (not sure why I hated classes in Java but I love them in JavaScript).

The colours are fun: The red is a London Bus, the yellow is a DHL Truck, the blue are two Tesco vans and the three green ones are minis in British Racing Green...

Wednesday 16 August 2017

Handicap curves from Archers Mate

I've been doing a little work on investigating archery handicaps for Witchford Archers, and it's been a fascinating journey into statistics. The Club Secretary, God-bless-him, has passed me all sorts of facts and figures and I've been looking at the code on the Archer's Mate website. We'd also attended a meeting of the CAA and discovered that working out your Handicap was a useful thing to do (especially if you enter competitions - for me, less so).

On the site, there is a simple form which allows a user to enter the round they are shooting, the type of bow they are using, the type of target they are shooting at, their gender and their score.

I was only really interested in the Portsmouth handicaps, so I was a wee bit of a pain and hit the server with lots and lots of requests (I did it during odd hours, so hopefully it didn't impact things too much).

Anyway, I discovered that the handicaps didn't differ between men and women but that the curve varied between the different bows and targets. I guess the main take away was that the user shooting a compound at a full target started off the same as someone shooting a recurve bow at the same target, as their score increased though, they began to follow the handicaps of those shooting a compound bow at a triple target.

Hopefully, the graph above illustrates what I mean. I'm not going to hit the server anymore, but I think I'll do the same to the handicaps published by Archery GB and see if there's any correlation.

There did seem to be a difference in the classifications between men and women though... not sure what that's all about.

Tuesday 8 August 2017

Complex object creation in JavaScript

In JavaScript you can get a little spoilt with the initialisation of variables. For example I might have something like this:

const start = {
    "x": 20,
    "y": 20
}, end = {
    "x": 40,
    "y": 60
}, width = end.x - start.x, height = end.y - start.y;

Where width and height rely on the previously set variables.

Today, at work, I was asked if there was a way of doing something similar regarding initializing an object. This pseudo code illustrates what I mean:

const psuedoObj = {
    "start": [20, 20],
    "end": [40, 60],
    "width": this.end[0] - this.start[0],
    "height": this.end[1] - this.start[1],
};

console.log(psuedoObj);

The thing is, that doesn't work.

So we got to looking and found this answer on Stack Overflow. To get around the issue we could then do this:

const psuedoObj = {
    "start": [20, 20],
    "end": [40, 60],
    "init": function(){
        this.x = this.start[0];
        this.y = this.start[1];
        this.width = this.end[0] - this.start[0],
        this.height = this.end[1] - this.start[1],
        delete this.init
        return this;
    }
}.init();

console.log(psuedoObj);

Nice ehh? And we even tidy up after ourselves!

Wednesday 2 August 2017

Some thoughts on pagination

I've written an adaptation of DataTables Pagination for use with MDB before but I've never really had to think about pagination much until, that is, a recent project at work came up. I was laid-up at home with a cold, so I used my time to think about it in some depth. For my use case, I wanted a set of buttons starting with a First and Previous button and ending with Next and Last buttons. I spent some time looking around, and it seemed as though the best arrangement of buttons for individual pages had an odd number.

This, then, was my desired output:

1 2 3 4 5

With the currently active page disabled for it to be distinguishable (and immune to clicking - I'm not sure why that's as important as it is to me, but it always pleases me if I'm unable to link back to the page I'm on from the page I'm on). I guessed that I'd also need to disable the First and Previous buttons when the first page was active and the Next and Last when the last page was active.

This then meant that the as far as possible the active number should be in the centre of the range of numbers. In the example above then when three was active I'd be happy. However, should four be the active page then the range would start at two and end at six. When one is the active page then it is not in the middle of the range; so we don't need to show numbers which are impossible to display (we really can't get to the page with the number minus one).

If we had a range of seven pages of data to display, this is what we should see while paging through the data:

Active: one

1 2 3 4 5

Active: two

1 2 3 4 5

Active: three

1 2 3 4 5

Active: four

2 3 4 5 6

Active: five

3 4 5 6 7

Active: six

3 4 5 6 7

Active: seven

3 4 5 6 7

This, then presented me with some considerable confusion, not least because I had trouble thinking clearly - but after far too many hours of pondering I can up with this spreadsheet and started reasoning about the logic behind calculating what I needed to happen.

After playing with the sheet, I clocked the algorithm required needed to check a number of different figures in order to decide where to start and end the range:

=IF(LTE(A3, CEILING(DIVIDE(D2, 2))),
    1,
    IF(GT(ADD(A3, FLOOR(DIVIDE(D2, 2))), A14),
        ADD(MINUS(A14, D2), 1),
        MINUS(A3, FLOOR(DIVIDE(D2,2)))))

Bonkers ehh?

I wrote the first line and clocked what was going on and decided to cheat after that, so I wrote a JSFiddle that generated the correct Google Sheet code for me, which I could then use to create the finished product. The final bit of code is rather simple, and this is it:

const generateCell = (text, active) => {
    const cell = document.createElement("td");
    cell.textContent = text;
    active && cell.classList.add("active");
    return cell;
}
const generateRow = (f, a, b) => {
    const row = document.createElement("tr");
    row.append(generateCell("", a === 1));
    row.append(generateCell("", a === 1));
    for(let i = f; i < (f + b); i++){
        row.append(generateCell(i, i === a));
    }
    row.append(generateCell("", a === total));
    row.append(generateCell("", a === total));
    return row;
};
const total = 12;
const table = document.getElementById("body");
const buttons = 5;
for(let active = 1; active <= total; active++){
    let first = (active <= Math.ceil(buttons / 2))
        ? 1
        : (active + Math.floor(buttons / 2) > total)
            ? (total - buttons) + 1
            : active - Math.floor(buttons / 2);
    table.append(generateRow(first, active, buttons));
}

I'm sort of ashamed that it took quite as long as it did, but I’m rather pleased with the result.

Friday 14 July 2017

d3.json and Data URLs

I've been playing with d3 quite a bit lately and I know there's a load of stuff I need to learn more about but I thought I'd share this little trick associated with d3.json.

I've had a couple of occasions where I needed to pre-process the json sent to a d3 script. Each time I've been stumped by ds.json requiring an URL, I'm not going to process the data and then send it back to the server in order to get it again, so I started thinking about Data URLs and base64 encoding (as is my wont) and this sorts it out a treat:

let url = "data:application/json;charset=utf-8;base64,";
url += btoa(JSON.stringify(processedObject));
d3.html(url, function(error, graph) {
    if (error) throw error;
    // do cool things with the data...
});

Anyway, I hope it helps someone (and yes, I do know you don’t need to do it, it’s just nice not to change the original scripts too much!).

Tuesday 13 June 2017

JavaScript - shorthand if statement (sans else)

I'm all in favour of shorthand statements and I often visit this Sitepoint page. I'm also keen on ES6 destructuring as I seem to spend a lot of time thinking about the shape and patterns of data anyway.

One shorthand I've always like is the if using &&:

Given these constants:

const thing  = "fred";

const helloWorld = () => {
    console.log("hello");
    console.log("world");
};

We can do this:

thing === "fred" ? helloWorld() : null;  // hello\nworld

thing === "Fred" ? helloWorld() : null;  // nowt

We can also do this:

thing === "fred" && helloWorld(); // hello\nworld

thing === "Fred" && helloWorld(); // nowt

But, even better, we can do this:

thing === "fred" && (() => {
    console.log("hello");
    console.log("world");
})();  // hello\nworld

thing === "Fred" && (() => {
    console.log("hello");
    console.log("world");
})();  // nowt

How cool is that? JSFiddle here.

Wednesday 26 April 2017

For loop -vs- Map

I got asked last week about getting the index of an object in an array which had the earliest date, so that this array:

let objArr = [
    {
        "date": "2011-08-12T20:17:46.384Z"
    }, {
        "date": "2012-08-12T20:17:46.384Z"
    }, {
        "date": "2013-08-12T20:17:46.384Z"
    }, {
        "date": "2014-08-12T20:17:46.384Z"
    }, {
        "date": "2010-08-12T20:17:46.384Z"
    }, {
        "date": "2009-08-12T20:17:46.384Z"
    }
];

Produces 5 as that element has the earliest day.

I pondered using map but then decided to go all old school and used a for loop like this:

let boringParse = i => parseInt(moment.utc(i.date).format("x"), 10);
let forLoopInt = objA => {
    let min, i, objALength = objA.length;
    for (let y = 0; y < objALength; y++) {
        let now = boringParse(objA[y])
        if (!min || now < min) {
            min = now, i = y;
        }
    }
    return i;
};

Once I got back to my desk I decided to talk it over with James and came up with this:

let boringParse = i => parseInt(moment.utc(i.date).format("x"), 10);
let mapOne = objA => {
    let datesArr = objA.map(boringParse);
    return datesArr.indexOf(Math.min(...datesArr));
};

Turns out that the for loop is the faster, but only just!

Saturday 22 April 2017

WebSQL bulk insert

I've been playing with WebSQL of late (after all, why wouldn't you play with a deprecated standard?). And I'm loving it for inserting data via Papa Parse, the thing is, it was proving to be really, really slow... right up until I clocked that I was doing it wrong!

The thing is, I was iterating over my data and firing a transaction with an SQL payload for each row of the CSV file I was parsing where I should've been firing a transaction and then iterating over my data and firing off multiple SQL payloads within the single transaction.

This was my first effort:

for (let record of results.data) {
    mydb.transaction(
        t => {
            t.executeSql(`
                INSERT INTO
                  accounts (
                    Date,
                    Company,
                    Account,
                    Amount
                  )
                VALUES (
                  ?,
                  ?,
                  ?,
                  ?
                )`,
                [
                    record.Date,
                    record.Company,
                    record.Account,
                    record.Amount
                ],
                t => {
                    let query = "SELECT COUNT(*) AS total FROM accounts";
                    t.executeSql(
                        query,
                        null,
                        (t, result) => {
                            if (~~result.rows[0].total === tableMetaData.total) {
                                createTable();
                            }
                        },
                        null
                    );
                },
                null
            );
        }
    );
}

Pretty, eh? But it took silly amounts of time to execute and I was planning on inserting 250,000 records. It simply wasn't practical to do it that way as it took something like 20 seconds to insert 1000 rows:

RowsTime
100.218
1001.711
100021.111
500090.703

Changing the code to this:

mydb.transaction(t => {
    for (let record of results.data) {
            t.executeSql(`
                INSERT INTO
                  accounts (
                    Date,
                    Company,
                    Account,
                    Amount
                  )
                VALUES (
                  ?,
                  ?,
                  ?,
                  ?
                )`,
                [
                    record.Date,
                    record.Company,
                    record.Account,
                    record.Amount
                ],
                null,
                null
            );
        }
    },
    error => {
        console.log("error", error);
    },
    () => {
        createTable();
    }
);
Produced these results:
RowsTime
100.081
1000.058
10000.083
50000.223

Not only quicker but I've also halved the number of queries as I don't need to check that all the transactions have completed after each insert, sweet eh? After figuring this out I came across a number of posts where the same thing had been discovered by others. Including RICAL and Gavin Pickin. Just a shame it's deprecated.