<commit_before>
function canvas_init () {
    var graph = new Graph()
    var n1 = new Node(1)
    graph.insert(n1)
    var n2 = new Node(2)
    graph.insert(n2)
    var n3 = new Node(3)
    graph.insert(n3)
    graph.connect(n1, n2)
    var n4 = new Node(4)
    graph.insert(n4)
    graph.connect(n4, n3)
    graph.connect(n1, n4)
    var n5 = new Node(5)
    var n6 = new Node(6)
    var n7 = new Node(7)
    var n8 = new Node(8)
    var n9 = new Node(9)
    graph.insert(n5)
    graph.insert(n6)
    graph.insert(n7)
    graph.insert(n8)
    graph.insert(n9)
    graph.connect(n1, n5)
    graph.connect(n1, n6)
    graph.connect(n7, n8)
    //graph.connect(n1, n9)
    graph.connect(n7, n9)
    //graph.connect(n1, n7)
    //graph.visit(function (x) { console.log("[neighbours] callback of " + x.value) })
    //graph.visit(function (x) { console.log("[up] callback of " + x.value) }, 'up')
    //graph.visit(function (x) { console.log("[down] callback of " + x.value) }, 'down')

    var canvas = document.getElementById('canvas')
    canvas.onclick = function (event) {
        var x = event.pageX - canvas.offsetLeft
        var y = event.pageY - canvas.offsetTop
        var node = graph.find(x, y)
        console.log("x: " + x + " y: " + y + " node ", node)
        graph.setselected(node)
    }

    graph.layout(canvas)
    var ctx = canvas.getContext('2d')
    graph.context = ctx
    graph.draw(ctx)
}

Function.prototype.compose  = function (argFunction) {
    var invokingFunction = this
    return function () {
        return  invokingFunction.call(this, argFunction.apply(this, arguments))
    }
}
function getProp (property, object) {
    return object[property]
}
function eq (a, b) {
    return a == b
}

function PolarPoint (theta, rho) {
    this.theta = theta || 0
    this.rho = rho || 0
}
function toPolar (x, y) {
    var pp = new PolarPoint()
    pp.setComplex(x, y)
    return pp
}

PolarPoint.prototype = {
    setComplex: function (x, y) {
        this.rho = Math.sqrt(x * x + y * y)
        this.theta = Math.atan2(y, x)
        if (this.theta < 0)
            this.theta += Math.PI * 2
    },

    toComplex: function () {
        var x = Math.cos(this.theta) * this.rho
        var y = Math.sin(this.theta) * this.rho
        return [x, y]
    },

    add: function (polar) {
        return new PolarPoint(this.theta + polar.theta, this.rho + polar.rho)
    },

    follow: function (polar) {
        var n1 = this.toComplex()
        var n2 = polar.toComplex()
        return toPolar(n1[0] + n2[0], n1[1] + n2[1])

    },

    scale: function (number) {
        return new PolarPoint(this.theta, this.rho * number);
    },

    copy: function () {
        return new PolarPoint(this.theta, this.rho)
    },
}

function Edge (source, destination, properties) {
    this.source = source
    this.destination = destination
    this.strokeStyle = "blue"
    this.directed = true
    if (properties)
        for (var i = 0; i < properties.length; i = i + 2)
            this.properties[i] = properties[i + 1]
}

Edge.prototype.draw = function (ctx, graph) {
    ctx.beginPath()
    var sour = this.source.position
    var dest = this.destination.position

    var spos = sour.toComplex()
    var dpos = dest.toComplex()
    var theta  = toPolar(dpos[0] - spos[0], dpos[1] - spos[1]).theta

    var start = sour.follow(new PolarPoint(theta, this.source.radius)).toComplex()
    if (Math.abs(dpos[0] - spos[0]) < 0.1) {
        theta = Math.PI / 2
        if (spos[1] < dpos[1])
            theta = - theta
    }

    var invvec = Math.PI - theta
    var endp = dest.follow(new PolarPoint(invvec, this.destination.radius))
    var end = endp.toComplex()

    ctx.strokeStyle = this.strokeStyle
    ctx.moveTo(start[0], start[1])
    ctx.lineTo(end[0], end[1])
    ctx.closePath()
    ctx.stroke()

    if (this.directed) {
        var arrowsize = 5
        var oneend = endp.follow(new PolarPoint(invvec + Math.PI / 8, arrowsize)).toComplex()
        var otherend = endp.follow(new PolarPoint(invvec - Math.PI / 8, arrowsize)).toComplex()

        ctx.beginPath()
        ctx.moveTo(end[0], end[1])
        ctx.lineTo(oneend[0], oneend[1])
        ctx.moveTo(end[0], end[1])
        ctx.lineTo(otherend[0], otherend[1])
        ctx.closePath()
        ctx.stroke()
    }
}

function Node (value, properties) {
    this.value = value
    this.position = null
    this.fillStyle = "orange"
    this.textStyle = "black"
    this.radius = 10
    if (properties)
        for (var i = 0; i < properties.length; i = i + 2)
            this.properties[i] = properties[i + 1]
}

Node.prototype = {
    redraw: function (ctx, graph) {
        var pos = this.position.toComplex()
        ctx.beginPath()
        ctx.arc(pos[0], pos[1], this.radius + 0.5, 0, Math.PI * 2, true)
        ctx.fillStyle = this.fillStyle
        ctx.closePath()
        ctx.fill()
        if (this.isselected) {
            var old = ctx.strokeStyle
            ctx.strokeStyle = "red"
            ctx.arc(pos[0], pos[1], this.radius - 0.5, 0, Math.PI * 2, true)
            ctx.arc(pos[0], pos[1], this.radius - 1.5, 0, Math.PI * 2, true)
            ctx.closePath()
            ctx.stroke()
            ctx.strokeStyle = old
        }
        ctx.fillStyle = this.textStyle
        ctx.fillText(this.value, pos[0], pos[1])
    },

    place: function (graph) {
        var childs = graph.children(this)
        for (var i = 0; i < childs.length; i++) {
            if (childs[i].position == null) {
                //that is not entirely true:
                //we need to move a bit if child already has a position
                var vec = new PolarPoint(i * (Math.PI * 2 / childs.length), 50)
                childs[i].position = this.position.follow(vec)
            }
        }
    },

    draw: function (ctx, graph) {
        //we better have a position
        this.redraw(ctx, graph)
        graph.outEdges(this).forEach(function (x) { x.draw(ctx, graph) })
    },
}

function Graph () {
    this.nodes = []
    this.edges = []
    this.selectedNode = null
    this.subgraphs = []
}

Graph.prototype = {
    layout: function (canvas) {
        this.findsubgraphs()
        var subgraphs = this.subgraphs
        for (var i = 0; i < subgraphs.length; i++) {
            var roots = this.getRoots(subgraphs[i])
            roots[0].position = toPolar(canvas.width / (2 * subgraphs.length) + (canvas.width * i / subgraphs.length), canvas.height / 2)
        }
        var cb = function (graph, x) { x.place(graph) }
        this.visit(cb.curry(this), 'down')
    },

    draw: function (ctx) {
        var cb = function (ctx, graph, x) { x.draw(ctx, graph) }
        this.visit(cb.curry(ctx, this))
    },

    find: function (x, y) {
        var res = null
        var hit =
            function (nod) {
                var nums = nod.position.toComplex()
                if (nums[0] - 10 < x && nums[0] + 10 > x)
                    if (nums[1] - 10 < y && nums[1] + 10 > y)
                        res = nod
            }

        this.visit(hit)
        return res
    },

    outEdges: function (node) {
        return this.edges.filter(eq.curry(node).compose(getProp.curry("source")))
    },

    children: function (node) {
        return this.outEdges(node).map(getProp.curry("destination"))
    },

    inEdges: function (node) {
        return this.edges.filter(eq.curry(node).compose(getProp.curry("destination")))
    },

    parents: function (node) {
        return this.inEdges(node).map(getProp.curry("source"))
    },

    neighbours: function (node) {
        return this.children(node).concat(this.parents(node))
    },

    connect: function (node1, node2) {
        this.edges.push(new Edge(node1, node2))
    },

    insert: function (node) {
        this.nodes.push(node)
    },

    contains: function (node) {
        return (this.nodes.filter(eq.curry(node)).length > 0)
    },

    findNode: function (val) {
        var nodes = this.nodes.filter(function (x) { return x.value == val })
        if (nodes.length == 1)
            return nodes[0]
        else
            return null
    },

    setselected: function (node) {
        var old = this.selectedNode
        if (old && node && old == node) { }
        else {
            if (old) {
                old.isselected = false
                old.redraw(this.context, this)
                this.selectedNode = null
            }

            if (node) {
                this.selectedNode = node
                node.isselected = true
                node.redraw(this.context, this)
            }
        }
    },

    findsubgraphs: function () {
        //implements a breadth-first search
        var todo = [this.nodes[0]]
        var visited = []
        var subgraph = []
        function doVisit (graph) {
            while (todo.length > 0) {
                var node = todo.shift()
                if (visited.filter(eq.curry(node)).length == 0) {
                    subgraph.push(node)
                    visited.push(node)
                    graph.neighbours(node).forEach(function (x) { todo.push(x) })
                }
            }
        }

        doVisit(this)
        this.subgraphs.push(subgraph)

        //find remaining, disconnected nodes
        for (var i = 0; i < this.nodes.length; i++)
            if (visited.filter(eq.curry(this.nodes[i])).length == 0) {
                console.log("disconnected graph!")
                subgraph = []
                todo.push(this.nodes[i])
                doVisit(this)
                this.subgraphs.push(subgraph)
            }
    },

    getRoots: function (nodelist) {
        return [nodelist[0]]
    },

    visit: function (callback, direction) {
        //implements a breadth-first search
        var todo = []
        var cb = function (graph, subgraph) {
            graph.getRoots(subgraph).forEach(function (r) { todo.push(r)})
        }
        this.subgraphs.forEach(cb.curry(this))
        var visited = []
        function doVisit (graph) {
            while (todo.length > 0) {
                var node = todo.shift()
                if (visited.filter(eq.curry(node)).length == 0) {
                    callback(node)
                    visited.push(node)
                    if (direction && direction == 'up')
                        graph.parents(node).forEach(function (x) { todo.push(x) })
                    if (direction && direction == 'down')
                        graph.children(node).forEach(function (x) { todo.push(x) })
                    if (direction == null)
                        graph.neighbours(node).forEach(function (x) { todo.push(x) })
                }
            }
        }

        doVisit(this)
    },

}
<commit_msg>actually return the root nodes of the subgraph<commit_after>
function canvas_init () {
    var graph = new Graph()
    var n1 = new Node(1)
    graph.insert(n1)
    var n2 = new Node(2)
    graph.insert(n2)
    var n3 = new Node(3)
    graph.insert(n3)
    graph.connect(n1, n2)
    var n4 = new Node(4)
    graph.insert(n4)
    graph.connect(n4, n3)
    graph.connect(n1, n4)
    var n5 = new Node(5)
    var n6 = new Node(6)
    var n7 = new Node(7)
    var n8 = new Node(8)
    var n9 = new Node(9)
    graph.insert(n5)
    graph.insert(n6)
    graph.insert(n7)
    graph.insert(n8)
    graph.insert(n9)
    graph.connect(n5, n4)
    graph.connect(n2, n3)
    graph.connect(n1, n5)
    graph.connect(n1, n6)
    graph.connect(n7, n8)
    //graph.connect(n1, n9)
    graph.connect(n9, n8)
    //graph.connect(n1, n7)
    //graph.visit(function (x) { console.log("[neighbours] callback of " + x.value) })
    //graph.visit(function (x) { console.log("[up] callback of " + x.value) }, 'up')
    //graph.visit(function (x) { console.log("[down] callback of " + x.value) }, 'down')

    var canvas = document.getElementById('canvas')
    canvas.onclick = function (event) {
        var x = event.pageX - canvas.offsetLeft
        var y = event.pageY - canvas.offsetTop
        var node = graph.find(x, y)
        console.log("x: " + x + " y: " + y + " node ", node)
        graph.setselected(node)
    }

    graph.layout(canvas)
    var ctx = canvas.getContext('2d')
    graph.context = ctx
    graph.draw(ctx)
}

Function.prototype.compose  = function (argFunction) {
    var invokingFunction = this
    return function () {
        return  invokingFunction.call(this, argFunction.apply(this, arguments))
    }
}
function getProp (property, object) {
    return object[property]
}
function eq (a, b) {
    return a == b
}

function PolarPoint (theta, rho) {
    this.theta = theta || 0
    this.rho = rho || 0
}
function toPolar (x, y) {
    var pp = new PolarPoint()
    pp.setComplex(x, y)
    return pp
}

PolarPoint.prototype = {
    setComplex: function (x, y) {
        this.rho = Math.sqrt(x * x + y * y)
        this.theta = Math.atan2(y, x)
        if (this.theta < 0)
            this.theta += Math.PI * 2
    },

    toComplex: function () {
        var x = Math.cos(this.theta) * this.rho
        var y = Math.sin(this.theta) * this.rho
        return [x, y]
    },

    add: function (polar) {
        return new PolarPoint(this.theta + polar.theta, this.rho + polar.rho)
    },

    follow: function (polar) {
        var n1 = this.toComplex()
        var n2 = polar.toComplex()
        return toPolar(n1[0] + n2[0], n1[1] + n2[1])

    },

    scale: function (number) {
        return new PolarPoint(this.theta, this.rho * number);
    },

    copy: function () {
        return new PolarPoint(this.theta, this.rho)
    },
}

function Edge (source, destination, properties) {
    this.source = source
    this.destination = destination
    this.strokeStyle = "blue"
    this.directed = true
    if (properties)
        for (var i = 0; i < properties.length; i = i + 2)
            this.properties[i] = properties[i + 1]
}

Edge.prototype.draw = function (ctx, graph) {
    ctx.beginPath()
    var sour = this.source.position
    var dest = this.destination.position

    var spos = sour.toComplex()
    var dpos = dest.toComplex()
    var theta  = toPolar(dpos[0] - spos[0], dpos[1] - spos[1]).theta

    var start = sour.follow(new PolarPoint(theta, this.source.radius)).toComplex()
    if (Math.abs(dpos[0] - spos[0]) < 0.1) {
        theta = Math.PI / 2
        if (spos[1] < dpos[1])
            theta = - theta
    }

    var invvec = Math.PI - theta
    var endp = dest.follow(new PolarPoint(invvec, this.destination.radius))
    var end = endp.toComplex()

    ctx.strokeStyle = this.strokeStyle
    ctx.moveTo(start[0], start[1])
    ctx.lineTo(end[0], end[1])
    ctx.closePath()
    ctx.stroke()

    if (this.directed) {
        var arrowsize = 5
        var oneend = endp.follow(new PolarPoint(invvec + Math.PI / 8, arrowsize)).toComplex()
        var otherend = endp.follow(new PolarPoint(invvec - Math.PI / 8, arrowsize)).toComplex()

        ctx.beginPath()
        ctx.moveTo(end[0], end[1])
        ctx.lineTo(oneend[0], oneend[1])
        ctx.moveTo(end[0], end[1])
        ctx.lineTo(otherend[0], otherend[1])
        ctx.closePath()
        ctx.stroke()
    }
}

function Node (value, properties) {
    this.value = value
    this.position = null
    this.fillStyle = "orange"
    this.textStyle = "black"
    this.radius = 10
    if (properties)
        for (var i = 0; i < properties.length; i = i + 2)
            this.properties[i] = properties[i + 1]
}

Node.prototype = {
    redraw: function (ctx, graph) {
        var pos = this.position.toComplex()
        ctx.beginPath()
        ctx.arc(pos[0], pos[1], this.radius + 0.5, 0, Math.PI * 2, true)
        ctx.fillStyle = this.fillStyle
        ctx.closePath()
        ctx.fill()
        if (this.isselected) {
            var old = ctx.strokeStyle
            ctx.strokeStyle = "red"
            ctx.arc(pos[0], pos[1], this.radius - 0.5, 0, Math.PI * 2, true)
            ctx.arc(pos[0], pos[1], this.radius - 1.5, 0, Math.PI * 2, true)
            ctx.closePath()
            ctx.stroke()
            ctx.strokeStyle = old
        }
        ctx.fillStyle = this.textStyle
        ctx.fillText(this.value, pos[0], pos[1])
    },

    place: function (graph) {
        var childs = graph.children(this)
        for (var i = 0; i < childs.length; i++) {
            if (childs[i].position == null) {
                var vec = new PolarPoint(i * (Math.PI * 2 / childs.length), 50)
                childs[i].position = this.position.follow(vec)
            }
        }
    },

    draw: function (ctx, graph) {
        //we better have a position
        this.redraw(ctx, graph)
        graph.outEdges(this).forEach(function (x) { x.draw(ctx, graph) })
    },
}

function Graph () {
    this.nodes = []
    this.edges = []
    this.selectedNode = null
    this.subgraphs = []
}

Graph.prototype = {
    layout: function (canvas) {
        this.findsubgraphs()
        var subgraphs = this.subgraphs
        for (var i = 0; i < subgraphs.length; i++) {
            var roots = this.getRoots(subgraphs[i])
            for (var r = 0; r < roots.length; r++) {
                var root = roots[r]
                var x = canvas.width / (2 * subgraphs.length) + (canvas.width * i / subgraphs.length)
                var y = canvas.height / (2 * roots.length) + (canvas.height * r / roots.length)
                root.position = toPolar(x, y)
            }
        }
        var cb = function (graph, x) { x.place(graph) }
        this.visit(cb.curry(this), 'down')
    },

    draw: function (ctx) {
        var cb = function (ctx, graph, x) { x.draw(ctx, graph) }
        this.visit(cb.curry(ctx, this))
    },

    find: function (x, y) {
        var res = null
        var hit =
            function (nod) {
                var nums = nod.position.toComplex()
                if (nums[0] - 10 < x && nums[0] + 10 > x)
                    if (nums[1] - 10 < y && nums[1] + 10 > y)
                        res = nod
            }

        this.visit(hit)
        return res
    },

    outEdges: function (node) {
        return this.edges.filter(eq.curry(node).compose(getProp.curry("source")))
    },

    children: function (node) {
        return this.outEdges(node).map(getProp.curry("destination"))
    },

    inEdges: function (node) {
        return this.edges.filter(eq.curry(node).compose(getProp.curry("destination")))
    },

    parents: function (node) {
        return this.inEdges(node).map(getProp.curry("source"))
    },

    neighbours: function (node) {
        return this.children(node).concat(this.parents(node))
    },

    connect: function (node1, node2) {
        this.edges.push(new Edge(node1, node2))
    },

    insert: function (node) {
        this.nodes.push(node)
    },

    contains: function (node) {
        return (this.nodes.filter(eq.curry(node)).length > 0)
    },

    findNode: function (val) {
        var nodes = this.nodes.filter(function (x) { return x.value == val })
        if (nodes.length == 1)
            return nodes[0]
        else
            return null
    },

    setselected: function (node) {
        var old = this.selectedNode
        if (old && node && old == node) { }
        else {
            if (old) {
                old.isselected = false
                old.redraw(this.context, this)
                this.selectedNode = null
            }

            if (node) {
                this.selectedNode = node
                node.isselected = true
                node.redraw(this.context, this)
            }
        }
    },

    findsubgraphs: function () {
        //implements a breadth-first search
        var todo = [this.nodes[0]]
        var visited = []
        var subgraph = []
        function doVisit (graph) {
            while (todo.length > 0) {
                var node = todo.shift()
                if (visited.filter(eq.curry(node)).length == 0) {
                    subgraph.push(node)
                    visited.push(node)
                    graph.neighbours(node).forEach(function (x) { todo.push(x) })
                }
            }
        }

        doVisit(this)
        this.subgraphs.push(subgraph)

        //find remaining, disconnected nodes
        for (var i = 0; i < this.nodes.length; i++)
            if (visited.filter(eq.curry(this.nodes[i])).length == 0) {
                console.log("disconnected graph!")
                subgraph = []
                todo.push(this.nodes[i])
                doVisit(this)
                this.subgraphs.push(subgraph)
            }
    },

    getRoots: function (nodelist) {
        var roots = []
        var cb = function (graph, x) {
            if (graph.inEdges(x).length == 0)
                roots.push(x)
        }
        nodelist.filter(cb.curry(this))
        return roots
    },

    visit: function (callback, direction) {
        //implements a breadth-first search
        var todo = []
        var cb = function (graph, subgraph) {
            graph.getRoots(subgraph).forEach(function (r) { todo.push(r)})
        }
        this.subgraphs.forEach(cb.curry(this))
        var visited = []
        function doVisit (graph) {
            while (todo.length > 0) {
                var node = todo.shift()
                if (visited.filter(eq.curry(node)).length == 0) {
                    callback(node)
                    visited.push(node)
                    if (direction && direction == 'up')
                        graph.parents(node).forEach(function (x) { todo.push(x) })
                    if (direction && direction == 'down')
                        graph.children(node).forEach(function (x) { todo.push(x) })
                    if (direction == null)
                        graph.neighbours(node).forEach(function (x) { todo.push(x) })
                }
            }
        }

        doVisit(this)
    },

}
<|endoftext|>


<commit_before>(function (Backbone, _, $, undefined) {
"use strict";

Backbone.$ = $;

window.Daybed = window.Daybed || {};

/**
 * Default settings
 */
Daybed.SETTINGS = {
    SERVER: "http://localhost:8000"
};

//
//  Item : a record
//
Daybed.Item = Backbone.Model.extend({
    url: function () {
        return Daybed.SETTINGS.SERVER +
               '/data/' + this.definition.id +
               '/' + this.id;
    },

    /**
     * Fields values
     */
    fieldsValues: function () {
        var columns = this.definition.fieldsNames(),
            fields = _.pick.apply(_, ([this.attributes]).concat(columns));
        return _.values(fields);
    }
});

Daybed.ItemList = Backbone.Collection.extend({
    model: Daybed.Item,

    initialize: function (definition) {
        this.definition = definition;
    },

    url: function () {
        return Daybed.SETTINGS.SERVER +
               '/data/' + this.definition.id;
    },

    parse: function (response) {
        return response.data;
    },

    /**
     * Fetch only once Definition is ready.
     */
    fetch: function () {
        var args = Array.prototype.slice.call(arguments);
        this.definition.whenReady((function () {
            Backbone.Collection.prototype.fetch.apply(this, args);
        }).bind(this));
    },

    /**
     * Override instanciation to link with Definition instance.
     */
    _prepareModel: function () {
        var m = Backbone.Collection.prototype._prepareModel.apply(this, arguments);
        m.definition = this.definition;
        return m;
    }
});

//
//  Definition : a model
//
Daybed.Definition = Backbone.Model.extend({
    /**
     * Backbone.forms schema for Definition forms
     */
    schema: {
        id:  { type: 'Hidden', title: '' },
        title: 'Text',
        description: 'Text',
        fields: { type: 'List', itemType: 'Object', subSchema: {
            name: { validators: ['required'] },
            description: 'Text',
            required: { type: 'Checkbox', editorAttrs: { 'checked' : 'checked' } },
            type: { type: 'Select', options: ['int', 'string', 'decimal', 'boolean',
                                              'email', 'url', 'point', 'line', 'polygon'] }
        }}
    },

    /**
     * Meta-types are fake types. Daybed does not know them.
     */
    metaTypes: {
        'text': 'string'
    },

    initialize: function () {
        // Add meta types to schema choice list
        var choices = this.schema.fields.subSchema.type.options;
        for (var metatype in this.metaTypes) {
            if (!_.contains(choices, metatype))
                choices.push(metatype);
        }
    },

    save: function () {
        // Substitute meta types by daybed types
        $(this.attributes.fields).each((function (i, field) {
            var meta = this.metaTypes[field.type];
            if (meta) {
                field.meta = field.type;
                field.type = meta;
            }
        }).bind(this));
        Backbone.Model.prototype.save.apply(this, arguments);
    },

    url: function () {
        return Daybed.SETTINGS.SERVER +
               '/definitions/' + this.id;
    },

    /**
     * @returns {boolean} True if *Definition* is initialized (fetched from server)
     */
    isReady: function () {
        return this.attributes.fields && this.attributes.fields.length > 0;
    },

    /**
     * Calls ``cb`` when *Definition* was fetched from server.
     * @param {function} cb
     */
    whenReady: function (cb) {
        if (this.isReady())
            cb();
        else {
            this.once('change', function () {
                this.whenReady(cb);
            }, this);
        }
    },

    /**
     * Fields names
     */
    fieldsNames: function () {
        return _.pluck(this.attributes.fields, 'name');
    },

    /**
     * Builds a Backbone.forms schema from the *Definition* record,
     * for {Item} forms.
     * For each model field, add a Backbone.forms declaration.
     * @returns {Object}
     */
    itemSchema: function () {
        if (!this.isReady())
            throw "Definition is not ready. Fetch it first.";
        var typeMapping = {
            'int': 'Number',
            'string': 'Text',
            'boolean': 'Checkbox'
        };
        var fieldMapping = {
            'default': function (f) {
                var d = {help: f.description},
                    t = typeMapping[f.type];
                if (t) d.type = t;
                return d;
            },
            'text': function () {
                return { type: 'TextArea' };
            },
            'decimal': function (f) {
                var d = fieldMapping['default'](f);
                d.editorAttrs = {pattern: '[-+]?[0-9]*\\.?[0-9]+'};
                return d;
            },
            'email': function (f) {
                var d = fieldMapping['default'](f);
                d.validators = ['required', 'email'];
                return d;
            },
            'url': function (f) {
                var d = fieldMapping['default'](f);
                d.validators = ['required', 'url'];
                return d;
            }
        };
        var schema = {};
        // Backbone.Forms fields from this Daybed definition
        $(this.attributes.fields).each(function (i, field) {
            var build = fieldMapping[field.meta || field.type];
            schema[field.name] = (build ?
                                  build(field) : fieldMapping['default'](field));
        });
        return schema;
    }
});

//
// FormView : Generic Backbone form
//
Daybed.FormView = Backbone.View.extend({
    model: Daybed.Item,
    tagName: "div",
    className: "well",

    template: Mustache.compile('
{{ title }}
' +
                               '

' +
                               'Cancel ' +
                               'Save'),
    templateError: Mustache.compile('{{ msg }}'),

    events: {
        "click .submit": "submit",
        "click .cancel": "cancel"
    },

    initialize: function () {
        this.definition = this.options.definition || this.options.instance.definition;
        this.definition.whenReady(this.setup.bind(this));
    },

    setup: function (instance) {
        this.instance = instance || this.options.instance || new this.model({});
        this.instance.definition = this.instance.definition || this.definition;
        this.instance.schema = this.instance.schema || this.definition.itemSchema();

        this.instance.on('change', this.refresh.bind(this));
        this.instance.on('sync', this.success.bind(this));
        this.instance.on('error', this.error.bind(this));

        this.creation = this.instance.attributes.id === undefined;

        this.title = this.options.title ||
                     (this.creation ? "Create " : "Edit ") + this.definition.attributes.title.toLowerCase();

        // Underlying backbone-forms object
        this.form = new Backbone.Form({
            model: this.instance
        });

        this.refresh();
    },

    render: function () {
        this.$el.html(this.template(this));
        this.$('.form').html(this.form.render().el);
        this.delegateEvents();
        return this;
    },

    cancel: function (e) {
        if (e) e.preventDefault();
        this.trigger('cancel');
        this.reset();
        return false;
    },

    reset: function () {
        this.$('form')[0].reset();
    },

    submit: function(e) {
        if (e) e.preventDefault();
        // Serialize form fields
        var data = this.form.getValue();
        this.trigger('submit', data);
        this.save();
        return false;
    },

    save: function () {
        // Hide previous validation errors (if any)
        this.$('.field-error').remove();
        // Store form data into instance, and save it
        this.form.commit();
        this.instance.save();
    },

    success: function (model, response, options) {
        this.trigger((this.creation ? 'created' : 'saved'), model);
        return false;
    },

    error: function (model, response, options) {
        var descriptions = [];
        try {
            descriptions = JSON.parse(response.responseText);
            this.showErrors(descriptions);
        }
        catch (e) {
            this.$el.html(this.templateError({msg: response.responseText}));
        }
        this.trigger('error', {model: model,
                               errors: descriptions});
        return false;
    },

    /**
     * Show each error along its field
     */
    showErrors: function (descriptions) {
        $(descriptions.errors).each((function (i, e) {
            var name = e.name.split('.')[0];
            this.$("[name='" + name + "']")
                .after(this.templateError({msg: e.description}));
        }).bind(this));
    },

    /**
     * Refresh field values from instance attributes.
     */
    refresh: function () {
        for (var f in this.instance.changed) {
            if (f == 'id') continue;
            var formfield = this.$("[name='"+ f + "']");
            if (formfield.length === 0)
                console.warn("Could not find form field '" + f + "'");
            formfield.val(this.instance.get(f));
        }
    }
});

/**
 * Form rendering helper
 */
Daybed.renderForm = function (selector, options) {
    var definition = new Daybed.Definition(options),
        formView = new Daybed.FormView({definition: definition});

    definition.whenReady(function () {
        $(selector).html(formView.render().el);
    });

    definition.fetch();
    return formView;
};

})(Backbone,  _, jQuery);<commit_msg>Fix single records URLs<commit_after>(function (Backbone, _, $, undefined) {
"use strict";

Backbone.$ = $;

window.Daybed = window.Daybed || {};

/**
 * Default settings
 */
Daybed.SETTINGS = {
    SERVER: "http://localhost:8000"
};

//
//  Item : a record
//
Daybed.Item = Backbone.Model.extend({
    urlRoot: function () {
        return Daybed.SETTINGS.SERVER +
               '/data/' + this.definition.id;
    },

    /**
     * Fields values
     */
    fieldsValues: function () {
        var columns = this.definition.fieldsNames(),
            fields = _.pick.apply(_, ([this.attributes]).concat(columns));
        return _.values(fields);
    }
});

Daybed.ItemList = Backbone.Collection.extend({
    model: Daybed.Item,

    initialize: function (definition) {
        this.definition = definition;
    },

    url: function () {
        return Daybed.SETTINGS.SERVER +
               '/data/' + this.definition.id;
    },

    parse: function (response) {
        return response.data;
    },

    /**
     * Fetch only once Definition is ready.
     */
    fetch: function () {
        var args = Array.prototype.slice.call(arguments);
        this.definition.whenReady((function () {
            Backbone.Collection.prototype.fetch.apply(this, args);
        }).bind(this));
    },

    /**
     * Override instanciation to link with Definition instance.
     */
    _prepareModel: function () {
        var m = Backbone.Collection.prototype._prepareModel.apply(this, arguments);
        m.definition = this.definition;
        return m;
    }
});

//
//  Definition : a model
//
Daybed.Definition = Backbone.Model.extend({
    /**
     * Backbone.forms schema for Definition forms
     */
    schema: {
        id:  { type: 'Hidden', title: '' },
        title: 'Text',
        description: 'Text',
        fields: { type: 'List', itemType: 'Object', subSchema: {
            name: { validators: ['required'] },
            description: 'Text',
            required: { type: 'Checkbox', editorAttrs: { 'checked' : 'checked' } },
            type: { type: 'Select', options: ['int', 'string', 'decimal', 'boolean',
                                              'email', 'url', 'point', 'line', 'polygon'] }
        }}
    },

    /**
     * Meta-types are fake types. Daybed does not know them.
     */
    metaTypes: {
        'text': 'string'
    },

    initialize: function () {
        // Add meta types to schema choice list
        var choices = this.schema.fields.subSchema.type.options;
        for (var metatype in this.metaTypes) {
            if (!_.contains(choices, metatype))
                choices.push(metatype);
        }
    },

    save: function () {
        // Substitute meta types by daybed types
        $(this.attributes.fields).each((function (i, field) {
            var meta = this.metaTypes[field.type];
            if (meta) {
                field.meta = field.type;
                field.type = meta;
            }
        }).bind(this));
        Backbone.Model.prototype.save.apply(this, arguments);
    },

    url: function () {
        return Daybed.SETTINGS.SERVER +
               '/definitions/' + this.id;
    },

    /**
     * @returns {boolean} True if *Definition* is initialized (fetched from server)
     */
    isReady: function () {
        return this.attributes.fields && this.attributes.fields.length > 0;
    },

    /**
     * Calls ``cb`` when *Definition* was fetched from server.
     * @param {function} cb
     */
    whenReady: function (cb) {
        if (this.isReady())
            cb();
        else {
            this.once('change', function () {
                this.whenReady(cb);
            }, this);
        }
    },

    /**
     * Fields names
     */
    fieldsNames: function () {
        return _.pluck(this.attributes.fields, 'name');
    },

    /**
     * Builds a Backbone.forms schema from the *Definition* record,
     * for {Item} forms.
     * For each model field, add a Backbone.forms declaration.
     * @returns {Object}
     */
    itemSchema: function () {
        if (!this.isReady())
            throw "Definition is not ready. Fetch it first.";
        var typeMapping = {
            'int': 'Number',
            'string': 'Text',
            'boolean': 'Checkbox'
        };
        var fieldMapping = {
            'default': function (f) {
                var d = {help: f.description},
                    t = typeMapping[f.type];
                if (t) d.type = t;
                return d;
            },
            'text': function () {
                return { type: 'TextArea' };
            },
            'decimal': function (f) {
                var d = fieldMapping['default'](f);
                d.editorAttrs = {pattern: '[-+]?[0-9]*\\.?[0-9]+'};
                return d;
            },
            'email': function (f) {
                var d = fieldMapping['default'](f);
                d.validators = ['required', 'email'];
                return d;
            },
            'url': function (f) {
                var d = fieldMapping['default'](f);
                d.validators = ['required', 'url'];
                return d;
            }
        };
        var schema = {};
        // Backbone.Forms fields from this Daybed definition
        $(this.attributes.fields).each(function (i, field) {
            var build = fieldMapping[field.meta || field.type];
            schema[field.name] = (build ?
                                  build(field) : fieldMapping['default'](field));
        });
        return schema;
    }
});

//
// FormView : Generic Backbone form
//
Daybed.FormView = Backbone.View.extend({
    model: Daybed.Item,
    tagName: "div",
    className: "well",

    template: Mustache.compile('
{{ title }}
' +
                               '

' +
                               'Cancel ' +
                               'Save'),
    templateError: Mustache.compile('{{ msg }}'),

    events: {
        "click .submit": "submit",
        "click .cancel": "cancel"
    },

    initialize: function () {
        this.definition = this.options.definition || this.options.instance.definition;
        this.definition.whenReady(this.setup.bind(this));
    },

    setup: function (instance) {
        this.instance = instance || this.options.instance || new this.model({});
        this.instance.definition = this.instance.definition || this.definition;
        this.instance.schema = this.instance.schema || this.definition.itemSchema();

        this.instance.on('change', this.refresh.bind(this));
        this.instance.on('sync', this.success.bind(this));
        this.instance.on('error', this.error.bind(this));

        this.creation = this.instance.attributes.id === undefined;

        this.title = this.options.title ||
                     (this.creation ? "Create " : "Edit ") + this.definition.attributes.title.toLowerCase();

        // Underlying backbone-forms object
        this.form = new Backbone.Form({
            model: this.instance
        });

        this.refresh();
    },

    render: function () {
        this.$el.html(this.template(this));
        this.$('.form').html(this.form.render().el);
        this.delegateEvents();
        return this;
    },

    cancel: function (e) {
        if (e) e.preventDefault();
        this.trigger('cancel');
        this.reset();
        return false;
    },

    reset: function () {
        this.$('form')[0].reset();
    },

    submit: function(e) {
        if (e) e.preventDefault();
        // Serialize form fields
        var data = this.form.getValue();
        this.trigger('submit', data);
        this.save();
        return false;
    },

    save: function () {
        // Hide previous validation errors (if any)
        this.$('.field-error').remove();
        // Store form data into instance, and save it
        this.form.commit();
        this.instance.save();
    },

    success: function (model, response, options) {
        this.trigger((this.creation ? 'created' : 'saved'), model);
        return false;
    },

    error: function (model, response, options) {
        var descriptions = [];
        try {
            descriptions = JSON.parse(response.responseText);
            this.showErrors(descriptions);
        }
        catch (e) {
            this.$el.html(this.templateError({msg: response.responseText}));
        }
        this.trigger('error', {model: model,
                               errors: descriptions});
        return false;
    },

    /**
     * Show each error along its field
     */
    showErrors: function (descriptions) {
        $(descriptions.errors).each((function (i, e) {
            var name = e.name.split('.')[0];
            this.$("[name='" + name + "']")
                .after(this.templateError({msg: e.description}));
        }).bind(this));
    },

    /**
     * Refresh field values from instance attributes.
     */
    refresh: function () {
        for (var f in this.instance.changed) {
            if (f == 'id') continue;
            var formfield = this.$("[name='"+ f + "']");
            if (formfield.length === 0)
                console.warn("Could not find form field '" + f + "'");
            formfield.val(this.instance.get(f));
        }
    }
});

/**
 * Form rendering helper
 */
Daybed.renderForm = function (selector, options) {
    var definition = new Daybed.Definition(options),
        formView = new Daybed.FormView({definition: definition});

    definition.whenReady(function () {
        $(selector).html(formView.render().el);
    });

    definition.fetch();
    return formView;
};

})(Backbone,  _, jQuery);<|endoftext|>


<commit_before>import LinearPieceWiseEditor from '../../../NativeUI/Canvas/LinearPieceWiseEditor';
import SvgIconWidget from '../SvgIconWidget';
import React from 'react';
import ReactDOM from 'react-dom';
import equals from 'mout/src/lang/deepEquals';
import clone from 'mout/src/lang/deepClone';

import style from 'PVWStyle/ReactWidgets/PieceWiseFunctionEditorWidget.mcss';

import sizeHelper from '../../../Common/Misc/SizeHelper';

import plusIcon from '../../../../svg/colors/Plus.svg';
import trashIcon from '../../../../svg/colors/Trash.svg';
// In javascript, you can't return an object from an => function like this:
// x => { property: x }.  But ESLint doesn't allow this:
// x => { return { property: x }; } since it says all one line returning =>
// functions should not include the outer {} or return keyword.  This is
// a function to allow this syntax: x => makeESLintHappy({ property: x })
function makeESLintHappy(x) {
  return x;
}

export default React.createClass({

  displayName: 'PieceWiseFunctionEditorWidget',

  propTypes: {
    initialPoints: React.PropTypes.array,
    rangeMin: React.PropTypes.number,
    rangeMax: React.PropTypes.number,
    onChange: React.PropTypes.func,
    visible: React.PropTypes.bool,
    height: React.PropTypes.number,
  },

  getDefaultProps() {
    return {
      height: 200,
      visible: false,
    };
  },

  getInitialState() {
    let controlPoints = [{ x: 0, y: 0 }, { x: 1, y: 1 }];
    if (this.props.initialPoints) {
      controlPoints = this.props.initialPoints.map(pt =>
        makeESLintHappy({
          x: (pt.x - this.props.rangeMin) / (this.props.rangeMax - this.props.rangeMin),
          y: pt.y,
        })
      );
    }
    return {
      activePoint: 0,
      width: -1,
      height: this.props.height,
      points: controlPoints,
    };
  },

  componentWillMount() {
    if (this.props.visible) {
      this.sizeSubscription = sizeHelper.onSizeChange(this.updateDimensions);
      sizeHelper.startListening();
    }
  },

  componentDidMount() {
    const canvas = this.refs.canvas;
    this.editor = new LinearPieceWiseEditor(canvas);

    this.editor.setControlPoints(this.state.points);
    this.editor.render();
    this.editor.onChange(this.updatePoints);

    if (this.sizeHelper) {
      sizeHelper.triggerChange();
    }
  },

  componentDidUpdate(prevProps, prevState) {
    if (this.props.visible && !prevProps.visible && this.state.width === -1) {
      this.sizeSubscription = sizeHelper.onSizeChange(this.updateDimensions);
      sizeHelper.startListening();
      sizeHelper.triggerChange();
    }
    if (this.state.width !== prevState.width ||
        (this.props.visible && !prevProps.visible)) {
      this.editor.render();
    }
    // We get some duplicate events from the editor, filter them out
    if (!equals(this.state.points, prevState.points) ||
        this.props.rangeMin !== prevProps.rangeMin ||
        this.props.rangeMax !== prevProps.rangeMax) {
      const dataPoints = this.state.points.map(pt => makeESLintHappy({
        x: pt.x * (this.props.rangeMax - this.props.rangeMin) + this.props.rangeMin,
        y: pt.y,
      }));
      if (this.props.onChange) {
        this.props.onChange(dataPoints);
      }
    }
  },

  componentWillUnmount() {
    if (this.sizeSubscription) {
      this.sizeSubscription.unsubscribe();
      this.sizeSubscription = null;
      this.editor = null;
    }
  },

  updateDimensions() {
    const { clientWidth } =
      sizeHelper.getSize(ReactDOM.findDOMNode(this));
    this.setState({ width: clientWidth });
  },

  updatePoints(newPoints, envelope) {
    const activePoint = this.editor.activeIndex;
    this.setState({ points: clone(newPoints), activePoint });
  },

  updateActivePointDataValue(e) {
    if (this.state.activePoint === -1) {
      return;
    }
    const value = parseFloat(e.target.value);
    const points = this.state.points.map(pt => makeESLintHappy({ x: pt.x, y: pt.y }));
    points[this.state.activePoint].x =
      (value - this.props.rangeMin) / (this.props.rangeMax - this.props.rangeMin);
    this.editor.setControlPoints(points, this.state.activePoint);
  },

  updateActivePointOpacity(e) {
    if (this.state.activePoint === -1) {
      return;
    }
    const value = parseFloat(e.target.value);
    const points = this.state.points.map(pt => makeESLintHappy({ x: pt.x, y: pt.y }));
    points[this.state.activePoint].y = value;
    this.editor.setControlPoints(points, this.state.activePoint);
  },

  addPoint(e) {
    const points = this.state.points.map(pt => makeESLintHappy({ x: pt.x, y: pt.y }));
    points.push({ x: 0.5, y: 0.5 });
    this.editor.setControlPoints(points, points.length - 1);
  },

  removePoint(e) {
    if (this.state.activePoint === -1) {
      return;
    }
    const points = this.state.points.map(pt => makeESLintHappy({ x: pt.x, y: pt.y }));
    points.splice(this.state.activePoint, 1);
    this.editor.setActivePoint(-1);
    this.editor.setControlPoints(points);
  },

  render() {
    const activePointDataValue = (this.state.activePoint !== -1 ?
      this.state.points[this.state.activePoint].x : 0.5) *
      (this.props.rangeMax - this.props.rangeMin) + this.props.rangeMin;
    const activePointOpacity = this.state.activePoint !== -1 ?
      this.state.points[this.state.activePoint].y : 0.5;
    return (

        <canvas
          className={style.canvas}
          width={this.state.width}
          height={this.state.height}
          ref="canvas"
        />

              Data
              <input
                className={style.input}
                type="number"
                step="any"
                min={this.props.rangeMin}
                max={this.props.rangeMax}
                value={activePointDataValue}
                onChange={this.updateActivePointDataValue}
              />

              Opacity
              <input
                className={style.input}
                type="number"
                step={0.01}
                min={0}
                max={1}
                value={Math.floor(100 * activePointOpacity) / 100}
                onChange={this.updateActivePointOpacity}
              />

          <SvgIconWidget className={style.svgIcon} icon={plusIcon} onClick={this.addPoint} />
          <SvgIconWidget className={style.svgIcon} icon={trashIcon} onClick={this.removePoint} />

    );
  },
});
<commit_msg>fix(PieceWiseFunctionEditorWidget): Make sure active point is initialized<commit_after>import LinearPieceWiseEditor from '../../../NativeUI/Canvas/LinearPieceWiseEditor';
import SvgIconWidget from '../SvgIconWidget';
import React from 'react';
import ReactDOM from 'react-dom';
import equals from 'mout/src/lang/deepEquals';
import clone from 'mout/src/lang/deepClone';

import style from 'PVWStyle/ReactWidgets/PieceWiseFunctionEditorWidget.mcss';

import sizeHelper from '../../../Common/Misc/SizeHelper';

import plusIcon from '../../../../svg/colors/Plus.svg';
import trashIcon from '../../../../svg/colors/Trash.svg';
// In javascript, you can't return an object from an => function like this:
// x => { property: x }.  But ESLint doesn't allow this:
// x => { return { property: x }; } since it says all one line returning =>
// functions should not include the outer {} or return keyword.  This is
// a function to allow this syntax: x => makeESLintHappy({ property: x })
function makeESLintHappy(x) {
  return x;
}

export default React.createClass({

  displayName: 'PieceWiseFunctionEditorWidget',

  propTypes: {
    initialPoints: React.PropTypes.array,
    rangeMin: React.PropTypes.number,
    rangeMax: React.PropTypes.number,
    onChange: React.PropTypes.func,
    visible: React.PropTypes.bool,
    height: React.PropTypes.number,
  },

  getDefaultProps() {
    return {
      height: 200,
      visible: false,
    };
  },

  getInitialState() {
    let controlPoints = [{ x: 0, y: 0 }, { x: 1, y: 1 }];
    if (this.props.initialPoints) {
      controlPoints = this.props.initialPoints.map(pt =>
        makeESLintHappy({
          x: (pt.x - this.props.rangeMin) / (this.props.rangeMax - this.props.rangeMin),
          y: pt.y,
        })
      );
    }
    return {
      activePoint: -1,
      width: -1,
      height: this.props.height,
      points: controlPoints,
    };
  },

  componentWillMount() {
    if (this.props.visible) {
      this.sizeSubscription = sizeHelper.onSizeChange(this.updateDimensions);
      sizeHelper.startListening();
    }
  },

  componentDidMount() {
    const canvas = this.refs.canvas;
    this.editor = new LinearPieceWiseEditor(canvas);

    this.editor.setControlPoints(this.state.points);
    this.editor.render();
    this.editor.onChange(this.updatePoints);

    if (this.sizeHelper) {
      sizeHelper.triggerChange();
    }
  },

  componentDidUpdate(prevProps, prevState) {
    if (this.props.visible && !prevProps.visible && this.state.width === -1) {
      this.sizeSubscription = sizeHelper.onSizeChange(this.updateDimensions);
      sizeHelper.startListening();
      sizeHelper.triggerChange();
    }
    if (this.state.width !== prevState.width ||
        (this.props.visible && !prevProps.visible)) {
      this.editor.render();
    }
    // We get some duplicate events from the editor, filter them out
    if (!equals(this.state.points, prevState.points) ||
        this.props.rangeMin !== prevProps.rangeMin ||
        this.props.rangeMax !== prevProps.rangeMax) {
      const dataPoints = this.state.points.map(pt => makeESLintHappy({
        x: pt.x * (this.props.rangeMax - this.props.rangeMin) + this.props.rangeMin,
        y: pt.y,
      }));
      if (this.props.onChange) {
        this.props.onChange(dataPoints);
      }
    }
  },

  componentWillUnmount() {
    if (this.sizeSubscription) {
      this.sizeSubscription.unsubscribe();
      this.sizeSubscription = null;
      this.editor = null;
    }
  },

  updateDimensions() {
    const { clientWidth } =
      sizeHelper.getSize(ReactDOM.findDOMNode(this));
    this.setState({ width: clientWidth });
  },

  updatePoints(newPoints, envelope) {
    const activePoint = this.editor.activeIndex;
    this.setState({ points: clone(newPoints), activePoint });
  },

  updateActivePointDataValue(e) {
    if (this.state.activePoint === -1) {
      return;
    }
    const value = parseFloat(e.target.value);
    const points = this.state.points.map(pt => makeESLintHappy({ x: pt.x, y: pt.y }));
    points[this.state.activePoint].x =
      (value - this.props.rangeMin) / (this.props.rangeMax - this.props.rangeMin);
    this.editor.setControlPoints(points, this.state.activePoint);
  },

  updateActivePointOpacity(e) {
    if (this.state.activePoint === -1) {
      return;
    }
    const value = parseFloat(e.target.value);
    const points = this.state.points.map(pt => makeESLintHappy({ x: pt.x, y: pt.y }));
    points[this.state.activePoint].y = value;
    this.editor.setControlPoints(points, this.state.activePoint);
  },

  addPoint(e) {
    const points = this.state.points.map(pt => makeESLintHappy({ x: pt.x, y: pt.y }));
    points.push({ x: 0.5, y: 0.5 });
    this.editor.setControlPoints(points, points.length - 1);
  },

  removePoint(e) {
    if (this.state.activePoint === -1) {
      return;
    }
    const points = this.state.points.map(pt => makeESLintHappy({ x: pt.x, y: pt.y }));
    points.splice(this.state.activePoint, 1);
    this.editor.setActivePoint(-1);
    this.editor.setControlPoints(points);
  },

  render() {
    const activePointDataValue = (this.state.activePoint !== -1 ?
      this.state.points[this.state.activePoint].x : 0.5) *
      (this.props.rangeMax - this.props.rangeMin) + this.props.rangeMin;
    const activePointOpacity = this.state.activePoint !== -1 ?
      this.state.points[this.state.activePoint].y : 0.5;
    return (

        <canvas
          className={style.canvas}
          width={this.state.width}
          height={this.state.height}
          ref="canvas"
        />

              Data
              <input
                className={style.input}
                type="number"
                step="any"
                min={this.props.rangeMin}
                max={this.props.rangeMax}
                value={activePointDataValue}
                onChange={this.updateActivePointDataValue}
              />

              Opacity
              <input
                className={style.input}
                type="number"
                step={0.01}
                min={0}
                max={1}
                value={Math.floor(100 * activePointOpacity) / 100}
                onChange={this.updateActivePointOpacity}
              />

          <SvgIconWidget className={style.svgIcon} icon={plusIcon} onClick={this.addPoint} />
          <SvgIconWidget className={style.svgIcon} icon={trashIcon} onClick={this.removePoint} />

    );
  },
});
<|endoftext|>


<commit_before>'use strict';
var wd;

// load the driver
var Driver = require('./index.js');
// initialize the driver
var driver = new Driver({
  host: '127.0.0.1'
});
// start the driver
driver.start(
  // callback invoked when driver is started
  function(options) {
    // load, connect and initialize WD.js
    wd = require('wd').promiseChainRemote(options.wd);
    wd.init(options.wd).then(
      haveSomeFunWithWebDriver,
      console.error.bind(console)
    );
  },
  // callback invoked when driver could not start
  console.error.bind(console),
  // callback invoked when driver crashed after successful start
  console.error.bind(console)
);

function stop() {
  // stop the client
  wd.quit().then(function() {
    // then stop the driver
    driver.stop(function() {
      // now we're done
      console.log("bye bye");
    });
  });
}

function haveSomeFunWithWebDriver() {
  // open a website
  wd.get('https://google.com')
    // find all its links
    .elements('css', 'a')
    // output what ever elements() returned
    .then(function(data) {
      console.log("success", JSON.stringify(data, null, 2));
    }, function(data) {
      console.log("error", JSON.stringify(data, null, 2));
    })
    // stop WD and the driver
    .then(stop)
    // woopsi
    .catch(function(error) {
      console.error("failure", error);
      stop();
    })
    // promises…
    .done();
}
<commit_msg>feature(example): improving output callbacks for qicker fiddling<commit_after>'use strict';
var wd;

// load the driver
var Driver = require('./index.js');
// initialize the driver
var driver = new Driver({
  host: '127.0.0.1'
});
// start the driver
driver.start(
  // callback invoked when driver is started
  function(options) {
    // load, connect and initialize WD.js
    wd = require('wd').promiseChainRemote(options.wd);
    wd.init(options.wd).then(
      haveSomeFunWithWebDriver,
      console.error.bind(console)
    );
  },
  // callback invoked when driver could not start
  console.error.bind(console),
  // callback invoked when driver crashed after successful start
  console.error.bind(console)
);

function stop() {
  // stop the client
  wd.quit().then(function() {
    // then stop the driver
    driver.stop(function() {
      // now we're done
      console.log("bye bye");
    });
  });
}

function success(data) {
  console.log("success", JSON.stringify(data, null, 2));
  return data;
}

function error(data) {
  console.log("error", JSON.stringify(data, null, 2));
}

function haveSomeFunWithWebDriver() {
  // open a website
  wd.get('https://google.com')
    // find all its links
    .elements('css', 'a').then(success, error)
    // stop WD and the driver
    .then(stop)
    // woopsi
    .catch(function(error) {
      console.error("failure", error);
      stop();
    })
    // promises…
    .done();
}
<|endoftext|>


<commit_before>(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/** @jsx React.DOM */
"use strict";

// Array.prototype.map polyfill - see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map#Polyfill
if (!Array.prototype.map) {
    Array.prototype.map = function(fun /*, thisArg */) {
        "use strict";

        if (this === void 0 || this === null) {
            throw new TypeError();
        }

        var t = Object(this);
        var len = t.length >>> 0;
        if (typeof fun !== "function") {
            throw new TypeError();
        }

        var res = new Array(len);
        var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
        for (var i = 0; i < len; i++) {
            // NOTE: Absolute correctness would demand Object.defineProperty
            //       be used.  But this method is fairly new, and failure is
            //       possible only if Object.prototype or Array.prototype
            //       has a property |i| (very unlikely), so use a less-correct
            //       but more portable alternative.
            if (i in t) {
                res[i] = fun.call(thisArg, t[i], i, t);
            }
        }

        return res;
    };
}

// Array.prototype.indexOf polyfill for IE8
if (!Array.prototype.indexOf)
{
      Array.prototype.indexOf = function(elt /*, from*/)
      {
            var len = this.length >>> 0;

            var from = Number(arguments[1]) || 0;
            from = (from < 0)
                 ? Math.ceil(from)
                 : Math.floor(from);
            if (from < 0) {
                from += len;
            }

            for (; from < len; from++)
            {
                if (from in this && this[from] === elt) {
                    return from;
                }
            }
            return -1;
      };
}

// Array.prototype.find polyfill - see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
if (!Array.prototype.find) {
    Object.defineProperty(Array.prototype, 'find', {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function(predicate) {
            if (this == null) {
                throw new TypeError('Array.prototype.find called on null or undefined');
            }
            if (typeof predicate !== 'function') {
                throw new TypeError('predicate must be a function');
            }
            var list = Object(this);
            var length = list.length >>> 0;
            var thisArg = arguments[1];
            var value;

            for (var i = 0; i < length; i++) {
                if (i in list) {
                    value = list[i];
                    if (predicate.call(thisArg, value, i, list)) {
                        return value;
                    }
                }
            }
            return undefined;
        }
    });
}

if (!Array.isArray) {
    Array.isArray = function (value) {
        return Object.prototype.toString.call(value) === '[object Array]';
    };
}

function Unsafe(content) {
    this.content = content;
}

Unsafe.prototype.toString = function() {
    return this.content;
}

exports.unsafe = function(str) {
    return new Unsafe(str);
}

exports.Sort = {
    Numeric: function(a, b) {
        var valA = parseFloat(a.toString().replace(',', ''));
        var valB = parseFloat(b.toString().replace(',', ''));

        // Sort non-numeric values alphabetically at the bottom of the list
        if (isNaN(valA) && isNaN(valB)) {
            valA = a;
            valB = b;
        } else {
            if (isNaN(valA)) {
                return 1;
            }
            if (isNaN(valB)) {
                return -1;
            }
        }

        if (valA < valB) {
            return -1;
        }
        if (valA > valB) {
            return 1;
        }

        return 0;
    },

    Currency: function(a, b) {
        // Parse out dollar signs, then do a regular numeric sort
        // TODO: handle non-American currency

        if (a[0] === '$') {
            a = a.substring(1);
        }
        if (b[0] === '$') {
            b = b.substring(1);
        }

        return exports.Sort.Numeric(a, b);
    },

    Date: function(a, b) {
        // Note: this function tries to do a standard javascript string -> date conversion
        // If you need more control over the date string format, consider using a different
        // date library and writing your own function
        var valA = Date.parse(a);
        var valB = Date.parse(b);

        // Handle non-date values with numeric sort
        // Sort non-numeric values alphabetically at the bottom of the list
        if (isNaN(valA) || isNaN(valB)) {
            return exports.Sort.Numeric(a, b);
        }

        if (valA > valB) {
            return 1;
        }
        if (valB > valA) {
            return -1;
        }

        return 0;
    },

    CaseInsensitive: function(a, b) {
        var valA = a.toLowerCase();
        var valB = b.toLowerCase();

        if(valA > valB) {
            return 1;
        }

        if(valB > valA) {
            return -1;
        }

        return 0;
    },
};

var Td = exports.Td = React.createClass({displayName: 'Td',
    handleClick: function(e){
        if (typeof this.props.handleClick !== 'undefined') {
            return this.props.handleClick(e, this);
        }
    },
    render: function() {
        var tdProps = {
            'data-column': this.props.column.key,
            className: this.props.className,
            onClick: this.handleClick
        }

        // Attach any properties on the column to this Td object to allow things like custom event handlers
        for (var key in this.props.column) {
            if (key !== 'key' && key !== 'name') {
                tdProps[key] = this.props.column[key];
            }
        }

        var data = this.props.data;
        if (
            typeof(this.props.data) === 'undefined' &&
            typeof(this.props.children) === 'string'
        ) {
            data = this.props.children;
        }

        if (typeof(this.props.children) !== 'undefined') {
            if (this.props.children instanceof Unsafe) {
                tdProps.dangerouslySetInnerHTML= { __html: this.props.children.toString() }
            } else {
                tdProps.children = data;
            }
        }

        return React.DOM.td(tdProps);
    }
});

var Tr = exports.Tr = React.createClass({displayName: 'Tr',
    statics: {
        childNode: Td,
        dataType: 'object'
    },
    getDefaultProps: function() {
        var defaultProps = {
            childNode: Td,
            columns: [],
            data: {}
        }

        return defaultProps;
    },
    render: function() {
        var children = this.props.children || [];

        if (
            this.props.data &&
            this.props.columns &&
            typeof this.props.columns.map === 'function'
        ) {
            children = children.concat(this.props.columns.map(function(column, i) {
                if (this.props.data.hasOwnProperty(column.key)) {
                    return Td({column: column, key: column.key}, this.props.data[column.key]);
                } else {
                    return Td({column: column, key: column.key});
                }
            }.bind(this)));
        }

        return this.transferPropsTo(
            React.DOM.tr(null, children)
        );
    }
});

var Thead = exports.Thead = React.createClass({displayName: 'Thead',
    getColumns: function() {
        return React.Children.map(this.props.children, function(th) {
            if (typeof th.props.children === 'string') {
                return th.props.children;
            } else {
                throw new TypeError('<th> must have a string child');
            }
        });
    },
    render: function() {
        return this.transferPropsTo(
            React.DOM.thead(null,
                this.props.filtering === true ?
                    Filterer({
                        colSpan: this.props.columns.length,
                        onFilter: this.props.onFilter}) : '',

                React.DOM.tr({className: "reactable-column-header"},
                    this.props.columns.map(function(column, index) {
                        var sortClass = '';

                        if (this.props.sort.column === column.key) {
                            sortClass = 'reactable-header-sort';
                            if (this.props.sort.direction === 1) {
                                sortClass += '-asc';
                            }
                            else {
                                sortClass += '-desc';
                            }
                        }

                        return (
                            React.DOM.th({
                                className: sortClass,
                                key: index,
                                onClick: function(){ this.props.onSort(column.key) }.bind(this)}, column.label)
                        );
                    }.bind(this))
                )
            )
        );
    }
});

var Th = exports.Th = React.createClass({displayName: 'Th',
    render: function() {
        return this.transferPropsTo(React.DOM.th(null, this.props.children));
    }
});

var FiltererInput = React.createClass({displayName: 'FiltererInput',
    render: function() {
        return (
            React.DOM.input({type: "text", className: "reactable-filter-input",
                onKeyUp: function(){
                    this.props.onFilter(this.getDOMNode().value);
                }.bind(this)})
        );
    },
});

var Filterer = React.createClass({displayName: 'Filterer',
    render: function() {
        if (typeof this.props.colSpan === 'undefined') {
            throw new TypeError('Must pass a colSpan argument to Filterer');
        }

        return (
            React.DOM.tr({className: "reactable-filterer"},
                React.DOM.td({colSpan: this.props.colSpan},
                    FiltererInput({onFilter: this.props.onFilter})
                )
            )
        );
    },
});

var Paginator = React.createClass({displayName: 'Paginator',
    render: function() {
        if (typeof this.props.colSpan === 'undefined') {
            throw new TypeError('Must pass a colSpan argument to Paginator');
        }

        if (typeof this.props.numPages === 'undefined') {
            throw new TypeError('Must pass a non-zero numPages argument to Paginator');
        }

        if (typeof this.props.currentPage === 'undefined') {
            throw new TypeError('Must pass a currentPage argument to Paginator');
        }

        var pageButtons = [];
        for (var i = 0; i < this.props.numPages; i++) {
            var pageNum = i;
            var className = "reactable-page-button";
            if (this.props.currentPage === i) {
                className += " reactable-current-page";
            }

            pageButtons.push(
                React.DOM.a({className: className, key: i,
                   // create function to get around for-loop closure issue
                   onClick: (function(pageNum) {
                       return function() {
                           this.props.onPageChange(pageNum);
                       }.bind(this)
                   }.bind(this))(i)}, i + 1)
            );
        }

        return (
            React.DOM.tbody({className: "reactable-pagination"},
                React.DOM.tr(null,
                    React.DOM.td({colSpan: this.props.colSpan},
                        pageButtons
                    )
                )
            )
        );
    }
});

var Table = exports.Table = React.createClass({displayName: 'Table',
    // Translate a user defined column array to hold column objects if strings are specified
    // (e.g. ['column1'] => [{key: 'column1', label: 'column1'}])
    translateColumnsArray: function(columns) {
        return columns.map(function(column, i) {
            if (typeof(column) === 'string') {
                return {
                    key:   column,
                    label: column
                };
            } else {
                if (typeof(column.sortable) !== 'undefined') {
                    var sortFunction = column.sortable === true ? 'default' : column.sortable;
                    this._sortable[column.key] = sortFunction;
                }

                return column;
            }
        }.bind(this));
    },
    parseChildData: function(props) {
        var data = [];

        // Transform any children back to a data array
        if (typeof(props.children) !== 'undefined') {
            React.Children.forEach(props.children, function(child) {
                // TODO: figure out a new way to determine the type of a component
                /*
                if (child.type.ConvenienceConstructor !== Tr) {
                    return; // (continue)
                }
                */

                var childData = child.props.data || {};

                // Given our modification of Array.prototype, this is the
                // best way to check if child.props.children is an array
                if (
                    typeof(child.props.children) !== 'undefined' &&
                    typeof(child.props.children.map) === 'function'
                ) {

                    var childChildren = child.props.children;
                    for (var i = 0; i < childChildren.length; i++) {
                        var descendant = childChildren[i];
                        // TODO
                        /* if (descendant.type.ConvenienceConstructor === Td) { */
                        if (true) {
                            if (typeof(descendant.props.column) !== 'undefined') {
                                if (typeof(descendant.props.data) !== 'undefined') {

                                    childData[descendant.props.column] =
                                        descendant.props.data;
                                } else if (
                                    typeof(descendant.props.children) !== 'undefined'
                                ) {
                                    childData[descendant.props.column] =
                                        descendant.props.children;
                                } else {
                                    console.warn('exports.Td specified without ' +
                                            'a `data` property or children, ' +
                                            'ignoring');
                                }
                            } else {
                                console.warn('exports.Td specified without a ' +
                                        '`column` property, ignoring');
                            }
                        }
                    }
                }

                data.push(childData);
            }.bind(this));
        }

        return data;
    },
    initialize: function(props) {
        this.data = props.data || [];
        this.data = this.data.concat(this.parseChildData(props));
        this.initializeSorts(props);
    },
    initializeSorts: function() {
        this._sortable = {};
        // Transform sortable properties into a more friendly list
        for (var i in this.props.sortable) {
            var column = this.props.sortable[i];
            var columnName, sortFunction;

            if (column instanceof Object) {
                if (typeof(column.column) !== 'undefined') {
                    columnName = column.column;
                } else {
                    console.warn('Sortable column specified without column name');
                    return;
                }

                if (typeof(column.sortFunction) !== 'undefined' && column.sortFunction instanceof Function) {
                    sortFunction = column.sortFunction;
                } else {
                    sortFunction = 'default';
                }
            } else {
                columnName      = column;
                sortFunction    = 'default';
            }

            this._sortable[columnName] = sortFunction;
        }
    },
    getDefaultProps: function() {
        var defaultProps = {
            columns: [],
            sortable: [],
            filterable: [],
            defaultSort: false,
            itemsPerPage: 0,
        }
        return defaultProps;
    },
    getInitialState: function() {
        var initialState = {
            currentPage: 0,
            currentSort: {
                column: null,
                direction: 1,
            },
            filter: '',
        }

        // Set the state of the current sort to the default sort
        if (this.props.defaultSort !== false) {
            var column = this.props.defaultSort;
            var currentSort = {};

            if (column instanceof Object) {
                var columnName, sortDirection;

                if (typeof(column.column) !== 'undefined') {
                    columnName = column.column;
                } else {
                    console.warn('Default column specified without column name');
                    return;
                }

                if (typeof(column.direction) !== 'undefined') {
                    if (column.direction === 1 || column.direction === 'asc') {
                        sortDirection = 1;
                    } else if (column.direction === -1 || column.direction === 'desc') {
                        sortDirection = -1;
                    } else {
                        console.warn('Invalid default sort specified.  Defaulting to ascending');
                        sortDirection = 1;
                    }
                } else {
                    sortFunction = 1;
                }
            } else {
                columnName      = column;
                sortDirection   = 1;
            }

            initialState.currentSort = {
                column: columnName,
                direction: sortDirection
            };
        }

        return initialState;
    },
    componentWillMount: function() {
        this.initialize(this.props);
        this.sortByCurrentSort();
    },
    componentWillReceiveProps: function(nextProps) {
        this.initialize(nextProps);
        this.sortByCurrentSort();
    },
    onPageChange: function(page) {
        this.setState({ currentPage: page });
    },
    onFilter: function(filter) {
        this.setState({ filter: filter });
    },
    applyFilter: function(filter, children) {
        // Helper function to apply filter text to a list of table rows
        var filter = filter.toLowerCase();
        var matchedChildren = [];

        for (var i = 0; i < children.length; i++) {
            var data = children[i].props.data;

            for (var j = 0; j < this.props.filterable.length; j++) {
                var filterColumn = this.props.filterable[j];

                if (
                    typeof(data[filterColumn]) !== 'undefined' &&
                    data[filterColumn].toString().toLowerCase().indexOf(filter) > -1
                ) {
                    matchedChildren.push(children[i]);
                    break;
                }
            }
        }

        return matchedChildren;
    },
    sortByCurrentSort: function(){
        // Apply a sort function according to the current sort in the state.
        // This allows us to perform a default sort even on a non sortable column.
        var currentSort = this.state.currentSort;

        if (currentSort.column === null) {
            return;
        }

        this.data.sort(function(a, b){
            var keyA = a[currentSort.column];
            keyA = keyA ? keyA.toString() : '';
            var keyB = b[currentSort.column];
            keyB = keyB ? keyB.toString() : '';

            // Default sort
            if (this._sortable[currentSort.column] === 'default') {

                // Reverse direction if we're doing a reverse sort
                if (keyA < keyB) {
                    return -1 * currentSort.direction;
                }

                if (keyA > keyB) {
                    return 1 * currentSort.direction;
                }

                return 0;
            } else {
                // Reverse columns if we're doing a reverse sort
                if (currentSort.direction === 1) {
                    return this._sortable[currentSort.column](keyA, keyB);
                } else {
                    return this._sortable[currentSort.column](keyB, keyA);
                }
            }
        }.bind(this));
    },
    onSort: function(column) {
        // Don't perform sort on unsortable columns
        if (typeof(this._sortable[column]) === 'undefined') {
            return;
        }

        var currentSort = this.state.currentSort;

        if (currentSort.column === column) {
            currentSort.direction *= -1;
        } else {
            currentSort.column = column;
            currentSort.direction = 1;
        }

        // Set the current sort and pass it to the sort function
        this.setState({ currentSort: currentSort });
        this.sortByCurrentSort();
    },
    render: function() {
        var children = [];
        var columns;
        var userColumnsSpecified = false;

        if (
            this.props.children &&
            this.props.children.length > 0 &&
            this.props.children[0].type.ConvenienceConstructor === Thead
        ) {
            columns = this.props.children[0].getColumns();
        } else {
            columns = this.props.columns || [];
        }

        if (columns.length > 0) {
            userColumnsSpecified = true;
            columns = this.translateColumnsArray(columns);
        }

        // Build up table rows
        if (this.data && typeof this.data.map === 'function') {
            // Build up the columns array
            children = children.concat(this.data.map(function(data, i) {
                // Loop through the keys in each data row and build a td for it
                for (var k in data) {
                    if (data.hasOwnProperty(k)) {
                        // Update the columns array with the data's keys if columns were not
                        // already specified
                        if (userColumnsSpecified === false) {
                            var column = {
                                key:   k,
                                label: k
                            };

                            // Only add a new column if it doesn't already exist in the columns array
                            if (
                                columns.find(function(element) {
                                    return element.key === column.key
                                }) === undefined
                            ) {
                                columns.push(column);
                            }
                        }
                    }
                }

                return (
                    Tr({columns: columns, key: i, data: data})
                );
            }.bind(this)));
        }

        if (this.props.sortable === true) {
            for (var i = 0; i < columns.length; i++) {
                this._sortable[columns[i].key] = 'default';
            }
        }

        // Determine if we render the filter box
        var filtering = false;
        if (
            this.props.filterable &&
            Array.isArray(this.props.filterable) &&
            this.props.filterable.length > 0
        ) {
            filtering = true;
        }

        // Apply filters
        var filteredChildren = children;
        if (this.state.filter !== '') {
            filteredChildren = this.applyFilter(this.state.filter, filteredChildren);
        }

        // Determine pagination properties and which columns to display
        var itemsPerPage = 0;
        var pagination = false;
        var numPages;
        var currentPage = this.state.currentPage;

        var currentChildren = filteredChildren;
        if (this.props.itemsPerPage > 0) {
            itemsPerPage = this.props.itemsPerPage;
            numPages = Math.ceil(filteredChildren.length / itemsPerPage)

            if (currentPage > numPages - 1) {
                currentPage = numPages - 1;
            }

            pagination = true;
            currentChildren = filteredChildren.slice(
                currentPage * itemsPerPage,
                (currentPage + 1) * itemsPerPage
            );
        }

        return this.transferPropsTo(
            React.DOM.table(null,
                columns && columns.length > 0 ?
                    Thead({
                        columns: columns,
                        filtering: filtering,
                        onFilter: this.onFilter,
                        sort: this.state.currentSort,
                        onSort: this.onSort})
                    : '',

                React.DOM.tbody({className: "reactable-data"},
                    currentChildren
                ),
                pagination === true ?
                    Paginator({
                        colSpan: columns.length,
                        numPages: numPages,
                        currentPage: currentPage,
                        onPageChange: this.onPageChange}) : ''

            )
        );
    }
});

},{}],2:[function(require,module,exports){
Reactable = require('../build/reactable.js');

},{"../build/reactable.js":1}]},{},[2]);
<commit_msg>Remove unneeded file<commit_after><|endoftext|>