<commit_before>angular.module("umbraco").controller("Our.Umbraco.NestedContent.Controllers.DocTypePickerController", [

    "$scope",
    "Our.Umbraco.NestedContent.Resources.NestedContentResources",

    function ($scope, ncResources) {
        ncResources.getContentTypes().then(function (docTypes) {
            $scope.model.docTypes = docTypes;
        });
    }
]);

angular.module("umbraco").controller("Our.Umbraco.NestedContent.Controllers.NestedContentPropertyEditorController", [

    "$scope",
    "$interpolate",
    "contentResource",
    "Our.Umbraco.NestedContent.Resources.NestedContentResources",

    function ($scope, $interpolate, contentResource, ncResources) {

        //$scope.model.config.docTypeGuids;
        //$scope.model.config.tabAlias;
        //$scope.model.config.nameTemplate;
        //$scope.model.config.minItems;
        //$scope.model.config.maxItems;
        //console.log($scope);

        var inited = false;
        var nameExp = !!$scope.model.config.nameTemplate
            ? $interpolate($scope.model.config.nameTemplate)
            : undefined;

        $scope.nodes = [];
        $scope.currentNode = undefined;
        $scope.scaffolds = undefined;
        $scope.sorting = false;

        $scope.tabAlias = $scope.model.config.tabAlias;
        $scope.minItems = $scope.model.config.minItems || 0;
        $scope.maxItems = $scope.model.config.maxItems || 0;

        if ($scope.maxItems == 0)
            $scope.maxItems = 1000;

        $scope.singleMode = $scope.minItems == 1 && $scope.maxItems == 1;

        $scope.overlayMenu = {
            show: false,
            style: {}
        };

        $scope.addNode = function (alias) {
            var scaffold = $scope.getScaffold(alias);
            var newNode = angular.copy(scaffold);
            newNode.id = guid();
            newNode.contentTypeAlias = alias;

            $scope.nodes.push(newNode);
            $scope.currentNode = newNode;

            $scope.closeNodeTypePicker();
        };

        $scope.openNodeTypePicker = function () {
            if ($scope.nodes.length >= $scope.maxItems) {
                return;
            }

            // this could be used for future limiting on node types
            $scope.overlayMenu.scaffolds = [];
            _.each($scope.scaffolds, function (scaffold) {
                $scope.overlayMenu.scaffolds.push({
                    alias: scaffold.contentTypeAlias,
                    name: scaffold.contentTypeName,
                    icon: scaffold.icon
                });
            });

            if ($scope.overlayMenu.scaffolds.length == 0) {
                return;
            }

            if ($scope.overlayMenu.scaffolds.length == 1) {
                // only one scaffold type - no need to display the picker
                $scope.addNode($scope.scaffolds[0].contentTypeAlias);
                return;
            }

            // calculate overlay position
            // - yeah... it's jQuery (ungh!) but that's how the Grid does it.
            var offset = $(event.target).offset();
            var scrollTop = $(event.target).closest(".umb-panel-body").scrollTop();
            if (offset.top < 400) {
                $scope.overlayMenu.style.top = 300 + scrollTop;
            }
            else {
                $scope.overlayMenu.style.top = offset.top - 150 + scrollTop;
            }
            $scope.overlayMenu.show = true;
        };

        $scope.closeNodeTypePicker = function () {
            $scope.overlayMenu.show = false;
        };

        $scope.editNode = function (idx) {
            if ($scope.currentNode && $scope.currentNode.id == $scope.nodes[idx].id) {
                $scope.currentNode = undefined;
            } else {
                $scope.currentNode = $scope.nodes[idx];
            }
        };

        $scope.deleteNode = function (idx) {
            if ($scope.nodes.length > $scope.model.config.minItems) {
                if ($scope.model.config.confirmDeletes && $scope.model.config.confirmDeletes == 1) {
                    if (confirm("Are you sure you want to delete this item?")) {
                        $scope.nodes.splice(idx, 1);
                    }
                } else {
                    $scope.nodes.splice(idx, 1);
                }
            }
        };

        $scope.getName = function (idx) {

            var name = "Item " + (idx + 1);

            if (nameExp) {
                var newName = nameExp($scope.model.value[idx]); // Run it against the stored dictionary value, NOT the node object
                if (newName && (newName = $.trim(newName))) {
                    name = newName;
                }
            }

            // Update the nodes actual name value
            if ($scope.nodes[idx].name != newName) {
                $scope.nodes[idx].name = name;
            }

            return name;
        };

        $scope.sortableOptions = {
            axis: 'y',
            cursor: "move",
            handle: ".nested-content__icon--move",
            start: function (ev, ui) {
                // Yea, yea, we shouldn't modify the dom, sue me
                $("#nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () {
                    tinymce.execCommand('mceRemoveEditor', false, $(this).attr('id'));
                    $(this).css("visibility", "hidden");
                });
                $scope.$apply(function () {
                    $scope.sorting = true;
                });
            },
            stop: function (ev, ui) {
                $("#nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () {
                    tinymce.execCommand('mceAddEditor', true, $(this).attr('id'));
                    $(this).css("visibility", "visible");
                });
                $scope.$apply(function () {
                    $scope.sorting = false;
                });
            }
        };

        $scope.getScaffold = function (alias) {
            return _.find($scope.scaffolds, function (scaffold) {
                return scaffold.contentTypeAlias == alias;
            });
        }

        // Initialize
        ncResources.getContentTypeAliasesByGuid($scope.model.config.docTypeGuids).then(function (data1) {
            $scope.scaffolds = [];
            _.each(data1.aliases, function (alias) {
                contentResource.getScaffold(-20, alias).then(function (data2) {
                    // Ignore the generic properties tab
                    data2.tabs.pop();

                    // Store the scaffold object
                    $scope.scaffolds.push(data2);

                    // Initialize when all scaffolds have loaded
                    if ($scope.scaffolds.length == data1.aliases.length) {

                        // Convert stored nodes
                        if ($scope.model.value) {
                            for (var i = 0; i < $scope.model.value.length; i++) {
                                var item = $scope.model.value[i];
                                var scaffold = $scope.getScaffold(item.contentTypeAlias);
                                if (scaffold == null) {
                                    // No such scaffold - the content type might have been deleted. We need to skip it.
                                    continue;
                                }
                                var node = angular.copy($scope.getScaffold(item.contentTypeAlias));
                                node.id = guid();

                                for (var t = 0; t < node.tabs.length; t++) {
                                    var tab = node.tabs[t];
                                    for (var p = 0; p < tab.properties.length; p++) {
                                        var prop = tab.properties[p];
                                        // Force validation to occur server side as this is the
                                        // only way we can have consistancy between mandatory and
                                        // regex validation messages. Not ideal, but it works.
                                        prop.validation = {
                                            mandatory: false,
                                            pattern: ""
                                        };

                                        if (item[prop.alias]) {
                                            prop.value = item[prop.alias];
                                        }
                                    }
                                }

                                $scope.nodes.push(node);
                            }
                        }

                        // Enforce min items
                        if ($scope.nodes.length < $scope.model.config.minItems) {
                            for (var i = $scope.nodes.length; i < $scope.model.config.minItems; i++) {
                                var node = angular.copy($scope.scaffolds[0]);
                                node.id = guid();
                                $scope.nodes.push(node);
                            }
                        }

                        // If there is only one item, set it as current node
                        if ($scope.singleMode) {
                            $scope.currentNode = $scope.nodes[0];
                        }

                        inited = true;
                    }
                });
            });
        });

        $scope.$watch("nodes", function () {
            if (inited) {
                var newValues = [];
                for (var i = 0; i < $scope.nodes.length; i++) {
                    var node = $scope.nodes[i];
                    var newValue = {
                        name: node.name,
                        contentTypeAlias: node.contentTypeAlias
                    };
                    for (var t = 0; t < node.tabs.length; t++) {
                        var tab = node.tabs[t];
                        for (var p = 0; p < tab.properties.length; p++) {
                            var prop = tab.properties[p];
                            if (typeof prop.value !== "function") {
                                newValue[prop.alias] = prop.value;
                            }
                        }
                    }
                    newValues.push(newValue);
                }
                $scope.model.value = newValues;
            }
        }, true);

        var guid = function () {
            function _p8(s) {
                var p = (Math.random().toString(16) + "000000000").substr(2, 8);
                return s ? "-" + p.substr(0, 4) + "-" + p.substr(4, 4) : p;
            }
            return _p8() + _p8(true) + _p8(true) + _p8();
        };
    }

]);<commit_msg>Add default folder icon when doctype has no icon<commit_after>angular.module("umbraco").controller("Our.Umbraco.NestedContent.Controllers.DocTypePickerController", [

    "$scope",
    "Our.Umbraco.NestedContent.Resources.NestedContentResources",

    function ($scope, ncResources) {
        ncResources.getContentTypes().then(function (docTypes) {
            $scope.model.docTypes = docTypes;
        });
    }
]);

angular.module("umbraco").controller("Our.Umbraco.NestedContent.Controllers.NestedContentPropertyEditorController", [

    "$scope",
    "$interpolate",
    "contentResource",
    "Our.Umbraco.NestedContent.Resources.NestedContentResources",

    function ($scope, $interpolate, contentResource, ncResources) {

        //$scope.model.config.docTypeGuids;
        //$scope.model.config.tabAlias;
        //$scope.model.config.nameTemplate;
        //$scope.model.config.minItems;
        //$scope.model.config.maxItems;
        //console.log($scope);

        var inited = false;
        var nameExp = !!$scope.model.config.nameTemplate
            ? $interpolate($scope.model.config.nameTemplate)
            : undefined;

        $scope.nodes = [];
        $scope.currentNode = undefined;
        $scope.scaffolds = undefined;
        $scope.sorting = false;

        $scope.tabAlias = $scope.model.config.tabAlias;
        $scope.minItems = $scope.model.config.minItems || 0;
        $scope.maxItems = $scope.model.config.maxItems || 0;

        if ($scope.maxItems == 0)
            $scope.maxItems = 1000;

        $scope.singleMode = $scope.minItems == 1 && $scope.maxItems == 1;

        $scope.overlayMenu = {
            show: false,
            style: {}
        };

        $scope.addNode = function (alias) {
            var scaffold = $scope.getScaffold(alias);
            var newNode = angular.copy(scaffold);
            newNode.id = guid();
            newNode.contentTypeAlias = alias;

            $scope.nodes.push(newNode);
            $scope.currentNode = newNode;

            $scope.closeNodeTypePicker();
        };

        $scope.openNodeTypePicker = function () {
            if ($scope.nodes.length >= $scope.maxItems) {
                return;
            }

            // this could be used for future limiting on node types
            $scope.overlayMenu.scaffolds = [];
            _.each($scope.scaffolds, function (scaffold) {
                var icon = scaffold.icon;
                // workaround for when no icon is chosen for a doctype
                if (icon == ".sprTreeFolder") {
                    icon = "icon-folder";
                }
                $scope.overlayMenu.scaffolds.push({
                    alias: scaffold.contentTypeAlias,
                    name: scaffold.contentTypeName,
                    icon: icon
                });
            });

            if ($scope.overlayMenu.scaffolds.length == 0) {
                return;
            }

            if ($scope.overlayMenu.scaffolds.length == 1) {
                // only one scaffold type - no need to display the picker
                $scope.addNode($scope.scaffolds[0].contentTypeAlias);
                return;
            }

            // calculate overlay position
            // - yeah... it's jQuery (ungh!) but that's how the Grid does it.
            var offset = $(event.target).offset();
            var scrollTop = $(event.target).closest(".umb-panel-body").scrollTop();
            if (offset.top < 400) {
                $scope.overlayMenu.style.top = 300 + scrollTop;
            }
            else {
                $scope.overlayMenu.style.top = offset.top - 150 + scrollTop;
            }
            $scope.overlayMenu.show = true;
        };

        $scope.closeNodeTypePicker = function () {
            $scope.overlayMenu.show = false;
        };

        $scope.editNode = function (idx) {
            if ($scope.currentNode && $scope.currentNode.id == $scope.nodes[idx].id) {
                $scope.currentNode = undefined;
            } else {
                $scope.currentNode = $scope.nodes[idx];
            }
        };

        $scope.deleteNode = function (idx) {
            if ($scope.nodes.length > $scope.model.config.minItems) {
                if ($scope.model.config.confirmDeletes && $scope.model.config.confirmDeletes == 1) {
                    if (confirm("Are you sure you want to delete this item?")) {
                        $scope.nodes.splice(idx, 1);
                    }
                } else {
                    $scope.nodes.splice(idx, 1);
                }
            }
        };

        $scope.getName = function (idx) {

            var name = "Item " + (idx + 1);

            if (nameExp) {
                var newName = nameExp($scope.model.value[idx]); // Run it against the stored dictionary value, NOT the node object
                if (newName && (newName = $.trim(newName))) {
                    name = newName;
                }
            }

            // Update the nodes actual name value
            if ($scope.nodes[idx].name != newName) {
                $scope.nodes[idx].name = name;
            }

            return name;
        };

        $scope.sortableOptions = {
            axis: 'y',
            cursor: "move",
            handle: ".nested-content__icon--move",
            start: function (ev, ui) {
                // Yea, yea, we shouldn't modify the dom, sue me
                $("#nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () {
                    tinymce.execCommand('mceRemoveEditor', false, $(this).attr('id'));
                    $(this).css("visibility", "hidden");
                });
                $scope.$apply(function () {
                    $scope.sorting = true;
                });
            },
            stop: function (ev, ui) {
                $("#nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () {
                    tinymce.execCommand('mceAddEditor', true, $(this).attr('id'));
                    $(this).css("visibility", "visible");
                });
                $scope.$apply(function () {
                    $scope.sorting = false;
                });
            }
        };

        $scope.getScaffold = function (alias) {
            return _.find($scope.scaffolds, function (scaffold) {
                return scaffold.contentTypeAlias == alias;
            });
        }

        // Initialize
        ncResources.getContentTypeAliasesByGuid($scope.model.config.docTypeGuids).then(function (data1) {
            $scope.scaffolds = [];
            _.each(data1.aliases, function (alias) {
                contentResource.getScaffold(-20, alias).then(function (data2) {
                    // Ignore the generic properties tab
                    data2.tabs.pop();

                    // Store the scaffold object
                    $scope.scaffolds.push(data2);

                    // Initialize when all scaffolds have loaded
                    if ($scope.scaffolds.length == data1.aliases.length) {

                        // Convert stored nodes
                        if ($scope.model.value) {
                            for (var i = 0; i < $scope.model.value.length; i++) {
                                var item = $scope.model.value[i];
                                var scaffold = $scope.getScaffold(item.contentTypeAlias);
                                if (scaffold == null) {
                                    // No such scaffold - the content type might have been deleted. We need to skip it.
                                    continue;
                                }
                                var node = angular.copy($scope.getScaffold(item.contentTypeAlias));
                                node.id = guid();

                                for (var t = 0; t < node.tabs.length; t++) {
                                    var tab = node.tabs[t];
                                    for (var p = 0; p < tab.properties.length; p++) {
                                        var prop = tab.properties[p];
                                        // Force validation to occur server side as this is the
                                        // only way we can have consistancy between mandatory and
                                        // regex validation messages. Not ideal, but it works.
                                        prop.validation = {
                                            mandatory: false,
                                            pattern: ""
                                        };

                                        if (item[prop.alias]) {
                                            prop.value = item[prop.alias];
                                        }
                                    }
                                }

                                $scope.nodes.push(node);
                            }
                        }

                        // Enforce min items
                        if ($scope.nodes.length < $scope.model.config.minItems) {
                            for (var i = $scope.nodes.length; i < $scope.model.config.minItems; i++) {
                                var node = angular.copy($scope.scaffolds[0]);
                                node.id = guid();
                                $scope.nodes.push(node);
                            }
                        }

                        // If there is only one item, set it as current node
                        if ($scope.singleMode) {
                            $scope.currentNode = $scope.nodes[0];
                        }

                        inited = true;
                    }
                });
            });
        });

        $scope.$watch("nodes", function () {
            if (inited) {
                var newValues = [];
                for (var i = 0; i < $scope.nodes.length; i++) {
                    var node = $scope.nodes[i];
                    var newValue = {
                        name: node.name,
                        contentTypeAlias: node.contentTypeAlias
                    };
                    for (var t = 0; t < node.tabs.length; t++) {
                        var tab = node.tabs[t];
                        for (var p = 0; p < tab.properties.length; p++) {
                            var prop = tab.properties[p];
                            if (typeof prop.value !== "function") {
                                newValue[prop.alias] = prop.value;
                            }
                        }
                    }
                    newValues.push(newValue);
                }
                $scope.model.value = newValues;
            }
        }, true);

        var guid = function () {
            function _p8(s) {
                var p = (Math.random().toString(16) + "000000000").substr(2, 8);
                return s ? "-" + p.substr(0, 4) + "-" + p.substr(4, 4) : p;
            }
            return _p8() + _p8(true) + _p8(true) + _p8();
        };
    }

]);<|endoftext|>


<commit_before>/**
 * Created by LongSky on 12/01/2017.
 */

function call() {
    "use strict";
    console.log("inizio call");
    clearjQueryCache();
    $.ajax({
        url: "/CrowdMine/updateChat",
        dataType: 'text',
        async: true,
        success: function (data) {
            console.log("__"+data+"__");
            if(data!=' ') {
                $('#messaggi_inviati').append(data);
                $('#messaggi_inviati').animate({scrollTop: $('#messaggi_inviati').prop("scrollHeight")}, 0);
            }
        },
        error: function (data) {
            console.log ("error");
            console.log (data);
        }
    });
}

$(document).ready(function(){
    console.log("inizio poll");
    setInterval(function(){
        call();
    },3000);
});

function clearjQueryCache() {
    for (var x in jQuery.cache) {
        delete jQuery.cache[x];
    }
}
<commit_msg>Aggiornamnto messaggio.<commit_after>/**
 * Created by LongSky on 12/01/2017.
 */

function call() {
    "use strict";
    console.log("inizio call");
    clearjQueryCache();
    $.ajax({
        url: "/CrowdMine/updateChat",
        dataType: 'text',
        async: true,
        success: function (data) {
            console.log("__"+data+"__");
            if(data!=' ') {
                $('#messaggi_inviati').empty();
                $('#messaggi_inviati').append(data);
                $('#messaggi_inviati').animate({scrollTop: $('#messaggi_inviati').prop("scrollHeight")}, 0);
            }
        },
        error: function (data) {
            console.log ("error");
            console.log (data);
        }
    });
}

$(document).ready(function(){
    console.log("inizio poll");
    setInterval(function(){
        call();
    },3000);
});

function clearjQueryCache() {
    for (var x in jQuery.cache) {
        delete jQuery.cache[x];
    }
}
<|endoftext|>


<commit_before>(function(root) {

var _,
    assert,
    Data,
    Document,
    registerTest,
    getSession;

if (typeof exports !== 'undefined') {
  _    = require('underscore');
  assert = require('substance-test/assert');
  Data = require('substance-data');
  Document = require('..');
  registerTest = require('substance-test').registerTest;
} else {
  _ = root._;
  assert = root.Substance.assert;
  Data = root.Substance.Document;
  Document = root.Substance.Document;
  registerTest = root.Substance.registerTest;
}

var test = {};

test.setup = function() {
  this.doc = new Document({});

  this.doc.apply(Data.Graph.Create({
    "id": "t1",
    "type": "text",
    "content": "The quick brown fox jumps over the lazy dog."
  }));

  this.doc.apply(Data.Graph.Create({
    "id": "t2",
    "type": "text",
    "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
  }));

  this.doc.apply(["position", "content", {"nodes": ["t1", "t2"], "target": -1}]);
};

test.actions = [
  "Select text in a single text node", function() {
    var selection = this.doc.select({start: [0, 4], end: [0, 9]});
    assert.isEqual(1, selection.getNodes().length);
    assert.isEqual("quick", selection.getText());
  },

  "Select text spanning over multiple text nodes", function() {
    var selection = this.doc.select({start: [0, 4], end: [1, 11]});

    // alternative selection api to be discussed
    // this.select({
    //   start: {"node": "t1", "offset": 4},
    //   end: {"node": "t2", "offset": 11}
    // });

    // assert.isEqual(selection, this.doc.selection);
    assert.isEqual(2, selection.getNodes().length);
    assert.isEqual("quick brown fox jumps over the lazy dog.Lorem ipsum", selection.getText());
  },

  "Break node into pieces", function() {
    var selection = this.doc.select({start: [0, 4], end: [0, 4]});
    this.doc.insertNode("text");

    // Original node
    var contentView = this.doc.get('content').nodes;
    assert.isEqual(4, contentView.length);

    var t1a = this.doc.get(contentView[0]);
    var tnew = this.doc.get(contentView[1]);
    var t1b = this.doc.get(contentView[2]);

    // New node
    assert.isEqual("The ", t1a.content);
    assert.isEqual("text", tnew.type);
    assert.isEqual("", tnew.content);

    assert.isEqual("quick brown fox jumps over the lazy dog.", t1b.content);
    assert.isEqual("text", t1b.type);

    assert.isEqual("", selection.getText());
  },

  "Type some new text into new node", function() {
    // Now you probably want to place the cursor at position 0 of the newly created node
    this.doc.select({start: [1, 0], end: [1,0]});

    // only one selected node
    assert.isEqual(1, this.doc.selection.getNodes().length);

    // Pull out the fresh node to be updated
    this.freshNode = this.doc.selection.getNodes()[0];

    var op = [
      "update", this.freshNode.id, "content", ["Hello Worrrrld!"]
    ];

    this.doc.apply(op);
    assert.isEqual("Hello Worrrrld!", this.freshNode.content);
  },

  "Delete selection", function() {
    this.doc.select({start: [1, 9], end: [1, 12]});
    this.doc.delete();
    assert.isEqual("Hello World!", this.freshNode.content);
  },

  "Copy selection and store in clipboard", function() {
    // Current state
    // t1a:  "The "
    // tnew: "Hello World!"
    // t1b:  "quick brown fox jumps over the lazy dog."
    // t2:   "Lorem ipsum dolor sit amet, consectetur adipiscing elit."

    this.doc.select({start: [0, 3], end: [2, 11]});

    assert.isEqual(3, this.doc.selection.getNodes().length);

    // Check selection
    assert.isEqual(" Hello World!quick brown", this.doc.selection.getText());

    // Stores the cutted document in this.session.clipboard
    this.clipboard = this.doc.copy();

    // Expected clipboard contents
    // t1: " "
    // t2: "Hello World!"
    // t3: "quick brown"

    var clipboardContent = this.clipboard.get('content').nodes;
    assert.isEqual(3, clipboardContent.length);

    assert.isEqual(" ", this.clipboard.get(clipboardContent[0]).content);
    assert.isEqual("Hello World!", this.clipboard.get(clipboardContent[1]).content);
    assert.isEqual("quick brown", this.clipboard.get(clipboardContent[2]).content);
  },

  "Delete the copied stuff", function() {
    this.doc.delete();

    // Desired new contents
    // t1a: "The"
    // t1b: " fox jumps over the lazy dog."
    // t2: "Lorem ipsum dolor sit amet, consectetur adipiscing elit."

    var contentView = this.doc.get('content').nodes;
    assert.isEqual(3, contentView.length);

    assert.isEqual("The", this.doc.get(contentView[0]).content);
    assert.isEqual(" fox jumps over the lazy dog.", this.doc.get(contentView[1]).content);
    assert.isEqual("Lorem ipsum dolor sit amet, consectetur adipiscing elit.", this.doc.get(contentView[2]).content);
  },

  "Paste clipboard into document", function() {
    this.doc.select({start: [2,12], end: [2,17]});

    this.doc.paste(this.clipboard);

    // Desired new contents
    // t1a:   "The"
    // t1b:   " fox jumps over the lazy dog."
    // t2:    "Lorem ipsum  "  -> one space more from first node in clipboard
    // tnew:  "Hello World!"
    // tnew2: "quick brown sit amet, consectetur adipiscing elit."

    var contentView = this.doc.get('content').nodes;
    assert.isEqual(5, contentView.length);

    assert.isEqual("The", this.doc.get(contentView[0]).content);
    assert.isEqual(" fox jumps over the lazy dog.", this.doc.get(contentView[1]).content);
    assert.isEqual("Lorem ipsum  ", this.doc.get(contentView[2]).content);

    assert.isEqual("Hello World!", this.doc.get(contentView[3]).content);
    assert.isEqual("quick brown sit amet, consectetur adipiscing elit.", this.doc.get(contentView[4]).content);
  }
];

registerTest(['Document', 'Document Selection'], test);

})(this);
<commit_msg>Fix tests to reflect changed semantics in selection API.<commit_after>(function(root) {

var _,
    assert,
    Data,
    Document,
    registerTest,
    getSession;

if (typeof exports !== 'undefined') {
  _    = require('underscore');
  assert = require('substance-test/assert');
  Data = require('substance-data');
  Document = require('..');
  registerTest = require('substance-test').registerTest;
} else {
  _ = root._;
  assert = root.Substance.assert;
  Data = root.Substance.Document;
  Document = root.Substance.Document;
  registerTest = root.Substance.registerTest;
}

var test = {};

test.setup = function() {
  this.doc = new Document({});

  this.doc.apply(Data.Graph.Create({
    "id": "t1",
    "type": "text",
    "content": "The quick brown fox jumps over the lazy dog."
  }));

  this.doc.apply(Data.Graph.Create({
    "id": "t2",
    "type": "text",
    "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
  }));

  this.doc.apply(["position", "content", {"nodes": ["t1", "t2"], "target": -1}]);
};

test.actions = [
  "Select text in a single text node", function() {
    var selection = this.doc.select({start: [0, 4], end: [0, 9]});
    assert.isEqual(1, selection.getNodes().length);
    assert.isEqual("quick", selection.getText());
  },

  "Select text spanning over multiple text nodes", function() {
    var selection = this.doc.select({start: [0, 4], end: [1, 11]});

    // alternative selection api to be discussed
    // this.select({
    //   start: {"node": "t1", "offset": 4},
    //   end: {"node": "t2", "offset": 11}
    // });

    // assert.isEqual(selection, this.doc.selection);
    assert.isEqual(2, selection.getNodes().length);
    assert.isEqual("quick brown fox jumps over the lazy dog.Lorem ipsum", selection.getText());
  },

  "Break node into pieces", function() {
    var selection = this.doc.select({start: [0, 4], end: [0, 4]});
    this.doc.insertNode("text");

    // Original node
    var contentView = this.doc.get('content').nodes;
    assert.isEqual(4, contentView.length);

    var t1a = this.doc.get(contentView[0]);
    var tnew = this.doc.get(contentView[1]);
    var t1b = this.doc.get(contentView[2]);

    // New node
    assert.isEqual("The ", t1a.content);
    assert.isEqual("text", tnew.type);
    assert.isEqual("", tnew.content);

    assert.isEqual("quick brown fox jumps over the lazy dog.", t1b.content);
    assert.isEqual("text", t1b.type);

    assert.isEqual("", selection.getText());
  },

  "Type some new text into new node", function() {
    // Now you probably want to place the cursor at position 0 of the newly created node
    this.doc.select({start: [1, 0], end: [1,0]});

    // only one selected node
    assert.isEqual(1, this.doc.selection.getNodes().length);

    // Pull out the fresh node to be updated
    this.freshNode = this.doc.selection.getNodes()[0];

    var op = [
      "update", this.freshNode.id, "content", ["Hello Worrrrld!"]
    ];

    this.doc.apply(op);
    assert.isEqual("Hello Worrrrld!", this.freshNode.content);
  },

  "Delete selection", function() {
    this.doc.select({start: [1, 9], end: [1, 12]});
    this.doc.delete();
    assert.isEqual("Hello World!", this.freshNode.content);
  },

  "Copy selection and store in clipboard", function() {
    // Current state
    // t1a:  "The "
    // tnew: "Hello World!"
    // t1b:  "quick brown fox jumps over the lazy dog."
    // t2:   "Lorem ipsum dolor sit amet, consectetur adipiscing elit."

    this.doc.select({start: [0, 3], end: [2, 11]});

    assert.isEqual(3, this.doc.selection.getNodes().length);

    // Check selection
    assert.isEqual(" Hello World!quick brown", this.doc.selection.getText());

    // Stores the cutted document in this.session.clipboard
    this.clipboard = this.doc.copy();

    // Expected clipboard contents
    // t1: " "
    // t2: "Hello World!"
    // t3: "quick brown"

    var clipboardContent = this.clipboard.get('content').nodes;
    assert.isEqual(3, clipboardContent.length);

    assert.isEqual(" ", this.clipboard.get(clipboardContent[0]).content);
    assert.isEqual("Hello World!", this.clipboard.get(clipboardContent[1]).content);
    assert.isEqual("quick brown", this.clipboard.get(clipboardContent[2]).content);
  },

  "Delete the copied stuff", function() {
    this.doc.delete();

    // Desired new contents
    // t1a: "The"
    // t1b: " fox jumps over the lazy dog."
    // t2: "Lorem ipsum dolor sit amet, consectetur adipiscing elit."

    var contentView = this.doc.get('content').nodes;
    assert.isEqual(3, contentView.length);

    assert.isEqual("The", this.doc.get(contentView[0]).content);
    assert.isEqual(" fox jumps over the lazy dog.", this.doc.get(contentView[1]).content);
    assert.isEqual("Lorem ipsum dolor sit amet, consectetur adipiscing elit.", this.doc.get(contentView[2]).content);
  },

  "Paste clipboard into document", function() {
    this.doc.select({start: [2,12], end: [2,17]});

    this.doc.paste(this.clipboard);

    // Desired new contents
    // t1a:   "The"
    // t1b:   " fox jumps over the lazy dog."
    // t2:    "Lorem ipsum  "  -> one space more from first node in clipboard
    // tnew:  "Hello World!"
    // tnew2: "quick brown sit amet, consectetur adipiscing elit."

    var contentView = this.doc.get('content').nodes;
    assert.isEqual(5, contentView.length);

    assert.isEqual("The", this.doc.get(contentView[0]).content);
    assert.isEqual(" fox jumps over the lazy dog.", this.doc.get(contentView[1]).content);

    // was "Lorem ipsum  " - please check!
    assert.isEqual("Lorem ipsum ", this.doc.get(contentView[2]).content);

    assert.isEqual("Hello World!", this.doc.get(contentView[3]).content);
    // was "quick brown sit amet, consectetur adipiscing elit." - pls check
    assert.isEqual("quick brown  sit amet, consectetur adipiscing elit.", this.doc.get(contentView[4]).content);
  }
];

registerTest(['Document', 'Document Selection'], test);

})(this);
<|endoftext|>


<commit_before>// @datatype_void

/*
 * Helper: root(), and rootDir() are defined at the bottom
 */
var helpers = require('./helpers');
// Webpack Plugins
var webpack = require('webpack');
var ProvidePlugin = require('webpack/lib/ProvidePlugin');
var DefinePlugin = require('webpack/lib/DefinePlugin');
var OccurenceOrderPlugin = require('webpack/lib/optimize/OccurenceOrderPlugin');
var DedupePlugin = require('webpack/lib/optimize/DedupePlugin');
var UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
var CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
var CompressionPlugin = require('compression-webpack-plugin');
var CopyWebpackPlugin = require('copy-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var WebpackMd5Hash    = require('webpack-md5-hash');
var ForkCheckerPlugin = require('awesome-typescript-loader').ForkCheckerPlug
var ENV = process.env.NODE_ENV = process.env.ENV = 'production';
var HOST = process.env.HOST || 'localhost';
var PORT = process.env.PORT || 8080;

var config = require('./config/config.json');

var metadata = {
  title: 'Angular 2 MEAN Webpack Starter Kit by @datatype_void',
  baseUrl: '/',
  host: HOST,
  port: PORT,
  ENV: ENV
};

/*
 * Config
 */
module.exports = {
  // static data for index.html
  metadata: metadata,

  devtool: 'source-map',
  debug: false,

  entry: {
    'polyfills':'./src/polyfills.ts',
    'main':'./src/main.ts' // our angular app
  },

  // Config for our build files
  output: {
    path: helpers.root('dist'),
    filename: '[name].[chunkhash].bundle.js',
    sourceMapFilename: '[name].[chunkhash].bundle.map',
    chunkFilename: '[id].[chunkhash].chunk.js'
  },

  resolve: {
    extensions: ['', '.ts', '.js', '.scss']
  },

  module: {
    preLoaders: [
      {
        test: /\.ts$/,
        loader: 'tslint-loader',
        exclude: [
          helpers.root('node_modules')
        ]
      },
      {
        test: /\.js$/,
        loader: 'source-map-loader',
        exclude: [
          helpers.root('node_modules/rxjs')
        ]
      }
    ],
    loaders: [
      // Support Angular 2 async routes via .async.ts
      // Support for .ts files.
      {
        test: /\.ts$/,
        loader: 'awesome-typescript-loader',
        query: {
          // remove TypeScript helpers to be injected below by DefinePlugin
          'compilerOptions': {
            'removeComments': true
          }
        },
        exclude: [
          /\.(spec|e2e)\.ts$/,
          helpers.root('node_modules')
        ]
      },

      // Support for *.json files.
      {
        test: /\.json$/,
        loader: 'json-loader',
        exclude: [ helpers.root('node_modules') ]
      },

      // Support for CSS as raw text
      {
        test: /\.css$/,
        loader: 'raw-loader',
        exclude: [ helpers.root('node_modules') ]
      },

      // support for .html as raw text
      {
        test: /\.html$/,
        loader: 'raw-loader',
        exclude: [
          helpers.root('src/index.html')
        ]
      },

      // support for sass imports
      // add CSS rules to your document:
      // `require("!style!css!sass!./file.scss");`
      {
        test: /\.scss$/,
        loader: 'style!css!autoprefixer-loader?browsers=last 2 versions!sass',
        exclude: [ helpers.root('node_modules') ]
      }

    ],
    noParse: [
      helpers.root('zone.js', 'dist'),
      helpers.root('angular2', 'bundles')
    ]

  },

  plugins: [
    new ForkCheckerPlugin(),
    new WebpackMd5Hash(),
    new DedupePlugin(),
    new OccurenceOrderPlugin(true),
    new CommonsChunkPlugin({
      name: 'polyfills',
      filename: 'polyfills.[chunkhash].bundle.js',
      chunks: Infinity
    }),
    // static assets
    new CopyWebpackPlugin([
      {
        from: 'src/assets',
        to: 'assets'
      }
    ]),
    // generating html
    new HtmlWebpackPlugin({ template: 'src/index.html' }),
    new DefinePlugin({
      // Environment helpers
      'process.env': {
        'ENV': JSON.stringify(metadata.ENV),
        'NODE_ENV': JSON.stringify(metadata.ENV)
      }
    }),
    new UglifyJsPlugin({
      // to debug prod builds uncomment //debug lines and comment //prod lines

      // beautify: true,//debug
      // mangle: false,//debug
      // dead_code: false,//debug
      // unused: false,//debug
      // deadCode: false,//debug
      // compress : { screw_ie8 : true, keep_fnames: true, drop_debugger: false, dead_code: false, unused: false, }, // debug
      // comments: true,//debug

      beautify: false,//prod
      // disable mangling because of a bug in angular2 beta.1, beta.2 and beta.3
      // TODO(mastertinner): enable mangling as soon as angular2 beta.4 is out
      // mangle: { screw_ie8 : true },//prod
      mangle: {
        screw_ie8 : true,
        except: [
          'RouterActive',
          'RouterLink',
          'RouterOutlet',
          'NgFor',
          'NgIf',
          'NgClass',
          'NgSwitch',
          'NgStyle',
          'NgSwitchDefault',
          'NgModel',
          'NgControl',
          'NgFormControl',
          'NgForm',
          'AsyncPipe',
          'DatePipe',
          'JsonPipe',
          'NumberPipe',
          'DecimalPipe',
          'PercentPipe',
          'CurrencyPipe',
          'LowerCasePipe',
          'UpperCasePipe',
          'SlicePipe',
          'ReplacePipe',
          'I18nPluralPipe',
          'I18nSelectPipe'
        ] // needed for uglify RouterLink problem
      },// prod
      compress : { screw_ie8 : true },//prod
      comments: false//prod

    }),
   // include uglify in production
    new CompressionPlugin({
      algorithm: helpers.gzipMaxLevel,
      regExp: /\.css$|\.html$|\.js$|\.map$/,
      threshold: 2 * 1024
    })
  ],
  // Other module loader config
  tslint: {
    emitErrors: true,
    failOnHint: true,
    resourcePath: 'src',
  },
  htmlLoader: {
    minimize: true,
    removeAttributeQuotes: false,
    caseSensitive: true,
    customAttrSurround: [ [/#/, /(?:)/], [/\*/, /(?:)/], [/\[?\(?/, /(?:)/] ],
    customAttrAssign: [ /\)?\]?=/ ]
  },
  // don't use devServer for production
  node: {
    global: 'window',
    progress: false,
    crypto: 'empty',
    module: false,
    clearImmediate: false,
    setImmediate: false
  }
};
<commit_msg>chore(_webpack.prod): include more classes to mangle except<commit_after>// @datatype_void

/*
 * Helper: root(), and rootDir() are defined at the bottom
 */
var helpers = require('./helpers');
// Webpack Plugins
var webpack = require('webpack');
var ProvidePlugin = require('webpack/lib/ProvidePlugin');
var DefinePlugin = require('webpack/lib/DefinePlugin');
var OccurenceOrderPlugin = require('webpack/lib/optimize/OccurenceOrderPlugin');
var DedupePlugin = require('webpack/lib/optimize/DedupePlugin');
var UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
var CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
var CompressionPlugin = require('compression-webpack-plugin');
var CopyWebpackPlugin = require('copy-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var WebpackMd5Hash    = require('webpack-md5-hash');
var ForkCheckerPlugin = require('awesome-typescript-loader').ForkCheckerPlug
var ENV = process.env.NODE_ENV = process.env.ENV = 'production';
var HOST = process.env.HOST || 'localhost';
var PORT = process.env.PORT || 8080;

var config = require('./config/config.json');

var metadata = {
  title: 'Angular 2 MEAN Webpack Starter Kit by @datatype_void',
  baseUrl: '/',
  host: HOST,
  port: PORT,
  ENV: ENV
};

/*
 * Config
 */
module.exports = {
  // static data for index.html
  metadata: metadata,

  devtool: 'source-map',
  debug: false,

  entry: {
    'polyfills':'./src/polyfills.ts',
    'main':'./src/main.ts' // our angular app
  },

  // Config for our build files
  output: {
    path: helpers.root('dist'),
    filename: '[name].[chunkhash].bundle.js',
    sourceMapFilename: '[name].[chunkhash].bundle.map',
    chunkFilename: '[id].[chunkhash].chunk.js'
  },

  resolve: {
    extensions: ['', '.ts', '.js', '.scss']
  },

  module: {
    preLoaders: [
      {
        test: /\.ts$/,
        loader: 'tslint-loader',
        exclude: [
          helpers.root('node_modules')
        ]
      },
      {
        test: /\.js$/,
        loader: 'source-map-loader',
        exclude: [
          helpers.root('node_modules/rxjs')
        ]
      }
    ],
    loaders: [
      // Support Angular 2 async routes via .async.ts
      // Support for .ts files.
      {
        test: /\.ts$/,
        loader: 'awesome-typescript-loader',
        query: {
          // remove TypeScript helpers to be injected below by DefinePlugin
          'compilerOptions': {
            'removeComments': true
          }
        },
        exclude: [
          /\.(spec|e2e)\.ts$/,
          helpers.root('node_modules')
        ]
      },

      // Support for *.json files.
      {
        test: /\.json$/,
        loader: 'json-loader',
        exclude: [ helpers.root('node_modules') ]
      },

      // Support for CSS as raw text
      {
        test: /\.css$/,
        loader: 'raw-loader',
        exclude: [ helpers.root('node_modules') ]
      },

      // support for .html as raw text
      {
        test: /\.html$/,
        loader: 'raw-loader',
        exclude: [
          helpers.root('src/index.html')
        ]
      },

      // support for sass imports
      // add CSS rules to your document:
      // `require("!style!css!sass!./file.scss");`
      {
        test: /\.scss$/,
        loader: 'style!css!autoprefixer-loader?browsers=last 2 versions!sass',
        exclude: [ helpers.root('node_modules') ]
      }

    ],
    noParse: [
      helpers.root('zone.js', 'dist'),
      helpers.root('angular2', 'bundles')
    ]

  },

  plugins: [
    new ForkCheckerPlugin(),
    new WebpackMd5Hash(),
    new DedupePlugin(),
    new OccurenceOrderPlugin(true),
    new CommonsChunkPlugin({
      name: 'polyfills',
      filename: 'polyfills.[chunkhash].bundle.js',
      chunks: Infinity
    }),
    // static assets
    new CopyWebpackPlugin([
      {
        from: 'src/assets',
        to: 'assets'
      }
    ]),
    // generating html
    new HtmlWebpackPlugin({ template: 'src/index.html' }),
    new DefinePlugin({
      // Environment helpers
      'process.env': {
        'ENV': JSON.stringify(metadata.ENV),
        'NODE_ENV': JSON.stringify(metadata.ENV)
      }
    }),
    new UglifyJsPlugin({
      // to debug prod builds uncomment //debug lines and comment //prod lines

      // beautify: true,//debug
      // mangle: false,//debug
      // dead_code: false,//debug
      // unused: false,//debug
      // deadCode: false,//debug
      // compress : { screw_ie8 : true, keep_fnames: true, drop_debugger: false, dead_code: false, unused: false, }, // debug
      // comments: true,//debug

      beautify: false,//prod
      // disable mangling because of a bug in angular2 beta.1, beta.2 and beta.3
      // TODO(mastertinner): enable mangling as soon as angular2 beta.4 is out
      // mangle: { screw_ie8 : true },//prod
      mangle: {
        screw_ie8 : true,
        except: [
          'App',
          'About',
          'Contact',
          'Home',
          'Menu',
          'Footer',
          'XLarge',
          'RouterActive',
          'RouterLink',
          'RouterOutlet',
          'NgFor',
          'NgIf',
          'NgClass',
          'NgSwitch',
          'NgStyle',
          'NgSwitchDefault',
          'NgModel',
          'NgControl',
          'NgFormControl',
          'NgForm',
          'AsyncPipe',
          'DatePipe',
          'JsonPipe',
          'NumberPipe',
          'DecimalPipe',
          'PercentPipe',
          'CurrencyPipe',
          'LowerCasePipe',
          'UpperCasePipe',
          'SlicePipe',
          'ReplacePipe',
          'I18nPluralPipe',
          'I18nSelectPipe'
        ] // needed for uglify RouterLink problem
      },// prod
      compress : { screw_ie8 : true },//prod
      comments: false//prod

    }),
   // include uglify in production
    new CompressionPlugin({
      algorithm: helpers.gzipMaxLevel,
      regExp: /\.css$|\.html$|\.js$|\.map$/,
      threshold: 2 * 1024
    })
  ],
  // Other module loader config
  tslint: {
    emitErrors: true,
    failOnHint: true,
    resourcePath: 'src',
  },
  htmlLoader: {
    minimize: true,
    removeAttributeQuotes: false,
    caseSensitive: true,
    customAttrSurround: [ [/#/, /(?:)/], [/\*/, /(?:)/], [/\[?\(?/, /(?:)/] ],
    customAttrAssign: [ /\)?\]?=/ ]
  },
  // don't use devServer for production
  node: {
    global: 'window',
    progress: false,
    crypto: 'empty',
    module: false,
    clearImmediate: false,
    setImmediate: false
  }
};
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2013 Akkroo Solutions Ltd
 *
 */

(function (root, factory) {
    if (typeof exports === 'object') {
        // CommonJS
        factory(exports, require('underscore'));
    } else if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['exports', 'underscore'], factory);
    } else {
        // Browser globals
        factory((root.JSMongoQuery = {}), root._);
    }
}(this, function (exports, _, undefined) {

	// PHP shims.  Most are just renamed underscore functions.
	var count = _.size,
		array_intersect = _.intersection,
		array_unique = _.uniq,

		empty = _.isEmpty,
		is_callable = _.isFunction,
		is_array = _.isArray,
		is_bool = _.isBoolean,
		is_string = _.isString,
		is_int = function(n) { return _.isNumber(n) && n % 1 == 0; },
		in_array = function(needle, haystack) { return _.contains(haystack, needle); },
		array_diff = function(a1, a2){ return _.difference(a2, a1); },
		array_slice = function(a, offset, length) { return a.slice(offset, length === undefined ? undefined : offset + length); },
		substr = function(string, start, length) { string.substr(start, length); },
		explode = function(sep, e) { return e.split(sep); },
		implode = function(sep, e) { return e.join(sep); },
		call_user_func = function(callback) {
			return callback.apply(null, Array.prototype.slice.call(arguments, 1));
		},
		array_merge = function(a1,a2) { return a1.concat(a2); };
	// recreate
	var Exception = function(e, code, previous) {
		this.exception = e;
		this.code = code;
		this.previous = previous;
		this.toString = function(){return e;}
	};

	JSMongoQuery = {
		/**
		 * Execute a Mongo query on a document
		 *
		 * @param $query		A boolean value or an array defining a query
		 * @param $document	The document to query
		 * @param $options	Any options:
		 *	'unknownOperatorCallback' - a callback to be called if an operator can't be found.  The function definition is function(operator, operatorValue, field, document). return true or false.
		 * @return boolean
		 * @throws Exception
		 */
		executeQuery: function executeQuery($query, $document, $options) {
			$options = $options || {};
			if($options.debug && $options.logger) {
				$options.logger.debug('executeQuery called', {query : $query, document : $document, options : $options});
			}

			if(!_.isObject($query)) return !!$query;
			if(!_.isObject($document)) throw new Exception("document should be an object");

			return JSMongoQuery._executeQuery($query, $document, $options);
		},

		/**
		 * Internal execute query
		 *
		 * This expects an array from the query and has an additional logical operator (for the root query object the logical operator is always $and so this is not required)
		 *
		 * @param $query			The query array
		 * @param $document			The document array to query
		 * @param $options			Options array
		 * @param $logicalOperator	Operator string
		 * @return boolean
		 * @throws Exception
		 */
		_executeQuery: function _executeQuery($query, $document, $options, $logicalOperator) {
			if(!$logicalOperator) {
				$logicalOperator = '$and';
			}
			if($logicalOperator !== '$and' && !count($query))
				throw new Exception($logicalOperator+' requires nonempty array');
			if($options.debug && $options.logger) {
				$options.logger.debug('_executeQuery called', {query:$query, document:$document, options:$options, logicalOperator:$logicalOperator});
			}
			var $pass, result = null;
			// use for to enable returning inside loop
			_.every($query, function($q, $k) {
				$pass = true;
				if(is_string($k) && substr($k, 0, 1) === '$') {
					// key is an operator at this level, except $not, which can be at any level
					if($k === '$not')
						$pass = !JSMongoQuery._executeQuery($q, $document, $options);
					else
						$pass = JSMongoQuery._executeQuery($q, $document, $options, $k);
				} else if($logicalOperator === '$and') { // special case for $and
					if(is_int($k)) { // $q is an object of query objects
						$pass = JSMongoQuery._executeQuery($q, $document, $options);
					} else if(_.isObject($q)) { // query is array, run all queries on field.  All queries must match. e.g { 'age': { $gt: 24, $lt: 52 } }
						$pass = JSMongoQuery._executeQueryOnElement($q, $k, $document, $options);
					} else {
						// key value means equality
						$pass = JSMongoQuery._executeOperatorOnElement('$e', $q, $k, $document, $options);
					}
				} else { // $q is array of query objects e.g '$or' => [{'fullName' => 'Nick'}]
					$pass = JSMongoQuery._executeQuery($q, $document, $options, '$and');
				}

				if ($options.debug && $options.logger) {
					$options.logger.debug('_executeQuery executed query part',
					{query : $q, result: $pass, document : $document, logicalOperator : $logicalOperator});
				}

				switch($logicalOperator) {
					case '$and': // if any fail, query fails
						if(!$pass) { result = false; return false; }
						break;
					case '$or': // if one succeeds, query succeeds
						if($pass) { result = true; return false; }
						break;
					case '$nor': // if one succeeds, query fails
						if($pass) { result = false; return false; }
						break;
					default:
						if ($options.logger) {
							$options.logger.warning('_executeQuery could not find logical operator',
							{query : $query, document : $document, logicalOperator : $logicalOperator});
						}
						result = false;
						return false;
				}
				return true;
			});

			// Will be set if we exited the loop early, so we should use it as the
			// return value for the function.
			if (result !== null) {
				return result;
			}

			switch($logicalOperator) {
				case '$and': // all succeeded, query succeeds
					return true;
				case '$or': // all failed, query fails
					return false;
				case '$nor': // all failed, query succeeded
					return true;
				default:
					if($options.logger) {
						$options.logger.warning('_executeQuery could not find logical operator', {query : $query, document : $document, logicalOperator : $logicalOperator});
					}
					return false;
			}
			throw new Exception('Reached end of _executeQuery without returning a value');
		},

		/**
		 * Execute a query object on an element
		 *
		 * @param $query
		 * @param $field
		 * @param $document
		 * @return boolean
		 * @throws Exception
		 */
		_executeQueryOnElement: function _executeQueryOnElement($query, $element, $document, $options) {
			if($options && $options.debug && $options.logger) {
				$options.logger.debug('_executeQueryOnElement called', {query : $query, element : $element, document : $document, options : $options});
			}
			// iterate through query operators
			return _.every($query, function($opVal, $op) {
				return JSMongoQuery._executeOperatorOnElement($op, $opVal, $element, $document, $options);
			});
		},

		/**
		* Check if an operator is equal to a value
		*
		* Equality includes direct equality, regular expression match, and checking if the operator value is one of the values in an array value
		*
		* @param $v
		* @param $operatorValue
		* @return boolean
		*/
		_isEqual: function isEqual($v, $operatorValue) {
			if(is_array($v)) return in_array($operatorValue, $v);
			var $matches;
			if(is_string($operatorValue) && ($matches = $operatorValue.match(/^\/(.*?)\/([a-z]*)$/i)))
				return (new RegExp($matches[1], $matches[2])).test($v);
			return _.isEqual($operatorValue, $v);
		},

		/**
		 * Execute a Mongo Operator on an element
		 *
		 * @param $operator		The operator to perform
		 * @param $operatorValue	The value to provide the operator
		 * @param $element		The target element.  Can be an object path eg price.shoes
		 * @param $document		The document in which to find the element
		 * @param $options		Options
		 * @return boolean				The result
		 * @throws Exception			Exceptions on invalid operators, invalid unknown operator callback, and invalid operator values
		 */
		_executeOperatorOnElement: function _executeOperatorOnElement($operator, $operatorValue, $element, $document, $options) {
			if($options && $options.debug && $options.logger) {
				$options.logger.debug('_executeOperatorOnElement called', {operatorValue : $operatorValue, element : $element, document : $document, options : $options});
			}

			if($operator === '$not') {
				return !JSMongoQuery._executeQueryOnElement($operatorValue, $element, $document, $options);
			}

			var $elementSpecifier = explode('.',$element),
				$v = $document,
				$exists = true,
				$result = null;
			_.every($elementSpecifier, function($es, $index) {
				if(empty($v)) {
					$exists = false;
					return false;
				}
				if(_.isArray($v)) {
					// value from document is an array, so we need to iterate through array and test the query on all elements of the array
					// if any elements match, then return true
					var $newSpecifier = implode('.', array_slice($elementSpecifier, $index));
					$result = _.some($v, function($item) {
						return JSMongoQuery._executeOperatorOnElement($operator, $operatorValue, $newSpecifier, $item, $options);
					});
					return false;
				}
				if($v[$es] === undefined) {
					$exists = false;
					return false;
				}
				$v = $v[$es];
				return true;
			});

			if($result !== null) {
				return $result;
			}

			switch($operator) {
				case '$all':
					if(!$exists) return false;
					if(!is_array($operatorValue)) throw '$all requires array';
					if(count($operatorValue) === 0) return false;
					if(!is_array($v)) {
						if(count($operatorValue) === 1)
							return $v === $operatorValue[0];
						return false;
					}
					return count(array_intersect($v, $operatorValue)) === count($operatorValue);
				case '$e': // non-mongo operator to allow same method call
					if(!$exists) return false;
					return JSMongoQuery._isEqual($v, $operatorValue);
				case '$in':
					if(!$exists) return false;
					if(!is_array($operatorValue)) throw new Exception('$in requires array');
					if(count($operatorValue) === 0) return false;
					if(is_array($v)) return count(array_diff($v, $operatorValue)) < count($operatorValue);
					return in_array($v, $operatorValue);
				case '$lt':		return $exists && $v < $operatorValue;
				case '$lte':	return $exists && $v <= $operatorValue;
				case '$gt':		return $exists && $v > $operatorValue;
				case '$gte':	return $exists && $v >= $operatorValue;
				case '$ne':		return (!$exists && $operatorValue !== null) || ($exists && !JSMongoQuery._isEqual($v, $operatorValue));
				case '$nin':
					if(!$exists) return true;
					if(!is_array($operatorValue)) throw new Exception('$nin requires array');
					if(count($operatorValue) === 0) return true;
					if(is_array($v)) {
						return !(count(array_diff($v, $operatorValue)) < count($operatorValue));
					}
					return !in_array($v, $operatorValue);

				case '$exists':	return ($operatorValue && $exists) || (!$operatorValue && !$exists);
				case '$mod':
					if(!$exists) return false;
					if(!is_array($operatorValue)) throw new Exception('$mod requires array');
					if(count($operatorValue) !== 2) throw new Exception('$mod requires two parameters in array: divisor and remainder');
					return $v % $operatorValue[0] === $operatorValue[1];

				default:
					if(empty($options['unknownOperatorCallback']) || !is_callable($options['unknownOperatorCallback']))
						throw new Exception('Operator '+$operator+' is unknown');

					var $res = call_user_func($options['unknownOperatorCallback'], $operator, $operatorValue, $element, $document);
					if($res === null)
						throw new Exception('Operator '+$operator+' is unknown');
					if(!is_bool($res))
						throw new Exception('Return value of unknownOperatorCallback must be boolean, actual value '.$res);
					return $res;
			}
			throw new Exception('Didn\'t return in switch');
		},

		/**
		 * Iterate through the query looking for field identifiers.  Append $append to the end of the identifier.
		 *
		 * @param $query
		 * @param $append
		 * @return	The new query
		 */
		appendFieldSpecifier: function appendFieldSpecifier($query, $append) {
			_.each($query, function($v, $k) {
				if(is_array($v))
					$query[$k] = JSMongoQuery.appendFieldSpecifier($v, $append);
				if(is_int($k) || $k[0] === '$') return;
				$query[$k+'.'+$append] = $v;
				delete $query[$k];
			});
			return $query;
		},

		/**
		 * Get the fields this query depends on
		 *
		 * @param $query	The query to analyse
		 * @return array	An array of fields this query depends on
		 */
		getDependentFields: function getDependentFields($query) {
			var $fields = [];
			_.each($query, function($v, $k) {
				if(is_array($v))
					$fields = array_merge($fields, JSMongoQuery.getDependentFields($v));
				if(is_int($k) || $k.charAt(0) === '$') return;
				$fields.push($k);
			});
			return array_unique($fields);
		}

	};

	_.extend(exports, JSMongoQuery);
}));<commit_msg>Fix for last commit<commit_after>/*
 * Copyright (C) 2013 Akkroo Solutions Ltd
 *
 */

(function (root, factory) {
    if (typeof exports === 'object') {
        // CommonJS
        factory(exports, require('underscore'));
    } else if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['exports', 'underscore'], factory);
    } else {
        // Browser globals
        factory((root.JSMongoQuery = {}), root._);
    }
}(this, function (exports, _, undefined) {

	// PHP shims.  Most are just renamed underscore functions.
	var count = _.size,
		array_intersect = _.intersection,
		array_unique = _.uniq,

		empty = _.isEmpty,
		is_callable = _.isFunction,
		is_array = _.isArray,
		is_bool = _.isBoolean,
		is_string = _.isString,
		is_int = function(n) { return _.isNumber(n) && n % 1 == 0; },
		in_array = function(needle, haystack) { return _.contains(haystack, needle); },
		array_diff = function(a1, a2){ return _.difference(a2, a1); },
		array_slice = function(a, offset, length) { return a.slice(offset, length === undefined ? undefined : offset + length); },
		substr = function(string, start, length) { return string.substr(start, length); },
		explode = function(sep, e) { return e.split(sep); },
		implode = function(sep, e) { return e.join(sep); },
		call_user_func = function(callback) {
			return callback.apply(null, Array.prototype.slice.call(arguments, 1));
		},
		array_merge = function(a1,a2) { return a1.concat(a2); };
	// recreate
	var Exception = function(e, code, previous) {
		this.exception = e;
		this.code = code;
		this.previous = previous;
		this.toString = function(){return e;}
	};

	JSMongoQuery = {
		/**
		 * Execute a Mongo query on a document
		 *
		 * @param $query		A boolean value or an array defining a query
		 * @param $document	The document to query
		 * @param $options	Any options:
		 *	'unknownOperatorCallback' - a callback to be called if an operator can't be found.  The function definition is function(operator, operatorValue, field, document). return true or false.
		 * @return boolean
		 * @throws Exception
		 */
		executeQuery: function executeQuery($query, $document, $options) {
			$options = $options || {};
			if($options.debug && $options.logger) {
				$options.logger.debug('executeQuery called', {query : $query, document : $document, options : $options});
			}

			if(!_.isObject($query)) return !!$query;
			if(!_.isObject($document)) throw new Exception("document should be an object");

			return JSMongoQuery._executeQuery($query, $document, $options);
		},

		/**
		 * Internal execute query
		 *
		 * This expects an array from the query and has an additional logical operator (for the root query object the logical operator is always $and so this is not required)
		 *
		 * @param $query			The query array
		 * @param $document			The document array to query
		 * @param $options			Options array
		 * @param $logicalOperator	Operator string
		 * @return boolean
		 * @throws Exception
		 */
		_executeQuery: function _executeQuery($query, $document, $options, $logicalOperator) {
			if(!$logicalOperator) {
				$logicalOperator = '$and';
			}
			if($logicalOperator !== '$and' && !count($query))
				throw new Exception($logicalOperator+' requires nonempty array');
			if($options.debug && $options.logger) {
				$options.logger.debug('_executeQuery called', {query:$query, document:$document, options:$options, logicalOperator:$logicalOperator});
			}
			var $pass, result = null;
			// use for to enable returning inside loop
			_.every($query, function($q, $k) {
				$pass = true;
				if(is_string($k) && substr($k, 0, 1) === '$') {
					// key is an operator at this level, except $not, which can be at any level
					if($k === '$not')
						$pass = !JSMongoQuery._executeQuery($q, $document, $options);
					else
						$pass = JSMongoQuery._executeQuery($q, $document, $options, $k);
				} else if($logicalOperator === '$and') { // special case for $and
					if(is_int($k)) { // $q is an object of query objects
						$pass = JSMongoQuery._executeQuery($q, $document, $options);
					} else if(_.isObject($q)) { // query is array, run all queries on field.  All queries must match. e.g { 'age': { $gt: 24, $lt: 52 } }
						$pass = JSMongoQuery._executeQueryOnElement($q, $k, $document, $options);
					} else {
						// key value means equality
						$pass = JSMongoQuery._executeOperatorOnElement('$e', $q, $k, $document, $options);
					}
				} else { // $q is array of query objects e.g '$or' => [{'fullName' => 'Nick'}]
					$pass = JSMongoQuery._executeQuery($q, $document, $options, '$and');
				}

				if ($options.debug && $options.logger) {
					$options.logger.debug('_executeQuery executed query part',
					{query : $q, result: $pass, document : $document, logicalOperator : $logicalOperator});
				}

				switch($logicalOperator) {
					case '$and': // if any fail, query fails
						if(!$pass) { result = false; return false; }
						break;
					case '$or': // if one succeeds, query succeeds
						if($pass) { result = true; return false; }
						break;
					case '$nor': // if one succeeds, query fails
						if($pass) { result = false; return false; }
						break;
					default:
						if ($options.logger) {
							$options.logger.warning('_executeQuery could not find logical operator',
							{query : $query, document : $document, logicalOperator : $logicalOperator});
						}
						result = false;
						return false;
				}
				return true;
			});

			// Will be set if we exited the loop early, so we should use it as the
			// return value for the function.
			if (result !== null) {
				return result;
			}

			switch($logicalOperator) {
				case '$and': // all succeeded, query succeeds
					return true;
				case '$or': // all failed, query fails
					return false;
				case '$nor': // all failed, query succeeded
					return true;
				default:
					if($options.logger) {
						$options.logger.warning('_executeQuery could not find logical operator', {query : $query, document : $document, logicalOperator : $logicalOperator});
					}
					return false;
			}
			throw new Exception('Reached end of _executeQuery without returning a value');
		},

		/**
		 * Execute a query object on an element
		 *
		 * @param $query
		 * @param $field
		 * @param $document
		 * @return boolean
		 * @throws Exception
		 */
		_executeQueryOnElement: function _executeQueryOnElement($query, $element, $document, $options) {
			if($options && $options.debug && $options.logger) {
				$options.logger.debug('_executeQueryOnElement called', {query : $query, element : $element, document : $document, options : $options});
			}
			// iterate through query operators
			return _.every($query, function($opVal, $op) {
				return JSMongoQuery._executeOperatorOnElement($op, $opVal, $element, $document, $options);
			});
		},

		/**
		* Check if an operator is equal to a value
		*
		* Equality includes direct equality, regular expression match, and checking if the operator value is one of the values in an array value
		*
		* @param $v
		* @param $operatorValue
		* @return boolean
		*/
		_isEqual: function isEqual($v, $operatorValue) {
			if(is_array($v)) return in_array($operatorValue, $v);
			var $matches;
			if(is_string($operatorValue) && ($matches = $operatorValue.match(/^\/(.*?)\/([a-z]*)$/i)))
				return (new RegExp($matches[1], $matches[2])).test($v);
			return _.isEqual($operatorValue, $v);
		},

		/**
		 * Execute a Mongo Operator on an element
		 *
		 * @param $operator		The operator to perform
		 * @param $operatorValue	The value to provide the operator
		 * @param $element		The target element.  Can be an object path eg price.shoes
		 * @param $document		The document in which to find the element
		 * @param $options		Options
		 * @return boolean				The result
		 * @throws Exception			Exceptions on invalid operators, invalid unknown operator callback, and invalid operator values
		 */
		_executeOperatorOnElement: function _executeOperatorOnElement($operator, $operatorValue, $element, $document, $options) {
			if($options && $options.debug && $options.logger) {
				$options.logger.debug('_executeOperatorOnElement called', {operatorValue : $operatorValue, element : $element, document : $document, options : $options});
			}

			if($operator === '$not') {
				return !JSMongoQuery._executeQueryOnElement($operatorValue, $element, $document, $options);
			}

			var $elementSpecifier = explode('.',$element),
				$v = $document,
				$exists = true,
				$result = null;
			_.every($elementSpecifier, function($es, $index) {
				if(empty($v)) {
					$exists = false;
					return false;
				}
				if(_.isArray($v)) {
					// value from document is an array, so we need to iterate through array and test the query on all elements of the array
					// if any elements match, then return true
					var $newSpecifier = implode('.', array_slice($elementSpecifier, $index));
					$result = _.some($v, function($item) {
						return JSMongoQuery._executeOperatorOnElement($operator, $operatorValue, $newSpecifier, $item, $options);
					});
					return false;
				}
				if($v[$es] === undefined) {
					$exists = false;
					return false;
				}
				$v = $v[$es];
				return true;
			});

			if($result !== null) {
				return $result;
			}

			switch($operator) {
				case '$all':
					if(!$exists) return false;
					if(!is_array($operatorValue)) throw '$all requires array';
					if(count($operatorValue) === 0) return false;
					if(!is_array($v)) {
						if(count($operatorValue) === 1)
							return $v === $operatorValue[0];
						return false;
					}
					return count(array_intersect($v, $operatorValue)) === count($operatorValue);
				case '$e': // non-mongo operator to allow same method call
					if(!$exists) return false;
					return JSMongoQuery._isEqual($v, $operatorValue);
				case '$in':
					if(!$exists) return false;
					if(!is_array($operatorValue)) throw new Exception('$in requires array');
					if(count($operatorValue) === 0) return false;
					if(is_array($v)) return count(array_diff($v, $operatorValue)) < count($operatorValue);
					return in_array($v, $operatorValue);
				case '$lt':		return $exists && $v < $operatorValue;
				case '$lte':	return $exists && $v <= $operatorValue;
				case '$gt':		return $exists && $v > $operatorValue;
				case '$gte':	return $exists && $v >= $operatorValue;
				case '$ne':		return (!$exists && $operatorValue !== null) || ($exists && !JSMongoQuery._isEqual($v, $operatorValue));
				case '$nin':
					if(!$exists) return true;
					if(!is_array($operatorValue)) throw new Exception('$nin requires array');
					if(count($operatorValue) === 0) return true;
					if(is_array($v)) {
						return !(count(array_diff($v, $operatorValue)) < count($operatorValue));
					}
					return !in_array($v, $operatorValue);

				case '$exists':	return ($operatorValue && $exists) || (!$operatorValue && !$exists);
				case '$mod':
					if(!$exists) return false;
					if(!is_array($operatorValue)) throw new Exception('$mod requires array');
					if(count($operatorValue) !== 2) throw new Exception('$mod requires two parameters in array: divisor and remainder');
					return $v % $operatorValue[0] === $operatorValue[1];

				default:
					if(empty($options['unknownOperatorCallback']) || !is_callable($options['unknownOperatorCallback']))
						throw new Exception('Operator '+$operator+' is unknown');

					var $res = call_user_func($options['unknownOperatorCallback'], $operator, $operatorValue, $element, $document);
					if($res === null)
						throw new Exception('Operator '+$operator+' is unknown');
					if(!is_bool($res))
						throw new Exception('Return value of unknownOperatorCallback must be boolean, actual value '.$res);
					return $res;
			}
			throw new Exception('Didn\'t return in switch');
		},

		/**
		 * Iterate through the query looking for field identifiers.  Append $append to the end of the identifier.
		 *
		 * @param $query
		 * @param $append
		 * @return	The new query
		 */
		appendFieldSpecifier: function appendFieldSpecifier($query, $append) {
			_.each($query, function($v, $k) {
				if(is_array($v))
					$query[$k] = JSMongoQuery.appendFieldSpecifier($v, $append);
				if(is_int($k) || $k[0] === '$') return;
				$query[$k+'.'+$append] = $v;
				delete $query[$k];
			});
			return $query;
		},

		/**
		 * Get the fields this query depends on
		 *
		 * @param $query	The query to analyse
		 * @return array	An array of fields this query depends on
		 */
		getDependentFields: function getDependentFields($query) {
			var $fields = [];
			_.each($query, function($v, $k) {
				if(is_array($v))
					$fields = array_merge($fields, JSMongoQuery.getDependentFields($v));
				if(is_int($k) || $k.charAt(0) === '$') return;
				$fields.push($k);
			});
			return array_unique($fields);
		}

	};

	_.extend(exports, JSMongoQuery);
}));<|endoftext|>


<commit_before>
google.load("visualization", "1", {packages:["corechart"]});
function drawChartSidebar(dataTable) {
	var options = {
		title: 'Calibration Curve',
		chartArea:{ left:40, top:3, width:150, height:160 },
		hAxis: { title: 'Reported Confidence', titleTextStyle: {color: 'black'}, ticks: [50, 60, 70, 80, 90, 100] },
		vAxis: { title: '% Correct',  titleTextStyle: {color: 'black'}, ticks: [20, 30, 40, 50, 60, 70, 80, 90, 100] },
		legend: { position: 'bottom', textStyle: {color: 'blue', fontSize: 10} },
		animation: { duration: 1000, easing: 'out' }
	};

	var chartContainer = document.getElementById('chartContainer');
	if(chartContainer) {
		var chart = new google.visualization.LineChart(chartContainer);
		chart.draw(google.visualization.arrayToDataTable(dataTable), options);
	}
}

function confidenceSliderUpdate(event, ui) {
	if( ui ) {
		$('.confidence-slider').prev('div').html("Confidence: " + ui.value + "%");
	} else {
		$('.confidence-slider').prev('div').html("Confidence: " + $( '.confidence-slider:visible' ).slider('value') + "%");
	}
}

function drawSidebarDifferenceChart(dataTable) {
	var margin = {top: 20, right: 10, bottom: 30, left: 30},
		width = 200 - margin.left - margin.right,
		height = 200 - margin.top - margin.bottom;

	$("#sidebarDifferenceChart").empty();
	drawDifferenceChart(dataTable,  "#sidebarDifferenceChart", margin, width, height)
}

function drawLargeDifferenceChart(dataTable) {
	var margin = {top: 20, right: 20, bottom: 30, left: 50},
		width = 600 - margin.left - margin.right,
		height = 400 - margin.top - margin.bottom;

	$("#largeDifferenceChart").empty();
	drawDifferenceChart(dataTable,  "#largeDifferenceChart", margin, width, height)
}

function drawDifferenceChart(dataTable, element, margin, width, height) {

	var x = d3.scale.linear().range([0, width]);
	var y = d3.scale.linear().range([height, 0]);

	var xAxis = d3.svg.axis().scale(x).orient("bottom").ticks(4);
	var yAxis = d3.svg.axis().scale(y).orient("left").ticks(7);

	var line = d3.svg.area()
		.interpolate("basis")
		.x(function(d) { return x(d.confidence); })
		.y(function(d) { return y(d["ideal"]); });

	var area = d3.svg.area()
		.interpolate("basis")
		.x(function(d) { return x(d.confidence); })
		.y1(function(d) { return y(d["ideal"]); });

	   var svg = d3.select(element)
		.append("svg:svg")
			.attr("width", width + margin.left + margin.right)
			.attr("height", height + margin.top + margin.bottom)
		.append("g")
			.attr("transform", "translate(" + margin.left + "," + margin.top + ")");

	var data = tableToJson(dataTable);
	data.forEach(function(d) {
		d["ideal"]= +d["ideal"];
		d["actual"] = +d["actual"];
	});

  //x.domain(d3.extent(data, function(d) { return d.confidence; }));
	x.domain([50, 100]);

	y.domain([
		d3.min(data, function(d) { return Math.min(d["ideal"], d["actual"]); }),
		d3.max(data, function(d) { return Math.max(d["ideal"], d["actual"]); })
	]);

	svg.datum(data);

	svg.append("clipPath")
		.attr("id", "clip-below")
	.append("path")
		.attr("d", area.y0(height));

	svg.append("clipPath")
		.attr("id", "clip-above")
	.append("path")
		.attr("d", area.y0(0));

	svg.append("path")
		.attr("class", "area above")
		.attr("clip-path", "url(#clip-above)")
		.attr("d", area.y0(function(d) { return y(d["actual"]); }));

	svg.append("path")
		.attr("class", "area below")
		.attr("clip-path", "url(#clip-below)")
		.attr("d", area);

	svg.append("path")
		.attr("class", "line")
		.attr("d", line);

	svg.append("g")
		.attr("class", "x axis")
		.attr("transform", "translate(0," + height + ")")
		.call(xAxis)
	.append("text")
		.attr("x", width)
		.attr("dy", "-.71em")
		.style("text-anchor", "end")
		.text("reported confidence");

	svg.append("g")
		.attr("class", "y axis")
		.call(yAxis)
	.append("text")
		.attr("transform", "rotate(-90)")
		.attr("y", 6)
		.attr("dy", ".71em")
		.style("text-anchor", "end")
		.text("% correct");
}

/*
 *  Main quiz function.
 */
$(function(){

	// Register [enter] keypress as default action
	$('*').keypress(function (e) {
		if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) {
			$('#next').click();
			return false;
		} else {
			return true;
		}
	});

	$( document ).tooltip({
		content: function() {
			return $(this).attr('title');
		}
	});

	$( ".boolean > a" )
		.button()
		.click(function( event ) {
			event.preventDefault();
			$(this).addClass('selected');
			$(this).siblings('.selected').removeClass('selected');
			$(this).siblings('input[type=text]').val($(this).text());
	});

	$( ".confidence-slider" ).slider({
		min : 50,
		max : 100,
		value : 50,
		step : 10,
		slide : confidenceSliderUpdate
	});

	$('.confidence-slider').slider().bind({
		update : confidenceSliderUpdate
	});
	$('.confidence-slider').trigger('update');

	var totalQuestions = $('.questionContainer').size();
	var currentQuestion = 0;
	var progressPixels = $('#progressContainer').width()/totalQuestions;
	$questions = $('.questionContainer');
	$questions.hide();
	$($questions.get(currentQuestion)).fadeIn();

	var jQuiz = {
		finish: function() {
			var resultDiv = '';
			var trueCount = 0;
			for (var key in this.responses) {
				var response = this.responses[key];
				if (response.correct) {
					trueCount++;
				}
				//resultDiv += '
 Question ' + (response.index + 1) + ' is ' + (response.correct ? "correct" : "incorrect") + '
'
			}
			resultDiv += '
Your total score is ' + parseInt(trueCount * (100/totalQuestions), 10) + ' / 100
'
			$('#resultContainer').html(resultDiv).show();
			$('#next').hide();
			$('#sidebarDifferenceChart').hide();
			drawLargeDifferenceChart(jQuiz.calibrationData());
		},
		init: function() {
			// create empty array for responses
			jQuiz.responses = [];

			//drawChartSidebar(jQuiz.calibrationData());
			drawSidebarDifferenceChart(jQuiz.calibrationData());

			$('#back').click(function() {
				if ( $(this).hasClass('disabled')) {
					// if link is diabled, then do not proceed
					return false;
				}

				$(this).addClass('disabled');
				$('#next').addClass('disabled');

				jQuiz.popResponse();
				drawSidebarDifferenceChart(jQuiz.calibrationData());

				$($questions.get(currentQuestion)).fadeOut(300, function() {
					currentQuestion = currentQuestion - 1;
					$($questions.get(currentQuestion)).fadeIn(300);
					$('.confidence-slider').trigger('update');
					$('#next').removeClass('disabled')
					$('#next').text('Next >>');
					jQuiz.showFeedback();
					if( currentQuestion != 0 ) {
						$('#back').removeClass('disabled')
					}

				});
				var el = $('#progress');
				el.width(el.width() - progressPixels + 'px');
			});

			// define next button behaviour
			$('#next').click(function(){
				if ( !$('.answers > .boolean > a:visible').hasClass('selected')
					||  $('.answers > input:visible').filter(function() { return !this.value;}).length > 0
					|| $(this).hasClass('disabled')) {
					// if all inputs are not provided or link is diabled, do not proceed
					return false;
				}

				// disable next button to prevent double-clicking
				$(this).addClass('disabled');
				$('#back').addClass('disabled');

				jQuiz.addResponse();
				//drawChartSidebar(jQuiz.calibrationData());
				drawSidebarDifferenceChart(jQuiz.calibrationData());

				$($questions.get(currentQuestion)).fadeOut(300, function() {
					// advance question index
					currentQuestion = currentQuestion + 1;

					if( currentQuestion == totalQuestions ) {
						// if on last question, finish quiz
						$('#feedbackContainer').hide();
						jQuiz.finish();
					} else {
						$($questions.get(currentQuestion)).fadeIn(300);
						$('.confidence-slider').trigger('update');
						$('#next').removeClass('disabled')
						$('#back').removeClass('disabled')

						if( currentQuestion == totalQuestions - 1 ) {
							$('#next').text('| Finish |');
						}
						jQuiz.showFeedback();
					}
				});

				var el = $('#progress');
				el.width(el.width() + progressPixels + 'px');
			});
		},
		addResponse: function() {
			var fact = $('.questionContainer:visible > .fact').text();
			var booleanResponse = $('.boolean:visible > input[type=text]').val();
			var confidence = $('.answers:visible > .confidence-slider').slider( 'value' );

			var response = {
				index: currentQuestion,
				response: booleanResponse,
				confidence: confidence,
				fact: fact,
				correct: (fact == booleanResponse)
			};
			jQuiz.responses.push(response);
		},
		popResponse: function() {
			jQuiz.responses.pop();
		},
		showFeedback: function() {
			$('#feedbackContainer').show();
			var canvas = document.getElementById('feedbackCanvas');
			var context = canvas.getContext('2d');
			canvas.width = $('#feedbackCanvas').width();
			canvas.height = $('#feedbackCanvas').height();
			context.clear();

			var scale = canvas.width;
			var feedbackString = $($questions.get(currentQuestion-1)).children('.feedback').text();
			var feedback = $.parseJSON(feedbackString);

			for( i = 0; i < feedback.length; i++ ) {
				var name = feedback[i].name;
				var area = feedback[i].area;
				var scaledArea = area / scale;
				var centerX = (canvas.width / 4) * (2*i + 1);
				var centerY = .50 * canvas.height;
				var radius = Math.sqrt(scaledArea)/Math.sqrt(Math.PI);

				context.fillStyle = '#2F4F4F';
				context.fillCircle(centerX, centerY, radius);

				context.font = '10px Verdana';
				context.fillStyle = '#000000';
				context.fillTextArc(name, centerX, centerY, radius + 20, (7/6)*Math.PI, Math.PI/name.length);

				context.fillStyle = '#2F4F4F';
				context.font = '12px Verdana';
				context.fillText(formatNumber(area) + "km", centerX - (26 + 2 * area.toString().length), centerY + radius + 20);
			}
		},
		calibrationData: function() {
			var dataTable = [['confidence', 'ideal', 'actual']];
			for(i = 50; i<=100; i+=10) {
				var total = 0;
				var correct = 0;
				$.each(jQuiz.responses, function() {
					if (this.confidence == i) {
						total += 1;
						correct += (this.correct ? 1 : 0);
					}
				});
				dataTable.push([i, i, (total == 0 ? i : correct/total*100)]);
			}
			return dataTable;
		}
	};
	jQuiz.init();
})
<commit_msg>Back button hidden on finish.<commit_after>
google.load("visualization", "1", {packages:["corechart"]});
function drawChartSidebar(dataTable) {
	var options = {
		title: 'Calibration Curve',
		chartArea:{ left:40, top:3, width:150, height:160 },
		hAxis: { title: 'Reported Confidence', titleTextStyle: {color: 'black'}, ticks: [50, 60, 70, 80, 90, 100] },
		vAxis: { title: '% Correct',  titleTextStyle: {color: 'black'}, ticks: [20, 30, 40, 50, 60, 70, 80, 90, 100] },
		legend: { position: 'bottom', textStyle: {color: 'blue', fontSize: 10} },
		animation: { duration: 1000, easing: 'out' }
	};

	var chartContainer = document.getElementById('chartContainer');
	if(chartContainer) {
		var chart = new google.visualization.LineChart(chartContainer);
		chart.draw(google.visualization.arrayToDataTable(dataTable), options);
	}
}

function confidenceSliderUpdate(event, ui) {
	if( ui ) {
		$('.confidence-slider').prev('div').html("Confidence: " + ui.value + "%");
	} else {
		$('.confidence-slider').prev('div').html("Confidence: " + $( '.confidence-slider:visible' ).slider('value') + "%");
	}
}

function drawSidebarDifferenceChart(dataTable) {
	var margin = {top: 20, right: 10, bottom: 30, left: 30},
		width = 200 - margin.left - margin.right,
		height = 200 - margin.top - margin.bottom;

	$("#sidebarDifferenceChart").empty();
	drawDifferenceChart(dataTable,  "#sidebarDifferenceChart", margin, width, height)
}

function drawLargeDifferenceChart(dataTable) {
	var margin = {top: 20, right: 20, bottom: 30, left: 50},
		width = 600 - margin.left - margin.right,
		height = 400 - margin.top - margin.bottom;

	$("#largeDifferenceChart").empty();
	drawDifferenceChart(dataTable,  "#largeDifferenceChart", margin, width, height)
}

function drawDifferenceChart(dataTable, element, margin, width, height) {

	var x = d3.scale.linear().range([0, width]);
	var y = d3.scale.linear().range([height, 0]);

	var xAxis = d3.svg.axis().scale(x).orient("bottom").ticks(4);
	var yAxis = d3.svg.axis().scale(y).orient("left").ticks(7);

	var line = d3.svg.area()
		.interpolate("basis")
		.x(function(d) { return x(d.confidence); })
		.y(function(d) { return y(d["ideal"]); });

	var area = d3.svg.area()
		.interpolate("basis")
		.x(function(d) { return x(d.confidence); })
		.y1(function(d) { return y(d["ideal"]); });

	var svg = d3.select(element)
		.append("svg:svg")
			.attr("width", width + margin.left + margin.right)
			.attr("height", height + margin.top + margin.bottom)
		.append("g")
			.attr("transform", "translate(" + margin.left + "," + margin.top + ")");

	var data = tableToJson(dataTable);
	data.forEach(function(d) {
		d["ideal"]= +d["ideal"];
		d["actual"] = +d["actual"];
	});

  //x.domain(d3.extent(data, function(d) { return d.confidence; }));
	x.domain([50, 100]);

	y.domain([
		d3.min(data, function(d) { return Math.min(d["ideal"], d["actual"]); }),
		d3.max(data, function(d) { return Math.max(d["ideal"], d["actual"]); })
	]);

	svg.datum(data);

	svg.append("clipPath")
		.attr("id", "clip-below")
	.append("path")
		.attr("d", area.y0(height));

	svg.append("clipPath")
		.attr("id", "clip-above")
	.append("path")
		.attr("d", area.y0(0));

	svg.append("path")
		.attr("class", "area above")
		.attr("clip-path", "url(#clip-above)")
		.attr("d", area.y0(function(d) { return y(d["actual"]); }));

	svg.append("path")
		.attr("class", "area below")
		.attr("clip-path", "url(#clip-below)")
		.attr("d", area);

	svg.append("path")
		.attr("class", "line")
		.attr("d", line);

	svg.append("g")
		.attr("class", "x axis")
		.attr("transform", "translate(0," + height + ")")
		.call(xAxis)
	.append("text")
		.attr("x", width)
		.attr("dy", "-.71em")
		.style("text-anchor", "end")
		.text("reported confidence");

	svg.append("g")
		.attr("class", "y axis")
		.call(yAxis)
	.append("text")
		.attr("transform", "rotate(-90)")
		.attr("y", 6)
		.attr("dy", ".71em")
		.style("text-anchor", "end")
		.text("% correct");
}

/*
 *  Main quiz function.
 */
$(function(){

	// Register [enter] keypress as default action
	$('*').keypress(function (e) {
		if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) {
			$('#next').click();
			return false;
		} else {
			return true;
		}
	});

	$( document ).tooltip({
		content: function() {
			return $(this).attr('title');
		}
	});

	$( ".boolean > a" )
		.button()
		.click(function( event ) {
			event.preventDefault();
			$(this).addClass('selected');
			$(this).siblings('.selected').removeClass('selected');
			$(this).siblings('input[type=text]').val($(this).text());
	});

	$( ".confidence-slider" ).slider({
		min : 50,
		max : 100,
		value : 50,
		step : 10,
		slide : confidenceSliderUpdate
	});

	$('.confidence-slider').slider().bind({
		update : confidenceSliderUpdate
	});
	$('.confidence-slider').trigger('update');

	var totalQuestions = $('.questionContainer').size();
	var currentQuestion = 0;
	var progressPixels = $('#progressContainer').width()/totalQuestions;
	$questions = $('.questionContainer');
	$questions.hide();
	$($questions.get(currentQuestion)).fadeIn();

	var jQuiz = {
		finish: function() {
			var resultDiv = '';
			var trueCount = 0;
			for (var key in this.responses) {
				var response = this.responses[key];
				if (response.correct) {
					trueCount++;
				}
				//resultDiv += '
 Question ' + (response.index + 1) + ' is ' + (response.correct ? "correct" : "incorrect") + '
'
			}
			resultDiv += '
Your total score is ' + parseInt(trueCount * (100/totalQuestions), 10) + ' / 100
'
			$('#resultContainer').html(resultDiv).show();
			$('#next').hide();
			$('#back').hide();
			$('#sidebarDifferenceChart').hide();
			drawLargeDifferenceChart(jQuiz.calibrationData());
		},
		init: function() {
			// create empty array for responses
			jQuiz.responses = [];

			//drawChartSidebar(jQuiz.calibrationData());
			drawSidebarDifferenceChart(jQuiz.calibrationData());

			$('#back').click(function() {
				if ( $(this).hasClass('disabled')) {
					// if link is diabled, then do not proceed
					return false;
				}

				$(this).addClass('disabled');
				$('#next').addClass('disabled');

				jQuiz.popResponse();
				drawSidebarDifferenceChart(jQuiz.calibrationData());

				$($questions.get(currentQuestion)).fadeOut(300, function() {
					currentQuestion = currentQuestion - 1;
					$($questions.get(currentQuestion)).fadeIn(300);
					$('.confidence-slider').trigger('update');
					$('#next').removeClass('disabled')
					$('#next').text('Next >>');
					jQuiz.showFeedback();
					if( currentQuestion != 0 ) {
						$('#back').removeClass('disabled')
					}

				});
				var el = $('#progress');
				el.width(el.width() - progressPixels + 'px');
			});

			// define next button behaviour
			$('#next').click(function(){
				if ( !$('.answers > .boolean > a:visible').hasClass('selected')
					||  $('.answers > input:visible').filter(function() { return !this.value;}).length > 0
					|| $(this).hasClass('disabled')) {
					// if all inputs are not provided or link is diabled, do not proceed
					return false;
				}

				// disable next button to prevent double-clicking
				$(this).addClass('disabled');
				$('#back').addClass('disabled');

				jQuiz.addResponse();
				//drawChartSidebar(jQuiz.calibrationData());
				drawSidebarDifferenceChart(jQuiz.calibrationData());

				$($questions.get(currentQuestion)).fadeOut(300, function() {
					// advance question index
					currentQuestion = currentQuestion + 1;

					if( currentQuestion == totalQuestions ) {
						// if on last question, finish quiz
						$('#feedbackContainer').hide();
						jQuiz.finish();
					} else {
						$($questions.get(currentQuestion)).fadeIn(300);
						$('.confidence-slider').trigger('update');
						$('#next').removeClass('disabled')
						$('#back').removeClass('disabled')

						if( currentQuestion == totalQuestions - 1 ) {
							$('#next').text('| Finish |');
						}
						jQuiz.showFeedback();
					}
				});

				var el = $('#progress');
				el.width(el.width() + progressPixels + 'px');
			});
		},
		addResponse: function() {
			var fact = $('.questionContainer:visible > .fact').text();
			var booleanResponse = $('.boolean:visible > input[type=text]').val();
			var confidence = $('.answers:visible > .confidence-slider').slider( 'value' );

			var response = {
				index: currentQuestion,
				response: booleanResponse,
				confidence: confidence,
				fact: fact,
				correct: (fact == booleanResponse)
			};
			jQuiz.responses.push(response);
		},
		popResponse: function() {
			jQuiz.responses.pop();
		},
		showFeedback: function() {
			$('#feedbackContainer').show();
			var canvas = document.getElementById('feedbackCanvas');
			var context = canvas.getContext('2d');
			canvas.width = $('#feedbackCanvas').width();
			canvas.height = $('#feedbackCanvas').height();
			context.clear();

			var scale = canvas.width;
			var feedbackString = $($questions.get(currentQuestion-1)).children('.feedback').text();
			var feedback = $.parseJSON(feedbackString);

			for( i = 0; i < feedback.length; i++ ) {
				var name = feedback[i].name;
				var area = feedback[i].area;
				var scaledArea = area / scale;
				var centerX = (canvas.width / 4) * (2*i + 1);
				var centerY = .50 * canvas.height;
				var radius = Math.sqrt(scaledArea)/Math.sqrt(Math.PI);

				context.fillStyle = '#2F4F4F';
				context.fillCircle(centerX, centerY, radius);

				context.font = '10px Verdana';
				context.fillStyle = '#000000';
				context.fillTextArc(name, centerX, centerY, radius + 20, (7/6)*Math.PI, Math.PI/name.length);

				context.fillStyle = '#2F4F4F';
				context.font = '12px Verdana';
				context.fillText(formatNumber(area) + "km", centerX - (26 + 2 * area.toString().length), centerY + radius + 20);
			}
		},
		calibrationData: function() {
			var dataTable = [['confidence', 'ideal', 'actual']];
			for(i = 50; i<=100; i+=10) {
				var total = 0;
				var correct = 0;
				$.each(jQuiz.responses, function() {
					if (this.confidence == i) {
						total += 1;
						correct += (this.correct ? 1 : 0);
					}
				});
				dataTable.push([i, i, (total == 0 ? i : correct/total*100)]);
			}
			return dataTable;
		}
	};
	jQuiz.init();
})
<|endoftext|>