<commit_before>define(['react', 'reactDom', 'reactAddons'], function(React, ReactDOM, ReactAddons) {
  "use strict";

  var columns = 31, rows = 51;
var create2DArray = function(num) {
    var obj = [];
    for (var i = 0; i < num; i++) {
      obj[i] = [];
    }
    return obj;
  };

  var getRandomNumber = function(num) {
    return Math.floor((Math.random() * num) + 0);
  };

var CommonMixins = {
columns: columns,
rows: rows,
rgba: 255,
      rgba1: create2DArray(columns),
      rgba2: create2DArray(columns),
      rgba3: create2DArray(columns),
      cells: create2DArray(columns),
  create2DArray: create2DArray,
  getRandomNumber: getRandomNumber
};

var Cell = React.createClass({
  mixins: [CommonMixins, ReactAddons.addons.PureRenderMixin],
  getInitialState: function() {
    return {
      rndX: 0,
      rndY: 0
    }
  },
  render: function() {
    var x = this.props.x;
    var y = this.props.y;
    var rndX = this.getRandomNumber(this.columns);
    var rndY = this.getRandomNumber(this.rows);

    this.cells[rndX][rndY] = this.props.values.rndNum;
    this.rgba1[rndX][rndY] = this.props.values.rgba1Val;
    this.rgba2[rndX][rndY] = this.props.values.rgba2Val;
    this.rgba3[rndX][rndY] = this.props.values.rgba3Val;

    var style = {
      backgroundColor: "rgba(" + this.rgba1[x][y] + "," + this.rgba2[x][y] + "," + this.rgba3[x][y] + "," + 1 + ")"
    };

    return (

{this.cells[x][y]}

    )
  }
});

var Column = React.createClass({
  mixins: [ReactAddons.addons.PureRenderMixin],
  render: function() {
    var column = [];
    for (var i = 0; i < this.props.columns; i++) {
      column.push(<Cell x={i} y={this.props.id} key={i} values={this.props.values} />);
    }
    return (
{column}
)
  }
});

var Row = React.createClass({
  mixins: [ReactAddons.addons.PureRenderMixin],
  render: function() {
    var row = [];
    for (var i = 0; i < this.props.rows; i++) {
      row.push(<Column id={i} key={i} columns={this.props.columns} values={this.props.values} />);
    }
    return (
{row}
)
  }
});

var Grid = React.createClass({
  mixins: [ReactAddons.addons.PureRenderMixin],
  render: function() {
    return (
<Row rows={this.props.rows} columns={this.props.columns} values={this.props.values} />
)
  }
});

var Buttons = React.createClass({
  mixins: [ReactAddons.addons.PureRenderMixin],
  render: function() {
  console.log(this.props.isInterval);
    return (

          {!this.props.isInterval
          ? <button className="btn btn-primary" onClick={this.props.start}>Start</button>
          : <button className="btn btn-danger" onClick={this.props.stop}>Stop</button>
          }

    )
  }
});

var GridMixins = {

  getInitialState: function() {
    return {
      interval: {},
        isInterval: false,
    values: {}
    }
  },
  randomizeCells: function() {
    this.setState({
    isInterval: true,
    values: {
    rndNum: this.getRandomNumber(this.rgba),
    rgba1Val: this.getRandomNumber(this.rgba),
    rgba2Val: this.getRandomNumber(this.rgba),
    rgba3Val: this.getRandomNumber(this.rgba)
    }
    });
  },
  startTimer: function() {
    console.log("Starting timer...");
    this.state.interval = setInterval(this.randomizeCells, 0);
  },
  stopTimer: function() {
    console.log("Stopping timer...");
    clearInterval(this.state.interval);
    this.setState({
      isInterval: false
    });
  }
};

var GridWrapper1 = React.createClass({
  mixins: [CommonMixins, GridMixins, ReactAddons.addons.PureRenderMixin],
  render: function() {
    return (

        <Buttons start={this.startTimer} stop={this.stopTimer} isInterval={this.state.isInterval} />

<Grid columns={this.columns} rows={this.rows} values={this.state.values} />

    )
  }
});

  var GridWrapper2 = React.createClass({
    mixins: [CommonMixins, GridMixins, ReactAddons.addons.PureRenderMixin],
    render: function() {
      return (

        <Buttons start={this.startTimer} stop={this.stopTimer} isInterval={this.state.isInterval} />

<Grid columns={this.columns} rows={this.rows} values={this.state.values} />

<Grid columns={this.columns} rows={this.rows} values={this.state.values} />

<Grid columns={this.columns} rows={this.rows} values={this.state.values} />

<Grid columns={this.columns} rows={this.rows} values={this.state.values} />

<Grid columns={this.columns} rows={this.rows} values={this.state.values} />

      )
    }
  });

  var Directive = function(log, stateParams) {
    return {
      restrict: "E",
      link: function() {

        // var perf = Perf.addons.Perf;
        // window.perf = perf;
        // perf.start();
        // perf.stop();
        // var measurements = perf.getLastMeasurements();
        // console.table(perf.getLastMeasurements());
        // console.table(perf.printWasted());
        // perf.printInclusive(measurements);
        // perf.printExclusive(measurements);
        // perf.printWasted(measurements);
        // perf.printDOM(measurements);

        if (stateParams.version === "1") {
          ReactDOM.render(
            <GridWrapper1 />,
              document.getElementById('react')
          );
        } else {
          ReactDOM.render(
            <GridWrapper2 />,
              document.getElementById('react')
          );
        }

      }

    }
  };

  Directive.$inject = ["$log", "$stateParams"];

  return Directive;

});

<commit_msg>undoing speedtest for react<commit_after>define(['react', 'reactDom', 'reactAddons'], function(React, ReactDOM, ReactAddons) {
  "use strict";

  var columns = 31, rows = 51;
var create2DArray = function(num) {
    var obj = [];
    for (var i = 0; i < num; i++) {
      obj[i] = [];
    }
    return obj;
  };

  var getRandomNumber = function(num) {
    return Math.floor((Math.random() * num) + 0);
  };

var CommonMixins = {
columns: columns,
rows: rows,
rgba: 255,
  create2DArray: create2DArray,
  getRandomNumber: getRandomNumber
};

var Cell = React.createClass({
  mixins: [CommonMixins, ReactAddons.addons.PureRenderMixin],
  getInitialState: function() {
    return {
      rndX: 0,
      rndY: 0,
      x: 0,
      y: 0,
      keys: {
        rgba1: create2DArray(columns),
        rgba2: create2DArray(columns),
        rgba3: create2DArray(columns),
        cells: create2DArray(columns)
      },
      style: {}
    }
  },
  render: function() {
    this.state.x = this.props.x;
    this.state.y = this.props.y;
    this.state.rndX = this.getRandomNumber(this.columns);
    this.state.rndY = this.getRandomNumber(this.rows);
    var x = this.state.x,
        y = this.state.y,
        rndX = this.state.rndX,
        rndY = this.state.rndY;

    var areKeys = Object.keys(this.state.keys).length > 0;
    if (areKeys) {
      var values = this.state.objects;
      this.state.keys.cells[rndX][rndY] = this.props.objects.rndNum;
      this.state.keys.rgba1[rndX][rndY] = this.props.objects.rgba1Val;
      this.state.keys.rgba2[rndX][rndY] = this.props.objects.rgba2Val;
      this.state.keys.rgba3[rndX][rndY] = this.props.objects.rgba3Val;

      this.state.style = {
        backgroundColor: "rgba(" + this.state.keys.rgba1[x][y] + "," + this.state.keys.rgba2[x][y] + "," + this.state.keys.rgba3[x][y] + "," + 1 + ")"
      };
    } else {
      this.state.style = {
        color: "white"
      }
    }

    return (

        {areKeys ? this.state.keys.cells[x][y] : ''}

    )
  }
});

var Column = React.createClass({
  mixins: [ReactAddons.addons.PureRenderMixin],
  render: function() {
    var column = [];
    for (var i = 0; i < this.props.columns; i++) {
      column.push(<Cell x={i} y={this.props.id} key={i} objects={this.props.objects} />);
    }
    return (
{column}
)
  }
});

var Row = React.createClass({
  mixins: [ReactAddons.addons.PureRenderMixin],
  render: function() {
    var row = [];
    for (var i = 0; i < this.props.rows; i++) {
      row.push(<Column id={i} key={i} columns={this.props.columns} objects={this.props.objects} />);
    }
    return (
{row}
)
  }
});

var Grid = React.createClass({
  mixins: [ReactAddons.addons.PureRenderMixin],
  render: function() {
    return (
<Row rows={this.props.rows} columns={this.props.columns} objects={this.props.objects} />
)
  }
});

var Buttons = React.createClass({
  mixins: [ReactAddons.addons.PureRenderMixin],
  render: function() {
  console.log(this.props.isInterval);
    return (

          {!this.props.isInterval
          ? <button className="btn btn-primary" onClick={this.props.start}>Start</button>
          : <button className="btn btn-danger" onClick={this.props.stop}>Stop</button>
          }

    )
  }
});

var GridMixins = {
  getInitialState: function() {
    return {
      interval: {},
      isInterval: false,
      objects: {}
    }
  },
  randomizeCells: function() {
    this.setState({
      isInterval: true,
      objects: {
        rndNum: this.getRandomNumber(this.rgba),
        rgba1Val: this.getRandomNumber(this.rgba),
        rgba2Val: this.getRandomNumber(this.rgba),
        rgba3Val: this.getRandomNumber(this.rgba)
      }
    });
  },
  startTimer: function() {
    console.log("Starting timer...");
    this.state.interval = setInterval(this.randomizeCells, 0);
  },
  stopTimer: function() {
    console.log("Stopping timer...");
    clearInterval(this.state.interval);
    this.setState({
      isInterval: false
    });
  }
};

var GridWrapper1 = React.createClass({
  mixins: [CommonMixins, GridMixins, ReactAddons.addons.PureRenderMixin],
  render: function() {
    return (

        <Buttons start={this.startTimer} stop={this.stopTimer} isInterval={this.state.isInterval} />

<Grid columns={this.columns} rows={this.rows} objects={this.state.objects} />

    )
  }
});

  var GridWrapper2 = React.createClass({
    mixins: [CommonMixins, GridMixins, ReactAddons.addons.PureRenderMixin],
    render: function() {
      return (

        <Buttons start={this.startTimer} stop={this.stopTimer} isInterval={this.state.isInterval} />

<Grid columns={this.columns} rows={this.rows} objects={this.state.objects} />

<Grid columns={this.columns} rows={this.rows} objects={this.state.objects} />

<Grid columns={this.columns} rows={this.rows} objects={this.state.objects} />

<Grid columns={this.columns} rows={this.rows} objects={this.state.objects} />

<Grid columns={this.columns} rows={this.rows} objects={this.state.objects} />

      )
    }
  });

  var Directive = function(log, stateParams) {
    return {
      restrict: "E",
      link: function() {

        // var perf = Perf.addons.Perf;
        // window.perf = perf;
        // perf.start();
        // perf.stop();
        // var measurements = perf.getLastMeasurements();
        // console.table(perf.getLastMeasurements());
        // console.table(perf.printWasted());
        // perf.printInclusive(measurements);
        // perf.printExclusive(measurements);
        // perf.printWasted(measurements);
        // perf.printDOM(measurements);

        if (stateParams.version === "1") {
          ReactDOM.render(
            <GridWrapper1 />,
              document.getElementById('react')
          );
        } else {
          ReactDOM.render(
            <GridWrapper2 />,
              document.getElementById('react')
          );
        }

      }

    }
  };

  Directive.$inject = ["$log", "$stateParams"];

  return Directive;

});

<|endoftext|>


<commit_before>(function () {
    'use strict';

    var website = openerp.website;
    website.templates.push('/website/static/src/xml/website.translator.xml');
    var nodialog = 'website_translator_nodialog';

    website.EditorBar.include({
        start: function () {
            var self = this;
            return this._super.apply(this, arguments).then(function () {
                // TODO: refactor once xmo's feature branch is merged
                //       and make t-field's work as well as text translation
                self.$('button[data-action=edit]').text("Translate");
                self.$('[data-action=snippet]').hide();
                self.$('#customize-menu-button').hide();
            });
        },
        edit: function () {
            var self = this;
            if (!localStorage[nodialog]) {
                var dialog = new website.TranslatorDialog();
                dialog.appendTo($(document.body));
                dialog.on('activate', this, function () {
                    localStorage[nodialog] = dialog.$('input[name=do_not_show]').prop('checked') || '';
                    dialog.$el.modal('hide');
                    this.translate();
                });
            } else {
                this.translate();
            }
        },
        translate: function () {
            var self = this;
            // this.edit();
            this.$('#website-top-view').hide();
            this.$('#website-top-edit').show();
            $('.css_non_editable_mode_hidden').removeClass("css_non_editable_mode_hidden");

            this.translations = null;
            openerp.jsonRpc('/website/get_view_translations', 'call', {
                'xml_id': $(document.documentElement).data('view-xmlid'),
                'lang': website.get_context().lang,
            }).then(function (translations) {
                self.translations = translations;
                self.processTranslatableNodes();
            });
        },
        processTranslatableNodes: function () {
            var self = this;
            var $editables = $('[data-oe-model="ir.ui.view"]')
                    .not('link, script')
                    .not('.oe_snippets,.oe_snippet, .oe_snippet *')
                    .not('[data-oe-type]');

            $editables.each(function () {
                var view_id = $(this).attr('data-oe-source-id') || $(this).attr('data-oe-id');
                self.transNode(this, view_id|0);
            });
            $('.oe_translatable_text').prop('contenteditable', true).on('paste', function () {
                var node = $(this);
                setTimeout(function () {
                    self.sanitizeNode(node);
                }, 0);
            });
            $(document).on('blur keyup paste', '.oe_translatable_text[contenteditable]', function(ev) {
                var $node = $(this);
                setTimeout(function () {
                    // Doing stuff next tick because paste and keyup events are
                    // fired before the content is changed
                    if (ev.type == 'paste') {
                        self.sanitizeNode($node[0]);
                    }
                    if ($node.data('initial_content') !== $node.text()) {
                        $node.addClass('oe_dirty').removeClass('oe_translatable_todo');
                    }
                }, 0);
            });
        },
        sanitizeNode: function (node) {
            node.text(node.text());
        },
        isTextNode: function (node) {
            return node.nodeType === 3 || node.nodeType === 4;
        },
        isTranslatable: function (text) {
            return text && _.str.trim(text) !== '';
        },
        markTranslatableNode: function (node, view_id) {
            // TODO: link nodes with same content
            node.className += ' oe_translatable_text';
            node.setAttribute('data-oe-translation-view-id', view_id);
            var content = node.childNodes[0].data.trim();
            var trans = this.translations.filter(function (t) {
                return t.res_id === view_id && t.value === content;
            });
            if (trans.length) {
                node.setAttribute('data-oe-translation-id', trans[0].id);
            } else {
                node.className += ' oe_translatable_todo';
            }
            node.contentEditable = true;
            $(node).data('initial_content', content);
        },
        save: function () {
            var trans = {};
            // this._super.apply(this, arguments);
            $('.oe_translatable_text.oe_dirty').each(function () {
                var $node = $(this);
                var data = $node.data();
                if (!trans[data.oeTranslationViewId]) {
                    trans[data.oeTranslationViewId] = [];
                }
                trans[data.oeTranslationViewId].push({
                    initial_content: data.initial_content,
                    new_content: $node.text(),
                    translation_id: data.oeTranslationId || null
                });
            });
            openerp.jsonRpc('/website/set_translations', 'call', {
                'data': trans,
                'lang': website.get_context()['lang'],
            }).then(function () {
                website.reload();
            }).fail(function () {
                // TODO: bootstrap alert with error message
                alert("Could not save translation");
            });
        },
        transNode: function (node, view_id) {
            if (node.childNodes.length === 1
                    && this.isTextNode(node.childNodes[0])
                    && !node.getAttribute('data-oe-model')) {
                this.markTranslatableNode(node, view_id);
            } else {
                for (var i = 0, l = node.childNodes.length; i < l; i ++) {
                    var n = node.childNodes[i];
                    if (this.isTextNode(n)) {
                        if (this.isTranslatable(n.data)) {
                            var container = document.createElement('span');
                            node.insertBefore(container, n);
                            container.appendChild(n);
                            this.markTranslatableNode(container, view_id);
                        }
                    } else {
                        this.transNode(n, view_id);
                    }
                }
            }
        },
    });

    website.TranslatorDialog = openerp.Widget.extend({
        events: _.extend({}, website.EditorBar.prototype.events, {
            'hidden.bs.modal': 'destroy',
            'click button[data-action=activate]': function (ev) {
                this.trigger('activate');
            },
        }),
        template: 'website.TranslatorDialog',
        start: function () {
            this.$el.modal();
        },
    });
})();
<commit_msg>[IMP] Enabling ckeditor on translatable t-fields<commit_after>(function () {
    'use strict';

    var website = openerp.website;
    website.templates.push('/website/static/src/xml/website.translator.xml');
    var nodialog = 'website_translator_nodialog';

    website.EditorBar.include({
        start: function () {
            var self = this;
            return this._super.apply(this, arguments).then(function () {
                self.$('button[data-action=edit]').text("Translate");
                self.$('[data-action=snippet]').hide();
                self.$('#customize-menu-button').hide();
            });
        },
        edit: function () {
            var self = this;
            var mysuper = this._super;
            if (!localStorage[nodialog]) {
                var dialog = new website.TranslatorDialog();
                dialog.appendTo($(document.body));
                dialog.on('activate', this, function () {
                    localStorage[nodialog] = dialog.$('input[name=do_not_show]').prop('checked') || '';
                    dialog.$el.modal('hide');
                    this.translate();
                });
            } else {
                this.translate().then(function () {
                    // Disable non translatable t-fields
                    $('[data-oe-type][data-oe-translate=0]').removeAttr('data-oe-type');
                    mysuper.call(self);
                });
            }
        },
        translate: function () {
            var self = this;
            this.translations = null;
            return openerp.jsonRpc('/website/get_view_translations', 'call', {
                'xml_id': $(document.documentElement).data('view-xmlid'),
                'lang': website.get_context().lang,
            }).then(function (translations) {
                self.translations = translations;
                self.processTranslatableNodes();
            });
        },
        processTranslatableNodes: function () {
            var self = this;
            var $editables = $('[data-oe-model="ir.ui.view"]')
                    .not('link, script')
                    .not('.oe_snippets,.oe_snippet, .oe_snippet *')
                    .not('[data-oe-type]');

            $editables.each(function () {
                var $node = $(this);
                var view_id = $node.attr('data-oe-source-id') || $node.attr('data-oe-id');
                self.transNode(this, view_id|0);
            });
            $('.oe_translatable_text').on('paste', function () {
                var node = $(this);
                setTimeout(function () {
                    self.sanitizeNode(node);
                }, 0);
            });
            $(document).on('blur keyup paste', '.oe_translatable_text[contenteditable]', function(ev) {
                var $node = $(this);
                setTimeout(function () {
                    // Doing stuff next tick because paste and keyup events are
                    // fired before the content is changed
                    if (ev.type == 'paste') {
                        self.sanitizeNode($node[0]);
                    }
                    if ($node.data('initial_content') !== $node.text()) {
                        $node.addClass('oe_dirty').removeClass('oe_translatable_todo');
                    }
                }, 0);
            });
        },
        sanitizeNode: function (node) {
            node.text(node.text());
        },
        isTextNode: function (node) {
            return node.nodeType === 3 || node.nodeType === 4;
        },
        isTranslatable: function (text) {
            return text && _.str.trim(text) !== '';
        },
        markTranslatableNode: function (node, view_id) {
            // TODO: link nodes with same content
            node.className += ' oe_translatable_text';
            node.setAttribute('data-oe-translation-view-id', view_id);
            var content = node.childNodes[0].data.trim();
            var trans = this.translations.filter(function (t) {
                return t.res_id === view_id && t.value === content;
            });
            if (trans.length) {
                node.setAttribute('data-oe-translation-id', trans[0].id);
            } else {
                node.className += ' oe_translatable_todo';
            }
            node.contentEditable = true;
            $(node).data('initial_content', content);
        },
        save: function () {
            var trans = {};
            // this._super.apply(this, arguments);
            $('.oe_translatable_text.oe_dirty').each(function () {
                var $node = $(this);
                var data = $node.data();
                if (!trans[data.oeTranslationViewId]) {
                    trans[data.oeTranslationViewId] = [];
                }
                trans[data.oeTranslationViewId].push({
                    initial_content: data.initial_content,
                    new_content: $node.text(),
                    translation_id: data.oeTranslationId || null
                });
            });
            openerp.jsonRpc('/website/set_translations', 'call', {
                'data': trans,
                'lang': website.get_context()['lang'],
            }).then(function () {
                website.reload();
            }).fail(function () {
                // TODO: bootstrap alert with error message
                alert("Could not save translation");
            });
        },
        transNode: function (node, view_id) {
            // Mostly handling text and cdata nodes here
            // so avoid jquery usage in this function
            if (node.attributes['data-oe-type']) {
                if (node.attributes['data-oe-translate'].value == '1') {
                    node.className += ' oe_translatable_field';
                }
                return;
            } else if (node.childNodes.length === 1
                    && this.isTextNode(node.childNodes[0])
                    && !node.getAttribute('data-oe-model')) {
                this.markTranslatableNode(node, view_id);
            } else {
                for (var i = 0, l = node.childNodes.length; i < l; i ++) {
                    var n = node.childNodes[i];
                    if (this.isTextNode(n)) {
                        if (this.isTranslatable(n.data)) {
                            var container = document.createElement('span');
                            node.insertBefore(container, n);
                            container.appendChild(n);
                            this.markTranslatableNode(container, view_id);
                        }
                    } else {
                        this.transNode(n, view_id);
                    }
                }
            }
        },
    });

    website.RTE.include({
        start: function () {
            this._super.apply(this, arguments);
            this.$el.hide();
        },
        fetch_editables: function (root) {
            $(root).click(function (ev) {
                ev.preventDefault();
            });
            return $('[data-oe-translate=1]');
        }
    });

    website.TranslatorDialog = openerp.Widget.extend({
        events: _.extend({}, website.EditorBar.prototype.events, {
            'hidden.bs.modal': 'destroy',
            'click button[data-action=activate]': function (ev) {
                this.trigger('activate');
            },
        }),
        template: 'website.TranslatorDialog',
        start: function () {
            this.$el.modal();
        },
    });
})();
<|endoftext|>


<commit_before>import React from 'react';
import { shallow } from 'enzyme';
import demand from 'must';
import AlertMessages from '../AlertMessages';
import { Alert } from 'elemental';

describe('<AlertMessages />', () => {
	it('should render null if no message is passed', () => {
		const component = shallow(<AlertMessages />);
		demand(component.type()).eql(null);
	});

	it('should render a success Alert on success', () => {
		const component = shallow(
			<AlertMessages
				alerts={{
					success: {
						success: 'Something went right!',
					},
				}}
			/>
		);
		demand(component.find(Alert).length).gt(0);
		demand(component.find({ type: 'success' }).length).gt(0);
	});

	it('should render an error message', () => {
		const errorMsg = 'Something went wrong!';
		const component = shallow(
			<AlertMessages
				alerts={{
					error: {
						error: errorMsg,
					},
				}}
			/>
		);
		demand(component.find(Alert).length).gt(0);
		demand(component.find({ type: 'danger' }).length).gt(0);
		demand(component.contains(errorMsg)).true();
	});

	it('should render "error" if no validation error is specified', () => {
		const errorMsg = 'error';
		const component = shallow(
			<AlertMessages
				alerts={{
					error: {
						error: errorMsg,
						detail: {
							name: 'Not A ValidationError',
						},
					},
				}}
			/>
		);
		demand(component.find(Alert).length).gt(0);
		demand(component.find({ type: 'danger' }).length).gt(0);
		demand(component.contains('Error')).true();
	});

	it('should render a single validation error', () => {
		const errorPath = 'some/path';
		const errorMsg = 'Some Validation Error';
		const component = shallow(
			<AlertMessages
				alerts={{
					error: {
						error: 'validation errors',
						detail: {
							[errorPath]: {
								error: errorMsg,
							},
						},
					},
				}}
			/>
		);
		demand(component.contains(errorMsg)).true();
	});

	it('should render a single validation error passed in a different format', () => {
		const errorPath = 'some/path';
		const errorMsg = 'Some error message';
		const component = shallow(
			<AlertMessages
				alerts={{
					error: {
						error: errorMsg,
						detail: {
							name: 'ValidationError',
							errors: {
								[errorPath]: {
									error: errorMsg,
								},
							},
						},
					},
				}}
			/>
		);
		demand(component.contains(errorMsg)).true();
	});

	it('should render multiple validation errors', () => {
		const errors = {
			'some/path': {
				error: 'Some Validation Error',
			},
			'some/other/path': {
				error: 'Some Other Validation Error',
			},
		};
		const component = shallow(
			<AlertMessages
				alerts={{
					error: {
						error: 'validation errors',
						detail: errors,
					},
				}}
			/>
		);

		demand(component.contains(errors['some/path'].error)).true();
		demand(component.contains(errors['some/other/path'].error)).true();
		demand(component.find('li').length).equal(2);
	})
});
<commit_msg>Fix linting<commit_after>import React from 'react';
import { shallow } from 'enzyme';
import demand from 'must';
import AlertMessages from '../AlertMessages';
import { Alert } from 'elemental';

describe('<AlertMessages />', () => {
	it('should render null if no message is passed', () => {
		const component = shallow(<AlertMessages />);
		demand(component.type()).eql(null);
	});

	it('should render a success Alert on success', () => {
		const component = shallow(
			<AlertMessages
				alerts={{
					success: {
						success: 'Something went right!',
					},
				}}
			/>
		);
		demand(component.find(Alert).length).gt(0);
		demand(component.find({ type: 'success' }).length).gt(0);
	});

	it('should render an error message', () => {
		const errorMsg = 'Something went wrong!';
		const component = shallow(
			<AlertMessages
				alerts={{
					error: {
						error: errorMsg,
					},
				}}
			/>
		);
		demand(component.find(Alert).length).gt(0);
		demand(component.find({ type: 'danger' }).length).gt(0);
		demand(component.contains(errorMsg)).true();
	});

	it('should render "error" if no validation error is specified', () => {
		const errorMsg = 'error';
		const component = shallow(
			<AlertMessages
				alerts={{
					error: {
						error: errorMsg,
						detail: {
							name: 'Not A ValidationError',
						},
					},
				}}
			/>
		);
		demand(component.find(Alert).length).gt(0);
		demand(component.find({ type: 'danger' }).length).gt(0);
		demand(component.contains('Error')).true();
	});

	it('should render a single validation error', () => {
		const errorPath = 'some/path';
		const errorMsg = 'Some Validation Error';
		const component = shallow(
			<AlertMessages
				alerts={{
					error: {
						error: 'validation errors',
						detail: {
							[errorPath]: {
								error: errorMsg,
							},
						},
					},
				}}
			/>
		);
		demand(component.contains(errorMsg)).true();
	});

	it('should render a single validation error passed in a different format', () => {
		const errorPath = 'some/path';
		const errorMsg = 'Some error message';
		const component = shallow(
			<AlertMessages
				alerts={{
					error: {
						error: errorMsg,
						detail: {
							name: 'ValidationError',
							errors: {
								[errorPath]: {
									error: errorMsg,
								},
							},
						},
					},
				}}
			/>
		);
		demand(component.contains(errorMsg)).true();
	});

	it('should render multiple validation errors', () => {
		const errors = {
			'some/path': {
				error: 'Some Validation Error',
			},
			'some/other/path': {
				error: 'Some Other Validation Error',
			},
		};
		const component = shallow(
			<AlertMessages
				alerts={{
					error: {
						error: 'validation errors',
						detail: errors,
					},
				}}
			/>
		);

		demand(component.contains(errors['some/path'].error)).true();
		demand(component.contains(errors['some/other/path'].error)).true();
		demand(component.find('li').length).equal(2);
	});
});
<|endoftext|>


<commit_before>var linz = require('../'),
    async = require('async'),
    moment = require('moment');

let useLocalTime = false;
let dateFormat = false;

var modelExportHelpers = function modelExportHelpers (req, res) {

    var getRecordData = function getRecordData (fields, record) {

        var orderedRecord = {};

        // order field names in the selected order
        fields.forEach(function (fieldName) {
            orderedRecord[fieldName] = prettifyData(fieldName, record[fieldName]);
        });

        return linz.utils.json2CSV(orderedRecord);

    };

    /**
     * Format a date object using the list dsl settings.
     * @param {Date} val A date object.
     * @return {String} A date string.
     */
    const formatDate = (val) => {

        if (!(val instanceof Date)) {
            return val;
        }

        return moment(val)
            .utcOffset((useLocalTime && linz.api.session.getTimezone(req)) || 0)
            .format((typeof dateFormat === 'string') && dateFormat);

    };

    var prettifyData = function prettifyData (fieldName, val) {

        if (val === undefined || val === '' || val === null) {
            return val;
        }

        if (typeof val === 'number') {
            return val;
        }

        var Model = req.linz.model;

        if (Model.schema.tree[fieldName].ref) {
            return val.title;
        }

        if (Array.isArray(val)) {

            if (val.length === 0) {
                return '';
            }

            var strArr = [];

            val.forEach(function (obj) {

                if (typeof obj === 'object') {

                    var arr = [];

                    // tostring embedded object
                    Object.keys(obj).forEach(function (key) {
                        if (key === '_id' || key === 'id' || key === 'dateModified' || key === 'dateCreated' || obj[key] === '' || obj[key] === undefined) {
                            return;
                        }
                        return arr.push(key + ': ' + (obj[key] || '').toString());
                    });

                    return strArr.push(arr.join(', '));
                }

                return strArr.push(obj.toString());

            });

            return strArr.join(', ');

        }

        if (typeof val === 'boolean' || 'true,false,yes,no'.indexOf(val) >= 0 ) {
            return (linz.utils.asBoolean(val) ? 'Yes' : 'No');
        }

        if (val instanceof Date) {
            val = formatDate(val);
        }

        return val;

    };

    return {

        mongooseToCSV: function mongooseToCSV (fields, form) {

            var count = 0;

            return function (doc) {

                var str = '',
                    record = doc.toObject({virtuals: true });

                if (count !== 0) {
                    return getRecordData(fields, record);
                }

                var arr = [];

                // get field labels
                fields.forEach(function (fieldName) {
                    arr.push(form[fieldName].label);
                });

                str = arr.join() + '\n';

                count++;

                return str + getRecordData(fields, record);

            };

        },

        getFilters: function getFilters (cb) {

            if (!req.body.filters.length) {
                return cb(null, {});
            }

            var Model = req.linz.model,
                form = JSON.parse(req.body.filters);

            // check if there are any filters in the form post
            if (!form.selectedFilters) {
                return cb(null, {});
            }

            async.waterfall([

                function (callback) {

                    Model.getList(req, function (err, list) {

                        if (err) {
                            return cb(err);
                        }

                        return callback(null, list);

                    });
                },

                function (list, callback) {

                    var filters = {};

                    async.each(form.selectedFilters.split(','), function (fieldName, filtersDone) {

                        // call the filter renderer and update the content with the result
                        list.filters[fieldName].filter.filter(fieldName, form, function (err, result) {

                            if (err) {
                                return filtersDone(err);
                            }

                            filters = Model.addSearchFilter(filters, result);

                            return filtersDone(null);

                        });

                    }, function (err) {

                        if (err) {
                            return cb(err);
                        }

                        // consolidate filters into query
                        if (Object.keys(filters).length) {
                            filters = Model.setFiltersAsQuery(filters);
                        }

                        return cb(err, filters);

                    });

                }

            ]);

        },

        addIdFilters: function addIdFilters (filters, cb) {

            if (!req.body.selectedIds.length) {
                return cb(null, filters);
            }

            filters = filters || { '$and': [] };
            filters['$and'] = filters['$and'] || [];

            var ids = [],
                db  = linz.mongoose.connection.db;

            // compile ids into ObjectId type
            req.body.selectedIds.split(',').forEach(function (id) {
                ids.push(new db.bsonLib.ObjectID(id));
            });

            // let's add selected Ids to the filters
            filters['$and'].push({
                _id: { $in: ids}
            });

            return cb(null, filters);

        },

        getForm: function getForm(filters, cb) {

            req.linz.model.getForm(req, function (err, form) {
                return cb(err, filters, form);
            });

        },

        getList: function getList (filters, form, cb) {

            req.linz.model.getList(req, function (err, list) {
                return cb(err, filters, form, list);
            });

        }

    };

};

// this will retrieve the export object, using Linz's default export handler amongst custom export handlers
// this is based on the knowledge that only Linz's default export handler can have an `action` of `export`
// `exports` should be model.list.export
var getExport = function (exports) {

    var exp = undefined;

    // retrieve the export object
    exports.forEach(function (_export) {

        // Linz's default export function is called export
        if (_export.action && _export.action === 'export') {
            exp = _export;
        }

    });

    // if the export object could not be found, throw an error
    if (!exp) {
        throw new Error('The export was using Linz default export method, yet the model\'s list.export object could not be found.');
    }

    return exp;

}

module.exports = {

    get: function (req, res, next) {

        req.linz.model.getList(req, function (err, list) {

            if (err) {
                return next(err);
            }

            // attach our list object to the model
            req.linz.model.list = list;

            // retrieve the export object
            req.linz.export = getExport(list.export);
            req.linz.export.fields = {};

            // retrieve the form to provide a list of fields to choose from
            req.linz.model.getForm(req, function (formErr, form){

                if (formErr) {
                    return next(formErr);
                }

                var excludedFieldNames = req.linz.export.exclusions.concat(',__v').split(','),
                    fieldLabels = {};

                // get a list of field names
                req.linz.model.schema.eachPath(function (pathname, schemaType) {

                    if (excludedFieldNames.indexOf(pathname) >= 0 || !form[pathname]) {
                        // exit if current field name is one of the exclusion fields or missing
                        return;
                    }

                    // get a list of fields by the label ready for sorting
                    fieldLabels[form[pathname].label] = pathname;

                });

                // sort fields by label
                var sortedFieldsByLabel = Object.keys(fieldLabels).sort();

                // iterate through sorted label and re-constructs in the order of labels
                sortedFieldsByLabel.forEach(function (label) {
                    req.linz.export.fields[fieldLabels[label]] = label;
                });

                return next(null);

            });

        });

    },

    post: function (req, res, next) {

        var Model = req.linz.model;

        // since a custom export function is not defined for model, use local export function
        var asyncFn = [],
            helpers = modelExportHelpers(req, res);

        asyncFn.push(helpers.getFilters);
        asyncFn.push(helpers.addIdFilters);
        asyncFn.push(helpers.getForm);
        asyncFn.push(helpers.getList);

        // get the actual export object
        asyncFn.push(function (filters, form, list, callback) {

            // retrieve the export
            var _export = getExport(list.export);
            _export.fields = {};

            return callback(null, filters, form, list, _export);

        });

        async.waterfall(asyncFn, function (err, filters, form, list, exportObj) {

            if (err) {
                return next(err);
            }

            useLocalTime = exportObj && exportObj.useLocalTime;
            dateFormat = exportObj && exportObj.dateFormat;

            var fields = req.body.selectedFields.split(','),
                refFieldNames = [],
                filterFieldNames = [];

            fields.forEach(function (fieldName, index, arr) {

                // remove any property that doesn't exist in the model
                if (!Model.schema.tree[fieldName]) {
                    return arr.splice(index, 1);
                }

                // check if there any ref fields selected to be exported
                if(!Model.schema.tree[fieldName].ref) {
                    return;
                }

                return refFieldNames.push(fieldName);

            });

            // check if _id is excluded
            if (exportObj.exclusions.indexOf('_id') >= 0) {
                filterFieldNames.push('-_id');
            }

            filterFieldNames = filterFieldNames.concat(fields);

            // serve .csv file
            res.setHeader('Content-disposition', 'attachment; filename=' + Model.linz.formtools.model.plural + '-' + moment(Date.now()).format('l').replace(/\//g, '.', 'g') + '.csv');
            res.writeHead(200, { 'Content-Type': 'text/csv' });

            // pipe data to response stream
            req.linz.model.getQuery(req, filters, function getQuery (err, query) {

                if (err) {
                    return next(err);
                }

                query.select(filterFieldNames.join(' '))
                    .populate(refFieldNames.join(' '), '-_id -__v -dateCreated -dateModified -createdBy -modifiedBy')
                    .stream({ transform: helpers.mongooseToCSV(fields, form) })
                    .pipe(res);

            });

        });

    }
};
<commit_msg>Make export fields opt out again<commit_after>var linz = require('../'),
    async = require('async'),
    moment = require('moment');

let useLocalTime = false;
let dateFormat = false;

var modelExportHelpers = function modelExportHelpers (req, res) {

    const { labels } = req.linz.model.linz.formtools;

    var getRecordData = function getRecordData (fields, record) {

        var orderedRecord = {};

        // order field names in the selected order
        fields.forEach(function (fieldName) {
            orderedRecord[fieldName] = prettifyData(fieldName, record[fieldName]);
        });

        return linz.utils.json2CSV(orderedRecord);

    };

    /**
     * Format a date object using the list dsl settings.
     * @param {Date} val A date object.
     * @return {String} A date string.
     */
    const formatDate = (val) => {

        if (!(val instanceof Date)) {
            return val;
        }

        return moment(val)
            .utcOffset((useLocalTime && linz.api.session.getTimezone(req)) || 0)
            .format((typeof dateFormat === 'string') && dateFormat);

    };

    var prettifyData = function prettifyData (fieldName, val) {

        if (val === undefined || val === '' || val === null) {
            return val;
        }

        if (typeof val === 'number') {
            return val;
        }

        var Model = req.linz.model;

        if (Model.schema.tree[fieldName].ref) {
            return val.title;
        }

        if (Array.isArray(val)) {

            if (val.length === 0) {
                return '';
            }

            var strArr = [];

            val.forEach(function (obj) {

                if (typeof obj === 'object') {

                    var arr = [];

                    // tostring embedded object
                    Object.keys(obj).forEach(function (key) {
                        if (key === '_id' || key === 'id' || key === 'dateModified' || key === 'dateCreated' || obj[key] === '' || obj[key] === undefined) {
                            return;
                        }
                        return arr.push(key + ': ' + (obj[key] || '').toString());
                    });

                    return strArr.push(arr.join(', '));
                }

                return strArr.push(obj.toString());

            });

            return strArr.join(', ');

        }

        if (typeof val === 'boolean' || 'true,false,yes,no'.indexOf(val) >= 0 ) {
            return (linz.utils.asBoolean(val) ? 'Yes' : 'No');
        }

        if (val instanceof Date) {
            val = formatDate(val);
        }

        return val;

    };

    return {

        mongooseToCSV: function mongooseToCSV (fields, form) {

            var count = 0;

            return function (doc) {

                var str = '',
                    record = doc.toObject({virtuals: true });

                if (count !== 0) {
                    return getRecordData(fields, record);
                }

                var arr = [];

                // get field labels
                fields.forEach(function (fieldName) {
                    arr.push(labels[fieldName]);
                });

                str = arr.join() + '\n';

                count++;

                return str + getRecordData(fields, record);

            };

        },

        getFilters: function getFilters (cb) {

            if (!req.body.filters.length) {
                return cb(null, {});
            }

            var Model = req.linz.model,
                form = JSON.parse(req.body.filters);

            // check if there are any filters in the form post
            if (!form.selectedFilters) {
                return cb(null, {});
            }

            async.waterfall([

                function (callback) {

                    Model.getList(req, function (err, list) {

                        if (err) {
                            return cb(err);
                        }

                        return callback(null, list);

                    });
                },

                function (list, callback) {

                    var filters = {};

                    async.each(form.selectedFilters.split(','), function (fieldName, filtersDone) {

                        // call the filter renderer and update the content with the result
                        list.filters[fieldName].filter.filter(fieldName, form, function (err, result) {

                            if (err) {
                                return filtersDone(err);
                            }

                            filters = Model.addSearchFilter(filters, result);

                            return filtersDone(null);

                        });

                    }, function (err) {

                        if (err) {
                            return cb(err);
                        }

                        // consolidate filters into query
                        if (Object.keys(filters).length) {
                            filters = Model.setFiltersAsQuery(filters);
                        }

                        return cb(err, filters);

                    });

                }

            ]);

        },

        addIdFilters: function addIdFilters (filters, cb) {

            if (!req.body.selectedIds.length) {
                return cb(null, filters);
            }

            filters = filters || { '$and': [] };
            filters['$and'] = filters['$and'] || [];

            var ids = [],
                db  = linz.mongoose.connection.db;

            // compile ids into ObjectId type
            req.body.selectedIds.split(',').forEach(function (id) {
                ids.push(new db.bsonLib.ObjectID(id));
            });

            // let's add selected Ids to the filters
            filters['$and'].push({
                _id: { $in: ids}
            });

            return cb(null, filters);

        },

        getForm: function getForm(filters, cb) {

            req.linz.model.getForm(req, function (err, form) {
                return cb(err, filters, form);
            });

        },

        getList: function getList (filters, form, cb) {

            req.linz.model.getList(req, function (err, list) {
                return cb(err, filters, form, list);
            });

        }

    };

};

// this will retrieve the export object, using Linz's default export handler amongst custom export handlers
// this is based on the knowledge that only Linz's default export handler can have an `action` of `export`
// `exports` should be model.list.export
var getExport = function (exports) {

    var exp = undefined;

    // retrieve the export object
    exports.forEach(function (_export) {

        // Linz's default export function is called export
        if (_export.action && _export.action === 'export') {
            exp = _export;
        }

    });

    // if the export object could not be found, throw an error
    if (!exp) {
        throw new Error('The export was using Linz default export method, yet the model\'s list.export object could not be found.');
    }

    return exp;

}

module.exports = {

    get: function (req, res, next) {

        const { labels } = req.linz.model.linz.formtools;

        req.linz.model.getList(req, function (err, list) {

            if (err) {
                return next(err);
            }

            // attach our list object to the model
            req.linz.model.list = list;

            // retrieve the export object
            req.linz.export = getExport(list.export);
            req.linz.export.fields = {};

            var excludedFieldNames = req.linz.export.exclusions.concat(',__v').split(','),
                    fieldLabels = {};

                // get a list of field names
                req.linz.model.schema.eachPath(function (pathname) {

                    if (excludedFieldNames.indexOf(pathname) >= 0) {
                        // exit if current field name is one of the exclusion fields
                        return;
                    }

                    // get a list of fields by the label ready for sorting
                    fieldLabels[labels[pathname]] = pathname;

                });

                // sort fields by label
                var sortedFieldsByLabel = Object.keys(fieldLabels).sort();

                // iterate through sorted label and re-constructs in the order of labels
                sortedFieldsByLabel.forEach(function (label) {
                    req.linz.export.fields[fieldLabels[label]] = label;
                });

                return next(null);

        });

    },

    post: function (req, res, next) {

        var Model = req.linz.model;

        // since a custom export function is not defined for model, use local export function
        var asyncFn = [],
            helpers = modelExportHelpers(req, res);

        asyncFn.push(helpers.getFilters);
        asyncFn.push(helpers.addIdFilters);
        asyncFn.push(helpers.getForm);
        asyncFn.push(helpers.getList);

        // get the actual export object
        asyncFn.push(function (filters, form, list, callback) {

            // retrieve the export
            var _export = getExport(list.export);
            _export.fields = {};

            return callback(null, filters, form, list, _export);

        });

        async.waterfall(asyncFn, function (err, filters, form, list, exportObj) {

            if (err) {
                return next(err);
            }

            useLocalTime = exportObj && exportObj.useLocalTime;
            dateFormat = exportObj && exportObj.dateFormat;

            var fields = req.body.selectedFields.split(','),
                refFieldNames = [],
                filterFieldNames = [];

            fields.forEach(function (fieldName, index, arr) {

                // remove any property that doesn't exist in the model
                if (!Model.schema.tree[fieldName]) {
                    return arr.splice(index, 1);
                }

                // check if there any ref fields selected to be exported
                if(!Model.schema.tree[fieldName].ref) {
                    return;
                }

                return refFieldNames.push(fieldName);

            });

            // check if _id is excluded
            if (exportObj.exclusions.indexOf('_id') >= 0) {
                filterFieldNames.push('-_id');
            }

            filterFieldNames = filterFieldNames.concat(fields);

            // serve .csv file
            res.setHeader('Content-disposition', 'attachment; filename=' + Model.linz.formtools.model.plural + '-' + moment(Date.now()).format('l').replace(/\//g, '.', 'g') + '.csv');
            res.writeHead(200, { 'Content-Type': 'text/csv' });

            // pipe data to response stream
            req.linz.model.getQuery(req, filters, function getQuery (err, query) {

                if (err) {
                    return next(err);
                }

                query.select(filterFieldNames.join(' '))
                    .populate(refFieldNames.join(' '), '-_id -__v -dateCreated -dateModified -createdBy -modifiedBy')
                    .stream({ transform: helpers.mongooseToCSV(fields, form) })
                    .pipe(res);

            });

        });

    }
};
<|endoftext|>


<commit_before>/**************************************************************************
*          (C) Vrije Universiteit, Amsterdam (the Netherlands)            *
*                                                                         *
* This file is part of AmCAT - The Amsterdam Content Analysis Toolkit     *
*                                                                         *
* AmCAT is free software: you can redistribute it and/or modify it under  *
* the terms of the GNU Affero General Public License as published by the  *
* Free Software Foundation, either version 3 of the License, or (at your  *
* option) any later version.                                              *
*                                                                         *
* AmCAT is distributed in the hope that it will be useful, but WITHOUT    *
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or   *
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public     *
* License for more details.                                               *
*                                                                         *
* You should have received a copy of the GNU Affero General Public        *
* License along with AmCAT.  If not, see <http://www.gnu.org/licenses/>.  *
***************************************************************************/

/*
 * Widgets represent codingschemafields in the DOM. For each type, a property
 * on `widgets` is registered which can be used to generate html elements,
 * get and set values. Furthermore, general functions are available which
 * don't require the caller to know anything about the schematypes.
 */

function get_input(widget){
    if (widget.hasClass("codingvalue")) return widget;
    return widget.find(".codingvalue");
}

widgets = (function(self){
    self.EMPTY_INTVAL = "null";
    self.SCHEMATYPES = {
        1: "text",
        2: "number",
        5: "codebook",
        7: "boolean",
        9: "quality"
    };

    /* WIDGET TYPES */
    /* A widget must implement three methods: get_html, get_value and set_value. The
     * documentation for the methods is given below, as well as a default implementation. */
    self.default = {
        /*
         * Returns exactly one element which is henceforth known as a 'widget'. It is later
         * passed to get_value and set_value.
         */
        get_html : function(schemafield, _intval){
            var widget = $("<input>").addClass("codingvalue");
            return (_intval) ? widget.attr("intval", self.EMPTY_INTVAL) : widget;
        },

        /*
         * Depending on the type, will return an intval or strval.
         *
         * @param widget: jQuery/DOM element
         * @return: null / int / string
         */
        get_value : function(widget){
            var value = get_input(widget).attr("intval");

            if (value === undefined){
                return get_input(widget).val() || null;
            } else if (value == self.EMPTY_INTVAL) {
                return null;
            }

            return parseInt(value);
        },

        /* Takes a CodingValue object and initialises the widget to this value */
        set_value : function(widget, codingvalue, _intval){
            widget = get_input(widget);

            if(_intval){
                widget.attr("intval", (codingvalue.intval === null) ? self.EMPTY_INTVAL : codingvalue.intval);
                widget.val(codingvalue.intval);
            } else {
                widget.val(codingvalue.strval);
            }
        }
    };

    /* SENTENCE TYPE */
    self.sentence = $.extend({}, self.default, {
        get_html : function(){
            var choices = $.map(annotator.state.sentences, function(sentence){
                return {
                    value : sentence.get_unit(),
                    label : sentence.get_unit(),
                    sentence : sentence,
                    intval : -1
                }
            });

            var widget = $("<input>").addClass("sentence").change(self.sentence.change);
            autocomplete.set(widget, choices);
            return widget;
        },
        get_value: function(widget){
            var value = widget.val();
            if (value === "") return null;

            var parnr = parseInt(value.split(".")[0]);
            var sentnr = parseInt(value.split(".")[1]);

            return $.grep($.values(annotator.state.sentences), function(sentence){
                return sentence.parnr == parnr && sentence.sentnr == sentnr;
            })[0];

        },
        set_value: function(widget, sentence){
            widget.val((sentence === null) ? "" : sentence.get_unit());
        },
        change : function(){
            var sentence = self.sentence.get_value($(this));
            var coding = annotator.state.codings[$(this).closest(".coding").attr("annotator_coding_id")];
            coding.sentence = sentence;
        }

    });

    /* TEXT TYPE */
    self.text = $.extend({}, self.default);

    /* BOOLEAN TYPE */
    self.boolean = $.extend({}, self.default, {
        get_html : function(){
            return self.default.get_html(null, true).attr("type", "checkbox").change(function(){
                $(this).attr("intval", $(this).prop("checked") ? 1 : 0);
            });
        },
        set_value : function(widget, codingvalue){
            self.default.set_value(widget, codingvalue, true);
            widget.prop("checked", !!codingvalue.intval);
        }
    });

    /* NUMBER TYPE */
    self.number = $.extend({}, self.default, {
        get_html : function(){
            return self.default.get_html(null, true).attr("type", "number").change(function(){
                var value = parseInt($(this).val());
                $(this).attr("intval", isNaN(value) ? self.EMPTY_INTVAL : value);
            });
        },
        set_value : function(widget, codingvalue){
            self.default.set_value(widget, codingvalue, true);
        }
    });

    /* FROM AND TO TYPES. These are, together with sentence, special types. From / to encode
     * subsentences and highlight them. */
    self.from = $.extend({}, self.number, {
        get_html : function(){
            return self.number.get_html().attr("placeholder", "from..").removeClass("codingvalue").addClass("from").change(function(){
                $(this).closest(".coding").trigger("sentence-changed");
            });
        }

    });

    self.to = $.extend({}, self.from, {
        get_html : function(){
            return self.from.get_html().removeClass("from").addClass("to").attr("placeholder", "to..");
        }
    });

    /* QUALITY TYPE */
    self.quality = $.extend({}, self.default, {
        get_html : function(){
            var widget = self.default.get_html(null, true);

            widget.attr("type", "number");
            widget.attr("step", 0.5).attr("min", -1).attr("max", 1);
            widget.change(function(){
                var target = $(this);
                var value = parseFloat(target.val());
                target.attr("intval", Math.round(value * 10));
            });

            return widget;
        },
        set_value : function(widget, codingvalue){
            self.default.set_value(widget, codingvalue, true);
        }

    });

    /* CODEBOOK TYPE */
    self.codebook = $.extend({}, self.default, {
        get_html : function(schemafield){
            var html = $("<span>");

            if(schemafield.split_codebook){
                var root = $("<input>");
                var descendant = $("<input>");
                var hidden = $("<input>").attr("intval", self.EMPTY_INTVAL).hide();

                root.addClass("codebook_root").attr("placeholder", "Root..").attr("intval", self.EMPTY_INTVAL);
                descendant.addClass("codebook_descendant").attr("placeholder", "Descendant..");
                descendant.attr("intval", self.EMPTY_INTVAL);
                hidden.addClass("codebook_value").addClass("codingvalue");

                autocomplete.set(root, schemafield.choices);
                autocomplete.set(descendant, schemafield.choices);

                descendant.on("autocompletechange", function(event, ui){
                    hidden.attr("intval", (ui.item === null) ? self.EMPTY_INTVAL : ui.item.get_code().code);
                    hidden.trigger("change");
                });

                html.append(root).append(descendant).append(hidden);
            } else {
                var input = $("<input>").attr("intval", self.EMPTY_INTVAL).addClass("codingvalue");
                autocomplete.set(input, schemafield.choices);
                html.append(input);

            }

            return html;
        },
        set_value : function(widget, codingvalue){
            self.default.set_value(widget, codingvalue, true);
            if (codingvalue.intval === null) return;

            var schemafield = annotator.models.schemafields[widget.attr("schemafield_id")];
            var code = schemafield.codebook.codes[codingvalue.intval];

            var find_code = function(code){
                return function(choice){
                    return choice.get_code() == code;
                }
            };

            if (!schemafield.split_codebook){
                var choice = $.grep(schemafield.choices, find_code(code))[0];
                return $(".codingvalue", widget).val(choice.label);
            }

            var root = code;
            while (root.parent !== null){
                root = root.parent;
            }

            var root_choice = $.grep(schemafield.choices, find_code(root))[0];
            var child_choice = $.grep(root_choice.descendant_choices, find_code(code))[0];

            // And we've got the labels!
            $(".codebook_root", widget).val(root_choice.label);
            $(".codebook_descendant", widget).val(child_choice.label);
        }

    });

    self.get_widget_type = function(schemafield){
        return self[self.SCHEMATYPES[schemafield.fieldtype]];
    };

    /* GENERAL FUNCTIONS */
    /* Generates an array of dom elements based on a list of schemafields. */
    self.get_html = function(schemafields){
        return $.map(schemafields, function(schemafield){
            var widget, widget_type;

            widget_type = self.get_widget_type(schemafield);
            widget = widget_type.get_html(schemafield);
            widget.attr("schemafield_id", schemafield.id);
            widget.addClass("codingvalue-container widget");

            return widget;
        });
    };

    /* Generates 'label' element based on a schemafield and a widget */
    self.get_label_html = function(schemafield, widget){
        var input = widget.find("input.codingvalue");
        return $("<label>").attr("for", input.attr("id")).text(schemafield.label);
    };

    /* Returns existing widget(s) given a schemafield and a scope
    *
    * @type scope: jQuery element
    * @requires: scope.hasClass("coding") */
    self.find = function(schemafield, scope){
        return scope.find("[schemafield_id={0}]".f(schemafield.id));
    };

    /*
     * Same as get_value, but returns as CodingValue object.
     */
    self.get_codingvalue = function(coding, sentence, widget) {
        var field = annotator.models.schemafields[widget.attr("schemafield_id")];
        var value = self.get_value(widget);

        return {
            sentence: sentence, coding: coding, field : field,
            strval: (value !== null && typeof(value) !== "number") ? value : null,
            intval: (value === null || typeof(value) === "number") ? value : null
        }

    };

    /*
     * Returns a list of codingvalues for the current coding element.
     *
     * @type coding_element: jQuery DOM element
     * @requires: coding_element.hasClass("coding")
     */
    self._get_codingvalues = function(coding_element){
        var coding = annotator.state.codings[coding_element.attr("annotator_coding_id")];
        var sentence_widget = $(".sentence", coding_element);

        // Sentence codings have a special widget with the class 'sentence' which
        // provides the sentence of the coding.
        var sentence = null;
        if (sentence_widget.length > 0){
            sentence = widgets.sentence.get_value(sentence_widget);
        }

        return $.map(coding_element.find(".widget"), function(widget){
            return self.get_codingvalue(coding, sentence, $(widget));
        })
    };

    /*
     * Returns a list of codingvalues, given a DOM element which contains
     * elements with the class 'coding'.
     *
     * @requires: !container.hasClass("coding")
     */
    self.get_coding_values = function(container){
        return $.grep($.map(container.find(".coding"), function(coding_el){
            return self._get_codingvalues($(coding_el));
        }), function(codingvalue){
            return codingvalue.strval !== null || codingvalue.intval !== null;
        });
    };

    self.set_value = function(widget, codingvalue){
        var field = annotator.models.schemafields[widget.attr("schemafield_id")];
        return self.get_widget_type(field).set_value(widget, codingvalue);
    };

    /*
     * Get value of coding. This function looks for an input element with
     * class 'coding', which can either have an attribute 'intval' which
     * will be returned (or null, if EMPTY_INTVAL is set). Else, it will
     * return a string which is equal to the inner value of the input element
     * or null if the value === ''.
     *
     * @param widget: jQuery/DOM element
     * @return: null / int / string
     */
    self.get_value = function(widget){
        var field = annotator.models.schemafields[widget.attr("schemafield_id")];
        return self.get_widget_type(field).get_value(widget);
    };

    return self;
})({});<commit_msg>Deserialise quality intval<commit_after>/**************************************************************************
*          (C) Vrije Universiteit, Amsterdam (the Netherlands)            *
*                                                                         *
* This file is part of AmCAT - The Amsterdam Content Analysis Toolkit     *
*                                                                         *
* AmCAT is free software: you can redistribute it and/or modify it under  *
* the terms of the GNU Affero General Public License as published by the  *
* Free Software Foundation, either version 3 of the License, or (at your  *
* option) any later version.                                              *
*                                                                         *
* AmCAT is distributed in the hope that it will be useful, but WITHOUT    *
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or   *
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public     *
* License for more details.                                               *
*                                                                         *
* You should have received a copy of the GNU Affero General Public        *
* License along with AmCAT.  If not, see <http://www.gnu.org/licenses/>.  *
***************************************************************************/

/*
 * Widgets represent codingschemafields in the DOM. For each type, a property
 * on `widgets` is registered which can be used to generate html elements,
 * get and set values. Furthermore, general functions are available which
 * don't require the caller to know anything about the schematypes.
 */

function get_input(widget){
    if (widget.hasClass("codingvalue")) return widget;
    return widget.find(".codingvalue");
}

widgets = (function(self){
    self.EMPTY_INTVAL = "null";
    self.SCHEMATYPES = {
        1: "text",
        2: "number",
        5: "codebook",
        7: "boolean",
        9: "quality"
    };

    /* WIDGET TYPES */
    /* A widget must implement three methods: get_html, get_value and set_value. The
     * documentation for the methods is given below, as well as a default implementation. */
    self.default = {
        /*
         * Returns exactly one element which is henceforth known as a 'widget'. It is later
         * passed to get_value and set_value.
         */
        get_html : function(schemafield, _intval){
            var widget = $("<input>").addClass("codingvalue");
            return (_intval) ? widget.attr("intval", self.EMPTY_INTVAL) : widget;
        },

        /*
         * Depending on the type, will return an intval or strval.
         *
         * @param widget: jQuery/DOM element
         * @return: null / int / string
         */
        get_value : function(widget){
            var value = get_input(widget).attr("intval");

            if (value === undefined){
                return get_input(widget).val() || null;
            } else if (value == self.EMPTY_INTVAL) {
                return null;
            }

            return parseInt(value);
        },

        /* Takes a CodingValue object and initialises the widget to this value */
        set_value : function(widget, codingvalue, _intval){
            widget = get_input(widget);

            if(_intval){
                widget.attr("intval", (codingvalue.intval === null) ? self.EMPTY_INTVAL : codingvalue.intval);
                widget.val(codingvalue.intval);
            } else {
                widget.val(codingvalue.strval);
            }
        }
    };

    /* SENTENCE TYPE */
    self.sentence = $.extend({}, self.default, {
        get_html : function(){
            var choices = $.map(annotator.state.sentences, function(sentence){
                return {
                    value : sentence.get_unit(),
                    label : sentence.get_unit(),
                    sentence : sentence,
                    intval : -1
                }
            });

            var widget = $("<input>").addClass("sentence").change(self.sentence.change);
            autocomplete.set(widget, choices);
            return widget;
        },
        get_value: function(widget){
            var value = widget.val();
            if (value === "") return null;

            var parnr = parseInt(value.split(".")[0]);
            var sentnr = parseInt(value.split(".")[1]);

            return $.grep($.values(annotator.state.sentences), function(sentence){
                return sentence.parnr == parnr && sentence.sentnr == sentnr;
            })[0];

        },
        set_value: function(widget, sentence){
            widget.val((sentence === null) ? "" : sentence.get_unit());
        },
        change : function(){
            var sentence = self.sentence.get_value($(this));
            var coding = annotator.state.codings[$(this).closest(".coding").attr("annotator_coding_id")];
            coding.sentence = sentence;
        }

    });

    /* TEXT TYPE */
    self.text = $.extend({}, self.default);

    /* BOOLEAN TYPE */
    self.boolean = $.extend({}, self.default, {
        get_html : function(){
            return self.default.get_html(null, true).attr("type", "checkbox").change(function(){
                $(this).attr("intval", $(this).prop("checked") ? 1 : 0);
            });
        },
        set_value : function(widget, codingvalue){
            self.default.set_value(widget, codingvalue, true);
            widget.prop("checked", !!codingvalue.intval);
        }
    });

    /* NUMBER TYPE */
    self.number = $.extend({}, self.default, {
        get_html : function(){
            return self.default.get_html(null, true).attr("type", "number").change(function(){
                var value = parseInt($(this).val());
                $(this).attr("intval", isNaN(value) ? self.EMPTY_INTVAL : value);
            });
        },
        set_value : function(widget, codingvalue){
            self.default.set_value(widget, codingvalue, true);
        }
    });

    /* FROM AND TO TYPES. These are, together with sentence, special types. From / to encode
     * subsentences and highlight them. */
    self.from = $.extend({}, self.number, {
        get_html : function(){
            return self.number.get_html().attr("placeholder", "from..").removeClass("codingvalue").addClass("from").change(function(){
                $(this).closest(".coding").trigger("sentence-changed");
            });
        }

    });

    self.to = $.extend({}, self.from, {
        get_html : function(){
            return self.from.get_html().removeClass("from").addClass("to").attr("placeholder", "to..");
        }
    });

    /* QUALITY TYPE */
    self.quality = $.extend({}, self.default, {
        get_html : function(){
            var widget = self.default.get_html(null, true);

            widget.attr("type", "number");
            widget.attr("step", 0.5).attr("min", -1).attr("max", 1);
            widget.change(function(){
                var target = $(this);
                var value = parseFloat(target.val());
                target.attr("intval", Math.round(value * 10));
            });

            return widget;
        },
        set_value : function(widget, codingvalue){
            self.default.set_value(widget, codingvalue, true);
            $(widget).val(codingvalue.intval / 10)
        }

    });

    /* CODEBOOK TYPE */
    self.codebook = $.extend({}, self.default, {
        get_html : function(schemafield){
            var html = $("<span>");

            if(schemafield.split_codebook){
                var root = $("<input>");
                var descendant = $("<input>");
                var hidden = $("<input>").attr("intval", self.EMPTY_INTVAL).hide();

                root.addClass("codebook_root").attr("placeholder", "Root..").attr("intval", self.EMPTY_INTVAL);
                descendant.addClass("codebook_descendant").attr("placeholder", "Descendant..");
                descendant.attr("intval", self.EMPTY_INTVAL);
                hidden.addClass("codebook_value").addClass("codingvalue");

                autocomplete.set(root, schemafield.choices);
                autocomplete.set(descendant, schemafield.choices);

                descendant.on("autocompletechange", function(event, ui){
                    hidden.attr("intval", (ui.item === null) ? self.EMPTY_INTVAL : ui.item.get_code().code);
                    hidden.trigger("change");
                });

                html.append(root).append(descendant).append(hidden);
            } else {
                var input = $("<input>").attr("intval", self.EMPTY_INTVAL).addClass("codingvalue");
                autocomplete.set(input, schemafield.choices);
                html.append(input);

            }

            return html;
        },
        set_value : function(widget, codingvalue){
            self.default.set_value(widget, codingvalue, true);
            if (codingvalue.intval === null) return;

            var schemafield = annotator.models.schemafields[widget.attr("schemafield_id")];
            var code = schemafield.codebook.codes[codingvalue.intval];

            var find_code = function(code){
                return function(choice){
                    return choice.get_code() == code;
                }
            };

            if (!schemafield.split_codebook){
                var choice = $.grep(schemafield.choices, find_code(code))[0];
                return $(".codingvalue", widget).val(choice.label);
            }

            var root = code;
            while (root.parent !== null){
                root = root.parent;
            }

            var root_choice = $.grep(schemafield.choices, find_code(root))[0];
            var child_choice = $.grep(root_choice.descendant_choices, find_code(code))[0];

            // And we've got the labels!
            $(".codebook_root", widget).val(root_choice.label);
            $(".codebook_descendant", widget).val(child_choice.label);
        }

    });

    self.get_widget_type = function(schemafield){
        return self[self.SCHEMATYPES[schemafield.fieldtype]];
    };

    /* GENERAL FUNCTIONS */
    /* Generates an array of dom elements based on a list of schemafields. */
    self.get_html = function(schemafields){
        return $.map(schemafields, function(schemafield){
            var widget, widget_type;

            widget_type = self.get_widget_type(schemafield);
            widget = widget_type.get_html(schemafield);
            widget.attr("schemafield_id", schemafield.id);
            widget.addClass("codingvalue-container widget");

            return widget;
        });
    };

    /* Generates 'label' element based on a schemafield and a widget */
    self.get_label_html = function(schemafield, widget){
        var input = widget.find("input.codingvalue");
        return $("<label>").attr("for", input.attr("id")).text(schemafield.label);
    };

    /* Returns existing widget(s) given a schemafield and a scope
    *
    * @type scope: jQuery element
    * @requires: scope.hasClass("coding") */
    self.find = function(schemafield, scope){
        return scope.find("[schemafield_id={0}]".f(schemafield.id));
    };

    /*
     * Same as get_value, but returns as CodingValue object.
     */
    self.get_codingvalue = function(coding, sentence, widget) {
        var field = annotator.models.schemafields[widget.attr("schemafield_id")];
        var value = self.get_value(widget);

        return {
            sentence: sentence, coding: coding, field : field,
            strval: (value !== null && typeof(value) !== "number") ? value : null,
            intval: (value === null || typeof(value) === "number") ? value : null
        }

    };

    /*
     * Returns a list of codingvalues for the current coding element.
     *
     * @type coding_element: jQuery DOM element
     * @requires: coding_element.hasClass("coding")
     */
    self._get_codingvalues = function(coding_element){
        var coding = annotator.state.codings[coding_element.attr("annotator_coding_id")];
        var sentence_widget = $(".sentence", coding_element);

        // Sentence codings have a special widget with the class 'sentence' which
        // provides the sentence of the coding.
        var sentence = null;
        if (sentence_widget.length > 0){
            sentence = widgets.sentence.get_value(sentence_widget);
        }

        return $.map(coding_element.find(".widget"), function(widget){
            return self.get_codingvalue(coding, sentence, $(widget));
        })
    };

    /*
     * Returns a list of codingvalues, given a DOM element which contains
     * elements with the class 'coding'.
     *
     * @requires: !container.hasClass("coding")
     */
    self.get_coding_values = function(container){
        return $.grep($.map(container.find(".coding"), function(coding_el){
            return self._get_codingvalues($(coding_el));
        }), function(codingvalue){
            return codingvalue.strval !== null || codingvalue.intval !== null;
        });
    };

    self.set_value = function(widget, codingvalue){
        var field = annotator.models.schemafields[widget.attr("schemafield_id")];
        return self.get_widget_type(field).set_value(widget, codingvalue);
    };

    /*
     * Get value of coding. This function looks for an input element with
     * class 'coding', which can either have an attribute 'intval' which
     * will be returned (or null, if EMPTY_INTVAL is set). Else, it will
     * return a string which is equal to the inner value of the input element
     * or null if the value === ''.
     *
     * @param widget: jQuery/DOM element
     * @return: null / int / string
     */
    self.get_value = function(widget){
        var field = annotator.models.schemafields[widget.attr("schemafield_id")];
        return self.get_widget_type(field).get_value(widget);
    };

    return self;
})({});<|endoftext|>


<commit_before>define(function (require) {

  var React = require('react/addons'),
    Backbone = require('backbone'),
    Button = require('./Button.react'),
    actions = require('actions'),
    modals = require('modals');

  return React.createClass({
    displayName: "InstanceActionButtons",

    propTypes: {
      multipleSelected: React.PropTypes.bool.isRequired,
      instance: React.PropTypes.instanceOf(Backbone.Model).isRequired,
      project: React.PropTypes.instanceOf(Backbone.Model).isRequired
    },

    onStart: function () {
      modals.InstanceModals.start(this.props.instance);
    },

    onSuspend: function () {
      modals.InstanceModals.suspend(this.props.instance);
    },

    onStop: function () {
      modals.InstanceModals.stop(this.props.instance);
    },

    onReboot: function(){
      modals.InstanceModals.reboot(this.props.instance);
    },

    onResume: function () {
      modals.InstanceModals.resume(this.props.instance);
    },

    onDelete: function () {
      this.props.onUnselect(this.props.instance);
      modals.InstanceModals.destroy({
        instance: this.props.instance,
        project: this.props.project
      });
    },

    render: function () {
      var instance = this.props.instance,
        status = instance.get('state').get('status'),
        linksArray = [];

      if (!this.props.multipleSelected && instance.get('state').isInFinalState()) {
        if (status === "active") {
          linksArray.push(
            <Button
              key="Suspend"
              icon="pause"
              tooltip="Suspend"
              onClick={this.onSuspend}
              isVisible={true}
              />
          );
          linksArray.push(
            <Button
              key="Stop"
              icon="stop"
              tooltip="Stop"
              onClick={this.onStop}
              isVisible={true}
              />
          );
          linksArray.push(
            <Button
              key="Reboot"
              icon="repeat"
              tooltip="Rebboot the selected instance"
              onClick={this.onReboot}
              isVisible={true}
              />
          );
        } else if (status === "suspended") {
          linksArray.push(
            <Button
              key="Resume"
              icon="play"
              tooltip="Resume"
              onClick={this.onResume}
              isVisible={true}
              />
          );
        } else if (status === "shutoff") {
          linksArray.push(
            <Button
              key="Start"
              icon="play"
              tooltip="Start"
              onClick={this.onStart}
              isVisible={true}
              />
          );
        }
      }

      linksArray.push(
        <Button
          key="Delete"
          icon="remove"
          tooltip="Delete"
          onClick={this.onDelete}
          isVisible={true}
          />
      );

      return (

          {linksArray}

      );
    }

  });

});
<commit_msg>Corrected typo in tooltip for Reboot action bar button.<commit_after>define(function (require) {

  var React = require('react/addons'),
    Backbone = require('backbone'),
    Button = require('./Button.react'),
    actions = require('actions'),
    modals = require('modals');

  return React.createClass({
    displayName: "InstanceActionButtons",

    propTypes: {
      multipleSelected: React.PropTypes.bool.isRequired,
      instance: React.PropTypes.instanceOf(Backbone.Model).isRequired,
      project: React.PropTypes.instanceOf(Backbone.Model).isRequired
    },

    onStart: function () {
      modals.InstanceModals.start(this.props.instance);
    },

    onSuspend: function () {
      modals.InstanceModals.suspend(this.props.instance);
    },

    onStop: function () {
      modals.InstanceModals.stop(this.props.instance);
    },

    onReboot: function(){
      modals.InstanceModals.reboot(this.props.instance);
    },

    onResume: function () {
      modals.InstanceModals.resume(this.props.instance);
    },

    onDelete: function () {
      this.props.onUnselect(this.props.instance);
      modals.InstanceModals.destroy({
        instance: this.props.instance,
        project: this.props.project
      });
    },

    render: function () {
      var instance = this.props.instance,
        status = instance.get('state').get('status'),
        linksArray = [];

      if (!this.props.multipleSelected && instance.get('state').isInFinalState()) {
        if (status === "active") {
          linksArray.push(
            <Button
              key="Suspend"
              icon="pause"
              tooltip="Suspend"
              onClick={this.onSuspend}
              isVisible={true}
              />
          );
          linksArray.push(
            <Button
              key="Stop"
              icon="stop"
              tooltip="Stop"
              onClick={this.onStop}
              isVisible={true}
              />
          );
          linksArray.push(
            <Button
              key="Reboot"
              icon="repeat"
              tooltip="Reboot the selected instance"
              onClick={this.onReboot}
              isVisible={true}
              />
          );
        } else if (status === "suspended") {
          linksArray.push(
            <Button
              key="Resume"
              icon="play"
              tooltip="Resume"
              onClick={this.onResume}
              isVisible={true}
              />
          );
        } else if (status === "shutoff") {
          linksArray.push(
            <Button
              key="Start"
              icon="play"
              tooltip="Start"
              onClick={this.onStart}
              isVisible={true}
              />
          );
        }
      }

      linksArray.push(
        <Button
          key="Delete"
          icon="remove"
          tooltip="Delete"
          onClick={this.onDelete}
          isVisible={true}
          />
      );

      return (

          {linksArray}

      );
    }

  });

});
<|endoftext|>


<commit_before>const {
  invokeCallbackHandler: invoke
} = UniServiceJSBridge
const eventNames = ['open', 'close', 'error', 'message']
const callbacks = {}
const socketTasks = []
/**
 * SocketTask
 */
class SocketTask {
  /**
   * WebSocket实例
   */
  _webSocket
  /**
   * 构造函数
   * @param {string} url
   * @param {Array} protocols
   */
  constructor (url, protocols, callback) {
    let error
    try {
      const webSocket = this._webSocket = new WebSocket(url, protocols)
      webSocket.binaryType = 'arraybuffer'
      this._callbacks = {}
      eventNames.forEach(name => {
        this._callbacks[name] = []
        webSocket.addEventListener(name, event => {
          const res = name === 'message' ? {
            data: event.data
          } : {}
          this._callbacks[name].forEach(callback => {
            try {
              callback(res)
            } catch (e) {
              console.error(`thirdScriptError\n${e};at socketTask.on${name[0].toUpperCase() + name.substr(1)} callback function\n`, e)
            }
          })
          if (this === socketTasks[0] && callbacks[name]) {
            invoke(callbacks[name], res)
          }
          if (name === 'error' || name === 'close') {
            const index = socketTasks.indexOf(this)
            if (index >= 0) {
              socketTasks.splice(index, 1)
            }
          }
        })
      })
      let propertys = ['CLOSED', 'CLOSING', 'CONNECTING', 'OPEN', 'readyState']
      propertys.forEach((property) => {
        Object.defineProperty(this, property, {
          get () {
            return webSocket[property]
          }
        })
      })
    } catch (e) {
      error = e
    }
    callback(error, this)
  }
  /**
   * 发送
   * @param {any} data
   */
  send (options = {}) {
    const data = options.data
    const ws = this._webSocket
    try {
      ws.send(data)
      this._callback(options, 'sendSocketMessage:ok')
    } catch (error) {
      this._callback(options, `sendSocketMessage:fail ${error}`)
    }
  }
  /**
   * 关闭
   * @param {number} code
   * @param {string} reason
   */
  close (options = {}) {
    const ws = this._webSocket
    const arrgs = []
    arrgs.push(options.code || 1000)
    if (typeof options.reason === 'string') {
      arrgs.push(options.reason)
    }
    try {
      ws.close(...arrgs)
      this._callback(options, 'sendSocketMessage:ok')
    } catch (error) {
      this._callback(options, `sendSocketMessage:fail ${error}`)
    }
  }
  /**
   * 通用回调处理
   */
  _callback ({
    success,
    fail,
    complete
  }, errMsg) {
    const data = {
      errMsg
    }
    if (/:ok$/.test(errMsg)) {
      if (typeof success === 'function') {
        success(data)
      }
    } else {
      if (typeof fail === 'function') {
        fail(data)
      }
    }
    if (typeof complete === 'function') {
      complete(data)
    }
  }
}
eventNames.forEach(item => {
  const name = item[0].toUpperCase() + item.substr(1)
  SocketTask.prototype[`on${name}`] = function (callback) {
    this._callbacks[item].push(callback)
  }
})
/**
 * 创建一个 WebSocket 连接
 * @param {any} data 数据
 * @return {SocketTask}
 */
export function connectSocket ({
  url,
  protocols
}, callbackId) {
  return new SocketTask(url, protocols, (error, socketTask) => {
    if (!error) {
      socketTasks.push(socketTask)
    }
    invoke(callbackId, {
      errMsg: 'connectSocket:' + (error ? `fail ${error}` : 'ok')
    })
  })
}
/**
 * 通过 WebSocket 连接发送数据
 * @param {any} options
 * @param {string} callbackId
 */
export function sendSocketMessage (options, callbackId) {
  const socketTask = socketTasks[0]
  if (socketTask && socketTask.readyState === socketTask.OPEN) {
    socketTask.send(Object.assign({}, options, {
      complete (res) {
        invoke(callbackId, res)
      }
    }))
  } else {
    invoke(callbackId, {
      errMsg: 'sendSocketMessage:fail WebSocket is not connected '
    })
  }
}
/**
 * 关闭WebSocket连接
 * @param {any} options
 * @param {string} callbackId
 */
export function closeSocket (options, callbackId) {
  const socketTask = socketTasks[0]
  if (socketTask) {
    socketTask.close(Object.assign({}, options, {
      complete (res) {
        invoke(callbackId, res)
      }
    }))
  } else {
    invoke(callbackId, {
      errMsg: 'closeSocket:fail WebSocket is not connected'
    })
  }
}
/**
 * 监听事件
 * @param {string} method
 */
function on (method) {
  return function (callbackId) {
    callbacks[method] = callbackId
  }
}
/**
 * 监听WebSocket连接打开事件
 * @param {Function} cb
 */
export const onSocketOpen = on('open')
/**
 * 监听WebSocket错误
 * @param {Function} cb
 */
export const onSocketError = on('error')
/**
 * 监听WebSocket接受到服务器的消息事件
 * @param {Function} cb
 */
export const onSocketMessage = on('message')
/**
 * 监听WebSocket关闭
 * @param {Function} callback
 */
export const onSocketClose = on('close')
<commit_msg>fix: 解决部分情况SocketTask.send回调不正确的问题 fixed #744<commit_after>const {
  invokeCallbackHandler: invoke
} = UniServiceJSBridge
const eventNames = ['open', 'close', 'error', 'message']
const callbacks = {}
const socketTasks = []
/**
 * SocketTask
 */
class SocketTask {
  /**
   * WebSocket实例
   */
  _webSocket
  /**
   * 构造函数
   * @param {string} url
   * @param {Array} protocols
   */
  constructor (url, protocols, callback) {
    let error
    try {
      const webSocket = this._webSocket = new WebSocket(url, protocols)
      webSocket.binaryType = 'arraybuffer'
      this._callbacks = {}
      eventNames.forEach(name => {
        this._callbacks[name] = []
        webSocket.addEventListener(name, event => {
          const res = name === 'message' ? {
            data: event.data
          } : {}
          this._callbacks[name].forEach(callback => {
            try {
              callback(res)
            } catch (e) {
              console.error(`thirdScriptError\n${e};at socketTask.on${name[0].toUpperCase() + name.substr(1)} callback function\n`, e)
            }
          })
          if (this === socketTasks[0] && callbacks[name]) {
            invoke(callbacks[name], res)
          }
          if (name === 'error' || name === 'close') {
            const index = socketTasks.indexOf(this)
            if (index >= 0) {
              socketTasks.splice(index, 1)
            }
          }
        })
      })
      let propertys = ['CLOSED', 'CLOSING', 'CONNECTING', 'OPEN', 'readyState']
      propertys.forEach((property) => {
        Object.defineProperty(this, property, {
          get () {
            return webSocket[property]
          }
        })
      })
    } catch (e) {
      error = e
    }
    callback(error, this)
  }
  /**
   * 发送
   * @param {any} data
   */
  send (options = {}) {
    const data = options.data
    const ws = this._webSocket
    try {
      if (ws.readyState !== ws.OPEN) {
        throw new Error('SocketTask.readyState is not OPEN')
      }
      ws.send(data)
      this._callback(options, 'sendSocketMessage:ok')
    } catch (error) {
      this._callback(options, `sendSocketMessage:fail ${error}`)
    }
  }
  /**
   * 关闭
   * @param {number} code
   * @param {string} reason
   */
  close (options = {}) {
    const ws = this._webSocket
    const arrgs = []
    arrgs.push(options.code || 1000)
    if (typeof options.reason === 'string') {
      arrgs.push(options.reason)
    }
    try {
      ws.close(...arrgs)
      this._callback(options, 'sendSocketMessage:ok')
    } catch (error) {
      this._callback(options, `sendSocketMessage:fail ${error}`)
    }
  }
  /**
   * 通用回调处理
   */
  _callback ({
    success,
    fail,
    complete
  }, errMsg) {
    const data = {
      errMsg
    }
    if (/:ok$/.test(errMsg)) {
      if (typeof success === 'function') {
        success(data)
      }
    } else {
      if (typeof fail === 'function') {
        fail(data)
      }
    }
    if (typeof complete === 'function') {
      complete(data)
    }
  }
}
eventNames.forEach(item => {
  const name = item[0].toUpperCase() + item.substr(1)
  SocketTask.prototype[`on${name}`] = function (callback) {
    this._callbacks[item].push(callback)
  }
})
/**
 * 创建一个 WebSocket 连接
 * @param {any} data 数据
 * @return {SocketTask}
 */
export function connectSocket ({
  url,
  protocols
}, callbackId) {
  return new SocketTask(url, protocols, (error, socketTask) => {
    if (!error) {
      socketTasks.push(socketTask)
    }
    invoke(callbackId, {
      errMsg: 'connectSocket:' + (error ? `fail ${error}` : 'ok')
    })
  })
}
/**
 * 通过 WebSocket 连接发送数据
 * @param {any} options
 * @param {string} callbackId
 */
export function sendSocketMessage (options, callbackId) {
  const socketTask = socketTasks[0]
  if (socketTask && socketTask.readyState === socketTask.OPEN) {
    socketTask.send(Object.assign({}, options, {
      complete (res) {
        invoke(callbackId, res)
      }
    }))
  } else {
    invoke(callbackId, {
      errMsg: 'sendSocketMessage:fail WebSocket is not connected '
    })
  }
}
/**
 * 关闭WebSocket连接
 * @param {any} options
 * @param {string} callbackId
 */
export function closeSocket (options, callbackId) {
  const socketTask = socketTasks[0]
  if (socketTask) {
    socketTask.close(Object.assign({}, options, {
      complete (res) {
        invoke(callbackId, res)
      }
    }))
  } else {
    invoke(callbackId, {
      errMsg: 'closeSocket:fail WebSocket is not connected'
    })
  }
}
/**
 * 监听事件
 * @param {string} method
 */
function on (method) {
  return function (callbackId) {
    callbacks[method] = callbackId
  }
}
/**
 * 监听WebSocket连接打开事件
 * @param {Function} cb
 */
export const onSocketOpen = on('open')
/**
 * 监听WebSocket错误
 * @param {Function} cb
 */
export const onSocketError = on('error')
/**
 * 监听WebSocket接受到服务器的消息事件
 * @param {Function} cb
 */
export const onSocketMessage = on('message')
/**
 * 监听WebSocket关闭
 * @param {Function} callback
 */
export const onSocketClose = on('close')
<|endoftext|>