Thursday, 8 September 2016

Yummy Szarlotka

"You know, I bloody love Kiwis, they're lovely", I heard myself say to poor, suffering 'er-indoors whilst taking the dogs out the night before last. I heard myself saying it and remembered saying much the same about the Polish, the Maltese, the Romanians, the Germans and the Bulgarians. I think I've even said it about the French... in fact, I'm pretty sure that I've said it about just about every nationality I've met. I've suggested that Malta is the Yorkshire of the Meditaranian and Poland is the Yorkshire of eastern Europe. I've also remembered an old nursing mentor saying that people from the South use language as a weapon rather than as a means to communicate like we do up North, but that's pants.

My Dad was from down here and I've spent more of my life down here than I did up there and I don't feel overly wounded in my interactions.

This sort of brings to mind the whole Brexit thing and why those areas that have least contact with those who are "different" (for a given quality of "different") voted to leave the EU and retain control of immigration. It's easy to be wary of those who are strangers, in fact, it's probably human nature! But, as I seem to have proven to myself at least, the more contact you have with people who are different the easier it is to see them as just like you.

We're a recruiter of those who are from "different" cultures not because we go out of our way to recruit those who are different but because we recruit those who are the best and who want to learn and grow with us. This approach serves to enrich not only the workplace but each of us as individuals. We get to spend time working and socializing together, working on problems where the different approaches people use helps us reach solutions much faster. We regularly share food from different cultures, but it's sharing those different ways of thinking that make the biggest differences. Some of us are even learning Spanish!

There was a film in the 80s (based on a book from the 70s) called Firefox. In the film, Clint Eastwood is tasked with stealing an advanced Russian fighter plane. He got the gig because he was a native Russian speaker and had the ability to think not only in English but also in Russian; the plane was controlled by thought you see.

This has always struck me as an example of how the language we use guides our thought processes. This is borne out by an article in The New Scientist where the brains of native English speakers were compared to those of native Chinese speakers whilst undertaking math problems. Different areas were active in each group. Malcolm Gladwell has noted the differences between the different languages and notes that the sounds associated with the numbers are significantly shorter in Chinese (I'm probably generalizing here, I know there is more than one language within China), leading to much greater ability to remember a number series. There are other differences as well.

These differences aren't limited to numbers, my favorite is the difference between blue and green in languages other than English. Why this should be the case is beyond my limited understanding but it's a fascinating read. And who can fail to appreciate the impact on unami on British cuisine?

Anyway, all this is by way of a very long thank you to my fantastic colleague Jaroslaw Tyl who made me the cake pictured above. He waited until my blood sugar was under control and then baked this masterpiece, it's called Szarlotka and is some sort of magical combination of cake and crumble and was utterly delicious, thank you Jarek!

Wednesday, 31 August 2016

Alien Summer review

Only once in a while do you read something that makes you question your own mental health... did I really just read that? Am I sure I should be feeling like this after that? Dhalgren and The Quantity Theory of Insanity did it to me and, for some reason best left unexplored, so did watching the recent remake of I Spit on Your Grave. Alien Summer by Robert Bayley did it to me too... I was reading sections - particularly those in the desert - and started to confuse myself with what was real and what wasn't, how had I as the reader, and Jim as the protagonist, got here? Putting the book down was a little like waking after a long nights sleep after taking Night Nurse and while wearing your most comfortable and warm poorly jumper; a huge relief! Though a relief tinged with confusion.

As a writer, he's just getting better and better and the story just flows beautifully with any lapses being perfectly in keeping with the overall story. Sometimes slightly purple in language but not overly and the bits where Jim was falling in and out of memories were tantalising: we see him as a down-and-out but there is a whole history there that we're given glimpses of - we see so much more than a snapshot of a person.

Friday, 19 August 2016

DataTables Search for, and order by, select value

There was a cracking question on stackoverflow the day before yesterday and it got me scratching my head.

It was to do with filtering and sorting rows when there was a select input in the row and the selected option was what was being searched or ordered by. DelightedD0D had the ordering down pat but couldn't do the search. I spent far too long thinking about an answer but my solution broke the universal search within DataTables themselves and I wanted to just add to the search not replace it. This was my first attempt:

$.fn.dataTable.ext.search.push(
    function(settings, data, dataIndex) {
        var dataLabel = table
            .row(dataIndex) //get the row to evaluate    
            .nodes()        //extract the HTML - node() does not support to$     
            .to$()          //get as jQuery object 
            .find('select') //find column with the select input
            .val();         //get the value of the select input
        // return true or false if the val matches the search parameter
        return !!~dataLabel.toLowerCase().indexOf(table.search().toLowerCase()); 
    }     
);

I felt sure that there must be a better way fo doing it though and finally I've found it, it depends upon the column having a type of "selected" so that given the following table markup:

<table id="example">
    <thead>
        <tr>
            <th>
                First name
            </th>
            <th>
                Last name
            </th>
            <th>
                Position
            </th>
            <th>
                Office
            </th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>
                Tiger
            </td>
            <td>
                Nixon
            </td>
            <td>
                <select>
                    <option 
                      value="">
                        Please choose
                    </option>
                    <option 
                      value="System Architect" 
                      selected="selected">
                        System Architect
                    </option>
                    <option 
                      value="Accountant">
                        Accountant
                    </option>
                    <option 
                      value="Senior Javascript Developer">
                        Senior Javascript Developer
                    </option>
                    <option 
                      value="Junior Technical Author">
                        Junior Technical Author
                    </option>
                </select>
            </td>
            <td>
                Edinburgh
            </td>
        </tr>
        <!-- more rows here -->
    </tbody>
</table>

This code will allow for ordering and filtering on the value of the select input:

(function() {
    $.fn.dataTable.ext.type.search.selected = (data) => !$(data).is("select") 
     ? '' 
        : $(data).val();
    $.fn.dataTable.ext.order['dom-select'] = function(settings, col) {
        return this.api().column(col, {
            order: 'index'
        }).nodes().map(td => $('select', td).val());
    }
})();
var table = $('#example').DataTable({
    "columnDefs": [{
        "orderDataType": "dom-select",
        "type": "selected",
        "targets": 2
    }]
});
$("#example select").on("change", function() {
    var $this = $(this),
        val = $this.val(),
        cellPosition = table.cell($this.parents("td")).index(),
        rowDate = table.row(cellPosition.row).data();
    $this.find("option").each((k, v) => ($(v).val() === val) 
     ? $(v).attr("selected", "selected") 
        : $(v).removeAttr("selected"));
    rowDate[cellPosition.column] = $this.prop("outerHTML");
    table.row(cellPosition.row).data(rowDate).draw();
});

The special stuff is in the change event of the select input as it updates the data for the row and then redraws the table, allowing the search extension to know that the table has changed.

Wednesday, 10 August 2016

Bootstrap DataTables and Modal Dialog Forms (CRUD)

So I've got this HTML snippet to generate the table above:

<div 
  class="container">
    <table 
      id="actionTabDataTable" 
      class="table table-striped table-bordered" 
      cellspacing="0" 
      width="100%"></table>
</div>
<!-- Modal -->
<div 
  class="modal fade" 
  id="myModal" 
  tabindex="-1" 
  role="dialog" 
  aria-labelledby="myModalLabel">
    <div 
      class="modal-dialog" 
      role="document">
        <div 
          class="modal-content">
            <div 
              class="modal-header">
                <button 
                  type="button" 
                  class="close" 
                  data-dismiss="modal" 
                  aria-label="Close">
                    <span 
                      aria-hidden="true">
                        &times;
                    </span>
                </button>
                <h4 
                  class="modal-title" 
                  id="myModalLabel">
                    Please tell us about the job 
                    <span 
                      id="name"></span> 
                    has:
                </h4>
            </div>
            <div 
              class="modal-body">
                <form>
                    <div 
                      class="form-group">
                        <label 
                          for="job">
                            Job title
                        </label>
                        <input 
                          type="text" 
                          class="form-control" 
                          id="job" 
                          name="job" />
                    </div>
                </form>
            </div>
            <div 
              class="modal-footer">
                <button 
                  type="button" 
                  class="btn btn-default" 
                  data-dismiss="modal">
                    Close
                </button>
                <button 
                  type="button" 
                  class="btn btn-primary">
                    Add
                </button>
            </div>
        </div>
    </div>
</div>

And I want to be able to interact with the underlying data which is an array of objects generated from a form further back in the mists of time... or at least earlier in the process anyway. So I use the following snippet of JavaScript:

var data = [{
    "name": "John Smith",
    "jobs": [
        "Bottle Washer",
        "Bus Boy"
    ]
}, {
    "name": "Jane Smith",
    "jobs": [
        "Head Chef",
        "Barmaid"
    ]
}, {
    "name": "Barry Smith"
}];
$(function(){
    var table = $("#actionTabDataTable").DataTable({
        "data": data,
        "columns": [{
            "title": "Name",
            "data": "name"
        }, {
            "title": "Jobs",
            "orderable": false,
            "data": "jobs",
            "render": function(d) {
                if (d) {
                    return $("<ul></ul>", {
                        "class": "list-group"
                    }).append(function() {
                        var lis = [];
                        for (var i = 0; i < d.length; i++) {
                            lis.push($("<li></li>", {
                                "text": d[i],
                                "class": "list-group-item"
                            }).append($("<i></i>", {
                                "class": "glyphicon glyphicon-remove"
                            })).append($("<i></i>", {
                                "class": "glyphicon glyphicon-edit",
                                "data-toggle": "modal",
                                "data-target": "#myModal"
                            })));
                        }
                        return lis;
                    }).prop("outerHTML");
                } else {
                    return "No jobs";
                }
            }
        }, {
            "title": "Action",
            "orderable": false,
            "render": function() {
                return $("<button></button>", {
                    "class": "btn btn-primary",
                    "text": "Add",
                    "data-toggle": "modal",
                    "data-target": "#myModal"
                }).append($("<i></i>", {
                    "class": "glyphicon glyphicon-plus"
                })).prop("outerHTML");
            }
        }]
    });

    $("#actionTabDataTable tbody").on("click", ".glyphicon-remove", function() {
        var d = table.row($(this).parents("tr")).data();
        var job = $(this).parents("li").text();
        $.each(data, function(k, v) {
            if (v.name === d.name) {
                console.log(v.jobs.length);
                for (var i = 0; i < v.jobs.length; i++) {
                    if (v.jobs[i] === job) {
                        v.jobs.splice(i, 1);
                        !v.jobs.length && delete v.jobs;
                        break;
                    }
                }
            }
        });
        table.clear().rows.add(data).draw();
    }).on("click", ".glyphicon-edit", function() {
        var d = table.row($(this).parents("tr")).data();
        var job = $(this).parents("li").text();
        $("#name").text(d.name);
        $("#job").val(job);
        $("#myModal").data({
            "original": d,
            "job": job
        }).find(".btn-primary").text("Update");
    }).on("click", ".btn-primary", function() {
        var d = table.row($(this).parents("tr")).data();
        $("#myModal").data("original", d);
        $("#name").text(d.name);
    });
    $("#myModal").on("click", ".btn-primary", function() {
        var d = $("#myModal").data("original");
        var j = $("#myModal").data("job");
        $.each(data, function(k, v) {
            if (v.name === d.name) {
                if ($("#myModal").find(".btn-primary").text() === "Update") {
                    $.each(v.jobs, function(a, b) {
                        if (b === j) {
                            v.jobs[a] = $("#job").val();
                        }
                    });
                } else {
                    if (v.hasOwnProperty("jobs") && Array.isArray(v.jobs)) {
                        v.jobs.push($("#job").val());
                    } else {
                        v.jobs = [$("#job").val()];
                    }
                }
            }
        });
        table.clear().rows.add(data).draw();
        $("#myModal").modal("hide");
    }).on("hidden.bs.modal", function() {
        $("#job").val("");
        $("#myModal")
         .removeData("original")
            .removeData("job")
            .find(".btn-primary")
             .text("Add");
    });
});

Basically this allows us to interact with the underlying data used to generate the table without having to worry about rendering the data again. It's up and running here.

I've had to tweak the CSS a little as well, here it is:

.list-group {
    text-align: left;
    margin-bottom:0;
}
.glyphicon {
    padding: 0 0 0 10px;
    cursor: pointer;
    float:right;
}

Monday, 18 July 2016

Painlessly upload a huge files in Salesforce Using a Visualforce Page

This article (Upload a File as Large as 2GB in Salesforce Using a Visualforce Page) is a brilliant read and well worth a squint if you're asked to facilitate attachments within a SalesForce VisualForce page. There are a couple of things that might warrant changing though. The <chatter:feed entityId="[ID of the custom object record]" /> bit brings in a shed load of CSS that may well not play nicely with your layout so please remember to disabled all CSS files with the class of user:

$(".user").attr("disabled", "disabled");

Of course, you might not mind, but I prefer having a wee bit more control. I prefer to have more control over the blocking behaviour as well so it's also worth including this in the script that's triggered by your uploading a file:

// Hide the chatter:feed file upload guff
setTimeout(function(){ 
    $("#uploadProgressDialog").addClass("hidden");
    $(".shadowDiv").addClass("hidden");
    $(".overlayBackground").addClass("hidden");
}, 100);

Should you want to listen to the return from the upload there is a really nice way of doing so detailed here. We use an awful lot of jQuery so being able to listen to other AJAX requests made from within jQuery is a really rather useful tool. Simply listening to the onReadyStateChangeReplacement and seeing if the readyState is 4 means that you can trigger your own events.

/*
 * This is so we can listen to ajax calls made outside of jQuery.
 */
var open = window.XMLHttpRequest.prototype.open,
    send = window.XMLHttpRequest.prototype.send,
    onReadyStateChange;
function openReplacement(method, url, async, user, password) {
    var syncMode = async !== false ? 'async' : 'sync';
    return open.apply(this, arguments);
}
function sendReplacement(data) {
    if(this.onreadystatechange) {
        this._onreadystatechange = this.onreadystatechange;
    }
    this.onreadystatechange = onReadyStateChangeReplacement;
    return send.apply(this, arguments);
}
function onReadyStateChangeReplacement() {
    // If the ajax is finished
    if(~~this.readyState === 4){
        // repopulate the fake file list
        setTimeout(function(){ 
            /* a setTimeout is probably not required but we 
             * might need to reference something that the 
             * original component has done to the DOM
             */
            // YOUR CODE HERE 
        }, 500);
    }
    if(this._onreadystatechange) {
        return this._onreadystatechange.apply(this, arguments);
    }
}
window.XMLHttpRequest.prototype.open = openReplacement;
window.XMLHttpRequest.prototype.send = sendReplacement;

We use this to check whether the number of attachments has changed, and if it has we re-draw our list.

Tuesday, 21 June 2016

Spring Clean by Proxy review

Spring-Clean by ProxySpring-Clean by Proxy by Robert Bayley
My rating: 4 of 5 stars

A little like The Girl with all the Gifts but with less hope. Slightly, and I mean ever-so-slightly, disjointed at times but on the whole a thoroughly engaging read with a male doing a really rather marvellous female perspective. This is something I've been thinking about for a while and I really couldn't tell that the author was male, not that one should with good writing as both "voices" are human, but I'm not sure I'd be brave enough to try. I do so look forward to reading his next works as he can only get better!

I'm reticent to go into more of the story but he seems to share Stephen Baxter's joy in destruction and it's effects on the people observing the chaos. The effects of the changing physical environment and the changing mental states of the characters are perfectly captured in the prose and I caught myself re-reading sections to try and figure out how he'd captured the feeling not only in the words but in their structure - an excellent and engaging read!

View all my reviews

Saturday, 23 April 2016

Recover images Snippet

As a side project for the archery club I'm a member of I've been looking at saving some stuff to Google Drive as a backup so I've been trawling the interweb for examples of how to upload backups weekly. I did find an old but interesting article but the images were broken :-(.

Then I got to thinking about how to recover them and clocked that the full size images which they linked to still existed so I cracked open Chrome's snippet pane and entered this as a new snippet:

Array.prototype.forEach.call(document.querySelectorAll(".no-line"), function(el, i){
    var img = el.querySelector("img");
    img.setAttribute("src", el.getAttribute("href"));
    img.setAttribute("width","100%");
});

This way of sorting stuff is just so unbelievably powerful and is probably why my codeivate score is as low as it is... Why bother with a full on IDE when you can do most, if not all, of your work in the browser - all without worrying that your changes will affect anyone else?