<commit_before>import { mount } from "@vue/test-utils";
import veTable from "@/ve-table";
import { later } from "../util";

describe("veTable row checkbox", () => {
    const TABLE_DATA = [
        {
            rowKey: 1001,
            name: "John",
            date: "1900-05-20",
            hobby: "coding",
            address: "No.1 Century Avenue, Shanghai",
        },
        {
            rowKey: 1002,
            name: "Dickerson",
            date: "1910-06-20",
            hobby: "coding",
            address: "No.1 Century Avenue, Beijing",
        },
        {
            rowKey: 1003,
            name: "Larsen",
            date: "2000-07-20",
            hobby: "coding and coding repeat",
            address: "No.1 Century Avenue, Chongqing",
        },
        {
            rowKey: 1004,
            name: "Geneva",
            date: "2010-08-20",
            hobby: "coding and coding repeat",
            address: "No.1 Century Avenue, Xiamen",
        },
        {
            rowKey: 1005,
            name: "Jami",
            date: "2020-09-20",
            hobby: "coding and coding repeat",
            address: "No.1 Century Avenue, Shenzhen",
        },
    ];

    const COLUMNS = [
        {
            field: "",
            key: "a",
            // type=checkbox
            type: "checkbox",
            title: "",
            width: 50,
            align: "center",
        },
        {
            field: "name",
            key: "b",
            title: "Name",
            width: 200,
            align: "left",
        },
        {
            field: "hobby",
            key: "c",
            title: "Hobby",
            width: 300,
            align: "left",
        },
        {
            field: "address",
            key: "d",
            title: "Address",
            width: "",
            align: "left",
        },
    ];

    it("render", () => {
        const wrapper = mount(veTable, {
            propsData: {
                columns: COLUMNS,
                tableData: TABLE_DATA,
                checkboxOption: {},
                rowKeyFieldName: "rowKey",
            },
        });

        expect(wrapper.html()).toMatchSnapshot();
    });

    it("has checkbox", () => {
        const wrapper = mount(veTable, {
            propsData: {
                columns: COLUMNS,
                tableData: TABLE_DATA,
                checkboxOption: {},
                rowKeyFieldName: "rowKey",
            },
        });

        expect(wrapper.find(".ve-table-header-tr .ve-checkbox").exists()).toBe(
            true,
        );
        expect(wrapper.find(".ve-table-body-tr .ve-checkbox").exists()).toBe(
            true,
        );
    });

    it("check default selected key", async () => {
        const wrapper = mount(veTable, {
            propsData: {
                columns: COLUMNS,
                tableData: TABLE_DATA,
                checkboxOption: {
                    defaultSelectedRowKeys: [1001, 1003, 1004],
                },
                rowKeyFieldName: "rowKey",
            },
        });

        await later();

        expect(
            wrapper
                .find(
                    ".ve-table-header-tr .ve-checkbox .ve-checkbox-indeterminate",
                )
                .exists(),
        ).toBe(true);

        // checked count
        expect(
            wrapper.findAll(
                ".ve-table-body-tr .ve-checkbox .ve-checkbox-checked",
            ).length,
        ).toBe(3);
    });

    it("check disable selected keys", async () => {
        const wrapper = mount(veTable, {
            propsData: {
                columns: COLUMNS,
                tableData: TABLE_DATA,
                checkboxOption: {
                    // 禁用的选择（禁止勾选或者禁止取消勾选）
                    disableSelectedRowKeys: [1002, 1005],
                    // 默认选择
                    defaultSelectedRowKeys: [1001, 1003, 1004, 1005],
                },
                rowKeyFieldName: "rowKey",
            },
        });

        await later();

        expect(
            wrapper
                .find(
                    ".ve-table-header-tr .ve-checkbox .ve-checkbox-indeterminate",
                )
                .exists(),
        ).toBe(true);

        // checked count
        expect(
            wrapper.findAll(
                ".ve-table-body-tr .ve-checkbox .ve-checkbox-checked",
            ).length,
        ).toBe(4);

        // disable checked count
        expect(
            wrapper.findAll(
                ".ve-table-body-tr .ve-checkbox .ve-checkbox-checked.ve-checkbox-disabled",
            ).length,
        ).toBe(1);

        // disable unchecked count
        expect(
            wrapper
                .findAll(".ve-table-body-tr")
                .at(1)
                .find(".ve-checkbox-content")
                .classes()
                .includes("ve-checkbox-checked"),
        ).toBe(false);
    });

    it("controllable attr selectedRowKey", async () => {
        const wrapper = mount(veTable, {
            propsData: {
                columns: COLUMNS,
                tableData: TABLE_DATA,
                checkboxOption: {
                    // 可控属性
                    selectedRowKeys: [1003],
                },
                rowKeyFieldName: "rowKey",
            },
        });

        await later();

        expect(
            wrapper
                .find(
                    ".ve-table-header-tr .ve-checkbox .ve-checkbox-indeterminate",
                )
                .exists(),
        ).toBe(true);

        expect(
            wrapper.findAll(
                ".ve-table-body-tr .ve-checkbox .ve-checkbox-checked",
            ).length,
        ).toBe(1);

        wrapper.setProps({
            checkboxOption: {
                selectedRowKeys: [1003, 1004],
            },
        });

        await later();

        expect(
            wrapper.findAll(
                ".ve-table-body-tr .ve-checkbox .ve-checkbox-checked",
            ).length,
        ).toBe(2);
    });

    it("header hideSelectAll", async () => {
        const wrapper = mount(veTable, {
            propsData: {
                columns: COLUMNS,
                tableData: TABLE_DATA,
                checkboxOption: {
                    // 可控属性
                    hideSelectAll: true,
                },
                rowKeyFieldName: "rowKey",
            },
        });

        await later();

        expect(wrapper.find(".ve-table-header-tr .ve-checkbox").exists()).toBe(
            false,
        );
    });

    it("checkboxOption selectedRowChange event", async () => {
        const mockFn = jest.fn();

        const wrapper = mount(veTable, {
            propsData: {
                columns: COLUMNS,
                tableData: TABLE_DATA,
                checkboxOption: {
                    selectedRowChange: ({
                        row,
                        isSelected,
                        selectedRowKeys,
                    }) => {
                        mockFn(row, isSelected, selectedRowKeys);
                    },
                },
                rowKeyFieldName: "rowKey",
            },
        });

        wrapper
            .findAll(".ve-table-body-tr")
            .at(0)
            .findAll(".ve-table-body-td")
            .at(0)
            .find(".ve-checkbox")
            .trigger("click");

        await later();

        expect(mockFn).toHaveBeenCalled();
        expect(mockFn).toHaveBeenCalledWith(TABLE_DATA[0], true, [1001]);
    });

    it("checkboxOption selectedAllChange event", async () => {
        const mockFn = jest.fn();

        const wrapper = mount(veTable, {
            propsData: {
                columns: COLUMNS,
                tableData: TABLE_DATA,
                checkboxOption: {
                    selectedAllChange: ({ isSelected, selectedRowKeys }) => {
                        mockFn(isSelected, selectedRowKeys);
                    },
                },
                rowKeyFieldName: "rowKey",
            },
        });

        wrapper
            .find(".ve-table-header-tr")
            .findAll(".ve-table-header-th")
            .at(0)
            .find(".ve-checkbox")
            .trigger("click");

        await later();

        expect(mockFn).toHaveBeenCalled();
        expect(mockFn).toHaveBeenCalledWith(
            true,
            [1001, 1002, 1003, 1004, 1005],
        );
    });
});
<commit_msg>完善多选单元测试<commit_after>import { mount } from "@vue/test-utils";
import veTable from "@/ve-table";
import { later } from "../util";

describe("veTable row checkbox", () => {
    const TABLE_DATA = [
        {
            rowKey: 1001,
            name: "John",
            date: "1900-05-20",
            hobby: "coding",
            address: "No.1 Century Avenue, Shanghai",
        },
        {
            rowKey: 1002,
            name: "Dickerson",
            date: "1910-06-20",
            hobby: "coding",
            address: "No.1 Century Avenue, Beijing",
        },
        {
            rowKey: 1003,
            name: "Larsen",
            date: "2000-07-20",
            hobby: "coding and coding repeat",
            address: "No.1 Century Avenue, Chongqing",
        },
        {
            rowKey: 1004,
            name: "Geneva",
            date: "2010-08-20",
            hobby: "coding and coding repeat",
            address: "No.1 Century Avenue, Xiamen",
        },
        {
            rowKey: 1005,
            name: "Jami",
            date: "2020-09-20",
            hobby: "coding and coding repeat",
            address: "No.1 Century Avenue, Shenzhen",
        },
    ];

    const COLUMNS = [
        {
            field: "",
            key: "a",
            // type=checkbox
            type: "checkbox",
            title: "",
            width: 50,
            align: "center",
        },
        {
            field: "name",
            key: "b",
            title: "Name",
            width: 200,
            align: "left",
        },
        {
            field: "hobby",
            key: "c",
            title: "Hobby",
            width: 300,
            align: "left",
        },
        {
            field: "address",
            key: "d",
            title: "Address",
            width: "",
            align: "left",
        },
    ];

    it("render", () => {
        const wrapper = mount(veTable, {
            propsData: {
                columns: COLUMNS,
                tableData: TABLE_DATA,
                checkboxOption: {},
                rowKeyFieldName: "rowKey",
            },
        });

        expect(wrapper.html()).toMatchSnapshot();
    });

    it("has checkbox", () => {
        const wrapper = mount(veTable, {
            propsData: {
                columns: COLUMNS,
                tableData: TABLE_DATA,
                checkboxOption: {},
                rowKeyFieldName: "rowKey",
            },
        });

        expect(wrapper.find(".ve-table-header-tr .ve-checkbox").exists()).toBe(
            true,
        );
        expect(wrapper.find(".ve-table-body-tr .ve-checkbox").exists()).toBe(
            true,
        );
    });

    it("check default selected key", async () => {
        const wrapper = mount(veTable, {
            propsData: {
                columns: COLUMNS,
                tableData: TABLE_DATA,
                checkboxOption: {
                    defaultSelectedRowKeys: [1001, 1003, 1004],
                },
                rowKeyFieldName: "rowKey",
            },
        });

        await later();

        expect(
            wrapper
                .find(
                    ".ve-table-header-tr .ve-checkbox .ve-checkbox-indeterminate",
                )
                .exists(),
        ).toBe(true);

        // checked count
        expect(
            wrapper.findAll(
                ".ve-table-body-tr .ve-checkbox .ve-checkbox-checked",
            ).length,
        ).toBe(3);
    });

    it("check disable selected keys with defaultSelectedRowKeys", async () => {
        const wrapper = mount(veTable, {
            propsData: {
                columns: COLUMNS,
                tableData: TABLE_DATA,
                checkboxOption: {
                    // 禁用的选择（禁止勾选或者禁止取消勾选）
                    disableSelectedRowKeys: [1002, 1005],
                    // 默认选择
                    defaultSelectedRowKeys: [1001, 1003, 1004, 1005],
                },
                rowKeyFieldName: "rowKey",
            },
        });

        await later();

        expect(
            wrapper
                .find(
                    ".ve-table-header-tr .ve-checkbox .ve-checkbox-indeterminate",
                )
                .exists(),
        ).toBe(true);

        // checked count
        expect(
            wrapper.findAll(
                ".ve-table-body-tr .ve-checkbox .ve-checkbox-checked",
            ).length,
        ).toBe(4);

        // disable checked count
        expect(
            wrapper.findAll(
                ".ve-table-body-tr .ve-checkbox .ve-checkbox-checked.ve-checkbox-disabled",
            ).length,
        ).toBe(1);

        // disable unchecked count
        expect(
            wrapper
                .findAll(".ve-table-body-tr")
                .at(1)
                .find(".ve-checkbox-content")
                .classes()
                .includes("ve-checkbox-checked"),
        ).toBe(false);
    });

    it("check disable selected keys", async () => {
        const wrapper = mount(veTable, {
            propsData: {
                columns: COLUMNS,
                tableData: TABLE_DATA,
                checkboxOption: {
                    // 禁用的选择（禁止勾选或者禁止取消勾选）
                    disableSelectedRowKeys: [1002, 1005],
                    // 默认选择
                    //defaultSelectedRowKeys: [1001, 1003, 1004, 1005],
                },
                rowKeyFieldName: "rowKey",
            },
        });

        await later();

        expect(
            wrapper
                .find(
                    ".ve-table-header-tr .ve-checkbox .ve-checkbox-indeterminate",
                )
                .exists(),
        ).toBe(false);

        // checked count
        expect(
            wrapper.findAll(
                ".ve-table-body-tr .ve-checkbox .ve-checkbox-checked",
            ).length,
        ).toBe(0);

        // disable checked count
        expect(
            wrapper.findAll(
                ".ve-table-body-tr .ve-checkbox .ve-checkbox-checked.ve-checkbox-disabled",
            ).length,
        ).toBe(0);

        // disable unchecked count
        expect(
            wrapper
                .findAll(".ve-table-body-tr")
                .at(1)
                .find(".ve-checkbox-content")
                .classes()
                .includes("ve-checkbox-checked"),
        ).toBe(false);
    });

    it("select all event with check disable selected keys", async () => {
        const wrapper = mount(veTable, {
            propsData: {
                columns: COLUMNS,
                tableData: TABLE_DATA,
                checkboxOption: {
                    // 禁用的选择（禁止勾选或者禁止取消勾选）
                    disableSelectedRowKeys: [1002, 1005],
                    // 默认选择
                    //defaultSelectedRowKeys: [1001, 1003, 1004, 1005],
                },
                rowKeyFieldName: "rowKey",
            },
        });

        await later();

        // select all
        wrapper
            .findAll(".ve-table-header-tr")
            .at(0)
            .findAll(".ve-table-header-th")
            .at(0)
            .find(".ve-checkbox")
            .trigger("click");

        await later();

        expect(
            wrapper
                .find(
                    ".ve-table-header-tr .ve-checkbox .ve-checkbox-indeterminate",
                )
                .exists(),
        ).toBe(true);

        // checked count
        expect(
            wrapper.findAll(
                ".ve-table-body-tr .ve-checkbox .ve-checkbox-checked",
            ).length,
        ).toBe(3);

        // disable unchecked count
        expect(
            wrapper
                .findAll(".ve-table-body-tr")
                .at(1)
                .find(".ve-checkbox-content")
                .classes()
                .includes("ve-checkbox-checked"),
        ).toBe(false);

        expect(
            wrapper
                .findAll(".ve-table-body-tr")
                .at(4)
                .find(".ve-checkbox-content")
                .classes()
                .includes("ve-checkbox-checked"),
        ).toBe(false);
    });

    it("controllable attr selectedRowKey", async () => {
        const wrapper = mount(veTable, {
            propsData: {
                columns: COLUMNS,
                tableData: TABLE_DATA,
                checkboxOption: {
                    // 可控属性
                    selectedRowKeys: [1003],
                },
                rowKeyFieldName: "rowKey",
            },
        });

        await later();

        expect(
            wrapper
                .find(
                    ".ve-table-header-tr .ve-checkbox .ve-checkbox-indeterminate",
                )
                .exists(),
        ).toBe(true);

        expect(
            wrapper.findAll(
                ".ve-table-body-tr .ve-checkbox .ve-checkbox-checked",
            ).length,
        ).toBe(1);

        wrapper.setProps({
            checkboxOption: {
                selectedRowKeys: [1003, 1004],
            },
        });

        await later();

        expect(
            wrapper.findAll(
                ".ve-table-body-tr .ve-checkbox .ve-checkbox-checked",
            ).length,
        ).toBe(2);
    });

    it("header hideSelectAll", async () => {
        const wrapper = mount(veTable, {
            propsData: {
                columns: COLUMNS,
                tableData: TABLE_DATA,
                checkboxOption: {
                    // 可控属性
                    hideSelectAll: true,
                },
                rowKeyFieldName: "rowKey",
            },
        });

        await later();

        expect(wrapper.find(".ve-table-header-tr .ve-checkbox").exists()).toBe(
            false,
        );
    });

    it("checkboxOption selectedRowChange event", async () => {
        const mockFn = jest.fn();

        const wrapper = mount(veTable, {
            propsData: {
                columns: COLUMNS,
                tableData: TABLE_DATA,
                checkboxOption: {
                    selectedRowChange: ({
                        row,
                        isSelected,
                        selectedRowKeys,
                    }) => {
                        mockFn(row, isSelected, selectedRowKeys);
                    },
                },
                rowKeyFieldName: "rowKey",
            },
        });

        wrapper
            .findAll(".ve-table-body-tr")
            .at(0)
            .findAll(".ve-table-body-td")
            .at(0)
            .find(".ve-checkbox")
            .trigger("click");

        await later();

        expect(mockFn).toHaveBeenCalled();
        expect(mockFn).toHaveBeenCalledWith(TABLE_DATA[0], true, [1001]);
    });

    it("checkboxOption selectedAllChange event", async () => {
        const mockFn = jest.fn();

        const wrapper = mount(veTable, {
            propsData: {
                columns: COLUMNS,
                tableData: TABLE_DATA,
                checkboxOption: {
                    selectedAllChange: ({ isSelected, selectedRowKeys }) => {
                        mockFn(isSelected, selectedRowKeys);
                    },
                },
                rowKeyFieldName: "rowKey",
            },
        });

        wrapper
            .find(".ve-table-header-tr")
            .findAll(".ve-table-header-th")
            .at(0)
            .find(".ve-checkbox")
            .trigger("click");

        await later();

        expect(mockFn).toHaveBeenCalled();
        expect(mockFn).toHaveBeenCalledWith(
            true,
            [1001, 1002, 1003, 1004, 1005],
        );
    });
});
<|endoftext|>


<commit_before>'use strict';

var inherits     = require('inherits');
var extend       = require('js-extend').extend;
var EventEmitter = require('events').EventEmitter;

/**
 * Old PouchDB did care about database endpoint type before using prefix.
 * If endpoint was remote ('http' or 'https') it did not use prefix.
 * For example 5.3.0 https://github.com/pouchdb/pouchdb/blob/greenkeeper-chai-as-promised-5.3.0/src/constructor.js#L103
 * New PouchDB just prepends prefix to endpoint before resolving adapter.
 * For example 5.4.5 https://github.com/pouchdb/pouchdb/blob/5.4.5-branch/packages/pouchdb-core/src/constructor.js#L109
 * We should use prefix only for local endpoints.
 *
 * This is a Factory that returns modified PouchDB class.
 * Its instances will use specified prefix and adapter for local endpoint and
 * use nothing for remote endpoint.
 *
 * @public
 * @param {Object} PouchDB This is PouchDB class or something that inherits
 * PouchDB that should be fixed.
 * @param {string} defaultDirectory The prefix for local endpoint.
 * @param {Object} options Options with adapter for local endpoint. Generally
 * speaking this should contain {defaultAdapter: require('leveldown-mobile')}
 * for mobile platforms. Otherwise you'll get whatever the default adapter
 * is and for JXcore that would be leveldown (not leveldown-mobile) which
 * doesn't actually work.
 * @returns {PouchDB}
 */
function PouchDBGenerator(PouchDB, defaultDirectory, options) {
  // Shamelessly stolen from https://github.com/pouchdb/pouchdb/blob/fb77927d2f14911478032884f1576b770815bcab/packages/pouchdb-core/src/setup.js#L108-L137

  function PouchAlt(name, opts, callback) {
    if (!(this instanceof PouchAlt)) {
      return new PouchAlt(name, opts, callback);
    }

    if (typeof opts === 'function' || typeof opts === 'undefined') {
      callback = opts;
      opts = {};
    }

    if (name && typeof name === 'object') {
      opts = name;
      name = undefined;
    }

    opts = extend({}, opts);

    // If database endpoint is not remote we are using defaultDirectory as
    // prefix and defaultAdapter as adapter for it.
    if (name !== undefined && name.indexOf('http') !== 0 &&
        name.indexOf('https') !== 0) {
      if (!opts.db && options.defaultAdapter) {
        opts.db = options.defaultAdapter;
      }
      if (!opts.prefix) {
        opts.prefix = defaultDirectory;
      }
    }

    PouchDB.call(this, name, opts, callback);
  }

  options = extend({}, options);

  // This is a workround for #970.
  var CustomEventEmitter = function () {
    return EventEmitter.apply(this, arguments);
  }

  inherits(CustomEventEmitter, EventEmitter);

  inherits(PouchAlt, PouchDB);

  PouchAlt.preferredAdapters = PouchDB.preferredAdapters.slice();
  Object.keys(PouchDB).forEach(function(key) {
    if (!(key in PouchAlt) ) {
      PouchAlt[key] = PouchDB[key];
    }
  });

  // This is a workaround for #870.
  PouchAlt.prototype.info = function () {
    var self = this;
    return PouchAlt.super_.prototype.info.apply(this, arguments)
    .catch(function () {
      return { update_seq: 0 };
    });
  }

  // This is a workaround for #970.
  PouchAlt.prototype.on =
  PouchAlt.prototype.addListener = function (type, listener) {
    var self = this;
    // We don't want PouchDB to catch our exception from 'listener'.
    // We want to cancel current PouchDB action and emit 'error' with exception.
    return PouchAlt.super_.prototype.addListener.call(this, type, function () {
      try {
        listener.apply(this, arguments);
      } catch (e) {
        self.emit('error', e);
      }
    });
  }

  // This is a woraround for #970.
  // 'Changes' has an 'EventEmitter' too, but it isn't exported.
  // We have to overwrite 'EventEmitter' methods here.
  PouchAlt.prototype.changes = function () {
    var changes = PouchAlt.super_.prototype.changes.apply(this, arguments);
    changes.on  = PouchAlt.prototype.on.bind(changes);
    return changes;
  };

  return PouchAlt;
}

module.exports = PouchDBGenerator;
<commit_msg>cosmetics<commit_after>'use strict';

var inherits     = require('inherits');
var extend       = require('js-extend').extend;
var EventEmitter = require('events').EventEmitter;

/**
 * Old PouchDB did care about database endpoint type before using prefix.
 * If endpoint was remote ('http' or 'https') it did not use prefix.
 * For example 5.3.0 https://github.com/pouchdb/pouchdb/blob/greenkeeper-chai-as-promised-5.3.0/src/constructor.js#L103
 * New PouchDB just prepends prefix to endpoint before resolving adapter.
 * For example 5.4.5 https://github.com/pouchdb/pouchdb/blob/5.4.5-branch/packages/pouchdb-core/src/constructor.js#L109
 * We should use prefix only for local endpoints.
 *
 * This is a Factory that returns modified PouchDB class.
 * Its instances will use specified prefix and adapter for local endpoint and
 * use nothing for remote endpoint.
 *
 * @public
 * @param {Object} PouchDB This is PouchDB class or something that inherits
 * PouchDB that should be fixed.
 * @param {string} defaultDirectory The prefix for local endpoint.
 * @param {Object} options Options with adapter for local endpoint. Generally
 * speaking this should contain {defaultAdapter: require('leveldown-mobile')}
 * for mobile platforms. Otherwise you'll get whatever the default adapter
 * is and for JXcore that would be leveldown (not leveldown-mobile) which
 * doesn't actually work.
 * @returns {PouchDB}
 */
function PouchDBGenerator(PouchDB, defaultDirectory, options) {
  // Shamelessly stolen from https://github.com/pouchdb/pouchdb/blob/fb77927d2f14911478032884f1576b770815bcab/packages/pouchdb-core/src/setup.js#L108-L137

  function PouchAlt(name, opts, callback) {
    if (!(this instanceof PouchAlt)) {
      return new PouchAlt(name, opts, callback);
    }

    if (typeof opts === 'function' || typeof opts === 'undefined') {
      callback = opts;
      opts = {};
    }

    if (name && typeof name === 'object') {
      opts = name;
      name = undefined;
    }

    opts = extend({}, opts);

    // If database endpoint is not remote we are using defaultDirectory as
    // prefix and defaultAdapter as adapter for it.
    if (name !== undefined && name.indexOf('http') !== 0 &&
        name.indexOf('https') !== 0) {
      if (!opts.db && options.defaultAdapter) {
        opts.db = options.defaultAdapter;
      }
      if (!opts.prefix) {
        opts.prefix = defaultDirectory;
      }
    }

    PouchDB.call(this, name, opts, callback);
  }

  options = extend({}, options);

  // This is a workround for #970.
  var CustomEventEmitter = function () {
    return EventEmitter.apply(this, arguments);
  }

  inherits(CustomEventEmitter, EventEmitter);

  inherits(PouchAlt, PouchDB);

  PouchAlt.preferredAdapters = PouchDB.preferredAdapters.slice();
  Object.keys(PouchDB).forEach(function(key) {
    if (!(key in PouchAlt) ) {
      PouchAlt[key] = PouchDB[key];
    }
  });

  // This is a workaround for #870.
  PouchAlt.prototype.info = function () {
    var self = this;
    return PouchAlt.super_.prototype.info.apply(this, arguments)
    .catch(function () {
      return { update_seq: 0 };
    });
  }

  // This is a workaround for #970.
  PouchAlt.prototype.on =
  PouchAlt.prototype.addListener = function (type, listener) {
    var self = this;
    // We don't want PouchDB to catch our exception from 'listener'.
    // We want to cancel current PouchDB action and emit 'error' with exception.
    return PouchAlt.super_.prototype.addListener.call(this, type, function () {
      try {
        listener.apply(this, arguments);
      } catch (e) {
        self.emit('error', e);
      }
    });
  }

  // This is a woraround for #970.
  // 'Changes' has an 'EventEmitter' too, but it isn't exported.
  // We have to overwrite 'EventEmitter' methods here.
  PouchAlt.prototype.changes = function () {
    var changes = PouchAlt.super_.prototype.changes.apply(this, arguments);
    // 'Changes' has its own 'addListener' method, we shouldn't overwrite it.
    // This method is based on 'EventEmitter.prototype.on' method.
    changes.on  = PouchAlt.prototype.on.bind(changes);
    return changes;
  };

  return PouchAlt;
}

module.exports = PouchDBGenerator;
<|endoftext|>


<commit_before>/*
 * Copyright 2017 Micro Focus International plc.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
 */

define([
    'underscore',
    'jquery',
    'find/app/util/global-key-listener',
    'find/idol/app/model/answer-bank/idol-answered-questions-collection',
    'find/idol/app/model/entitysearch/entity-search-collection',
    'find/app/page/search/template-helpers/pretty-print-number-helper',
    'text!find/templates/app/page/loading-spinner.html',
    'i18n!find/nls/bundle'
], function(_, $, globalKeyListener, AnsweredQuestionsCollection, EntitySearchCollection, prettyPrintNumberHelper, loadingSpinnerTemplate, i18n) {
    'use strict';

    const loadingHtml = _.template(loadingSpinnerTemplate)({i18n: i18n, large: false});

    function SelectionEntitySearch(options) {
        const documentRenderer = options.documentRenderer;
        const answerServer = options.answerServer;
        // You can control which elements the popup will appear on by adjusting this selector.
        const selector = options.selector || '.main-results-container,.parametric-value-element,.dt-bootstrap,.trending-chart,.sunburst,.entity-topic-map,.leaflet-popup-content,.document-detail-tabs-content,.entity-search-messages';
        const debounceMillis = options.debounceMillis || 250;
        // The length of time you have to hover over the text in the topic map before we automatically entity-search it.
        const hoverDelay = options.hoverDelay || 1000;
        let element = options.element || document.body;

        const entityModels = new EntitySearchCollection();
        let lastQueryText, lastFetch;

        function loadModel(text, bounds) {
            if (lastFetch && lastQueryText !== text) {
                lastFetch.abort();
            }

            lastQueryText = text;

            const $hover = $('
');
            $hover.one('mouseover', function(){
                $hover.removeClass('first-appearance');
            })

            updateIndicator($hover, loadingHtml, bounds, null);

            lastFetch = entityModels.fetch({
                data: { text: text }
            }).done(function(){
                if (text === lastQueryText && entityModels.length) {
                    const result = entityModels.first();
                    const html = documentRenderer.renderEntity(result);

                    $hover.addClass('first-appearance').one('mouseover', function(){
                        $hover.removeClass('first-appearance');
                    })

                    updateIndicator($hover, html, bounds, result.get('title'));
                }
                else {
                    clearIndicator($hover)
                }
            }).fail(function(){
                clearIndicator($hover);
            })
        }

        function clearIndicator($hover) {
            $hover.remove();
        }

        function clearAllIndicators() {
            // We have to clear all selections which were triggered by other selections
            $('.selection-entity').remove();
        }

        function clearClickedIndicator(e) {
            const $closest = $(e.currentTarget).closest('.selection-entity');

            $closest.remove();
        }

        function updateIndicator($hover, html, bounds, context){
            const top = bounds.bottom + 10;
            const left = bounds.left;

            let dragLastX, dragLastY, dragX, dragY, dragging = false;

            function moveElement(evt) {
                if (!dragging) {
                    $hover.addClass('selection-entity-drag');

                    const offset = $hover.offset();
                    dragX = offset.left;
                    dragY = offset.top;
                    dragLastX = evt.pageX;
                    dragLastY = evt.pageY;
                    $hover.css({
                        bottom: 'auto',
                        right: 'auto'
                    })
                }

                dragging = true;

                dragX += evt.pageX - dragLastX;
                dragY += evt.pageY - dragLastY;
                dragLastX = evt.pageX;
                dragLastY = evt.pageY;

                $hover.addClass('selection-entity-drag')
                    .css({
                        left: dragX,
                        top: dragY
                    });
            }

            function onMouseUp() {
                dragging = false;
                $(document)
                    .off('mousemove', moveElement)
                    .off('mouseup', onMouseUp)
            }

            $hover.css({
                top: top,
                left: left
            }).html(html).appendTo(element).on('mousedown', function(evt){
                if ($(evt.target).closest('.entity-search-controls,.entity-search-messages').length) {
                    // Prevent drag start if they're trying to select text in the conversation history.
                    return
                }

                dragLastX = evt.screenX;
                dragLastY = evt.screenY;
                $(document)
                    .on('mousemove', moveElement)
                    .on('mouseup', onMouseUp);
            });

            function reposition(){
                if ($hover.hasClass('selection-entity-drag')) {
                    return;
                }

                if ($hover.height() + top > window.innerHeight) {
                    // If the popup goes below the page, show it above the selection instead of below,
                    // except if that would take it off the top edge of the screen, in which case just lock it to the bottom.
                    const desiredBottom = window.innerHeight - bounds.top + 10;

                    if (desiredBottom + $hover.height() < window.innerHeight) {
                        $hover.css({
                            top: 'auto',
                            bottom: desiredBottom
                        })
                    }
                    else {
                        $hover.css({
                            top: 'auto',
                            bottom: 10
                        })
                    }
                }

                if ($hover.width() + left > window.innerWidth) {
                    // If the selection is off the right edge of the screen, lock it to the right edge of the screen.
                    $hover.css({
                        left: 'auto',
                        right: 10
                    })
                }
            }

            reposition();

            $hover.find('img').on('load', reposition).attr('draggable', false);

            if (!answerServer) {
                $hover.find('.entity-search-messages,.entity-search-controls').remove();
            }

            $hover.find('input.entity-search-question').closest('form').on('submit', function(evt){
                const $input = $(evt.currentTarget).find('input.entity-search-question');
                const text = $input.val().trim();

                const $answerEl = $hover.find('.entity-search-messages');

                if (text && $answerEl.length) {
                    $input.val('');
                    addMessage('entity-search-user', text);

                    const $loading = addHtmlMessage('entity-search-loading', loadingHtml);

                    const questionText = /^(what|who|how|when|where|why)/i.exec(text) ? text : 'what is the ' + text + ' of ' + $input.data('context')

                    const answeredQuestionsCollection = new AnsweredQuestionsCollection();
                    answeredQuestionsCollection.url = 'api/public/answer/entity-search-ask';

                    answeredQuestionsCollection.fetch({
                        data: {
                            text: questionText,
                            maxResults: 1,
                            context: context,
                        },
                        reset: true,
                        success: _.bind(function() {
                            const answer = answeredQuestionsCollection.map(function(model){
                                const text = model.get('answer');
                                const source = model.get('source');
                                const systemName = model.get('systemName');

                                const title = _.compact([systemName, source]).join(': ');
                                const link = /^https?:/i.test(source) ? ''+_.escape(source)+'' : '';

                                const formattedText = text && isFinite(text) && (text >= 10000 || text < 0)? prettyPrintNumberHelper(text, { hash: {}}) : text;

                                return '' + _.escape(formattedText) + ' ' + link + '';
                            }).join('');
                            addHtmlMessage('entity-search-server', answer || _.escape(i18n['entitySearch.template.question.answerMissing']), $loading);
                        }, this),
                        error: _.bind(function() {
                            addMessage('entity-search-server', i18n['entitySearch.template.question.answerError'], $loading);
                        }, this)
                    }, this);
                }

                function addMessage(cssClass, text, $targetEl) {
                    const $el = $('
').text('\n' + text + '\n');
                    insertMessage($el, $targetEl);
                    return $el;
                }

                function addHtmlMessage(cssClass, html, $targetEl) {
                    const $el = $('
').html('\n' + html + '\n');
                    insertMessage($el, $targetEl)
                    return $el;
                }

                function insertMessage($el, $targetEl) {
                    if ($targetEl && $targetEl.length) {
                        $targetEl.replaceWith($el);
                    }
                    else {
                        $el.appendTo($answerEl);
                    }
                    scrollDown($el);
                    reposition();
                    // On IE, we keep losing the focus after the user presses 'Enter', so we reclaim the focus.
                    $input.focus();
                }

                function scrollDown($el) {
                    const dom = $answerEl[0];
                    if (dom.scrollHeight) {
                        dom.scrollTop = $el && $el.length ? ($el[0].offsetTop - $el[0].parentNode.offsetTop) || dom.scrollHeight: dom.scrollHeight;
                    }
                }

                return false;
            })
        }

        function onSelectionChange() {
            const sel = window.getSelection();

            if (sel.rangeCount !== 1) {
                return;
            }

            const range = sel.getRangeAt(0);
            const text = range.toString().trim();

            const $selectEnd = $(sel.focusNode);

            // We're doing selection stuff on the selection popup, we don't want to trigger load indicator changes
            //  otherwise the selection popup will disappear.
            const isInSelection = $selectEnd.closest('.selection-entity').length;

            if (!isInSelection) {
                clearAllIndicators();
            }

            if (text && text.length >= 2) {
                const $summary = $selectEnd.closest(selector);

                if ($summary.length && $(sel.anchorNode).closest(selector).is($summary)) {
                    // We're in a summary, try fetching stuff
                    loadModel(text, range.getBoundingClientRect());
                }
            }
        }

        const debounced = _.debounce(onSelectionChange, debounceMillis);

        let $hoveredEl, hoverTimeout;

        function onMouseOver(evt) {
            const target = evt.currentTarget;
            const $textEl = $(target);
            if (target.textContent && $textEl.closest(selector).length) {
                if ($hoveredEl !== target) {
                    $hoveredEl = target;

                    if (hoverTimeout) {
                        clearTimeout(hoverTimeout);
                    }

                    hoverTimeout = setTimeout(function(){
                        // Load a model for the full text that you're hovering over, unless the user is
                        //   already selecting text on it with the mouse.
                        if (!window.getSelection().length) {
                            clearAllIndicators();
                            loadModel(target.textContent, target.getBoundingClientRect());
                        }
                    }, hoverDelay);
                }
            }
        }

        function onMouseOut(evt) {
            if ($hoveredEl && evt.currentTarget === $hoveredEl) {
                $hoveredEl = null;

                if (hoverTimeout) {
                    clearTimeout(hoverTimeout);
                    hoverTimeout = null;
                }
            }
        }

        $(document)
            .on('selectionchange', debounced)
            .on('click', '.selection-entity-close', clearClickedIndicator)
            .on('mouseover', 'text', onMouseOver)
            .on('mouseout', 'text', onMouseOut)

        globalKeyListener.on('escape', clearAllIndicators);

        this.stopListening = function(){
            $(document)
                .off('selectionchange', debounced)
                .off('click', '.selection-entity-close', clearClickedIndicator)
                .off('mouseover', 'text', onMouseOver)
                .off('mouseover', 'text', onMouseOut)
            globalKeyListener.off('escape', clearAllIndicators);
            clearAllIndicators();
        }

        this.setElement = function(dom) {
            element = dom;
        }
    }

    return SelectionEntitySearch;
});
<commit_msg>(DEMOS-72, CORE-4439) Make the sunburst hover-selectable as well.<commit_after>/*
 * Copyright 2017 Micro Focus International plc.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
 */

define([
    'underscore',
    'jquery',
    'd3',
    'find/app/util/global-key-listener',
    'find/idol/app/model/answer-bank/idol-answered-questions-collection',
    'find/idol/app/model/entitysearch/entity-search-collection',
    'find/app/page/search/template-helpers/pretty-print-number-helper',
    'text!find/templates/app/page/loading-spinner.html',
    'i18n!find/nls/bundle'
], function(_, $, d3, globalKeyListener, AnsweredQuestionsCollection, EntitySearchCollection, prettyPrintNumberHelper, loadingSpinnerTemplate, i18n) {
    'use strict';

    const loadingHtml = _.template(loadingSpinnerTemplate)({i18n: i18n, large: false});

    function SelectionEntitySearch(options) {
        const documentRenderer = options.documentRenderer;
        const answerServer = options.answerServer;
        // You can control which elements the popup will appear on by adjusting this selector.
        const selector = options.selector || '.main-results-container,.parametric-value-element,.dt-bootstrap,.trending-chart,.sunburst,.entity-topic-map,.leaflet-popup-content,.document-detail-tabs-content,.entity-search-messages';
        const debounceMillis = options.debounceMillis || 250;
        // The length of time you have to hover over the text in the topic map before we automatically entity-search it.
        const hoverDelay = options.hoverDelay || 1000;
        let element = options.element || document.body;

        const entityModels = new EntitySearchCollection();
        let lastQueryText, lastFetch;

        function loadModel(text, bounds) {
            if (lastFetch && lastQueryText !== text) {
                lastFetch.abort();
            }

            lastQueryText = text;

            const $hover = $('
');
            $hover.one('mouseover', function(){
                $hover.removeClass('first-appearance');
            })

            updateIndicator($hover, loadingHtml, bounds, null);

            lastFetch = entityModels.fetch({
                data: { text: text }
            }).done(function(){
                if (text === lastQueryText && entityModels.length) {
                    const result = entityModels.first();
                    const html = documentRenderer.renderEntity(result);

                    $hover.addClass('first-appearance').one('mouseover', function(){
                        $hover.removeClass('first-appearance');
                    })

                    updateIndicator($hover, html, bounds, result.get('title'));
                }
                else {
                    clearIndicator($hover)
                }
            }).fail(function(){
                clearIndicator($hover);
            })
        }

        function clearIndicator($hover) {
            $hover.remove();
        }

        function clearAllIndicators() {
            // We have to clear all selections which were triggered by other selections
            $('.selection-entity').remove();
        }

        function clearClickedIndicator(e) {
            const $closest = $(e.currentTarget).closest('.selection-entity');

            $closest.remove();
        }

        function updateIndicator($hover, html, bounds, context){
            const top = bounds.bottom + 10;
            const left = bounds.left;

            let dragLastX, dragLastY, dragX, dragY, dragging = false;

            function moveElement(evt) {
                if (!dragging) {
                    $hover.addClass('selection-entity-drag');

                    const offset = $hover.offset();
                    dragX = offset.left;
                    dragY = offset.top;
                    dragLastX = evt.pageX;
                    dragLastY = evt.pageY;
                    $hover.css({
                        bottom: 'auto',
                        right: 'auto'
                    })
                }

                dragging = true;

                dragX += evt.pageX - dragLastX;
                dragY += evt.pageY - dragLastY;
                dragLastX = evt.pageX;
                dragLastY = evt.pageY;

                $hover.addClass('selection-entity-drag')
                    .css({
                        left: dragX,
                        top: dragY
                    });
            }

            function onMouseUp() {
                dragging = false;
                $(document)
                    .off('mousemove', moveElement)
                    .off('mouseup', onMouseUp)
            }

            $hover.css({
                top: top,
                left: left
            }).html(html).appendTo(element).on('mousedown', function(evt){
                if ($(evt.target).closest('.entity-search-controls,.entity-search-messages').length) {
                    // Prevent drag start if they're trying to select text in the conversation history.
                    return
                }

                dragLastX = evt.screenX;
                dragLastY = evt.screenY;
                $(document)
                    .on('mousemove', moveElement)
                    .on('mouseup', onMouseUp);
            });

            function reposition(){
                if ($hover.hasClass('selection-entity-drag')) {
                    return;
                }

                if ($hover.height() + top > window.innerHeight) {
                    // If the popup goes below the page, show it above the selection instead of below,
                    // except if that would take it off the top edge of the screen, in which case just lock it to the bottom.
                    const desiredBottom = window.innerHeight - bounds.top + 10;

                    if (desiredBottom + $hover.height() < window.innerHeight) {
                        $hover.css({
                            top: 'auto',
                            bottom: desiredBottom
                        })
                    }
                    else {
                        $hover.css({
                            top: 'auto',
                            bottom: 10
                        })
                    }
                }

                if ($hover.width() + left > window.innerWidth) {
                    // If the selection is off the right edge of the screen, lock it to the right edge of the screen.
                    $hover.css({
                        left: 'auto',
                        right: 10
                    })
                }
            }

            reposition();

            $hover.find('img').on('load', reposition).attr('draggable', false);

            if (!answerServer) {
                $hover.find('.entity-search-messages,.entity-search-controls').remove();
            }

            $hover.find('input.entity-search-question').closest('form').on('submit', function(evt){
                const $input = $(evt.currentTarget).find('input.entity-search-question');
                const text = $input.val().trim();

                const $answerEl = $hover.find('.entity-search-messages');

                if (text && $answerEl.length) {
                    $input.val('');
                    addMessage('entity-search-user', text);

                    const $loading = addHtmlMessage('entity-search-loading', loadingHtml);

                    const questionText = /^(what|who|how|when|where|why)/i.exec(text) ? text : 'what is the ' + text + ' of ' + $input.data('context')

                    const answeredQuestionsCollection = new AnsweredQuestionsCollection();
                    answeredQuestionsCollection.url = 'api/public/answer/entity-search-ask';

                    answeredQuestionsCollection.fetch({
                        data: {
                            text: questionText,
                            maxResults: 1,
                            context: context,
                        },
                        reset: true,
                        success: _.bind(function() {
                            const answer = answeredQuestionsCollection.map(function(model){
                                const text = model.get('answer');
                                const source = model.get('source');
                                const systemName = model.get('systemName');

                                const title = _.compact([systemName, source]).join(': ');
                                const link = /^https?:/i.test(source) ? ''+_.escape(source)+'' : '';

                                const formattedText = text && isFinite(text) && (text >= 10000 || text < 0)? prettyPrintNumberHelper(text, { hash: {}}) : text;

                                return '' + _.escape(formattedText) + ' ' + link + '';
                            }).join('');
                            addHtmlMessage('entity-search-server', answer || _.escape(i18n['entitySearch.template.question.answerMissing']), $loading);
                        }, this),
                        error: _.bind(function() {
                            addMessage('entity-search-server', i18n['entitySearch.template.question.answerError'], $loading);
                        }, this)
                    }, this);
                }

                function addMessage(cssClass, text, $targetEl) {
                    const $el = $('
').text('\n' + text + '\n');
                    insertMessage($el, $targetEl);
                    return $el;
                }

                function addHtmlMessage(cssClass, html, $targetEl) {
                    const $el = $('
').html('\n' + html + '\n');
                    insertMessage($el, $targetEl)
                    return $el;
                }

                function insertMessage($el, $targetEl) {
                    if ($targetEl && $targetEl.length) {
                        $targetEl.replaceWith($el);
                    }
                    else {
                        $el.appendTo($answerEl);
                    }
                    scrollDown($el);
                    reposition();
                    // On IE, we keep losing the focus after the user presses 'Enter', so we reclaim the focus.
                    $input.focus();
                }

                function scrollDown($el) {
                    const dom = $answerEl[0];
                    if (dom.scrollHeight) {
                        dom.scrollTop = $el && $el.length ? ($el[0].offsetTop - $el[0].parentNode.offsetTop) || dom.scrollHeight: dom.scrollHeight;
                    }
                }

                return false;
            })
        }

        function onSelectionChange() {
            const sel = window.getSelection();

            if (sel.rangeCount !== 1) {
                return;
            }

            const range = sel.getRangeAt(0);
            const text = range.toString().trim();

            const $selectEnd = $(sel.focusNode);

            // We're doing selection stuff on the selection popup, we don't want to trigger load indicator changes
            //  otherwise the selection popup will disappear.
            const isInSelection = $selectEnd.closest('.selection-entity').length;

            if (!isInSelection) {
                clearAllIndicators();
            }

            if (text && text.length >= 2) {
                const $summary = $selectEnd.closest(selector);

                if ($summary.length && $(sel.anchorNode).closest(selector).is($summary)) {
                    // We're in a summary, try fetching stuff
                    loadModel(text, range.getBoundingClientRect());
                }
            }
        }

        const debounced = _.debounce(onSelectionChange, debounceMillis);

        let $hoveredEl, hoverTimeout;

        function onMouseOver(evt) {
            const target = evt.currentTarget;
            const $textEl = $(target);
            // textContent is a standard property on SVG text elements,
            //   while 'text' is a property on the sunburst data object.
            const text = target.textContent || (d3.select(evt.currentTarget).datum() || {}).text
            if (text && $textEl.closest(selector).length) {
                if ($hoveredEl !== target) {
                    $hoveredEl = target;

                    if (hoverTimeout) {
                        clearTimeout(hoverTimeout);
                    }

                    hoverTimeout = setTimeout(function(){
                        // Load a model for the full text that you're hovering over, unless the user is
                        //   already selecting text on it with the mouse.
                        if (!window.getSelection().length) {
                            clearAllIndicators();
                            loadModel(text, target.getBoundingClientRect());
                        }
                    }, hoverDelay);
                }
            }
        }

        function onMouseOut(evt) {
            if ($hoveredEl && evt.currentTarget === $hoveredEl) {
                $hoveredEl = null;

                if (hoverTimeout) {
                    clearTimeout(hoverTimeout);
                    hoverTimeout = null;
                }
            }
        }

        $(document)
            .on('selectionchange', debounced)
            .on('click', '.selection-entity-close', clearClickedIndicator)
            .on('mouseover', 'text,path', onMouseOver)
            .on('mouseout', 'text,path', onMouseOut)

        globalKeyListener.on('escape', clearAllIndicators);

        this.stopListening = function(){
            $(document)
                .off('selectionchange', debounced)
                .off('click', '.selection-entity-close', clearClickedIndicator)
                .off('mouseover', 'text,path', onMouseOver)
                .off('mouseover', 'text,path', onMouseOut)
            globalKeyListener.off('escape', clearAllIndicators);
            clearAllIndicators();
        }

        this.setElement = function(dom) {
            element = dom;
        }
    }

    return SelectionEntitySearch;
});
<|endoftext|>


<commit_before>import { seq } from '../common/lazyseq';

import * as covers from '../dsymbols/covers';
import * as DS from '../dsymbols/delaney';
import * as DS2D from '../dsymbols/delaney2d';
import * as props from '../dsymbols/properties';

import * as branch from './branchings2d';

const noEdgeSharingQuads = ds => DS.orbitReps2(ds, 0, 2)
  .every(D => DS.m(ds, 0, 1, D) > 4 || DS.m(ds, 0, 1, ds.s(2, D)) > 4);

if (require.main == module) {
  const n = parseInt(process.argv[2])

  seq(covers.covers(DS.parse('<1.1:1:1,1,1:0,3>'), n * 6))
    .filter(ds => DS.orbitReps2(ds, 1, 2).length == n)
    .filter(DS2D.isProtoEuclidean)
    .flatMap(ds => branch.branchings(ds, 4))
    .filter(noEdgeSharingQuads)
    .filter(props.isMinimal)
    .filter(DS2D.isEuclidean)
    .forEach(ds => console.log(`${ds}`));
}
<commit_msg>2dzeo: rewrite as plain generator and export it<commit_after>import { seq } from '../common/lazyseq';
import * as covers from '../dsymbols/covers';
import * as DS from '../dsymbols/delaney';
import * as DS2D from '../dsymbols/delaney2d';
import * as props from '../dsymbols/properties';
import * as branch from './branchings2d';

export function* twoDimensionalZeolites(nrVertices) {
  const base = DS.parse('<1.1:1:1,1,1:0,3>');

  for (const dset of covers.covers(base, nrVertices * 6)) {
    if (dset.orbitReps2(1, 2).length != nrVertices)
      continue;

    if (!DS2D.isProtoEuclidean(dset))
      continue;

    for (const ds of branch.branchings(dset, 4)) {
      const reps = ds.orbitReps2(0, 2);

      if (reps.some(D => ds.m(0, 1, D) == 4 && ds.m(0, 1, ds.s(2, D)) == 4))
        continue;

      if (props.isMinimal(ds) && DS2D.isEuclidean(ds))
        yield ds;
    }
  }
}

if (require.main == module) {
  const n = parseInt(process.argv[2])

  for (const ds of twoDimensionalZeolites(n))
    console.log(`${ds}`);
}
<|endoftext|>


<commit_before>// This plugin replace Parsley default form behavior that auto bind its fields children
// With this plugin you must register in constructor your form's fields and their constraints
// You have this way a total javascript control over your form validation, and nothing needed in DOM

import jQuery from 'jquery'; // Remove this line in ES3

(function ($) {

  window.ParsleyConfig = $.extend(true, window.ParsleyConfig, { autoBind: false });
  window.ParsleyExtend = window.ParsleyExtend || {};

  window.ParsleyExtend = $.extend(window.ParsleyExtend, {
    // { '#selector' : { constraintName1: value, constraintName2: value2 }, #selector2: { constraintName: value } }
    // { '#selector' : { constraintName1: { requirements: value, priority: value }, constraintName2: value2 } }
    _bindFields: function () {
      if ('ParsleyForm' !== this.__class__)
        throw new Error('`_bindFields` must be called on a form instance');

      if ('undefined' === typeof this.options.fields)
        throw new Error('bind.js plugin needs to have Parsley instantiated with fields');

      var field;
      this.fields = [];

      for (var selector in this.options.fields) {
        if (0 === $(selector).length)
          continue;

        field = $(selector).parsley();

        for (var name in this.options.fields[selector]) {
          if ('object' === typeof this.options.fields[selector][name] && !(this.options.fields[selector][name] instanceof Array))
            field.addConstraint(name.toLowerCase(), this.options.fields[selector][name].requirements, this.options.fields[selector][name].priority || 32);
          else
            field.addConstraint(name.toLowerCase(), this.options.fields[selector][name]);
        }
      }

      this.fields.push(field);

      return this;
    },

    // Do nothing
    _bindConstraints: function () {
      return this;
    }
  });

})(jQuery);
<commit_msg>Tweak<commit_after>// This plugin replace Parsley default form behavior that auto bind its fields children
// With this plugin you must register in constructor your form's fields and their constraints
// You have this way a total javascript control over your form validation, and nothing needed in DOM

import jQuery from 'jquery'; // Remove this line in ES3

(function ($) {

  window.ParsleyConfig = $.extend(true, window.ParsleyConfig, {autoBind: false});
  window.ParsleyExtend = window.ParsleyExtend || {};

  window.ParsleyExtend = $.extend(window.ParsleyExtend, {
    // { '#selector' : { constraintName1: value, constraintName2: value2 }, #selector2: { constraintName: value } }
    // { '#selector' : { constraintName1: { requirements: value, priority: value }, constraintName2: value2 } }
    _bindFields: function () {
      if ('ParsleyForm' !== this.__class__)
        throw new Error('`_bindFields` must be called on a form instance');

      if ('undefined' === typeof this.options.fields)
        throw new Error('bind.js plugin needs to have Parsley instantiated with fields');

      var field;
      this.fields = [];

      for (var selector in this.options.fields) {
        if (0 === $(selector).length)
          continue;

        field = $(selector).parsley();

        for (var name in this.options.fields[selector]) {
          if ('object' === typeof this.options.fields[selector][name] && !(this.options.fields[selector][name] instanceof Array))
            field.addConstraint(name.toLowerCase(), this.options.fields[selector][name].requirements, this.options.fields[selector][name].priority || 32);
          else
            field.addConstraint(name.toLowerCase(), this.options.fields[selector][name]);
        }
      }

      this.fields.push(field);

      return this;
    },

    // Do nothing
    _bindConstraints: function () {
      return this;
    }
  });

})(jQuery);
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';
import Helmet from 'react-helmet';
import { connect } from 'react-redux';
import _ from 'lodash';
import moment from 'moment';
//
import { Basic, Advanced, Managers, Enums } from 'czechidm-core';
import { ProvisioningOperationManager, ProvisioningArchiveManager } from '../../redux';
import ProvisioningOperationTableComponent, { ProvisioningOperationTable } from './ProvisioningOperationTable';
import ProvisioningOperationTypeEnum from '../../domain/ProvisioningOperationTypeEnum';
import SystemEntityTypeEnum from '../../domain/SystemEntityTypeEnum';
//
const manager = new ProvisioningOperationManager();
const archiveManager = new ProvisioningArchiveManager();

/**
 * Active and archived provisioning operations
 *
 * @author Radek Tomiška
 */
class ProvisioningOperations extends Basic.AbstractContent {

  constructor(props, context) {
    super(props, context);
    this.state = {
      detail: {
        show: false,
        entity: null
      },
      retryDialog: {
        show: false,
        bulkActionValue: null,
        ids: []
      }
    };
  }

  getManager() {
    return manager;
  }

  getContentKey() {
    return 'acc:content.provisioningOperations';
  }

  /**
   * Shows retry dialog
   *
   * @return {[type]} [description]
   */
  showRetryDialog(bulkActionValue, ids) {
    this.setState({
      retryDialog: {
        show: true,
        bulkActionValue,
        ids
      }
    });
  }

  onRetry(batch = true) {
    const { retryDialog } = this.state;
    this.closeRetryDialog();
    this.context.store.dispatch(manager.retry(retryDialog.ids, retryDialog.bulkActionValue, batch, () => {
      // clear selected rows and reload
      this.refs.table.getWrappedInstance().clearSelectedRows();
      this.refs.table.getWrappedInstance().reload();
      this.refs.archiveTable.getWrappedInstance().reload();
    }));
  }

  /**
   * Close modal retry dialog
   */
  closeRetryDialog() {
    this.setState({
      retryDialog: {
        show: false,
        bulkActionValue: null,
        ids: []
      }
    });
  }

  /**
   * Shows modal detail with given entity
   */
  showDetail(entity) {
    this.setState({
      detail: {
        show: true,
        entity
      }
    });
  }

  /**
   * Close modal detail
   */
  closeDetail() {
    this.setState({
      detail: {
        show: false,
        entity: null
      }
    });
  }

  render() {
    const { forceSearchParameters, columns, uiKey } = this.props;
    const { detail, retryDialog } = this.state;
    // accountObject to table
    const accountData = [];
    if (detail.entity && detail.entity.provisioningContext.accountObject) {
      const accountObject = detail.entity.provisioningContext.accountObject;
      for (const schemaAttributeId in accountObject) {
        if (!accountObject.hasOwnProperty(schemaAttributeId)) {
          continue;
        }
        let content = '';
        const propertyValue = accountObject[schemaAttributeId];
        if (_.isArray(propertyValue)) {
          content = propertyValue.join(', ');
        } else {
          content = propertyValue;
        }

        accountData.push({
          property: schemaAttributeId,
          value: content
        });
      }
    }
    //
    const connectorData = [];
    if (detail.entity && detail.entity.provisioningContext.connectorObject) {
      const connectorObject = detail.entity.provisioningContext.connectorObject;
      connectorObject.attributes.forEach(attribute => {
        connectorData.push({
          property: attribute.name,
          value: attribute.values.join(', ')
        });
      });
    }
    //
    return (

        <Helmet title={this.i18n('title')} />

        <Basic.Tabs>
          <Basic.Tab eventKey={1} title={this.i18n('tabs.active.label')}>
            <ProvisioningOperationTableComponent
              ref="table"
              uiKey={ uiKey }
              manager={manager}
              showDetail={this.showDetail.bind(this)}
              showRowSelection={Managers.SecurityManager.hasAnyAuthority(['SYSTEM_ADMIN'])}
              forceSearchParameters={forceSearchParameters}
              columns={columns}
              actions={
                [
                  { value: 'retry', niceLabel: this.i18n('action.retry.action'), action: this.showRetryDialog.bind(this) },
                  { value: 'cancel', niceLabel: this.i18n('action.cancel.action'), action: this.showRetryDialog.bind(this) }
                ]
              }/>
          </Basic.Tab>

          <Basic.Tab eventKey={2} title={this.i18n('tabs.archive.label')}>
            <ProvisioningOperationTableComponent
              ref="archiveTable"
              uiKey={ `archive-${uiKey}` }
              manager={ archiveManager }
              showDetail={ this.showDetail.bind(this) }
              forceSearchParameters={ forceSearchParameters }
              columns={ columns }/>
          </Basic.Tab>
        </Basic.Tabs>

        <Basic.Modal
          bsSize="large"
          show={detail.show}
          onHide={this.closeDetail.bind(this)}
          backdrop="static">
          <Basic.Modal.Header closeButton text={this.i18n('detail.header')}/>
          <Basic.Modal.Body>
            {
              !detail.entity
              ||

                <Basic.AbstractForm data={detail.entity} readOnly>

                  <Basic.Row>

                      <Basic.LabelWrapper label={this.i18n('entity.created')}>

                          <Advanced.DateValue value={detail.entity.created} showTime/>

                      </Basic.LabelWrapper>

                      <Basic.EnumLabel ref="operationType" label={this.i18n('acc:entity.ProvisioningOperation.operationType')} enum={ProvisioningOperationTypeEnum}/>

                  </Basic.Row>

                  <Basic.Row>

                      <Basic.EnumLabel ref="entityType" label={this.i18n('acc:entity.SystemEntity.entityType')} enum={SystemEntityTypeEnum}/>

                      <Basic.LabelWrapper label={this.i18n('acc:entity.ProvisioningOperation.entity')}>
                        {
                          !detail.entity.entityIdentifier
                          ?
                          N/A
                          :
                          <Advanced.EntityInfo entityType={detail.entity.entityType} entityIdentifier={detail.entity.entityIdentifier} style={{ margin: 0 }} face="popover"/>
                        }
                      </Basic.LabelWrapper>

                  </Basic.Row>

                  <Basic.Row>

                      <Basic.LabelWrapper label={this.i18n('acc:entity.System.name')}>
                        <Advanced.EntityInfo
                          entityType="system"
                          entityIdentifier={ detail.entity.system }
                          entity={ detail.entity._embedded.system }
                          style={{ margin: 0 }}
                          face="popover"/>
                      </Basic.LabelWrapper>

                      <Basic.LabelWrapper label={this.i18n('acc:entity.SystemEntity.uid')}>

                          {detail.entity.systemEntityUid}

                      </Basic.LabelWrapper>

                  </Basic.Row>

                </Basic.AbstractForm>

{ this.i18n('detail.result') }

                  <Basic.EnumValue value={detail.entity.resultState} enum={Enums.OperationStateEnum}/>
                  {
                    (!detail.entity.result || !detail.entity.result.code)
                    ||

                      {this.i18n('detail.resultCode')}: {detail.entity.result.code}

                  }
                  <Basic.FlashMessage message={this.getFlashManager().convertFromResultModel(detail.entity.result.model)} style={{ marginTop: 15 }}/>
                  {
                    (!detail.entity.request || !detail.entity.request.nextAttempt)
                    ||

                  }

                {
                  (!detail.entity.result || !detail.entity.result.stackTrace)
                  ||

                    <textArea
                      rows="10"
                      value={detail.entity.result.stackTrace}
                      readOnly
                      style={{ width: '100%', marginBottom: 15 }}/>

                }
                <Basic.Row>

{this.i18n('detail.accountObject')}

                    <Basic.Table
                      data={accountData}
                      noData={this.i18n('component.basic.Table.noData')}
                      className="table-bordered">
                      <Basic.Column property="property" header={this.i18n('label.property')}/>
                      <Basic.Column property="value" header={this.i18n('label.value')}/>
                    </Basic.Table>

{this.i18n('detail.connectorObject')}

                    <Basic.Table
                      data={connectorData}
                      noData={this.i18n('component.basic.Table.noData')}
                      className="table-bordered">
                      <Basic.Column property="property" header={this.i18n('label.property')}/>
                      <Basic.Column property="value" header={this.i18n('label.value')}/>
                    </Basic.Table>

                </Basic.Row>

            }
          </Basic.Modal.Body>

          <Basic.Modal.Footer>
            <Basic.Button
              level="link"
              onClick={this.closeDetail.bind(this)}>
              {this.i18n('button.close')}
            </Basic.Button>
          </Basic.Modal.Footer>
        </Basic.Modal>

        <Basic.Modal
          show={retryDialog.show}
          onHide={this.closeRetryDialog.bind(this)}
          backdrop="static">
          <Basic.Modal.Header closeButton text={this.i18n(`action.${retryDialog.bulkActionValue}.header`, { count: retryDialog.ids.length})}/>
          <Basic.Modal.Body>
            <span dangerouslySetInnerHTML={{__html: this.i18n(`action.${retryDialog.bulkActionValue}.batchMessage`, { count: retryDialog.ids.length, name: manager.getNiceLabel(manager.getEntity(this.context.store.getState(), retryDialog.ids[0])) })}}/>
          </Basic.Modal.Body>

          <Basic.Modal.Footer>
            <Basic.Button
              level="success"
              onClick={this.onRetry.bind(this, true)}>
              {this.i18n(`action.${retryDialog.bulkActionValue}.button.batch`)}
            </Basic.Button>
          </Basic.Modal.Footer>
          <Basic.Modal.Body>
            <span dangerouslySetInnerHTML={{__html: this.i18n(`action.${retryDialog.bulkActionValue}.message`, { count: retryDialog.ids.length, name: manager.getNiceLabel(manager.getEntity(this.context.store.getState(), retryDialog.ids[0])) })}}/>
          </Basic.Modal.Body>
          <Basic.Modal.Footer>
            <Basic.Button
              level="link"
              onClick={this.closeRetryDialog.bind(this)}>
              {this.i18n('button.close')}
            </Basic.Button>
            <Basic.Button
              level="warning"
              onClick={this.onRetry.bind(this, false)}>
              {this.i18n(`action.${retryDialog.bulkActionValue}.button.selected`)}
            </Basic.Button>
          </Basic.Modal.Footer>
        </Basic.Modal>

    );
  }
}

ProvisioningOperations.propTypes = {
  uiKey: PropTypes.string.isRequired,
  /**
   * Force searchparameters - system id
   */
  forceSearchParameters: PropTypes.object
};
ProvisioningOperations.defaultProps = {
  forceSearchParameters: null,
  columns: ProvisioningOperationTable.defaultProps.columns
};

function select() {
  return {
  };
}

export default connect(select)(ProvisioningOperations);
<commit_msg>#698 fix provisioning detail<commit_after>import React, { PropTypes } from 'react';
import Helmet from 'react-helmet';
import { connect } from 'react-redux';
import _ from 'lodash';
import moment from 'moment';
//
import { Basic, Advanced, Managers, Enums } from 'czechidm-core';
import { ProvisioningOperationManager, ProvisioningArchiveManager } from '../../redux';
import ProvisioningOperationTableComponent, { ProvisioningOperationTable } from './ProvisioningOperationTable';
import ProvisioningOperationTypeEnum from '../../domain/ProvisioningOperationTypeEnum';
import SystemEntityTypeEnum from '../../domain/SystemEntityTypeEnum';
//
const manager = new ProvisioningOperationManager();
const archiveManager = new ProvisioningArchiveManager();

/**
 * Active and archived provisioning operations
 *
 * @author Radek Tomiška
 */
class ProvisioningOperations extends Basic.AbstractContent {

  constructor(props, context) {
    super(props, context);
    this.state = {
      detail: {
        show: false,
        entity: null
      },
      retryDialog: {
        show: false,
        bulkActionValue: null,
        ids: []
      }
    };
  }

  getManager() {
    return manager;
  }

  getContentKey() {
    return 'acc:content.provisioningOperations';
  }

  /**
   * Shows retry dialog
   *
   * @return {[type]} [description]
   */
  showRetryDialog(bulkActionValue, ids) {
    this.setState({
      retryDialog: {
        show: true,
        bulkActionValue,
        ids
      }
    });
  }

  onRetry(batch = true) {
    const { retryDialog } = this.state;
    this.closeRetryDialog();
    this.context.store.dispatch(manager.retry(retryDialog.ids, retryDialog.bulkActionValue, batch, () => {
      // clear selected rows and reload
      this.refs.table.getWrappedInstance().clearSelectedRows();
      this.refs.table.getWrappedInstance().reload();
      this.refs.archiveTable.getWrappedInstance().reload();
    }));
  }

  /**
   * Close modal retry dialog
   */
  closeRetryDialog() {
    this.setState({
      retryDialog: {
        show: false,
        bulkActionValue: null,
        ids: []
      }
    });
  }

  /**
   * Shows modal detail with given entity
   */
  showDetail(entity) {
    this.setState({
      detail: {
        show: true,
        entity
      }
    });
  }

  /**
   * Close modal detail
   */
  closeDetail() {
    this.setState({
      detail: {
        show: false,
        entity: null
      }
    });
  }

  render() {
    const { forceSearchParameters, columns, uiKey } = this.props;
    const { detail, retryDialog } = this.state;
    // accountObject to table
    const accountData = [];
    if (detail.entity && detail.entity.provisioningContext.accountObject) {
      const accountObject = detail.entity.provisioningContext.accountObject;
      for (const schemaAttributeId in accountObject) {
        if (!accountObject.hasOwnProperty(schemaAttributeId)) {
          continue;
        }
        let content = '';
        const propertyValue = accountObject[schemaAttributeId];
        if (_.isArray(propertyValue)) {
          content = propertyValue.join(', ');
        } else if (_.isObject(propertyValue)) {
          content = JSON.stringify(propertyValue);
        } else {
          content = propertyValue;
        }

        accountData.push({
          property: schemaAttributeId,
          value: content
        });
      }
    }
    //
    const connectorData = [];
    if (detail.entity && detail.entity.provisioningContext.connectorObject) {
      const connectorObject = detail.entity.provisioningContext.connectorObject;
      connectorObject.attributes.forEach(attribute => {
        connectorData.push({
          property: attribute.name,
          value: attribute.values.join(', ')
        });
      });
    }
    //
    return (

        <Helmet title={this.i18n('title')} />

        <Basic.Tabs>
          <Basic.Tab eventKey={1} title={this.i18n('tabs.active.label')}>
            <ProvisioningOperationTableComponent
              ref="table"
              uiKey={ uiKey }
              manager={manager}
              showDetail={this.showDetail.bind(this)}
              showRowSelection={Managers.SecurityManager.hasAnyAuthority(['SYSTEM_ADMIN'])}
              forceSearchParameters={forceSearchParameters}
              columns={columns}
              actions={
                [
                  { value: 'retry', niceLabel: this.i18n('action.retry.action'), action: this.showRetryDialog.bind(this) },
                  { value: 'cancel', niceLabel: this.i18n('action.cancel.action'), action: this.showRetryDialog.bind(this) }
                ]
              }/>
          </Basic.Tab>

          <Basic.Tab eventKey={2} title={this.i18n('tabs.archive.label')}>
            <ProvisioningOperationTableComponent
              ref="archiveTable"
              uiKey={ `archive-${uiKey}` }
              manager={ archiveManager }
              showDetail={ this.showDetail.bind(this) }
              forceSearchParameters={ forceSearchParameters }
              columns={ columns }/>
          </Basic.Tab>
        </Basic.Tabs>

        <Basic.Modal
          bsSize="large"
          show={detail.show}
          onHide={this.closeDetail.bind(this)}
          backdrop="static">
          <Basic.Modal.Header closeButton text={this.i18n('detail.header')}/>
          <Basic.Modal.Body>
            {
              !detail.entity
              ||

                <Basic.AbstractForm data={detail.entity} readOnly>

                  <Basic.Row>

                      <Basic.LabelWrapper label={this.i18n('entity.created')}>

                          <Advanced.DateValue value={detail.entity.created} showTime/>

                      </Basic.LabelWrapper>

                      <Basic.EnumLabel ref="operationType" label={this.i18n('acc:entity.ProvisioningOperation.operationType')} enum={ProvisioningOperationTypeEnum}/>

                  </Basic.Row>

                  <Basic.Row>

                      <Basic.EnumLabel ref="entityType" label={this.i18n('acc:entity.SystemEntity.entityType')} enum={SystemEntityTypeEnum}/>

                      <Basic.LabelWrapper label={this.i18n('acc:entity.ProvisioningOperation.entity')}>
                        {
                          !detail.entity.entityIdentifier
                          ?
                          N/A
                          :
                          <Advanced.EntityInfo entityType={detail.entity.entityType} entityIdentifier={detail.entity.entityIdentifier} style={{ margin: 0 }} face="popover"/>
                        }
                      </Basic.LabelWrapper>

                  </Basic.Row>

                  <Basic.Row>

                      <Basic.LabelWrapper label={this.i18n('acc:entity.System.name')}>
                        <Advanced.EntityInfo
                          entityType="system"
                          entityIdentifier={ detail.entity.system }
                          entity={ detail.entity._embedded.system }
                          style={{ margin: 0 }}
                          face="popover"/>
                      </Basic.LabelWrapper>

                      <Basic.LabelWrapper label={this.i18n('acc:entity.SystemEntity.uid')}>

                          {detail.entity.systemEntityUid}

                      </Basic.LabelWrapper>

                  </Basic.Row>

                </Basic.AbstractForm>

{ this.i18n('detail.result') }

                  <Basic.EnumValue value={detail.entity.resultState} enum={Enums.OperationStateEnum}/>
                  {
                    (!detail.entity.result || !detail.entity.result.code)
                    ||

                      {this.i18n('detail.resultCode')}: {detail.entity.result.code}

                  }
                  <Basic.FlashMessage message={this.getFlashManager().convertFromResultModel(detail.entity.result.model)} style={{ marginTop: 15 }}/>
                  {
                    (!detail.entity.request || !detail.entity.request.nextAttempt)
                    ||

                  }

                {
                  (!detail.entity.result || !detail.entity.result.stackTrace)
                  ||

                    <textArea
                      rows="10"
                      value={detail.entity.result.stackTrace}
                      readOnly
                      style={{ width: '100%', marginBottom: 15 }}/>

                }
                <Basic.Row>

{this.i18n('detail.accountObject')}

                    <Basic.Table
                      data={accountData}
                      noData={this.i18n('component.basic.Table.noData')}
                      className="table-bordered">
                      <Basic.Column property="property" header={this.i18n('label.property')}/>
                      <Basic.Column property="value" header={this.i18n('label.value')}/>
                    </Basic.Table>

{this.i18n('detail.connectorObject')}

                    <Basic.Table
                      data={connectorData}
                      noData={this.i18n('component.basic.Table.noData')}
                      className="table-bordered">
                      <Basic.Column property="property" header={this.i18n('label.property')}/>
                      <Basic.Column property="value" header={this.i18n('label.value')}/>
                    </Basic.Table>

                </Basic.Row>

            }
          </Basic.Modal.Body>

          <Basic.Modal.Footer>
            <Basic.Button
              level="link"
              onClick={this.closeDetail.bind(this)}>
              {this.i18n('button.close')}
            </Basic.Button>
          </Basic.Modal.Footer>
        </Basic.Modal>

        <Basic.Modal
          show={retryDialog.show}
          onHide={this.closeRetryDialog.bind(this)}
          backdrop="static">
          <Basic.Modal.Header closeButton text={this.i18n(`action.${retryDialog.bulkActionValue}.header`, { count: retryDialog.ids.length})}/>
          <Basic.Modal.Body>
            <span dangerouslySetInnerHTML={{__html: this.i18n(`action.${retryDialog.bulkActionValue}.batchMessage`, { count: retryDialog.ids.length, name: manager.getNiceLabel(manager.getEntity(this.context.store.getState(), retryDialog.ids[0])) })}}/>
          </Basic.Modal.Body>

          <Basic.Modal.Footer>
            <Basic.Button
              level="success"
              onClick={this.onRetry.bind(this, true)}>
              {this.i18n(`action.${retryDialog.bulkActionValue}.button.batch`)}
            </Basic.Button>
          </Basic.Modal.Footer>
          <Basic.Modal.Body>
            <span dangerouslySetInnerHTML={{__html: this.i18n(`action.${retryDialog.bulkActionValue}.message`, { count: retryDialog.ids.length, name: manager.getNiceLabel(manager.getEntity(this.context.store.getState(), retryDialog.ids[0])) })}}/>
          </Basic.Modal.Body>
          <Basic.Modal.Footer>
            <Basic.Button
              level="link"
              onClick={this.closeRetryDialog.bind(this)}>
              {this.i18n('button.close')}
            </Basic.Button>
            <Basic.Button
              level="warning"
              onClick={this.onRetry.bind(this, false)}>
              {this.i18n(`action.${retryDialog.bulkActionValue}.button.selected`)}
            </Basic.Button>
          </Basic.Modal.Footer>
        </Basic.Modal>

    );
  }
}

ProvisioningOperations.propTypes = {
  uiKey: PropTypes.string.isRequired,
  /**
   * Force searchparameters - system id
   */
  forceSearchParameters: PropTypes.object
};
ProvisioningOperations.defaultProps = {
  forceSearchParameters: null,
  columns: ProvisioningOperationTable.defaultProps.columns
};

function select() {
  return {
  };
}

export default connect(select)(ProvisioningOperations);
<|endoftext|>


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

  function FacetlyDirective(Utils) {

    return {
      restrict: 'E',
      replace: true,
      templateUrl: 'angular-facetly.html',
      scope: {
        options: '=?',
        filteredBy: '=ngModel',
        facets: '=',
        doSearch: '&?'
      },
      link: function (scope) {

        var updateTypeaheadSuggestions = function (facets, filters) {
          return _.chain(facets)
                  .filter(function (facet) {
                    return _.findIndex(filters, { id: facet.id }) === -1;
                  })
                  .map(function (facet) {
                    return facet.label;
                  })
                  .value();
        };

        scope.options = _.assign({}, scope.options);

        scope.filters = Utils.setFilters(scope.filteredBy, scope.facets);

        scope.typeaheadSuggestions = updateTypeaheadSuggestions(scope.facets, scope.filters);

        scope.addFilter = function (facet) {
          scope.filters = Utils.addFilter(scope.filters, facet);
        };

        scope.removeFilter = function (key) {
          scope.filters = Utils.removeFilter(scope.filters, key);
        };

        scope.removeAllFilters = function () {
          scope.filters = Utils.removeAllFilters();

          // FIXME: Duplication
          if (typeof scope.doSearch === 'function') {
            scope.filteredBy = Utils.updateModel(scope.filters);
            scope.doSearch();
          }
        };

        scope.availableFacets = function (facet) {
          var filters = scope.filters || [];
          return _.findIndex(filters, { id: facet.id }) === -1;
        };

        scope.search = function () {
          // Add default filter
          if (
            scope.filters.length === 0 && // no filters added
            scope.options.defaultFacet.length && // default Facet is present in options
            scope.query.length // there is some query entered
          ) {
            var idx = Utils.findFilterByKey(scope.facets, 'id', scope.options.defaultFacet);
            if (idx !== -1) {
              scope.filters = [_.assign({}, scope.facets[idx], { value: scope.query })];
            }
          }

          scope.query = '';

          // TODO: perform validations

          if (typeof scope.doSearch === 'function') {
            scope.filteredBy = Utils.updateModel(scope.filters);
            scope.doSearch();
          }
        };

        scope.addTypeaheadSuggestion = function (suggestion) {
          var idx = Utils.findFilterByKey(scope.facets, 'label', suggestion);
          if (idx !== -1) {
            scope.filters = Utils.addFilter(scope.filters, scope.facets[idx]);
            scope.focusIndex = scope.filters.indexOf(scope.facets[idx]);
            scope.query = '';
            scope.typeaheadSuggestions = updateTypeaheadSuggestions(scope.facets, scope.filters);
          }
        };

        // Watch the model
        scope.$watch('filteredBy', function (value, oldValue) {
          if (value !== oldValue) {
            scope.filters = Utils.setFilters(scope.filteredBy, scope.facets);
          }
        });

      }
    };
  }

  FacetlyDirective.$inject = ['FacetlyUtils'];

  angular.module('ngFacetly')
    .directive('facetly', FacetlyDirective);

})();
<commit_msg>Fix bug where defaultFacet wasn't removed from the typeaheadSuggestions after searching<commit_after>(function () {
  'use strict';

  function FacetlyDirective(Utils) {

    return {
      restrict: 'E',
      replace: true,
      templateUrl: 'angular-facetly.html',
      scope: {
        options: '=?',
        filteredBy: '=ngModel',
        facets: '=',
        doSearch: '&?'
      },
      link: function (scope) {

        var updateTypeaheadSuggestions = function (facets, filters) {
          return _.chain(facets)
                  .filter(function (facet) {
                    return _.findIndex(filters, { id: facet.id }) === -1;
                  })
                  .map(function (facet) {
                    return facet.label;
                  })
                  .value();
        };

        scope.options = _.assign({}, scope.options);

        scope.filters = Utils.setFilters(scope.filteredBy, scope.facets);

        scope.typeaheadSuggestions = updateTypeaheadSuggestions(scope.facets, scope.filters);

        scope.addFilter = function (facet) {
          scope.filters = Utils.addFilter(scope.filters, facet);
        };

        scope.removeFilter = function (key) {
          scope.filters = Utils.removeFilter(scope.filters, key);
        };

        scope.removeAllFilters = function () {
          scope.filters = Utils.removeAllFilters();

          // FIXME: Duplication
          if (typeof scope.doSearch === 'function') {
            scope.filteredBy = Utils.updateModel(scope.filters);
            scope.doSearch();
          }
        };

        scope.availableFacets = function (facet) {
          var filters = scope.filters || [];
          return _.findIndex(filters, { id: facet.id }) === -1;
        };

        scope.search = function () {
          // Add default filter
          if (
            scope.filters.length === 0 && // no filters added
            scope.options.defaultFacet.length && // default Facet is present in options
            scope.query.length // there is some query entered
          ) {
            var idx = Utils.findFilterByKey(scope.facets, 'id', scope.options.defaultFacet);
            if (idx !== -1) {
              scope.filters = [_.assign({}, scope.facets[idx], { value: scope.query })];
            }
          }

          scope.query = '';

          // TODO: perform validations

          if (typeof scope.doSearch === 'function') {
            scope.filteredBy = Utils.updateModel(scope.filters);
            scope.doSearch();
            scope.typeaheadSuggestions = updateTypeaheadSuggestions(scope.facets, scope.filters);
          }
        };

        scope.addTypeaheadSuggestion = function (suggestion) {
          var idx = Utils.findFilterByKey(scope.facets, 'label', suggestion);
          if (idx !== -1) {
            scope.filters = Utils.addFilter(scope.filters, scope.facets[idx]);
            scope.focusIndex = scope.filters.indexOf(scope.facets[idx]);
            scope.query = '';
            scope.typeaheadSuggestions = updateTypeaheadSuggestions(scope.facets, scope.filters);
          }
        };

        // Watch the model
        scope.$watch('filteredBy', function (value, oldValue) {
          if (value !== oldValue) {
            scope.filters = Utils.setFilters(scope.filteredBy, scope.facets);
          }
        });

      }
    };
  }

  FacetlyDirective.$inject = ['FacetlyUtils'];

  angular.module('ngFacetly')
    .directive('facetly', FacetlyDirective);

})();
<|endoftext|>