Tech Stuff Tales from the trenches

jQuery/Underscore/Lodash extend, without the mutation

I was writing a little Javascript today where I wanted to scale a bunch of coordinate data from a source object to a new set of scaled objects. So I did something like this...

var originalData = JSON.parse(source);
var scaledData = _.extend(
    originalData,
    {objects: _.map(toScaledObject, originalData.objects)}
);

But surprisingly didn't get the results I expected. Turns out firstly I had the arguments to map the wrong way around, but before realising this I also noticed that my original source object was also getting updated. Wat?!?

Destination Object

After checking the docs I realised both jQuery and Underscore/Lodash refer to the first object passed to the extend function as the destination object. So while the function also (curiously) returns this destination object, it is in fact mutated in-place as part of the extend operation.

The Workaround

The workaround I used was to provide a new object for this destination object, and then as part of extend map the source object onto this. Like...

var originalData = JSON.parse(source);
var scaledData = _extend(
    {},
    originalData,
    {objects: _.map(originalData.objects, toScaledObject)}
);

Simple enough. So library writers please try to be immutable by default.

jQuery UI onChangeMonthYear Event Gotchas

Today involved fixing a bug where I have a some date fields that use the datepicker component from jQuery UI, and I've added some helper links next to them when you can set the date to 3, 6 12, etc months in the future. The bug reported was that these links only work the second time they are clicked. Hmmm...

I spent more time than I'd like to admit reading and stepping through the code to work out what was going on, and just found myself going round in circles.

onChangeMonthYear

Recently I had added a hook for the event onChangeMonthYear so that when the user changed the month or year from the open datepicker it would default to the first of that month (as this is what my users usually want to do, and the out-of-the-box behaviour is to do nothing). Here it is...

var onChangeMonthYearHandler = function(year, month, inst)
{
    var picker = $(this);
    var current = picker.datepicker('getDate');

    if (current) {
        current.setDate(1);
        current.setYear(year);
        current.setMonth(month - 1);

        picker.datepicker('setDate', current);
    }
};

Gotcha #1

Can you see my first bug? Well, when setting the value of the datepicker via my helper links if the date being set is different to the current month/year of the datepicker then this event gets fired. The flow being...

  • Set date to 6 months in future
  • onChangeMonthYear fires
  • onChangeMonthYear handler sets the date to the first of the month
  • Doh!

Ok, so I guess it's expected and probably useful that this event fires both when the user changes the month/year via the control and also when the date is changed via the API. So here's my first fix, checking if the datepicker is visible...

var onChangeMonthYearHandler = function(year, month, inst)
{
    var picker = $(this);
    var current = picker.datepicker('getDate');
    var isVisible = $(inst.dpDiv).is(':visible');

    if (isVisible && current) {
        current.setDate(1);
        current.setYear(year);
        current.setMonth(month - 1);

        picker.datepicker('setDate', current);
    }
};

Gotcha #2

I thought this would have fixed it, but no... I can see that my check as to wether or not the datepicker is visible is working, and am confident it's required... but the date is still being set incorrectly and needs two clicks.

After more debugging and code reading it turns out the bug comes from my usage of the getDate API method. This method does not just return the date from the datepicker, but in doing so mutates the internal state of the control. The flow being...

  • Set date to 6 months in future
  • onChangeMonthYear fires
  • getDate called, which resets the date using current data (not applied from new date yet)
  • Doh!

Unfortunately there is no mention in the docs that calling any datepicker methods from within this handler could lead to undesirable results... Gah!

Here's my fix...

var onChangeMonthYearHandler = function(year, month, inst)
{
    var isVisible = $(inst.dpDiv).is(':visible');

    if (isVisible) {
        var picker = $(this);
        var current = picker.datepicker('getDate');

        if (current) {
            current.setDate(1);
            current.setYear(year);
            current.setMonth(month - 1);

            picker.datepicker('setDate', current);
        }
    }
};

I'm only trying to set the date to the 1st if the datepicker is open, but also only accessing the current date of the control when this is the case. This means that when the handler fires after the date has been set programatically it won't step in and bugger things up.

Conclusion

My take away from this is a reminder as to the complications and dangers of managing state, and how immutability makes things safer and less unexpected. I took a look to see if I could contribute this note to the official docs but there was no obvious way to do so.

P.S.

I'm aware of Javascript scope rules and the fact my picker and current vars are actually visible to the entire function, but I prefer to ignore particular gotcha in favour of readability.

Update

After writing this I realised that my second gotcha (while valid) is actually calling the getDate API unnecessarily. So I've cleaned it up a little...

var onChangeMonthYearHandler = function(year, month, inst)
{
    var isVisible = $(inst.dpDiv).is(':visible');

    if (isVisible) {
        var current = new Date();

        current.setDate(1);
        current.setYear(year);
        current.setMonth(month - 1);

        $(this).datepicker('setDate', current);
    }
};

Which then avoids the second gotcha entirely!

Mysql Datetime Indexes And Intervals

Today I ran into an interesting (though afterwards 'doh! of course...') problem involving a slow MySQL query that should have been a hell of a lot quicker. The application I work on supports users in many timezones, so when it comes to query time we always need to adjust system recorded times (which are always UTC) to allow for the users current timezone.

So for example consider a table...

1 CREATE TABLE mytable (
2     id INT UNSIGNED NOT NULL AUTO_INCREMENT,
3     date_created DATETIME NOT NULL,
4     KEY date_created_idx (date_created),
5     PRIMARY KEY (id)
6 );

And a made-up query against it, where :tzOffset is the users UTC offset in hours, and :theDate is a datetime we'd like to query against...

1 SELECT *
2 FROM mytable
3 WHERE date_created + INTERVAL :tzOffset HOUR > :theDate

Given a relatively small number of rows you'll have no problem with this, but open the EXPLAIN output and you'll notice MySQL ignores the index.

Change Perspective

I suppose it makes sense that MySQL is now unable to use the index, it's an index on the date_created column, not on the date_created_plus_some_timezone_ column. So the fix is to move the date wrangling to the query parameter instead.

1 SELECT *
2 FROM mytable
3 WHERE date_created > :theDate - INTERVAL :tzOffset HOUR

Notice how the sign on the INTERVAL modifier changes as we're moving the date the other way. You could of course do this calculation on the date before binding it to the query, but in my case we often need to do the INTERVAL adjustment on the column itself so it's nicer to keep it in one place.

A reminder to always keep one eye on EXPLAIN...

Drawing Rings With Css

I mentioned in my last post that the main application I work on has just undergone a redesign. One part of this was adding more visual goodness to the dashboard which greets users on login and serves as their information overview hub. A new component here displayed a progress wheel to keep the user up to date with their target for the month. Here's the finished article...

Image

So how to render this... let's pretend you didn't read the title of this post, and I'll tell you the first thing I considered was to use SVG. But I don't really know anything about SVG... so as well as having to start from scratch from an implementation point of view I've also no idea about the trade-offs of deploying it. So I thought I'd try using CSS.

Design Plan

The basic plan I had was to draw two rings, the green one placed infront of the grey one. I hoped that I could use percentage sizes inside a container to allow the whole thing to flex and move with the page.

The Easy Way...

The easiest part is obviously the backing grey ring so I won't spend too much time on that. Essentially it's two divs, styled to be circles, the back one grey and the front one white to overlay the middle part and give the illusion of the grey being a ring. Like so...

<div class="progress">
    <div class="back"></div>
    <div class="back-centre"></div>
</div>
.progress .back {
    position: absolute;
    background-color: #e7eeeb;
    width: calc(100% - 10px);
    height: calc(100% - 10px);
    top: 5px;
    left: 5px;
    border-radius: 50%;
}

.progress .back-centre {
    position: absolute;
    background-color: #fff;
    width: calc(100% - 40px);
    height: calc(100% - 40px);
    top: 20px;
    left: 20px;
    border-radius: 50%;
}

As you can see from the code above I'm not worrying too much about backwards compatibility here, I'm lucky enough to be in the position where we can expect a modern browser. It's fairly self explanatory so I won't go through it. The only thing to note is the offsets and calcs which make the circle slightly inset (which will become important when drawing the green portion as it's a wider ring that overlaps on both sides).

The Hard Way!

That's the easy part done, but the green ring is tricker as it's partial. The plan for this part is to draw it in four segments by skewing a div around the origin of the circle, and then cutting out the middle with another overlayed circle. Maybe a disgram will explain...

Image

Going through the bits...

  1. The containing circle will hide the overflow
  2. The white circle will cut the centre out
  3. The green coloured skewed div will provide the appearance of the ring

Here's the code for the first segment, 0-25%.

<div class="seg seg-1" style="transform: skew(0deg, 45deg)"></div>
.seg {
    position: absolute;
    background-color: #4ecaab;
    width: calc(100% / 2);
    height: calc(100% / 2);
}

.seg-1 {
    top: 0;
    left: 0;
    transform-origin: bottom right;
}

The transform uses a skew to achieve the rotation effect, the important part to note here for the first segment is transform-origin which specifies the corner of the div that the skew will be based on. The plan for the next three segments will be to move the transform-origin for each to be the centre of the circle...

.seg-2 {
    top: calc(100% / 2);
    left: 0;
    transform-origin: top right;
}

.seg-3 {
    top: calc(100% / 2);
    left: calc(100% / 2);
    transform-origin: top left;
}

.seg-4 {
    top: 0;
    left: calc(100% / 2);
    transform-origin: bottom left;
}

As I said I use a little templating server-side to decide which segments to draw and what angle to draw, here's the first segment...

{% if percentage <= 25 %}
    {% set angle = (percentage / 25) * 90 %}
    {% set skew = 90 - angle %}

    <div class="seg seg-1" style="transform: skew(0deg, {{ skew }}deg);"></div>
{% elseif etc... %}

The Final Result

The completed thing looks great, and resizes really nicely with the page. Obviously if you don't have a modern browser then you're going to see an absolute mess, but that's ok for my case.

As I mentioned at the start of the article I didn't choose to use SVG because of my complete lack of knowledge of it - but I'm really pleased with how it came out with CSS. If I've missed some tricks and there are actually much simpler ways to do this then I'd love to hear about it so leave a comment.

Server To Client Templating

The PHP application I work on uses Symfony2 with Twig. Recently we've undergone a redesign and it was a good chance to consolidate a lot of things that have grown out in their own directions over the last few years. One of those things involved listing content, so I created a listing template for other code to extend (obviously simplified for this example) ...

<li>
    <div class="title">{% block title %}{% endblock %}</div>
    <div class="body">{% block body %}{% endblock %}</div>
</li>

This worked great and allowed me to really simplify and centralise the styling of these kinds of elements (of which there are lots!). But anyway, one of the places where content was listed was in a Javascript-only component that did the usual fetching JSON from the server and then rendering it ting...

So how to use my lovely listing templating here?

I'm using Lodash templating in the client so wanted to get the code there somehow. I decided to try using script templates...

<script id="tpl-listing" type="text/template">
    {% include 'MyBundle:Foo:listing.html.twig' %}
</script>

With the template then including the Lodash bits...

{% extends 'MyBundle::listing.html.twig' %}

{% block title %}<%= item.title %>{% endblock %}

{% block body %}
    <ul>
        <% _.each(item.children, function(child) { %>
            <li>
                <%= child.title %>
            </li>
        <% }); %>
    </ul>
{% endblock %}

I can then use this in the client...

var tpl = $('#tpl-listing').html();
var html = _.template(tpl, {item: foo});

// etc...

When I first did this I thought it was a bit insane, but I've grown to like it and haven't run into any problems yet. If you know of any, or can suggest some improvements please let me know (using a single language or templating library between client and server probably being one of them...)