Monday 26 March 2012

Exam Prepration jQuery Tool

I do love 'er-indoors but she's not the most patient of people. Sure, she manages to actively listen for a while but ask her to do something that doesn't interest her and the result is obvious boredom. Which is a shame as I needed her help to revise for my DEV 401 exam.

I asked if I could borrow her for an hour or two on Saturday and Sunday. "Sure", says she in a nice surrendered wife type of way. My idea was to go through possible questions and give answers and ask her to say if I'd got it right or wrong. She agreed and we sat down but the delay of a few micro-seconds before I got a reply when I asked her a question was doing my nut in. I looked down to see her farming Smurfs or something. I really couldn't face putting her, or myself, through it all again the next day.

jQuery to the rescue!

I copied the questions I had into an array of JSON objects with the questions, the possible answers and the solution. This worked a treat as I could swap over questions easily enough and re-task it for other purposes.

I thought about trying to pluginify it but I don't really think it needs it as it takes a set of questions, answers and solutions and spits back a set of divs into the container of your choice. Props to David for the compare function.

(function(){
    prepareExam("chap1.json", "body");
    $("select#paper").on("change", function(){
        $val = $(this).val();
        $("div.question").empty().remove();
        $("div.score").empty().remove();
        prepareExam($val, "body");                    });
})();
jQuery.fn.compare = function(t) {
    if (this.length != t.length) {         return false;     }
    var a = this.sort(),
        b = t.sort();
    for (var i = 0; t[i]; i++) {
        if (a[i] !== b[i]) {             return false;
        }
    }
    return true;
};
function prepareExam(paper, target) {
    $.getJSON(paper, function(exam) {
        $.each(exam.exam, function(i, question){
            var d = $("<div></div>").addClass("question").appendTo(target);
            var q = $("<p></p>").html(i+1+". "+question.question).appendTo(d);
            var f = $("<fieldset></fieldset>").appendTo(d);
            $.each(question.answers, function(letter, possible){
                var labelID = letter+(i+1);
                $("<input></input>", {"type":"checkbox", "name":i+1,"value":letter, "id":labelID}).appendTo(f);
                $("<label></label>", {"for":labelID}).text(letter+". "+possible).appendTo(f);
                f.append('<br />');
            });
            d.data({"answer":question.solution});
            var c = $("<span></span>").addClass("check").text("Submit").button().appendTo(f).on("click", function(){
                var $parent = $(this).parent().parent();
                var allVals = [];
                $.each($parent.find("input:checked"), function(x, y){
                    allVals.push($(y).attr("value"));
                })
                if($(allVals).compare($parent.data("answer"))){
                    $parent.css("background-color","#ccffcc");
                    $parent.find("span.check").remove();
                    $parent.data({"success":true});
                }else{
                    $parent.css("background-color","#ffcccc");
                    $parent.find("span.check").remove();
                    $parent.data({"success":false});
                }
            });
            var leg = (question.solution.length !== 1) ? "answers" : "answer";
            $("<legend></legend>").text("Please choose "+question.solution.length+" "+leg+":").prependTo(f);
        });
        var s = $("<div></div>").addClass("score").appendTo(target);
        $("<p></p>").text("Ready to calculate your final score?").appendTo(s);
        $("<span></span>").addClass("final").text("Check").button().appendTo(s).on("click", function(){
            var total = 0, correct = 0;
            $.each($("div.question"), function(i, div){
                total++;
                if($(div).data("success")){
                    correct++;
                }
            });
            var l = $("<p></p>").text("Total Score = "+(((correct/total)*100).toFixed(2))+"%").appendTo($("div.score"));
            if(((correct/total)*100).toFixed(2) >= 68){
                l.css("color","green")
            }else{
                l.css("color","red")
            }
            $("span.final").remove();
        });
    });
};

As you can probably tell I used a lot of different sets of questions and I added them to a select input so I could load up a set of new questions when I'd done a paper and scored myself.

The format of the JSON should be:

{
    "exam": [
        {
            "question": "Jack and Jill went up the hill to fetch?",
            "answers": {
                "a": "A pail of water",
                "b": "Porridge",
                "c": "Vinegar"
            },
            "solution": [
                "a"
            ]
        },
        {
            "question": "What did the old woman who lived in a shoe do to her children?",
            "answers": {
                "a": "She gave them some broth without any bread.",
                "b": "She starved them.",
                "c": "She sent them to sweep chimneys.",
                "d": "She whipped them all soundly and put them to bed."
            },
            "solution": [
                "a",
                "d"
            ]
        }
    ]
}

Have fun!

I think it's lovely so by all means take it, use it and let me know if you improve it, let me see where you use it too ehh?

This is now on github.

Tuesday 20 March 2012

Arsing data object

I don't know how many times I've had to re-research this so this is the last time as I'm going to document it!

I really like jQuery UI modal dialogs but I always have trouble getting them to do the proper thing when they've closed! Poor Alex is forever reminding me after I've spent 20 fruitless minutes looking so I'll save his sanity by noting that you can pass data to some (maybe all) UI elements.

The use case was simply that some cells within a table had to be populated with user content, it needn't be saved to the server or anything, just needed to be displayed.

It's basically done like this:

$('.editableContent').on("click", function(event) {
    $("#dialog-form").find("textarea").val($(this).text());
    $("#dialog-form").data('bob', $(this)).dialog("open");
});

Here we're adding the existing content to the textarea in the first line of the function and then adding the calling object to the data of the dialog and calling it bob (hey, it's short enough ehh?)

Then, in the dialog code:

$("#dialog-form").dialog({
    autoOpen: false,
    height: 300,
    width: 350,
    modal: true,
    buttons: {
        "Add Content": function() {
            $(this).data('bob').text($(this).find("textarea").val());
            $(this).dialog("close");
        },
        Cancel: function() {
            $(this).dialog("close");
        }
    },
    close: function() {
    }
});

The important bit here is the line:

$(this).data('bob').text($(this).find("textarea").val());

Which calls the bob bit of the data object (giving us a reference to the original td) and populating it with the content of the text area. Bloody brilliant, now I shouldn't forget it!