<commit_before>dc.coordinateGridChart = function(_chart) {
    var DEFAULT_Y_AXIS_TICKS = 5;
    var GRID_LINE_CLASS = "grid-line";
    var HORIZONTAL_CLASS = "horizontal";
    var VERTICAL_CLASS = "vertical";

    _chart = dc.baseChart(_chart);

    var _margin = {top: 10, right: 50, bottom: 30, left: 30};

    var _g;

    var _x;
    var _xAxis = d3.svg.axis();
    var _xUnits = dc.units.integers;
    var _xAxisPadding = 0;
    var _xElasticity = false;

    var _y;
    var _yAxis = d3.svg.axis();
    var _yAxisPadding = 0;
    var _yElasticity = false;

    var _filter;
    var _brush = d3.svg.brush();
    var _brushOn = true;
    var _round;

    var _renderHorizontalGridLine = false;
    var _renderVerticalGridLine = false;

    _chart.generateG = function(parent) {
        if (parent == null)
            parent = _chart.svg();

        _g = parent.append("g");
        return _g;
    };

    _chart.g = function(_) {
        if (!arguments.length) return _g;
        _g = _;
        return _chart;
    };

    _chart.margins = function(m) {
        if (!arguments.length) return _margin;
        _margin = m;
        return _chart;
    };

    _chart.x = function(_) {
        if (!arguments.length) return _x;
        _x = _;
        return _chart;
    };

    _chart.xAxis = function(_) {
        if (!arguments.length) return _xAxis;
        _xAxis = _;
        return _chart;
    };

    function prepareXAxis(g) {
        if (_chart.elasticX()) {
            _x.domain([_chart.xAxisMin(), _chart.xAxisMax()]);
        }

        _x.range([0, _chart.xAxisLength()]);
        _xAxis = _xAxis.scale(_chart.x()).orient("bottom");

        renderVerticalGridLines(g);
    }

    _chart.renderXAxis = function(g) {
        var axisXG = g.selectAll("g.x");

        if (axisXG.empty())
            axisXG = g.append("g")
                .attr("class", "axis x")
                .attr("transform", "translate(" + _chart.margins().left + "," + _chart.xAxisY() + ")");

        axisXG.call(_xAxis);
    };

    function renderVerticalGridLines(g) {
        if (_renderVerticalGridLine) {
            var gridLineG = g.selectAll("g." + VERTICAL_CLASS);

            if (gridLineG.empty())
                gridLineG = g.insert("g", ":first-child")
                    .attr("class", GRID_LINE_CLASS + " " + VERTICAL_CLASS)
                    .attr("transform", "translate(" + _chart.yAxisX() + "," + _chart.margins().top + ")");

            var ticks = _xAxis.tickValues() ? _xAxis.tickValues() : _x.ticks(_xAxis.ticks()[0]);

            var lines = gridLineG.selectAll("line")
                .data(ticks);

            // enter
            lines.enter()
                .append("line")
                .attr("x1", function(d) {
                    return _x(d);
                })
                .attr("y1", _chart.xAxisY() - _chart.margins().top)
                .attr("x2", function(d) {
                    return _x(d);
                })
                .attr("y2", 0);

            // update
            lines.attr("x1", function(d) {
                return _x(d);
            })
                .attr("y1", _chart.xAxisY() - _chart.margins().top)
                .attr("x2", function(d) {
                    return _x(d);
                })
                .attr("y2", 0);

            // exit
            lines.exit().remove();
        }
    }

    _chart.xAxisY = function() {
        return (_chart.height() - _chart.margins().bottom);
    };

    _chart.xAxisLength = function() {
        return _chart.width() - _chart.margins().left - _chart.margins().right;
    };

    _chart.xUnits = function(_) {
        if (!arguments.length) return _xUnits;
        _xUnits = _;
        return _chart;
    };

    function prepareYAxis(g) {
        if (_y == null || _chart.elasticY()) {
            _y = d3.scale.linear();
            _y.domain([_chart.yAxisMin(), _chart.yAxisMax()]).rangeRound([_chart.yAxisHeight(), 0]);
        }

        _y.range([_chart.yAxisHeight(), 0]);
        _yAxis = _yAxis.scale(_y).orient("left").ticks(DEFAULT_Y_AXIS_TICKS);

        renderHorizontalGridLines(g);
    }

    _chart.renderYAxis = function(g) {
        var axisYG = g.selectAll("g.y");
        if (axisYG.empty())
            axisYG = g.append("g")
                .attr("class", "axis y")
                .attr("transform", "translate(" + _chart.yAxisX() + "," + _chart.margins().top + ")");

        axisYG.call(_yAxis);
    };

    function renderHorizontalGridLines(g) {
        if (_renderHorizontalGridLine) {
            var gridLineG = g.selectAll("g." + HORIZONTAL_CLASS);

            var ticks = _yAxis.tickValues() ? _yAxis.tickValues() : _y.ticks(_yAxis.ticks()[0]);

            if (gridLineG.empty())
                gridLineG = g.insert("g", ":first-child")
                    .attr("class", GRID_LINE_CLASS + " " + HORIZONTAL_CLASS)
                    .attr("transform", "translate(" + _chart.yAxisX() + "," + _chart.margins().top + ")");

            var lines = gridLineG.selectAll("line")
                .data(ticks);

            // enter
            lines.enter()
                .append("line")
                .attr("x1", 1)
                .attr("y1", function(d) {
                    return _y(d);
                })
                .attr("x2", _chart.xAxisLength())
                .attr("y2", function(d) {
                    return _y(d);
                });

            // update
            lines.attr("x1", 1)
                .attr("y1", function(d) {
                    return _y(d);
                })
                .attr("x2", _chart.xAxisLength())
                .attr("y2", function(d) {
                    return _y(d);
                });

            // exit
            lines.exit().remove();
        }
    }

    _chart.renderHorizontalGridLines = function(_) {
        if (!arguments.length) return _renderHorizontalGridLine;
        _renderHorizontalGridLine = _;
        return _chart;
    };

    _chart.renderVerticalGridLines = function(_) {
        if (!arguments.length) return _renderVerticalGridLine;
        _renderVerticalGridLine = _;
        return _chart;
    };

    _chart.yAxisX = function() {
        return _chart.margins().left;
    };

    _chart.y = function(_) {
        if (!arguments.length) return _y;
        _y = _;
        return _chart;
    };

    _chart.yAxis = function(y) {
        if (!arguments.length) return _yAxis;
        _yAxis = y;
        return _chart;
    };

    _chart.elasticY = function(_) {
        if (!arguments.length) return _yElasticity;
        _yElasticity = _;
        return _chart;
    };

    _chart.elasticX = function(_) {
        if (!arguments.length) return _xElasticity;
        _xElasticity = _;
        return _chart;
    };

    _chart.xAxisMin = function() {
        var min = d3.min(_chart.group().all(), function(e) {
            return _chart.keyAccessor()(e);
        });
        return dc.utils.subtract(min, _xAxisPadding);
    };

    _chart.xAxisMax = function() {
        var max = d3.max(_chart.group().all(), function(e) {
            return _chart.keyAccessor()(e);
        });
        return dc.utils.add(max, _xAxisPadding);
    };

    _chart.yAxisMin = function() {
        var min = d3.min(_chart.group().all(), function(e) {
            return _chart.valueAccessor()(e);
        }) - _yAxisPadding;
        return min;
    };

    _chart.yAxisMax = function() {
        var max = d3.max(_chart.group().all(), function(e) {
            return _chart.valueAccessor()(e);
        });
        return dc.utils.add(max, _yAxisPadding);
    };

    _chart.xAxisPadding = function(_) {
        if (!arguments.length) return _xAxisPadding;
        _xAxisPadding = _;
        return _chart;
    };

    _chart.yAxisPadding = function(_) {
        if (!arguments.length) return _yAxisPadding;
        _yAxisPadding = _;
        return _chart;
    };

    _chart.yAxisHeight = function() {
        return _chart.height() - _chart.margins().top - _chart.margins().bottom;
    };

    _chart.round = function(_) {
        if (!arguments.length) return _round;
        _round = _;
        return _chart;
    };

    _chart.filter = function(_) {
        if (!arguments.length) return _filter;

        if (_) {
            _filter = _;
            _chart.brush().extent(_);
            _chart.dimension().filterRange(_);
            _chart.turnOnControls();
        } else {
            _filter = null;
            _chart.brush().clear();
            _chart.dimension().filterAll();
            _chart.turnOffControls();
        }

        return _chart;
    };

    _chart.brush = function(_) {
        if (!arguments.length) return _brush;
        _brush = _;
        return _chart;
    };

    function brushHeight() {
        return _chart.xAxisY() - _chart.margins().top;
    }

    _chart.renderBrush = function(g) {
        if (_brushOn) {
            _brush.on("brushstart", brushStart)
                .on("brush", brushing)
                .on("brushend", brushEnd);

            var gBrush = g.append("g")
                .attr("class", "brush")
                .attr("transform", "translate(" + _chart.margins().left + "," + _chart.margins().top + ")")
                .call(_brush.x(_chart.x()));
            gBrush.selectAll("rect").attr("height", brushHeight());
            gBrush.selectAll(".resize").append("path").attr("d", _chart.resizeHandlePath);

            if (_filter) {
                _chart.redrawBrush(g);
            }
        }
    };

    function brushStart(p) {
    }

    function brushing(p) {
        var extent = _brush.extent();
        if (_chart.round()) {
            extent[0] = extent.map(_chart.round())[0];
            extent[1] = extent.map(_chart.round())[1];
            _g.select(".brush")
                .call(_brush.extent(extent));
        }
        extent = _brush.extent();

        _chart.redrawBrush(_g);

        if(_brush.empty()){
            dc.events.trigger(function() {
                _chart.filter(null);
                dc.redrawAll(_chart.chartGroup());
            });
        }else{
            dc.events.trigger(function() {
                _chart.filter([extent[0], extent[1]]);
                dc.redrawAll(_chart.chartGroup());
            }, dc.constants.EVENT_DELAY);
        }
    }

    function brushEnd(p) {
    }

    _chart.redrawBrush = function(g) {
        if (_brushOn) {
            if (_chart.filter() && _chart.brush().empty())
                _chart.brush().extent(_chart.filter());

            var gBrush = g.select("g.brush");
            gBrush.call(_chart.brush().x(_chart.x()));
            gBrush.selectAll("rect").attr("height", brushHeight());

            _chart.fadeDeselectedArea();
        }
    };

    _chart.fadeDeselectedArea = function() {
        // do nothing, sub-chart should override this function
    };

    // borrowed from Crossfilter example
    _chart.resizeHandlePath = function(d) {
        var e = +(d == "e"), x = e ? 1 : -1, y = brushHeight() / 3;
        return "M" + (.5 * x) + "," + y
            + "A6,6 0 0 " + e + " " + (6.5 * x) + "," + (y + 6)
            + "V" + (2 * y - 6)
            + "A6,6 0 0 " + e + " " + (.5 * x) + "," + (2 * y)
            + "Z"
            + "M" + (2.5 * x) + "," + (y + 8)
            + "V" + (2 * y - 8)
            + "M" + (4.5 * x) + "," + (y + 8)
            + "V" + (2 * y - 8);
    };

    _chart.doRender = function() {
        if(_x == null)
            throw new dc.errors.InvalidStateException("Mandatory attribute chart.x is missing on chart["
                + _chart.anchor() + "]");

        _chart.resetSvg();

        if (_chart.dataAreSet()) {
            _chart.generateG();

            prepareXAxis(_chart.g());
            prepareYAxis(_chart.g());

            _chart.plotData();

            _chart.renderXAxis(_chart.g());
            _chart.renderYAxis(_chart.g());

            _chart.renderBrush(_chart.g());
        }

        return _chart;
    };

    _chart.doRedraw = function() {
        prepareXAxis(_chart.g());
        prepareYAxis(_chart.g());

        _chart.plotData();

        if (_chart.elasticY())
            _chart.renderYAxis(_chart.g());

        if (_chart.elasticX())
            _chart.renderXAxis(_chart.g());

        _chart.redrawBrush(_chart.g());

        return _chart;
    };

    _chart.subRender = function() {
        if (_chart.dataAreSet()) {
            _chart.plotData();
        }

        return _chart;
    };

    _chart.brushOn = function(_) {
        if (!arguments.length) return _brushOn;
        _brushOn = _;
        return _chart;
    };

    return _chart;
};
<commit_msg>[#77] Extended brushing logic to check for empty extent<commit_after>dc.coordinateGridChart = function(_chart) {
    var DEFAULT_Y_AXIS_TICKS = 5;
    var GRID_LINE_CLASS = "grid-line";
    var HORIZONTAL_CLASS = "horizontal";
    var VERTICAL_CLASS = "vertical";

    _chart = dc.baseChart(_chart);

    var _margin = {top: 10, right: 50, bottom: 30, left: 30};

    var _g;

    var _x;
    var _xAxis = d3.svg.axis();
    var _xUnits = dc.units.integers;
    var _xAxisPadding = 0;
    var _xElasticity = false;

    var _y;
    var _yAxis = d3.svg.axis();
    var _yAxisPadding = 0;
    var _yElasticity = false;

    var _filter;
    var _brush = d3.svg.brush();
    var _brushOn = true;
    var _round;

    var _renderHorizontalGridLine = false;
    var _renderVerticalGridLine = false;

    _chart.generateG = function(parent) {
        if (parent == null)
            parent = _chart.svg();

        _g = parent.append("g");
        return _g;
    };

    _chart.g = function(_) {
        if (!arguments.length) return _g;
        _g = _;
        return _chart;
    };

    _chart.margins = function(m) {
        if (!arguments.length) return _margin;
        _margin = m;
        return _chart;
    };

    _chart.x = function(_) {
        if (!arguments.length) return _x;
        _x = _;
        return _chart;
    };

    _chart.xAxis = function(_) {
        if (!arguments.length) return _xAxis;
        _xAxis = _;
        return _chart;
    };

    function prepareXAxis(g) {
        if (_chart.elasticX()) {
            _x.domain([_chart.xAxisMin(), _chart.xAxisMax()]);
        }

        _x.range([0, _chart.xAxisLength()]);
        _xAxis = _xAxis.scale(_chart.x()).orient("bottom");

        renderVerticalGridLines(g);
    }

    _chart.renderXAxis = function(g) {
        var axisXG = g.selectAll("g.x");

        if (axisXG.empty())
            axisXG = g.append("g")
                .attr("class", "axis x")
                .attr("transform", "translate(" + _chart.margins().left + "," + _chart.xAxisY() + ")");

        axisXG.call(_xAxis);
    };

    function renderVerticalGridLines(g) {
        if (_renderVerticalGridLine) {
            var gridLineG = g.selectAll("g." + VERTICAL_CLASS);

            if (gridLineG.empty())
                gridLineG = g.insert("g", ":first-child")
                    .attr("class", GRID_LINE_CLASS + " " + VERTICAL_CLASS)
                    .attr("transform", "translate(" + _chart.yAxisX() + "," + _chart.margins().top + ")");

            var ticks = _xAxis.tickValues() ? _xAxis.tickValues() : _x.ticks(_xAxis.ticks()[0]);

            var lines = gridLineG.selectAll("line")
                .data(ticks);

            // enter
            lines.enter()
                .append("line")
                .attr("x1", function(d) {
                    return _x(d);
                })
                .attr("y1", _chart.xAxisY() - _chart.margins().top)
                .attr("x2", function(d) {
                    return _x(d);
                })
                .attr("y2", 0);

            // update
            lines.attr("x1", function(d) {
                return _x(d);
            })
                .attr("y1", _chart.xAxisY() - _chart.margins().top)
                .attr("x2", function(d) {
                    return _x(d);
                })
                .attr("y2", 0);

            // exit
            lines.exit().remove();
        }
    }

    _chart.xAxisY = function() {
        return (_chart.height() - _chart.margins().bottom);
    };

    _chart.xAxisLength = function() {
        return _chart.width() - _chart.margins().left - _chart.margins().right;
    };

    _chart.xUnits = function(_) {
        if (!arguments.length) return _xUnits;
        _xUnits = _;
        return _chart;
    };

    function prepareYAxis(g) {
        if (_y == null || _chart.elasticY()) {
            _y = d3.scale.linear();
            _y.domain([_chart.yAxisMin(), _chart.yAxisMax()]).rangeRound([_chart.yAxisHeight(), 0]);
        }

        _y.range([_chart.yAxisHeight(), 0]);
        _yAxis = _yAxis.scale(_y).orient("left").ticks(DEFAULT_Y_AXIS_TICKS);

        renderHorizontalGridLines(g);
    }

    _chart.renderYAxis = function(g) {
        var axisYG = g.selectAll("g.y");
        if (axisYG.empty())
            axisYG = g.append("g")
                .attr("class", "axis y")
                .attr("transform", "translate(" + _chart.yAxisX() + "," + _chart.margins().top + ")");

        axisYG.call(_yAxis);
    };

    function renderHorizontalGridLines(g) {
        if (_renderHorizontalGridLine) {
            var gridLineG = g.selectAll("g." + HORIZONTAL_CLASS);

            var ticks = _yAxis.tickValues() ? _yAxis.tickValues() : _y.ticks(_yAxis.ticks()[0]);

            if (gridLineG.empty())
                gridLineG = g.insert("g", ":first-child")
                    .attr("class", GRID_LINE_CLASS + " " + HORIZONTAL_CLASS)
                    .attr("transform", "translate(" + _chart.yAxisX() + "," + _chart.margins().top + ")");

            var lines = gridLineG.selectAll("line")
                .data(ticks);

            // enter
            lines.enter()
                .append("line")
                .attr("x1", 1)
                .attr("y1", function(d) {
                    return _y(d);
                })
                .attr("x2", _chart.xAxisLength())
                .attr("y2", function(d) {
                    return _y(d);
                });

            // update
            lines.attr("x1", 1)
                .attr("y1", function(d) {
                    return _y(d);
                })
                .attr("x2", _chart.xAxisLength())
                .attr("y2", function(d) {
                    return _y(d);
                });

            // exit
            lines.exit().remove();
        }
    }

    _chart.renderHorizontalGridLines = function(_) {
        if (!arguments.length) return _renderHorizontalGridLine;
        _renderHorizontalGridLine = _;
        return _chart;
    };

    _chart.renderVerticalGridLines = function(_) {
        if (!arguments.length) return _renderVerticalGridLine;
        _renderVerticalGridLine = _;
        return _chart;
    };

    _chart.yAxisX = function() {
        return _chart.margins().left;
    };

    _chart.y = function(_) {
        if (!arguments.length) return _y;
        _y = _;
        return _chart;
    };

    _chart.yAxis = function(y) {
        if (!arguments.length) return _yAxis;
        _yAxis = y;
        return _chart;
    };

    _chart.elasticY = function(_) {
        if (!arguments.length) return _yElasticity;
        _yElasticity = _;
        return _chart;
    };

    _chart.elasticX = function(_) {
        if (!arguments.length) return _xElasticity;
        _xElasticity = _;
        return _chart;
    };

    _chart.xAxisMin = function() {
        var min = d3.min(_chart.group().all(), function(e) {
            return _chart.keyAccessor()(e);
        });
        return dc.utils.subtract(min, _xAxisPadding);
    };

    _chart.xAxisMax = function() {
        var max = d3.max(_chart.group().all(), function(e) {
            return _chart.keyAccessor()(e);
        });
        return dc.utils.add(max, _xAxisPadding);
    };

    _chart.yAxisMin = function() {
        var min = d3.min(_chart.group().all(), function(e) {
            return _chart.valueAccessor()(e);
        }) - _yAxisPadding;
        return min;
    };

    _chart.yAxisMax = function() {
        var max = d3.max(_chart.group().all(), function(e) {
            return _chart.valueAccessor()(e);
        });
        return dc.utils.add(max, _yAxisPadding);
    };

    _chart.xAxisPadding = function(_) {
        if (!arguments.length) return _xAxisPadding;
        _xAxisPadding = _;
        return _chart;
    };

    _chart.yAxisPadding = function(_) {
        if (!arguments.length) return _yAxisPadding;
        _yAxisPadding = _;
        return _chart;
    };

    _chart.yAxisHeight = function() {
        return _chart.height() - _chart.margins().top - _chart.margins().bottom;
    };

    _chart.round = function(_) {
        if (!arguments.length) return _round;
        _round = _;
        return _chart;
    };

    _chart.filter = function(_) {
        if (!arguments.length) return _filter;

        if (_) {
            _filter = _;
            _chart.brush().extent(_);
            _chart.dimension().filterRange(_);
            _chart.turnOnControls();
        } else {
            _filter = null;
            _chart.brush().clear();
            _chart.dimension().filterAll();
            _chart.turnOffControls();
        }

        return _chart;
    };

    _chart.brush = function(_) {
        if (!arguments.length) return _brush;
        _brush = _;
        return _chart;
    };

    function brushHeight() {
        return _chart.xAxisY() - _chart.margins().top;
    }

    _chart.renderBrush = function(g) {
        if (_brushOn) {
            _brush.on("brushstart", brushStart)
                .on("brush", brushing)
                .on("brushend", brushEnd);

            var gBrush = g.append("g")
                .attr("class", "brush")
                .attr("transform", "translate(" + _chart.margins().left + "," + _chart.margins().top + ")")
                .call(_brush.x(_chart.x()));
            gBrush.selectAll("rect").attr("height", brushHeight());
            gBrush.selectAll(".resize").append("path").attr("d", _chart.resizeHandlePath);

            if (_filter) {
                _chart.redrawBrush(g);
            }
        }
    };

    function brushStart(p) {
    }

    function brushing(p) {
        var extent = _brush.extent();
        if (_chart.round()) {
            extent[0] = extent.map(_chart.round())[0];
            extent[1] = extent.map(_chart.round())[1];
            _g.select(".brush")
                .call(_brush.extent(extent));
        }
        extent = _brush.extent();

        _chart.redrawBrush(_g);

        if(_brush.empty() || !extent || extent[1] <= extent[0]){
            dc.events.trigger(function() {
                _chart.filter(null);
                dc.redrawAll(_chart.chartGroup());
            });
        }else{
            dc.events.trigger(function() {
                _chart.filter([extent[0], extent[1]]);
                dc.redrawAll(_chart.chartGroup());
            }, dc.constants.EVENT_DELAY);
        }
    }

    function brushEnd(p) {
    }

    _chart.redrawBrush = function(g) {
        if (_brushOn) {
            if (_chart.filter() && _chart.brush().empty())
                _chart.brush().extent(_chart.filter());

            var gBrush = g.select("g.brush");
            gBrush.call(_chart.brush().x(_chart.x()));
            gBrush.selectAll("rect").attr("height", brushHeight());

            _chart.fadeDeselectedArea();
        }
    };

    _chart.fadeDeselectedArea = function() {
        // do nothing, sub-chart should override this function
    };

    // borrowed from Crossfilter example
    _chart.resizeHandlePath = function(d) {
        var e = +(d == "e"), x = e ? 1 : -1, y = brushHeight() / 3;
        return "M" + (.5 * x) + "," + y
            + "A6,6 0 0 " + e + " " + (6.5 * x) + "," + (y + 6)
            + "V" + (2 * y - 6)
            + "A6,6 0 0 " + e + " " + (.5 * x) + "," + (2 * y)
            + "Z"
            + "M" + (2.5 * x) + "," + (y + 8)
            + "V" + (2 * y - 8)
            + "M" + (4.5 * x) + "," + (y + 8)
            + "V" + (2 * y - 8);
    };

    _chart.doRender = function() {
        if(_x == null)
            throw new dc.errors.InvalidStateException("Mandatory attribute chart.x is missing on chart["
                + _chart.anchor() + "]");

        _chart.resetSvg();

        if (_chart.dataAreSet()) {
            _chart.generateG();

            prepareXAxis(_chart.g());
            prepareYAxis(_chart.g());

            _chart.plotData();

            _chart.renderXAxis(_chart.g());
            _chart.renderYAxis(_chart.g());

            _chart.renderBrush(_chart.g());
        }

        return _chart;
    };

    _chart.doRedraw = function() {
        prepareXAxis(_chart.g());
        prepareYAxis(_chart.g());

        _chart.plotData();

        if (_chart.elasticY())
            _chart.renderYAxis(_chart.g());

        if (_chart.elasticX())
            _chart.renderXAxis(_chart.g());

        _chart.redrawBrush(_chart.g());

        return _chart;
    };

    _chart.subRender = function() {
        if (_chart.dataAreSet()) {
            _chart.plotData();
        }

        return _chart;
    };

    _chart.brushOn = function(_) {
        if (!arguments.length) return _brushOn;
        _brushOn = _;
        return _chart;
    };

    return _chart;
};
<|endoftext|>


<commit_before>/**
 * A Gulp task for running specs using Karma.
 *
 * To use this in a gulpfile.js:
 *
 *    import SpecTask from 'carbon-factory/lib/gulp/spec';
 *    gulp.task('test', SpecTask());
 *
 * You can also pass a hash of options to customise the task:
 *
 *    import SpecTask from 'carbon-factory/lib/gulp/spec';
 *
 *    var opts = {
 *      path: "/src/*.js",
 *      specs: "/src/*.spec.js",
 *      preProcessors: [ 'eslint', 'browserify' ],
 *      specPreProcessors: [ 'browserify' ],
 *      ignoreCoverage: [ '/path/to/ignore' ],
 *      reporters: ['progress'],
 *      coverageReporters: [{ type: 'text-summary' }, { type: 'html' }],
 *      coverage: {
 *        statements: 100,
 *        branches: 100,
 *        functions: 100,
 *        lines: 100
 *      },
 *      configFile: 'karma.conf.js'
 *    }
 *
 *    gulp.task('test', SpecTask(opts));
 *
 * If you have setup the task using the task name 'test' then you can run it
 * through the command line like this:
 *
 *    gulp test
 *
 * By default this will automatically run in PhantomJS and will watch for any
 * changes. You can supply additional arguments to run in alternative modes:
 *
 * == To run in watch mode with coverage reports:
 *
 *    gulp test --coverage
 *
 * == To perform a single run with coverage:
 *
 *    gulp test --build
 *
 * == To run the tests in a specific browser:
 *
 *    gulp test -b chrome
 *
 * == To run the test across all browsers:
 *
 *    gulp test -b all
 */

import './utils/fs-patch';
import gutil from 'gulp-util';
import S from 'string';
import yargs from 'yargs';
import karma from 'karma';
import istanbul from 'browserify-istanbul';
import babelify from 'babelify';
import fs from 'fs';

var argv = yargs.argv;
var Server = karma.Server;

export default function(opts) {
  var options = opts;

  return function(done) {
    var opts = options || {};
    // the js files to test (everything except __spec__ files)
    var path = opts.path || '/src/***/**/!(__spec__).js';
    // the specs
    var specs = opts.specs || '/src/***/**/__spec__.js';
    // an array of paths to ignore from coverage reports
    var ignoreCoverage = opts.ignoreCoverage || [ '**/node_modules/**', '**/__spec__.js' ];
    // an array to specify what kind of reporters should karma generate
    var reporters = opts.reporters || ['progress'];
    // an array to specify what kind of reporters type should karma generate
    var coverageReporters = opts.coverageReporters || [{ type: 'text-summary' }, { type: 'html' }];
    // which preprocessors the js files should run through
    var preProcessors = opts.preProcessors || [ 'browserify' ];
    // which preprocessors the spec files should run through
    var specpreProcessors = opts.specpreProcessors || [ 'browserify' ];
    // where to find the karma config file
    var configFile = opts.configFile || __dirname + '/karma.conf.js';
    // defaults the coverage thresholds
    var coverageThreshold = Object.assign({}, {
      statements: 100,
      branches: 100,
      functions: 100,
      lines: 100
    }, opts.coverage);
    // eslintThreshold for failing build
    var eslintThreshold = opts.eslintThreshold || null;
    // Stop build if above eslintThreshold
    var stopAboveEslintThreshold = !!eslintThreshold;
    // coverage thresholds for each file
    var coverageThresholdEachFile = opts.coverageEachFile || {};
    // where the gulp task was ran from
    var originPath = process.cwd();

    // check if eslintrc file exists, if not then prompt dev to create one
    fs.stat(originPath + '/.eslintrc', function(err, stat) {
      if (err == null) { return }
      if (err.code == 'ENOENT') {
        gutil.log(gutil.colors.red("Cannot find '.eslintrc' file"));
        gutil.log(gutil.colors.white("Create an '.eslintrc' file in the root of your project and add the following code:\n{\n  \"extends\": \"./node_modules/carbon-factory/.eslintrc\"\n}"));
        process.exit();
      }
    });

    if (!argv['skip-eslint']) {
      specpreProcessors.unshift('eslint');
    }

    // prefix the paths with where the gulp task was ran from so the files can
    // be found from the correct location
    var src = originPath + path,
        specSrc = originPath + specs;

    // default configuration for the spec build
    var config = {
      // all the files that should be included
      files: [ '__spec_helper__/*.js', src, { pattern: specSrc, watched: false, included: true, served: true } ],
      // the karma config file
      configFile: configFile,
      // the preprocessors to run the files through
      preprocessors: {},
      // which browsers to run the specs through
      browsers: [ 'PhantomJS' ],
      // options for browserify
      browserify: {
        debug: true,
        // lookup paths for any imported files
        paths: [ originPath + '/src' ],
        // configure any transforms for browserify
        transform: [
          babelify.configure({
            // only babelify files in the src directory
            ignore: /node_modules/,
            babelrc: false, // do not use babelrc files in gulp task
            extends: originPath + '/node_modules/carbon-factory/.babelrc' // manually set babelrc for gulp task
          })
        ],
        configure: function(bundle) {
          bundle.on('prebundle', function() {
            bundle.external('react/addons');
            bundle.external('react/lib/ReactContext');
            bundle.external('react/lib/ExecutionEnvironment');
          });
        },
        cache: {},
        packageCache: {}
      },
      // what kind of reporters should karma generate
      reporters: reporters,
      // auto watch for any changes to rerun specs
      autoWatch: true,
      // only run the specs once
      singleRun: true,
      // setup config for coverage
      coverageReporter: {
        dir: originPath + '/coverage',
        reporters: coverageReporters,
        check: {
          global: coverageThreshold,
          each: coverageThresholdEachFile
        }
      },
      // config for eslint
      eslint: {
        stopOnError: false,
        stopOnWarning: false
      },
      // adds additional opts for chrome browser - remembers prefs for console
      customLaunchers: {
        Chrome_dev: {
          base: 'Chrome',
          flags: ['--user-data-dir=./.browser-preferences']
        }
      }
    };

    // Report tests slower than value
    if (argv['report-slow']) {
      config.reportSlowerThan = 100;
    }

    // tie the preprocessors to the relevant sources
    config.preprocessors[src] = preProcessors;
    config.preprocessors[specSrc] = specpreProcessors;

    if (argv.b == 'all') {
      // if `gulp -b all` then run through all browsers
      config.browsers = [ 'PhantomJS', 'Chrome_dev', 'Firefox', 'Safari' ];
    } else if (argv.b) {
      // if `gulp -b [browser]` then use the browser supplied
      config.browsers = [ S(argv.b).capitalize().s ];

      // use the custom chrome launcher
      if (config.browsers[0] === 'Chrome') {
        config.browsers = [ config.browsers[0] + '_dev' ];
      }
    }

    // if coverage is enabled
    if (argv.build || argv.coverage) {
      process.env.NODE_ENV = 'test';

      config.reporters.push('coverage');
      config.browserify.transform.push(
        istanbul({
          // ignore these files from code coverage
          ignore: ignoreCoverage
        })
      );
    }

    if (argv.build) {
      // if `gulp --build` then use single run mode
      config.autoWatch = false;
      // stop on lint failures in build mode
      config.eslint.stopOnError = !stopAboveEslintThreshold;
      // error threshold above which build fails
      config.eslint.errorThreshold = eslintThreshold;
      // Fail build above error threshold
      config.eslint.stopAboveErrorThreshold = stopAboveEslintThreshold;
      // disable source maps in build mode
      config.browserify.debug = false;
    } else {
      // default to watch mode
      config.singleRun = false;
    }

    var server = new Server(config, function(status) {
      if (status) {
        gutil.log(gutil.colors.red('UI Tests FAILED'));
      } else {
        gutil.log(gutil.colors.green('UI Tests SUCCEEDED'));
      }

      done();

      process.exit(status);
    });

    server.start();
  };
}
<commit_msg>add typescript to spec<commit_after>/**
 * A Gulp task for running specs using Karma.
 *
 * To use this in a gulpfile.js:
 *
 *    import SpecTask from 'carbon-factory/lib/gulp/spec';
 *    gulp.task('test', SpecTask());
 *
 * You can also pass a hash of options to customise the task:
 *
 *    import SpecTask from 'carbon-factory/lib/gulp/spec';
 *
 *    var opts = {
 *      path: "/src/*.js",
 *      specs: "/src/*.spec.js",
 *      preProcessors: [ 'eslint', 'browserify' ],
 *      specPreProcessors: [ 'browserify' ],
 *      ignoreCoverage: [ '/path/to/ignore' ],
 *      reporters: ['progress'],
 *      coverageReporters: [{ type: 'text-summary' }, { type: 'html' }],
 *      coverage: {
 *        statements: 100,
 *        branches: 100,
 *        functions: 100,
 *        lines: 100
 *      },
 *      configFile: 'karma.conf.js'
 *    }
 *
 *    gulp.task('test', SpecTask(opts));
 *
 * If you have setup the task using the task name 'test' then you can run it
 * through the command line like this:
 *
 *    gulp test
 *
 * By default this will automatically run in PhantomJS and will watch for any
 * changes. You can supply additional arguments to run in alternative modes:
 *
 * == To run in watch mode with coverage reports:
 *
 *    gulp test --coverage
 *
 * == To perform a single run with coverage:
 *
 *    gulp test --build
 *
 * == To run the tests in a specific browser:
 *
 *    gulp test -b chrome
 *
 * == To run the test across all browsers:
 *
 *    gulp test -b all
 */

import './utils/fs-patch';
import gutil from 'gulp-util';
import S from 'string';
import yargs from 'yargs';
import karma from 'karma';
import istanbul from 'browserify-istanbul';
import babelify from 'babelify';
import tsify from 'tsify';
import fs from 'fs';

var argv = yargs.argv;
var Server = karma.Server;

export default function(opts) {
  var options = opts;

  return function(done) {
    var opts = options || {};
    // the js files to test (everything except __spec__ files)
    var path = opts.path || '/src/***/**/!(__spec__).js';
    // the specs
    var specs = opts.specs || '/src/***/**/__spec__.js';
    // an array of paths to ignore from coverage reports
    var ignoreCoverage = opts.ignoreCoverage || [ '**/node_modules/**', '**/__spec__.js' ];
    // an array to specify what kind of reporters should karma generate
    var reporters = opts.reporters || ['progress'];
    // an array to specify what kind of reporters type should karma generate
    var coverageReporters = opts.coverageReporters || [{ type: 'text-summary' }, { type: 'html' }];
    // which preprocessors the js files should run through
    var preProcessors = opts.preProcessors || [ 'browserify' ];
    // which preprocessors the spec files should run through
    var specpreProcessors = opts.specpreProcessors || [ 'browserify' ];
    // where to find the karma config file
    var configFile = opts.configFile || __dirname + '/karma.conf.js';
    // defaults the coverage thresholds
    var coverageThreshold = Object.assign({}, {
      statements: 100,
      branches: 100,
      functions: 100,
      lines: 100
    }, opts.coverage);
    // eslintThreshold for failing build
    var eslintThreshold = opts.eslintThreshold || null;
    // Stop build if above eslintThreshold
    var stopAboveEslintThreshold = !!eslintThreshold;
    // coverage thresholds for each file
    var coverageThresholdEachFile = opts.coverageEachFile || {};
    // if using typescript
    var typescript = opts.typescript || false,
    // where the gulp task was ran from
    var originPath = process.cwd();

    // check if eslintrc file exists, if not then prompt dev to create one
    fs.stat(originPath + '/.eslintrc', function(err, stat) {
      if (err == null) { return }
      if (err.code == 'ENOENT') {
        gutil.log(gutil.colors.red("Cannot find '.eslintrc' file"));
        gutil.log(gutil.colors.white("Create an '.eslintrc' file in the root of your project and add the following code:\n{\n  \"extends\": \"./node_modules/carbon-factory/.eslintrc\"\n}"));
        process.exit();
      }
    });

    if (!argv['skip-eslint']) {
      specpreProcessors.unshift('eslint');
    }

    // prefix the paths with where the gulp task was ran from so the files can
    // be found from the correct location
    var src = originPath + path,
        specSrc = originPath + specs;

    // default configuration for the spec build
    var config = {
      // all the files that should be included
      files: [ '__spec_helper__/*.js', src, { pattern: specSrc, watched: false, included: true, served: true } ],
      // the karma config file
      configFile: configFile,
      // the preprocessors to run the files through
      preprocessors: {},
      // which browsers to run the specs through
      browsers: [ 'PhantomJS' ],
      // options for browserify
      browserify: {
        debug: true,
        // lookup paths for any imported files
        paths: [ originPath + '/src' ],
        // configure any transforms for browserify
        transform: [
          babelify.configure({
            // only babelify files in the src directory
            ignore: /node_modules/,
            babelrc: false, // do not use babelrc files in gulp task
            extends: originPath + '/node_modules/carbon-factory/.babelrc' // manually set babelrc for gulp task
          })
        ],
        configure: function(bundle) {
          bundle.on('prebundle', function() {
            bundle.external('react/addons');
            bundle.external('react/lib/ReactContext');
            bundle.external('react/lib/ExecutionEnvironment');
          });
        },
        cache: {},
        packageCache: {}
      },
      // what kind of reporters should karma generate
      reporters: reporters,
      // auto watch for any changes to rerun specs
      autoWatch: true,
      // only run the specs once
      singleRun: true,
      // setup config for coverage
      coverageReporter: {
        dir: originPath + '/coverage',
        reporters: coverageReporters,
        check: {
          global: coverageThreshold,
          each: coverageThresholdEachFile
        }
      },
      // config for eslint
      eslint: {
        stopOnError: false,
        stopOnWarning: false
      },
      // adds additional opts for chrome browser - remembers prefs for console
      customLaunchers: {
        Chrome_dev: {
          base: 'Chrome',
          flags: ['--user-data-dir=./.browser-preferences']
        }
      }
    };

    // Report tests slower than value
    if (argv['report-slow']) {
      config.reportSlowerThan = 100;
    }

    if (typescript) {
      config.browserify.plugin = tsify;
    }

    // tie the preprocessors to the relevant sources
    config.preprocessors[src] = preProcessors;
    config.preprocessors[specSrc] = specpreProcessors;

    if (argv.b == 'all') {
      // if `gulp -b all` then run through all browsers
      config.browsers = [ 'PhantomJS', 'Chrome_dev', 'Firefox', 'Safari' ];
    } else if (argv.b) {
      // if `gulp -b [browser]` then use the browser supplied
      config.browsers = [ S(argv.b).capitalize().s ];

      // use the custom chrome launcher
      if (config.browsers[0] === 'Chrome') {
        config.browsers = [ config.browsers[0] + '_dev' ];
      }
    }

    // if coverage is enabled
    if (argv.build || argv.coverage) {
      process.env.NODE_ENV = 'test';

      config.reporters.push('coverage');
      config.browserify.transform.push(
        istanbul({
          // ignore these files from code coverage
          ignore: ignoreCoverage
        })
      );
    }

    if (argv.build) {
      // if `gulp --build` then use single run mode
      config.autoWatch = false;
      // stop on lint failures in build mode
      config.eslint.stopOnError = !stopAboveEslintThreshold;
      // error threshold above which build fails
      config.eslint.errorThreshold = eslintThreshold;
      // Fail build above error threshold
      config.eslint.stopAboveErrorThreshold = stopAboveEslintThreshold;
      // disable source maps in build mode
      config.browserify.debug = false;
    } else {
      // default to watch mode
      config.singleRun = false;
    }

    var server = new Server(config, function(status) {
      if (status) {
        gutil.log(gutil.colors.red('UI Tests FAILED'));
      } else {
        gutil.log(gutil.colors.green('UI Tests SUCCEEDED'));
      }

      done();

      process.exit(status);
    });

    server.start();
  };
}
<|endoftext|>


<commit_before>frappe.provide("frappe.wiz");
frappe.provide("frappe.wiz.events");

frappe.wiz = {
	slides: [],
	events: {},
	on: function(event, fn) {
		if(!frappe.wiz.events[event]) {
			frappe.wiz.events[event] = [];
		}
		frappe.wiz.events[event].push(fn);
	},
	add_slide: function(slide) {
		frappe.wiz.slides.push(slide);
	},
	run_event: function(event) {
		$.each(frappe.wiz.events[event] || [], function(i, fn) {
			fn(frappe.wiz.wizard);
		});
	}
}

frappe.pages['setup-wizard'].on_page_load = function(wrapper) {
	// setup page ui
	$(".navbar:first").toggle(false);
	$("body").css({"padding-top":"30px"});

	frappe.require("/assets/frappe/css/animate.min.css");

	$.each(frappe.boot.setup_wizard_requires || [], function(i, path) {
		frappe.require(path);
	});

	// add welcome slide
	frappe.wiz.add_slide(frappe.wiz.welcome);
	frappe.wiz.add_slide(frappe.wiz.region);

	frappe.wiz.run_event("before_load");

	var wizard_settings = {
		page_name: "setup-wizard",
		parent: wrapper,
		slides: frappe.wiz.slides,
		title: __("Welcome")
	}

	frappe.wiz.wizard = new frappe.wiz.Wizard(wizard_settings)

}

frappe.pages['setup-wizard'].on_page_show = function(wrapper) {
	if(frappe.get_route()[1]) {
		frappe.wiz.wizard.show(frappe.get_route()[1]);
	}

}

frappe.wiz.Wizard = Class.extend({
	init: function(opts) {
		$.extend(this, opts);
		this.make();
		this.slides;
		this.slide_dict = {};
		this.welcomed = true;
		frappe.set_route("setup-wizard/0");
	},
	make: function() {
		this.parent = $('
').appendTo(this.parent);
	},
	get_message: function(html) {
		return $(repl('
\

%(html)s
\

', {html:html}))
	},
	show_working: function() {
		this.hide_current_slide();
		frappe.set_route(this.page_name);
		this.current_slide = {"$wrapper": this.get_message(this.working_html()).appendTo(this.parent)};
	},
	show_complete: function() {
		this.hide_current_slide();
		this.current_slide = {"$wrapper": this.get_message(this.complete_html()).appendTo(this.parent)};
	},
	show: function(id) {
		if(!this.welcomed) {
			frappe.set_route(this.page_name);
			return;
		}
		id = cint(id);
		if(this.current_slide && this.current_slide.id===id)
			return;
		if(!this.slide_dict[id]) {
			this.slide_dict[id] = new frappe.wiz.WizardSlide($.extend(this.slides[id], {wiz:this, id:id}));
			this.slide_dict[id].make();
		}

		this.hide_current_slide();

		this.current_slide = this.slide_dict[id];
		this.current_slide.$wrapper.removeClass("hidden");
	},
	hide_current_slide: function() {
		if(this.current_slide) {
			this.current_slide.$wrapper.addClass("hidden");
			this.current_slide = null;
		}
	},
	get_values: function() {
		var values = {};
		$.each(this.slide_dict, function(id, slide) {
			$.extend(values, slide.values)
		})
		return values;
	},
	working_html: function() {
		var msg = $(frappe.render_template("setup_wizard_message", {
			image: "/assets/frappe/images/ui/bubble-tea-smile.svg",
			title: __("Setting Up"),
			message: __('Sit tight while your system is being setup. This may take a few moments.')
		}));
		msg.find(".setup-wizard-message-image").addClass("animated infinite bounce");
		return msg.html();
	},

	complete_html: function() {
		return frappe.render_template("setup_wizard_message", {
			image: "/assets/frappe/images/ui/bubble-tea-happy.svg",
			title: __('Setup Complete'),
			message: ""
		});
	},

	on_complete: function() {
		var me = this;
		var values = me.get_values();
		me.show_working();
		return frappe.call({
			method: "frappe.desk.page.setup_wizard.setup_wizard.setup_complete",
			args: {args: values},
			callback: function(r) {
				me.show_complete();
				if(frappe.wiz.welcome_page) {
					localStorage.setItem("session_last_route", frappe.wiz.welcome_page);
				}
				setTimeout(function() {
					window.location = "/desk";
				}, 2000);
			},
			error: function(r) {
				var d = msgprint(__("There were errors."));
				d.custom_onhide = function() {
					frappe.set_route(me.page_name, me.slides.length - 1);
				};
			}
		});
	}

});

frappe.wiz.WizardSlide = Class.extend({
	init: function(opts) {
		$.extend(this, opts);
		this.$wrapper = $('

')
			.appendTo(this.wiz.parent)
			.attr("data-slide-id", this.id);
	},
	make: function() {
		var me = this;
		if(this.$body) this.$body.remove();

		if(this.before_load) {
			this.before_load(this);
		}

		this.$body = $(frappe.render_template("setup_wizard_page", {
				help: __(this.help),
				title:__(this.title),
				main_title:__(this.wiz.title),
				step: this.id + 1,
				name: this.name,
				css_class: this.css_class || "",
				slides_count: this.wiz.slides.length
			})).appendTo(this.$wrapper);

		this.body = this.$body.find(".form")[0];

		if(this.fields) {
			this.form = new frappe.ui.FieldGroup({
				fields: this.fields,
				body: this.body,
				no_submit_on_enter: true
			});
			this.form.make();
		} else {
			$(this.body).html(this.html);
		}

		if(this.id > 0) {
			this.$prev = this.$body.find('.prev-btn').removeClass("hide")
				.click(function() {
					frappe.set_route(me.wiz.page_name, me.id-1 + "");
				})
				.css({"margin-right": "10px"});
			}
		if(this.id+1 < this.wiz.slides.length) {
			this.$next = this.$body.find('.next-btn').removeClass("hide")
				.click(function() {
					me.values = me.form.get_values();
					if(me.values===null)
						return;
					if(me.validate && !me.validate())
						return;
					frappe.set_route(me.wiz.page_name, me.id+1 + "");
				})
		} else {
			this.$complete = this.$body.find('.complete-btn').removeClass("hide")
				.click(function() {
					me.values = me.form.get_values();
					if(me.values===null)
						return;
					if(me.validate && !me.validate())
						return;
					me.wiz.on_complete(me.wiz);
				})
		}

		if(this.onload) {
			this.onload(this);
		}

	},
	get_input: function(fn) {
		return this.form.get_input(fn);
	},
	get_field: function(fn) {
		return this.form.get_field(fn);
	}
});

// language selection
frappe.wiz.welcome = {
	name: "welcome",
	title: __("Welcome"),
	icon: "icon-world",
	help: __("Let's prepare the system for first use."),

	fields: [
		{ fieldname: "language", label: __("Select Your Language"), reqd:1,
			fieldtype: "Select" },
	],

	onload: function(slide) {
		if (!frappe.wiz.welcome.data) {
			frappe.wiz.welcome.load_languages(slide);
		} else {
			frappe.wiz.welcome.setup_fields(slide);
		}
	},

	css_class: "single-column",
	load_languages: function(slide) {
		frappe.call({
			method: "frappe.desk.page.setup_wizard.setup_wizard.load_languages",
			callback: function(r) {
				frappe.wiz.welcome.data = r.message;
				frappe.wiz.welcome.setup_fields(slide);

				slide.get_field("language")
					.set_input(frappe.wiz.welcome.data.default_language || "english");
				moment.locale("en");
			}
		});
	},

	setup_fields: function(slide) {
		var select = slide.get_field("language");
		select.df.options = frappe.wiz.welcome.data.languages;
		select.refresh();
		frappe.wiz.welcome.bind_events(slide);
	},

	bind_events: function(slide) {
		slide.get_input("language").unbind("change").on("change", function() {
			var lang = $(this).val() || "english";
			frappe._messages = {};
			frappe.call({
				method: "frappe.desk.page.setup_wizard.setup_wizard.load_messages",
				args: {
					language: lang
				},
				callback: function(r) {
					// TODO save values!

					// re-render all slides
					$.each(slide.wiz.slide_dict, function(key, s) {
						s.make();
					});

					// select is re-made after language change
					var select = slide.get_field("language");
					select.set_input(lang);
				}
			});
		});
	}
}

// region selection
frappe.wiz.region = {
	title: __("Region"),
	icon: "icon-flag",
	help: __("Select your Country, Time Zone and Currency"),
	fields: [
		{ fieldname: "country", label: __("Country"), reqd:1,
			fieldtype: "Select" },
		{ fieldname: "timezone", label: __("Time Zone"), reqd:1,
			fieldtype: "Select" },
		{ fieldname: "currency", label: __("Currency"), reqd:1,
			fieldtype: "Select" },
	],

	onload: function(slide) {
		frappe.call({
			method:"frappe.geo.country_info.get_country_timezone_info",
			callback: function(data) {
				frappe.wiz.region.data = data.message;
				frappe.wiz.region.setup_fields(slide);
				frappe.wiz.region.bind_events(slide);
			}
		});
	},
	css_class: "single-column",
	setup_fields: function(slide) {
		var data = frappe.wiz.region.data;

		slide.get_input("country").empty()
			.add_options([""].concat(keys(data.country_info).sort()));

		slide.get_input("currency").empty()
			.add_options(frappe.utils.unique([""].concat($.map(data.country_info,
				function(opts, country) { return opts.currency; }))).sort());

		slide.get_input("timezone").empty()
			.add_options([""].concat(data.all_timezones));

		if (data.default_country) {
			slide.set_input("country", data.default_country);
		}
	},

	bind_events: function(slide) {
		slide.get_input("country").on("change", function() {
			var country = slide.get_input("country").val();
			var $timezone = slide.get_input("timezone");
			var data = frappe.wiz.region.data;

			$timezone.empty();

			// add country specific timezones first
			if(country) {
				var timezone_list = data.country_info[country].timezones || [];
				$timezone.add_options(timezone_list.sort());
				slide.get_field("currency").set_input(data.country_info[country].currency);
				slide.get_field("currency").$input.trigger("change");
			}

			// add all timezones at the end, so that user has the option to change it to any timezone
			$timezone.add_options([""].concat(data.all_timezones));

			slide.get_field("timezone").set_input($timezone.val());

			// temporarily set date format
			frappe.boot.sysdefaults.date_format = (data.country_info[country].date_format
				|| "dd-mm-yyyy");
		});

		slide.get_input("currency").on("change", function() {
			var currency = slide.get_input("currency").val();
			if (!currency) return;
			frappe.model.with_doc("Currency", currency, function() {
				frappe.provide("locals.:Currency." + currency);
				var currency_doc = frappe.model.get_doc("Currency", currency);
				var number_format = currency_doc.number_format;
				if (number_format==="#.###") {
					number_format = "#.###,##";
				} else if (number_format==="#,###") {
					number_format = "#,###.##"
				}

				frappe.boot.sysdefaults.number_format = number_format;
				locals[":Currency"][currency] = $.extend({}, currency_doc);
			});
		});
	}
};
<commit_msg>[fix] re-make setup wizard slides on change of language<commit_after>frappe.provide("frappe.wiz");
frappe.provide("frappe.wiz.events");

frappe.wiz = {
	slides: [],
	events: {},
	on: function(event, fn) {
		if(!frappe.wiz.events[event]) {
			frappe.wiz.events[event] = [];
		}
		frappe.wiz.events[event].push(fn);
	},
	add_slide: function(slide) {
		frappe.wiz.slides.push(slide);
	},
	run_event: function(event) {
		$.each(frappe.wiz.events[event] || [], function(i, fn) {
			fn(frappe.wiz.wizard);
		});
	}
}

frappe.pages['setup-wizard'].on_page_load = function(wrapper) {
	// setup page ui
	$(".navbar:first").toggle(false);
	$("body").css({"padding-top":"30px"});

	frappe.require("/assets/frappe/css/animate.min.css");

	$.each(frappe.boot.setup_wizard_requires || [], function(i, path) {
		frappe.require(path);
	});

	frappe.wiz.run_event("before_load");

	var wizard_settings = {
		page_name: "setup-wizard",
		parent: wrapper,
		slides: frappe.wiz.slides,
		title: __("Welcome")
	}

	frappe.wiz.wizard = new frappe.wiz.Wizard(wizard_settings)

}

frappe.pages['setup-wizard'].on_page_show = function(wrapper) {
	if(frappe.get_route()[1]) {
		frappe.wiz.wizard.show(frappe.get_route()[1]);
	}
}

frappe.wiz.Wizard = Class.extend({
	init: function(opts) {
		$.extend(this, opts);
		this.make();
		this.slides;
		this.slide_dict = {};
		this.welcomed = true;
		frappe.set_route("setup-wizard/0");
	},
	make: function() {
		this.parent = $('
').appendTo(this.parent);
	},
	get_message: function(html) {
		return $(repl('
\

%(html)s
\

', {html:html}))
	},
	show_working: function() {
		this.hide_current_slide();
		frappe.set_route(this.page_name);
		this.current_slide = {"$wrapper": this.get_message(this.working_html()).appendTo(this.parent)};
	},
	show_complete: function() {
		this.hide_current_slide();
		this.current_slide = {"$wrapper": this.get_message(this.complete_html()).appendTo(this.parent)};
	},
	show: function(id) {
		if(!this.welcomed) {
			frappe.set_route(this.page_name);
			return;
		}
		id = cint(id);
		if(this.current_slide && this.current_slide.id===id)
			return;
		if(!this.slide_dict[id]) {
			this.slide_dict[id] = new frappe.wiz.WizardSlide($.extend(this.slides[id], {wiz:this, id:id}));
			this.slide_dict[id].make();
		}

		this.hide_current_slide();

		this.current_slide = this.slide_dict[id];
		this.current_slide.$wrapper.removeClass("hidden");
	},
	hide_current_slide: function() {
		if(this.current_slide) {
			this.current_slide.$wrapper.addClass("hidden");
			this.current_slide = null;
		}
	},
	get_values: function() {
		var values = {};
		$.each(this.slide_dict, function(id, slide) {
			$.extend(values, slide.values)
		})
		return values;
	},
	working_html: function() {
		var msg = $(frappe.render_template("setup_wizard_message", {
			image: "/assets/frappe/images/ui/bubble-tea-smile.svg",
			title: __("Setting Up"),
			message: __('Sit tight while your system is being setup. This may take a few moments.')
		}));
		msg.find(".setup-wizard-message-image").addClass("animated infinite bounce");
		return msg.html();
	},

	complete_html: function() {
		return frappe.render_template("setup_wizard_message", {
			image: "/assets/frappe/images/ui/bubble-tea-happy.svg",
			title: __('Setup Complete'),
			message: ""
		});
	},

	on_complete: function() {
		var me = this;
		var values = me.get_values();
		me.show_working();
		return frappe.call({
			method: "frappe.desk.page.setup_wizard.setup_wizard.setup_complete",
			args: {args: values},
			callback: function(r) {
				me.show_complete();
				if(frappe.wiz.welcome_page) {
					localStorage.setItem("session_last_route", frappe.wiz.welcome_page);
				}
				setTimeout(function() {
					window.location = "/desk";
				}, 2000);
			},
			error: function(r) {
				var d = msgprint(__("There were errors."));
				d.custom_onhide = function() {
					frappe.set_route(me.page_name, me.slides.length - 1);
				};
			}
		});
	}

});

frappe.wiz.WizardSlide = Class.extend({
	init: function(opts) {
		$.extend(this, opts);
		this.$wrapper = $('

')
			.appendTo(this.wiz.parent)
			.attr("data-slide-id", this.id);
	},
	make: function() {
		var me = this;
		if(this.$body) this.$body.remove();

		if(this.before_load) {
			this.before_load(this);
		}

		this.$body = $(frappe.render_template("setup_wizard_page", {
				help: __(this.help),
				title:__(this.title),
				main_title:__(this.wiz.title),
				step: this.id + 1,
				name: this.name,
				css_class: this.css_class || "",
				slides_count: this.wiz.slides.length
			})).appendTo(this.$wrapper);

		this.body = this.$body.find(".form")[0];

		if(this.fields) {
			this.form = new frappe.ui.FieldGroup({
				fields: this.fields,
				body: this.body,
				no_submit_on_enter: true
			});
			this.form.make();
		} else {
			$(this.body).html(this.html);
		}

		if(this.id > 0) {
			this.$prev = this.$body.find('.prev-btn').removeClass("hide")
				.click(function() {
					frappe.set_route(me.wiz.page_name, me.id-1 + "");
				})
				.css({"margin-right": "10px"});
			}
		if(this.id+1 < this.wiz.slides.length) {
			this.$next = this.$body.find('.next-btn').removeClass("hide")
				.click(function() {
					me.values = me.form.get_values();
					if(me.values===null)
						return;
					if(me.validate && !me.validate())
						return;
					frappe.set_route(me.wiz.page_name, me.id+1 + "");
				})
		} else {
			this.$complete = this.$body.find('.complete-btn').removeClass("hide")
				.click(function() {
					me.values = me.form.get_values();
					if(me.values===null)
						return;
					if(me.validate && !me.validate())
						return;
					me.wiz.on_complete(me.wiz);
				})
		}

		if(this.onload) {
			this.onload(this);
		}

	},
	get_input: function(fn) {
		return this.form.get_input(fn);
	},
	get_field: function(fn) {
		return this.form.get_field(fn);
	}
});

function load_frappe_slides() {
	// language selection
	frappe.wiz.welcome = {
		name: "welcome",
		title: __("Welcome"),
		icon: "icon-world",
		help: __("Let's prepare the system for first use."),

		fields: [
			{ fieldname: "language", label: __("Select Your Language"), reqd:1,
				fieldtype: "Select" },
		],

		onload: function(slide) {
			if (!frappe.wiz.welcome.data) {
				frappe.wiz.welcome.load_languages(slide);
			} else {
				frappe.wiz.welcome.setup_fields(slide);
			}
		},

		css_class: "single-column",
		load_languages: function(slide) {
			frappe.call({
				method: "frappe.desk.page.setup_wizard.setup_wizard.load_languages",
				callback: function(r) {
					frappe.wiz.welcome.data = r.message;
					frappe.wiz.welcome.setup_fields(slide);

					slide.get_field("language")
						.set_input(frappe.wiz.welcome.data.default_language || "english");
					moment.locale("en");
				}
			});
		},

		setup_fields: function(slide) {
			var select = slide.get_field("language");
			select.df.options = frappe.wiz.welcome.data.languages;
			select.refresh();
			frappe.wiz.welcome.bind_events(slide);
		},

		bind_events: function(slide) {
			slide.get_input("language").unbind("change").on("change", function() {
				var lang = $(this).val() || "english";
				frappe._messages = {};
				frappe.call({
					method: "frappe.desk.page.setup_wizard.setup_wizard.load_messages",
					args: {
						language: lang
					},
					callback: function(r) {
						// TODO save values!

						// reset all slides so that labels are translated
						frappe.wiz.slides = [];
						frappe.wiz.run_event("before_load");
						frappe.wiz.wizard.slides = frappe.wiz.slides;

						// re-render all slides
						$.each(slide.wiz.slide_dict, function(id, s) {
							$.extend(s, frappe.wiz.slides[id]);
							s.make();
						});

						// select is re-made after language change
						var select = slide.get_field("language");
						select.set_input(lang);
					}
				});
			});
		}
	}

	// region selection
	frappe.wiz.region = {
		title: __("Region"),
		icon: "icon-flag",
		help: __("Select your Country, Time Zone and Currency"),
		fields: [
			{ fieldname: "country", label: __("Country"), reqd:1,
				fieldtype: "Select" },
			{ fieldname: "timezone", label: __("Time Zone"), reqd:1,
				fieldtype: "Select" },
			{ fieldname: "currency", label: __("Currency"), reqd:1,
				fieldtype: "Select" },
		],

		onload: function(slide) {
			frappe.call({
				method:"frappe.geo.country_info.get_country_timezone_info",
				callback: function(data) {
					frappe.wiz.region.data = data.message;
					frappe.wiz.region.setup_fields(slide);
					frappe.wiz.region.bind_events(slide);
				}
			});
		},
		css_class: "single-column",
		setup_fields: function(slide) {
			var data = frappe.wiz.region.data;

			slide.get_input("country").empty()
				.add_options([""].concat(keys(data.country_info).sort()));

			slide.get_input("currency").empty()
				.add_options(frappe.utils.unique([""].concat($.map(data.country_info,
					function(opts, country) { return opts.currency; }))).sort());

			slide.get_input("timezone").empty()
				.add_options([""].concat(data.all_timezones));

			if (data.default_country) {
				slide.set_input("country", data.default_country);
			}
		},

		bind_events: function(slide) {
			slide.get_input("country").on("change", function() {
				var country = slide.get_input("country").val();
				var $timezone = slide.get_input("timezone");
				var data = frappe.wiz.region.data;

				$timezone.empty();

				// add country specific timezones first
				if(country) {
					var timezone_list = data.country_info[country].timezones || [];
					$timezone.add_options(timezone_list.sort());
					slide.get_field("currency").set_input(data.country_info[country].currency);
					slide.get_field("currency").$input.trigger("change");
				}

				// add all timezones at the end, so that user has the option to change it to any timezone
				$timezone.add_options([""].concat(data.all_timezones));

				slide.get_field("timezone").set_input($timezone.val());

				// temporarily set date format
				frappe.boot.sysdefaults.date_format = (data.country_info[country].date_format
					|| "dd-mm-yyyy");
			});

			slide.get_input("currency").on("change", function() {
				var currency = slide.get_input("currency").val();
				if (!currency) return;
				frappe.model.with_doc("Currency", currency, function() {
					frappe.provide("locals.:Currency." + currency);
					var currency_doc = frappe.model.get_doc("Currency", currency);
					var number_format = currency_doc.number_format;
					if (number_format==="#.###") {
						number_format = "#.###,##";
					} else if (number_format==="#,###") {
						number_format = "#,###.##"
					}

					frappe.boot.sysdefaults.number_format = number_format;
					locals[":Currency"][currency] = $.extend({}, currency_doc);
				});
			});
		}
	};
};

frappe.wiz.on("before_load", function() {
	load_frappe_slides();

	// add welcome slide
	frappe.wiz.add_slide(frappe.wiz.welcome);
	frappe.wiz.add_slide(frappe.wiz.region);
});
<|endoftext|>


<commit_before>/**
@author Michael Murphy
*/
console.time("Database ready");
var _ = require('underscore');
var config = require('./config');
var util = require('./util');
var verboseLog = util.verboseLog;
var isEmbedded = process.argv.indexOf("--mongo") == -1;
if (isEmbedded) {
   verboseLog("Using an embedded database.");
   require('tungus');
   // Tungus uses this object to define the TingoDB configuration options.
   global.TUNGUS_DB_OPTIONS = {
      memStore: config.db.memStore,
      searchInArray: true,
      nativeObjectID: false
   };
}
var mongoose = require('mongoose');
var fs = require('fs');

try {
   if (!isEmbedded) {
      verboseLog("Using MongoDB for database.");
      mongoose.connect(config.db.mongoUrl);
      mongoose.connection.on('error', function(err) {
         console.log(er);
      });
   }
   else {
      var absPath = setupDatabaseDirectory();
      mongoose.connect('tingodb://'.concat(absPath));
   }

   mongoose.connection.on('error', onError);
   mongoose.connection.once('open', function() {
      console.timeEnd("Database ready");
   });
}
catch(e) {
   onError(e);
}

module.exports = mongoose;

/**
 * If the database directory doesn't exist and the flag to create missing
 * directories is on, then this function creates the database directory.
 */
function setupDatabaseDirectory() {
   var fs = require('fs');
   var path = require('path');
   var util = require('./util');

   var absPath = path.resolve(config.db.path);
   if (!config.db.memStore && !fs.existsSync(absPath)) {
      if (config.createMissingDirectories) {
         console.warn("Creating directory for database files: \"" + absPath + "\"");
         util.makeDirectoryPlusParents(absPath);
      }
      else {
         throw absPath;
      }
   }

   return absPath;
}

function onError(err) {
   console.log(err);
   var err = new Error("Could not setup " + (isEmbedded ? "an embedded database in directory " + absPath : "a MongoDB connection to " + config.db.mongoUrl));

   console.log(err);
}<commit_msg>Shows more helpful database startup errors.<commit_after>/**
@author Michael Murphy
*/
console.time("Database ready");
var _ = require('underscore');
var config = require('./config');
var util = require('./util');
var verboseLog = util.verboseLog;
var isEmbedded = process.argv.indexOf("--mongo") == -1;
if (isEmbedded) {
   verboseLog("Using an embedded database.");
   require('tungus');
   // Tungus uses this object to define the TingoDB configuration options.
   global.TUNGUS_DB_OPTIONS = {
      memStore: config.db.memStore,
      searchInArray: true,
      nativeObjectID: false
   };
}
var mongoose = require('mongoose');
var fs = require('fs');

try {
   if (!isEmbedded) {
      verboseLog("Using MongoDB for database.");
      mongoose.connect(config.db.mongoUrl);
      mongoose.connection.on('error', function(err) {
         if (err.message == 'connect ECONNREFUSED') {
            console.warn('Error: Could not connect to MongoDB server. Is MongoDB server running?');
            process.exit(1);
         }
         else
            console.warn(err.message);
      });
   }
   else {
      var absPath = setupDatabaseDirectory();
      mongoose.connect('tingodb://'.concat(absPath));
   }

   mongoose.connection.on('error', onError);
   mongoose.connection.once('open', function() {
      console.timeEnd("Database ready");
   });
}
catch(e) {
   onError(e);
}

module.exports = mongoose;

/**
 * If the database directory doesn't exist and the flag to create missing
 * directories is on, then this function creates the database directory.
 */
function setupDatabaseDirectory() {
   var fs = require('fs');
   var path = require('path');
   var util = require('./util');

   var absPath = path.resolve(config.db.path);
   if (!config.db.memStore && !fs.existsSync(absPath)) {
      if (config.createMissingDirectories) {
         console.warn("Creating directory for database files: \"" + absPath + "\"");
         util.makeDirectoryPlusParents(absPath);
      }
      else {
         throw absPath;
      }
   }

   return absPath;
}

function onError(err) {
   console.log(err);
   var err = new Error("Could not setup " + (isEmbedded ? "an embedded database in directory " + absPath : "a MongoDB connection to " + config.db.mongoUrl));

   console.log(err);
}<|endoftext|>


<commit_before>/**
 * The Relais card communication protocol implementation.
 * Taken from my old "Relais.cs".
 * @module io/relais
 * @author Sören Gade
 *
 * @requires serialport
 * */

/**
 * A callback that simly indicated whether a certain task was successful.
 * @callback SuccessCallback
 * @param {bool} success
 * */

var util = require('util');
var events = require('events');
var async = require('async');
var serialport = require('serialport');
var SerialPort = serialport.SerialPort;

/**
 * Options for the serial port.
 * @constant
 * @type {Object}
 * @default
 * */
var SERIAL_OPTIONS = {
  baudrate: 19200,
  databits: 8,
  stopbits: 1,
  parity: 'none'
};
var Commands = {
  NoOperation: 0,
  Setup: 1,
  GetPort: 2,
  SetPort: 3,
  Getption: 4,
  SetOption: 5,
  SetSingle: 6,
  DelSingle: 7,
  Toggle: 8
};
var RelaisByteCount = {
  One: 1,
  Two: 2,
  Three: 4,
  Four: 8,
  Five: 16,
  Six: 32,
  Seven: 64,
  Eight: 128
};
var RelaisByteNames = {
  Command: 0,
  Address: 1,
  DataByte: 2,
  CheckSum: 3
};

/**
 * @constructor
 * @extends events.EventEmitter
 * */
function Relais(port) {
  /**
   * @instance
   * */
  var serialPort = null;
  /**
   * @instance
   * */
  var relaisID = 0;
  /**
   * @instance
   * */
  var isOpen = false;

  this.serialPort = new SerialPort(port, SERIAL_OPTIONS, false);
}
util.inherits(Relais, events.EventEmitter);

Relais.prototype.open = function(callback) {
  var self = this;

  self.serialPort.open(function() {
    self.isOpen = true;

    if ( callback ) {
      callback();
    }
    self.emit('open');
  });
};

/*
 * Disclaimer:
 * Ported directly from old .cs source file:
 * | | | | | | | | | | | | | | | | | | | | |
 * | | | | | | | | | | | | | | | | | | | | |
 * v v v v v v v v v v v v v v v v v v v v v
 * */

Relais.prototype.writeOK = function() {
  return this.isOpen;
};
/**
 * Writes the buffer to the serial port.
 * @param {Buffer} buffer - The buffer to write.
 * @param {ErrorCallback} [callback] - The callback that is called upon finish. Data might not be flushed at that point.
 *
 * {@link https://github.com/voodootikigod/node-serialport#write-buffer-callback}
 * @fires Relais#written
 * */
Relais.prototype.write = function(buffer, callback) {
  var self = this;

  if ( self.writeOK() ) {
    self.serialPort.write(buffer, function(err) {
      if ( callback ) {
        callback(err);
      }
      /**
       * Written event.
       * @event Relais#written
       * @type {Object}
       * @param {Buffer} buffer
       * @param {Exception} err
       * */
      self.emit('written', {
        buffer: buffer,
        err: err
      });
    });
  }
};

/**
 * @param {byte} command
 * @param {byte} data
 * @param {ErrorResultCallback} callback - Callback for Relais#write (Error) and Relais#read (Result).
 * */
Relais.prototype.send = function(command, data, callback) {
  if ( !this.writeOK() ) {
    return;
  }
  var self = this;

  var buffer = new Array(4);
  buffer[RelaisByteNames.Command] = command;
  buffer[RelaisByteNames.Address] = self.relaisID;
  buffer[RelaisByteNames.DataByte] = data;
  buffer[RelaisByteNames.CheckSum] = ( command ^ self.relaisID ^ data );

  // call callback after write
  if ( callback ) {
    self.serialPort.once('data', function(data) {
      if ( callback ) {
        callback(null, data);
      }
    });
  }

  self.write(buffer, function(err) {
    if ( err ) {
      if ( callback ) {
        callback(err);
      }
    }
  });
};

Relais.prototype.getMoreRelais = function(relais1, relais2, relais3, relais4, relais5, relais6, relais7, relais8) {
  var retVal = ( RelaisByteCount.One * relais1 );
  retVal += ( RelaisByteCount.Two * relais2 );
  retVal += ( RelaisByteCount.Three * relais3 );
  retVal += ( RelaisByteCount.Four * relais4 );
  retVal += ( RelaisByteCount.Five * relais5 );
  retVal += ( RelaisByteCount.Six * relais6 );
  retVal += ( RelaisByteCount.Seven * relais7 );
  retVal += ( RelaisByteCount.Eight * relais8 );

  return retVal;
};
Relais.prototype.getAllRelais = function() {
  return this.getMoreRelais(1, 1, 1, 1, 1, 1, 1, 1);
};

/**
 * @param {SuccessCallback} callback
 * */
Relais.prototype.noOperation = function(callback) {
  var self = this;
  if ( !callback ) {
    callback = function() {};
  }

  self.send(Commands.NoOperation, 0, function(err, data) {
    if ( err ) {
      callback(false);
    } else {

      console.log(data);
      // TODO validate buffer, first test with real data
      var ok = ( data[RelaisByteNames.Command] == 255 );
      callback(ok);

    }
  });
};
/**
 * @param {SuccessCallback} callback
 * */
Relais.prototype.NOP = function(callback) {
  this.noOperation(callback);
};

/**
 * @param {SuccessCallback} callback
 * */
Relais.prototype.setup = function(callback) {
  var self = this;
  if ( !callback ) {
    callback = function() {};
  }

  if ( self.relaisID === 0 ) {
    self.send(Commands.Setup, 0, function(err, data) {
      if ( err ) {
        callback(false);
      } else {

        if ( data[RelaisByteNames.Command] == ( 255 - Commands.Setup ) ) {
          self.relaisID = data[RelaisByteNames.Address];

          callback(true);
        }

      }
    });
  }

  callback(false);
};

/**
 * @param {ErrorResultCallback}
 * */
Relais.prototype.getPort = function(callback) {
  var self = this;
  if ( !callback ) {
    return;
  }

  self.send(Commands.GetPort, 0, function(err, data) {
    if ( err ) {
      callback(err);
    } else {

      if ( data[RelaisByteNames.Command] == ( 255 - Commands.GetPort ) ) {
        var port = data[RelaisByteNames.DataByte];
        callback(null, port);
      } else {
        callback(null, 0);
      }

    }
  });
};

/**
 * @param {byte} relais
 * */
Relais.prototype.setPort = function(relais) {
  this.send(Commands.SetPort, relais);
};

/**
 * @param {ErrorResultCallback} callback
 * */
Relais.prototype.getOption = function(callback) {
  var self = this;
  if ( !callback ) {
    return;
  }

  self.send(Commands.GetOption, 0, function(err, data) {
    if ( err ) {
      callback(err);
    } else {

      if ( data[RelaisByteNames.Command] == ( 255 - Commands.GetOption) ) {
        var dataByte = data[RelaisByteNames.DataByte];
        callback(null, dataByte);
      } else {
        callback(null, 0);
      }

    }
  });
};

/**
 * @param {byte} option
 * */
Relais.prototype.setOption = function(option) {
  this.send(Commands.SetOption, option);
};

/**
 * @param {byte} relais
 * @param {ErrorResultCallback} callback
 * */
Relais.prototype.setSingle = function(relais, callback) {
  var self = this;
  if ( !callback ) {
    callback = function() {};
  }

  self.send(Commands.SetSingle, relais, function(err, data) {
    if ( err ) {
      callback(err);
    } else {

      if ( data[RelaisByteNames.Command] == ( 255 - Commands.SetSingle ) ) {
        var single = data[RelaisByteNames.DataByte];
        callback(null, single);
      } else {
        callback(null, 0);
      }

    }
  });
};

/**
 * @param {byte} relais
 * @param {ErrorResultCallback} callback
 * */
Relais.prototype.delSingle = function(relais, callback) {
  var self = this;
  if ( !callback ) {
    callback = function() {};
  }

  self.send(Commands.DelSingle, relais, function(err, data) {
    if ( err ) {
      callback(err);
    } else {

      if ( data[RelaisByteNames.Command] == ( 255 - Commands.DelSingle ) ) {
        var single = data[RelaisByteNames.DataByte];
        callback(null, single);
      } else {
        callback(null, 0);
      }

    }
  });
};

/**
 * @param {byte} relais
 * @param {ErrorResultCallback} callback
 * */
Relais.prototype.toggle = function(relais, callback) {
  var self = this;
  if ( !callback ) {
    callback = function() {};
  }

  self.send(Commands.Toggle, relais, function(err, data) {
    if ( err ) {
      callback(err);
    } else {

      if ( data[RelaisByteNames.Command] == ( 255 - Commands.Toggle ) ) {
        var resp = data[RelaisByteNames.DataByte];
        callback(null, resp);
      } else {
        callback(null, 0);
      }

    }
  });
};

/**
 * @param {int} delay
 * @param {Callback} callback
 * */
Relais.prototype.activateAll = function(delay, callback) {
  this._iterateAllRelais(delay, this.setSingle, callback);
};

/**
 * @param {int} delay
 * @param {Callback} callback
 * */
Relais.prototype.deactivateAll = function(delay, callback) {
  this._iterateAllRelais(delay, this.delSingle, callback);
};

/**
 * @param {uint} delay
 * @param {Function} relaisOperation - An operation that is passed a relais number that it should work on.
 * @param {Callback} callback
 * */
Relais.prototype._iterateAllRelais = function(delay, relaisOperation, callback) {
  var self = this;
  if ( !delay ) {
    delay = 0;
  }
  if ( !callback ) {
    callback = function() {};
  }

  if ( delay === 0 ) {
    relaisOperation.call(self, self.getAllRelais());
  } else {
    var relaisNums = [ 1, 2, 4, 8, 16, 32, 64, 128 ];

    async.each(relaisNums, function(item, cb) {

      // item == relaisnum
      relaisOperation.call(self, item);

      setTimeout(function() {
        cb();
      }, delay);

    }, function(err) {
      // done
      callback();
    });

  }
};

module.exports = Relais;
<commit_msg>Fixed iterative relais setting.<commit_after>/**
 * The Relais card communication protocol implementation.
 * Taken from my old "Relais.cs".
 * @module io/relais
 * @author Sören Gade
 *
 * @requires serialport
 * */

/**
 * A callback that simly indicated whether a certain task was successful.
 * @callback SuccessCallback
 * @param {bool} success
 * */

var util = require('util');
var events = require('events');
var async = require('async');
var serialport = require('serialport');
var SerialPort = serialport.SerialPort;

/**
 * Options for the serial port.
 * @constant
 * @type {Object}
 * @default
 * */
var SERIAL_OPTIONS = {
  baudrate: 19200,
  databits: 8,
  stopbits: 1,
  parity: 'none'
};
var Commands = {
  NoOperation: 0,
  Setup: 1,
  GetPort: 2,
  SetPort: 3,
  Getption: 4,
  SetOption: 5,
  SetSingle: 6,
  DelSingle: 7,
  Toggle: 8
};
var RelaisByteCount = {
  One: 1,
  Two: 2,
  Three: 4,
  Four: 8,
  Five: 16,
  Six: 32,
  Seven: 64,
  Eight: 128
};
var RelaisByteNames = {
  Command: 0,
  Address: 1,
  DataByte: 2,
  CheckSum: 3
};

/**
 * @constructor
 * @extends events.EventEmitter
 * */
function Relais(port) {
  /**
   * @instance
   * */
  var serialPort = null;
  /**
   * @instance
   * */
  var relaisID = 0;
  /**
   * @instance
   * */
  var isOpen = false;

  this.serialPort = new SerialPort(port, SERIAL_OPTIONS, false);
}
util.inherits(Relais, events.EventEmitter);

Relais.prototype.open = function(callback) {
  var self = this;

  self.serialPort.open(function() {
    self.isOpen = true;

    if ( callback ) {
      callback();
    }
    self.emit('open');
  });
};

/*
 * Disclaimer:
 * Ported directly from old .cs source file:
 * | | | | | | | | | | | | | | | | | | | | |
 * | | | | | | | | | | | | | | | | | | | | |
 * v v v v v v v v v v v v v v v v v v v v v
 * */

Relais.prototype.writeOK = function() {
  return this.isOpen;
};
/**
 * Writes the buffer to the serial port.
 * @param {Buffer} buffer - The buffer to write.
 * @param {ErrorCallback} [callback] - The callback that is called upon finish. Data might not be flushed at that point.
 *
 * {@link https://github.com/voodootikigod/node-serialport#write-buffer-callback}
 * @fires Relais#written
 * */
Relais.prototype.write = function(buffer, callback) {
  var self = this;

  if ( self.writeOK() ) {
    self.serialPort.write(buffer, function(err) {
      if ( callback ) {
        callback(err);
      }
      /**
       * Written event.
       * @event Relais#written
       * @type {Object}
       * @param {Buffer} buffer
       * @param {Exception} err
       * */
      self.emit('written', {
        buffer: buffer,
        err: err
      });
    });
  }
};

/**
 * @param {byte} command
 * @param {byte} data
 * @param {ErrorResultCallback} callback - Callback for Relais#write (Error) and Relais#read (Result).
 * */
Relais.prototype.send = function(command, data, callback) {
  if ( !this.writeOK() ) {
    return;
  }
  var self = this;

  var buffer = new Array(4);
  buffer[RelaisByteNames.Command] = command;
  buffer[RelaisByteNames.Address] = self.relaisID;
  buffer[RelaisByteNames.DataByte] = data;
  buffer[RelaisByteNames.CheckSum] = ( command ^ self.relaisID ^ data );

  // call callback after write
  if ( callback ) {
    self.serialPort.once('data', function(data) { // could be a data leak
      if ( callback ) {
        callback(null, data);
      }
    });
  }

  self.write(buffer, function(err) {
    if ( err ) {
      if ( callback ) {
        callback(err);
      }
    }
  });
};

Relais.prototype.getMoreRelais = function(relais1, relais2, relais3, relais4, relais5, relais6, relais7, relais8) {
  var retVal = ( RelaisByteCount.One * relais1 );
  retVal += ( RelaisByteCount.Two * relais2 );
  retVal += ( RelaisByteCount.Three * relais3 );
  retVal += ( RelaisByteCount.Four * relais4 );
  retVal += ( RelaisByteCount.Five * relais5 );
  retVal += ( RelaisByteCount.Six * relais6 );
  retVal += ( RelaisByteCount.Seven * relais7 );
  retVal += ( RelaisByteCount.Eight * relais8 );

  return retVal;
};
Relais.prototype.getAllRelais = function() {
  return this.getMoreRelais(1, 1, 1, 1, 1, 1, 1, 1);
};

/**
 * @param {SuccessCallback} callback
 * */
Relais.prototype.noOperation = function(callback) {
  var self = this;
  if ( !callback ) {
    callback = function() {};
  }

  self.send(Commands.NoOperation, 0, function(err, data) {
    if ( err ) {
      callback(false);
    } else {

      console.log(data);
      // TODO validate buffer, first test with real data
      var ok = ( data[RelaisByteNames.Command] == 255 );
      callback(ok);

    }
  });
};
/**
 * @param {SuccessCallback} callback
 * */
Relais.prototype.NOP = function(callback) {
  this.noOperation(callback);
};

/**
 * @param {SuccessCallback} callback
 * */
Relais.prototype.setup = function(callback) {
  var self = this;
  if ( !callback ) {
    callback = function() {};
  }

  if ( self.relaisID === 0 ) {
    self.send(Commands.Setup, 0, function(err, data) {
      if ( err ) {
        callback(false);
      } else {

        if ( data[RelaisByteNames.Command] == ( 255 - Commands.Setup ) ) {
          self.relaisID = data[RelaisByteNames.Address];

          callback(true);
        }

      }
    });
  }

  callback(false);
};

/**
 * @param {ErrorResultCallback}
 * */
Relais.prototype.getPort = function(callback) {
  var self = this;
  if ( !callback ) {
    return;
  }

  self.send(Commands.GetPort, 0, function(err, data) {
    if ( err ) {
      callback(err);
    } else {

      if ( data[RelaisByteNames.Command] == ( 255 - Commands.GetPort ) ) {
        var port = data[RelaisByteNames.DataByte];
        callback(null, port);
      } else {
        callback(null, 0);
      }

    }
  });
};

/**
 * @param {byte} relais
 * */
Relais.prototype.setPort = function(relais) {
  this.send(Commands.SetPort, relais);
};

/**
 * @param {ErrorResultCallback} callback
 * */
Relais.prototype.getOption = function(callback) {
  var self = this;
  if ( !callback ) {
    return;
  }

  self.send(Commands.GetOption, 0, function(err, data) {
    if ( err ) {
      callback(err);
    } else {

      if ( data[RelaisByteNames.Command] == ( 255 - Commands.GetOption) ) {
        var dataByte = data[RelaisByteNames.DataByte];
        callback(null, dataByte);
      } else {
        callback(null, 0);
      }

    }
  });
};

/**
 * @param {byte} option
 * */
Relais.prototype.setOption = function(option) {
  this.send(Commands.SetOption, option);
};

/**
 * @param {byte} relais
 * @param {ErrorResultCallback} callback
 * */
Relais.prototype.setSingle = function(relais, callback) {
  var self = this;
  if ( !callback ) {
    callback = function() {};
  }

  self.send(Commands.SetSingle, relais, function(err, data) {
    if ( err ) {
      callback(err);
    } else {

      if ( data[RelaisByteNames.Command] == ( 255 - Commands.SetSingle ) ) {
        var single = data[RelaisByteNames.DataByte];
        callback(null, single);
      } else {
        callback(null, 0);
      }

    }
  });
};

/**
 * @param {byte} relais
 * @param {ErrorResultCallback} callback
 * */
Relais.prototype.delSingle = function(relais, callback) {
  var self = this;
  if ( !callback ) {
    callback = function() {};
  }

  self.send(Commands.DelSingle, relais, function(err, data) {
    if ( err ) {
      callback(err);
    } else {

      if ( data[RelaisByteNames.Command] == ( 255 - Commands.DelSingle ) ) {
        var single = data[RelaisByteNames.DataByte];
        callback(null, single);
      } else {
        callback(null, 0);
      }

    }
  });
};

/**
 * @param {byte} relais
 * @param {ErrorResultCallback} callback
 * */
Relais.prototype.toggle = function(relais, callback) {
  var self = this;
  if ( !callback ) {
    callback = function() {};
  }

  self.send(Commands.Toggle, relais, function(err, data) {
    if ( err ) {
      callback(err);
    } else {

      if ( data[RelaisByteNames.Command] == ( 255 - Commands.Toggle ) ) {
        var resp = data[RelaisByteNames.DataByte];
        callback(null, resp);
      } else {
        callback(null, 0);
      }

    }
  });
};

/**
 * @param {int} delay
 * @param {Callback} callback
 * */
Relais.prototype.activateAll = function(delay, callback) {
  this._iterateAllRelais(delay, this.setSingle, callback);
};

/**
 * @param {int} delay
 * @param {Callback} callback
 * */
Relais.prototype.deactivateAll = function(delay, callback) {
  this._iterateAllRelais(delay, this.delSingle, callback);
};

/**
 * @param {uint} delay
 * @param {Function} relaisOperation - An operation that is passed a relais number that it should work on.
 * @param {Callback} callback
 * */
Relais.prototype._iterateAllRelais = function(delay, relaisOperation, callback) {
  var self = this;
  if ( !delay ) {
    delay = 0;
  }
  if ( !callback ) {
    callback = function() {};
  }

  if ( delay === 0 ) {
    relaisOperation.call(self, self.getAllRelais());
  } else {
    var relaisNums = [ 1, 2, 4, 8, 16, 32, 64, 128 ];

    async.eachSeries(relaisNums, function(item, cb) {

      // item == relaisnum
      relaisOperation.call(self, item);

      setTimeout(function() {
        cb();
      }, delay);

    }, function(err) {
      // done
      callback();
    });

  }
};

module.exports = Relais;
<|endoftext|>


<commit_before>/**
 * Copyright 2013 Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @providesModule ReactDOMTextarea
 */

"use strict";

var ReactCompositeComponent = require('ReactCompositeComponent');
var ReactDOM = require('ReactDOM');

var invariant = require('invariant');

// Store a reference to the <textarea> `ReactNativeComponent`.
var textarea = ReactDOM.textarea;

// For quickly matching children type, to test if can be treated as content.
var CONTENT_TYPES = {'string': true, 'number': true};

var getTextContent = function(props) {
  return props.content != null ? '' + props.content :
    CONTENT_TYPES[typeof props.children] ? '' + props.children : '';
};

/**
 * Since setting .textContent on a dirty <textarea> doesn't update its value,
 * we intercept prop changes here to make sure that value is updated.
 */
var ReactDOMTextarea = ReactCompositeComponent.createClass({
  render: function() {
    invariant(
      this.props.dangerouslySetInnerHTML == null,
      '`dangerouslySetInnerHTML` does not make sense on textarea.'
    );
    invariant(
      this.props.children == null || CONTENT_TYPES[typeof this.props.children],
      'When present, textarea children must be a single string or number.'
    );
    return this.transferPropsTo(textarea(null, this.props.children));
  },

  componentDidUpdate: function(prevProps, prevState, rootNode) {
    var oldContent = getTextContent(prevProps);
    var newContent = getTextContent(this.props);
    if (oldContent !== newContent) {
      rootNode.value = newContent;
    }
  }
});

module.exports = ReactDOMTextarea;
<commit_msg>Don't set value if it'll be a noop<commit_after>/**
 * Copyright 2013 Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @providesModule ReactDOMTextarea
 */

"use strict";

var ReactCompositeComponent = require('ReactCompositeComponent');
var ReactDOM = require('ReactDOM');

var invariant = require('invariant');

// Store a reference to the <textarea> `ReactNativeComponent`.
var textarea = ReactDOM.textarea;

// For quickly matching children type, to test if can be treated as content.
var CONTENT_TYPES = {'string': true, 'number': true};

var getTextContent = function(props) {
  return props.content != null ? '' + props.content :
    CONTENT_TYPES[typeof props.children] ? '' + props.children : '';
};

/**
 * Since setting .textContent on a dirty <textarea> doesn't update its value,
 * we intercept prop changes here to make sure that value is updated.
 */
var ReactDOMTextarea = ReactCompositeComponent.createClass({
  render: function() {
    invariant(
      this.props.dangerouslySetInnerHTML == null,
      '`dangerouslySetInnerHTML` does not make sense on textarea.'
    );
    invariant(
      this.props.children == null || CONTENT_TYPES[typeof this.props.children],
      'When present, textarea children must be a single string or number.'
    );
    return this.transferPropsTo(textarea(null, this.props.children));
  },

  componentDidUpdate: function(prevProps, prevState, rootNode) {
    var oldContent = getTextContent(prevProps);
    var newContent = getTextContent(this.props);
    if (oldContent !== newContent && rootNode.value !== newContent) {
      rootNode.value = newContent;
    }
  }
});

module.exports = ReactDOMTextarea;
<|endoftext|>