From baf95a8c05bcbaa3d46f8bf68c2691c1f8f6842e Mon Sep 17 00:00:00 2001
From: Ravindra Adireddy
Date: Wed, 18 Apr 2018 13:53:44 +0530
Subject: [PATCH 1/5] feat(oui-dual-list): add dual-list component
---
packages/oui-angular/src/index.js | 2 +
packages/oui-angular/src/index.spec.js | 1 +
packages/oui-dual-list/README.md | 123 +++++++++
.../oui-dual-list/src/dual-list.component.js | 13 +
.../oui-dual-list/src/dual-list.controller.js | 58 ++++
.../oui-dual-list/src/dual-list.provider.js | 39 +++
packages/oui-dual-list/src/index.js | 12 +
.../oui-dual-list/src/index.spec.data.json | 65 +++++
packages/oui-dual-list/src/index.spec.js | 256 ++++++++++++++++++
.../src/source/dual-list-source.component.js | 16 ++
.../src/source/dual-list-source.controller.js | 38 +++
.../src/source/dual-list-source.html | 82 ++++++
.../src/target/dual-list-target.component.js | 16 ++
.../src/target/dual-list-target.controller.js | 38 +++
.../src/target/dual-list-target.html | 82 ++++++
.../src/inline-adder.provider.js | 2 +-
packages/oui-navbar/src/navbar.provider.js | 2 +-
17 files changed, 843 insertions(+), 2 deletions(-)
create mode 100644 packages/oui-dual-list/README.md
create mode 100644 packages/oui-dual-list/src/dual-list.component.js
create mode 100644 packages/oui-dual-list/src/dual-list.controller.js
create mode 100644 packages/oui-dual-list/src/dual-list.provider.js
create mode 100644 packages/oui-dual-list/src/index.js
create mode 100644 packages/oui-dual-list/src/index.spec.data.json
create mode 100644 packages/oui-dual-list/src/index.spec.js
create mode 100644 packages/oui-dual-list/src/source/dual-list-source.component.js
create mode 100644 packages/oui-dual-list/src/source/dual-list-source.controller.js
create mode 100644 packages/oui-dual-list/src/source/dual-list-source.html
create mode 100644 packages/oui-dual-list/src/target/dual-list-target.component.js
create mode 100644 packages/oui-dual-list/src/target/dual-list-target.controller.js
create mode 100644 packages/oui-dual-list/src/target/dual-list-target.html
diff --git a/packages/oui-angular/src/index.js b/packages/oui-angular/src/index.js
index c32f6756..745f69b4 100644
--- a/packages/oui-angular/src/index.js
+++ b/packages/oui-angular/src/index.js
@@ -10,6 +10,7 @@ import CriteriaAdder from "@ovh-ui/oui-criteria-adder";
import CriteriaContainer from "@ovh-ui/oui-criteria-container";
import Datagrid from "@ovh-ui/oui-datagrid";
import Dropdown from "@ovh-ui/oui-dropdown";
+import DualList from "@ovh-ui/oui-dual-list";
import Field from "@ovh-ui/oui-field";
import FormActions from "@ovh-ui/oui-form-actions";
import GuideMenu from "@ovh-ui/oui-guide-menu";
@@ -50,6 +51,7 @@ export default angular
CriteriaContainer,
Datagrid,
Dropdown,
+ DualList,
Field,
FormActions,
GuideMenu,
diff --git a/packages/oui-angular/src/index.spec.js b/packages/oui-angular/src/index.spec.js
index 67b46a72..42ff6b48 100644
--- a/packages/oui-angular/src/index.spec.js
+++ b/packages/oui-angular/src/index.spec.js
@@ -12,6 +12,7 @@ loadTests(require.context("../../oui-criteria-adder/src/", true, /.*((\.spec)|(i
loadTests(require.context("../../oui-criteria-container/src/", true, /.*((\.spec)|(index))$/));
loadTests(require.context("../../oui-datagrid/src/", true, /.*((\.spec)|(index))$/));
loadTests(require.context("../../oui-dropdown/src/", true, /.*((\.spec)|(index))$/));
+loadTests(require.context("../../oui-dual-list/src/", true, /.*((\.spec)|(index))$/));
loadTests(require.context("../../oui-field/src/", true, /.*((\.spec)|(index))$/));
loadTests(require.context("../../oui-form-actions/src/", true, /.*((\.spec)|(index))$/));
loadTests(require.context("../../oui-guide-menu/src/", true, /.*((\.spec)|(index))$/));
diff --git a/packages/oui-dual-list/README.md b/packages/oui-dual-list/README.md
new file mode 100644
index 00000000..ba2baf79
--- /dev/null
+++ b/packages/oui-dual-list/README.md
@@ -0,0 +1,123 @@
+# Dual List
+
+
+
+## Usage
+
+### Basic
+
+#### Array of strings
+
+```html:preview
+
+
+
+
+```
+
+#### Array of objects
+
+```html:preview
+
+
+
+
+```
+
+#### Array of objects (deep nested property)
+
+```html:preview
+
+
+
+
+```
+
+### Loading state
+
+**Note**: If `source` or `target` attribute are undefined, the loading will be automatically active.
+
+```html:preview
+
+
+
+
+
+
+
+
+
+
+```
+
+### Events
+
+**Note**: Bulk actions `Add all` and `Remove all` will trigger callbacks for each moved items.
+
+```html:preview
+
+
+
+
+
+
onAdd ({{$ctrl.onAddCount}}): {{$ctrl.onAddItem | json}}
+
onRemove ({{$ctrl.onRemoveCount}}): {{$ctrl.onRemoveItem | json}}
+
onChange ({{$ctrl.onChangeCount}}): {{$ctrl.onChangeItem | json}}
+
+```
+
+## API
+
+### oui-dual-list
+
+| Attribute | Type | Binding | One-time binding | Values | Default | Description
+| ---- | ---- | ---- | ---- | ---- | ---- | ----
+| `source` | array | = | no | n/a | n/a | source model bound to component
+| `target` | array | = | no | n/a | n/a | target model bound to component
+| `property` | string | @? | no | n/a | n/a | property path used to get value from item
+| `on-add` | function | & | no | n/a | n/a | handler triggered when an item is added
+| `on-remove` | function | & | no | n/a | n/a | handler triggered when an item is removed
+| `on-change` | function | & | no | n/a | n/a | handler triggered when items have changed
+
+### oui-dual-list-source
+
+| Attribute | Type | Binding | One-time binding | Values | Default | Description
+| ---- | ---- | ---- | ---- | ---- | ---- | ----
+| `heading` | string | @? | yes | n/a | n/a | heading text
+| `placeholder` | string | @? | yes | n/a | n/a | placeholder text
+| `loading` | boolean | | no | `true`, `false` | `false` | loading flag
+| `searchable` | boolean | | no | `true`, `false` | `false` | searchbale flag
+
+### oui-dual-list-target
+
+| Attribute | Type | Binding | One-time binding | Values | Default | Description
+| ---- | ---- | ---- | ---- | ---- | ---- | ----
+| `heading` | string | @? | yes | n/a | n/a | heading text
+| `placeholder` | string | @? | yes | n/a | n/a | placeholder text
+| `loading` | boolean | | no | `true`, `false` | `false` | loading flag
+| `searchable` | boolean | | no | `true`, `false` | `false` | searchbale flag
diff --git a/packages/oui-dual-list/src/dual-list.component.js b/packages/oui-dual-list/src/dual-list.component.js
new file mode 100644
index 00000000..0926099d
--- /dev/null
+++ b/packages/oui-dual-list/src/dual-list.component.js
@@ -0,0 +1,13 @@
+import controller from "./dual-list.controller";
+
+export default {
+ controller,
+ bindings: {
+ source: "=",
+ target: "=",
+ property: "@?",
+ onAdd: "&",
+ onRemove: "&",
+ onChange: "&"
+ }
+};
diff --git a/packages/oui-dual-list/src/dual-list.controller.js b/packages/oui-dual-list/src/dual-list.controller.js
new file mode 100644
index 00000000..73f65bb6
--- /dev/null
+++ b/packages/oui-dual-list/src/dual-list.controller.js
@@ -0,0 +1,58 @@
+import get from "lodash/get";
+import remove from "lodash/remove";
+
+export default class {
+ constructor ($attrs, $element, $timeout) {
+ "ngInject";
+
+ this.$attrs = $attrs;
+ this.$element = $element;
+ this.$timeout = $timeout;
+ }
+
+ $postLink () {
+ this.$timeout(() =>
+ this.$element.addClass("oui-dual-list")
+ );
+ }
+
+ getProperty (item) {
+ return get(item, this.property, item);
+ }
+
+ moveToSource (item) {
+ if (angular.isArray(this.source)) {
+ remove(this.target, (source) => source === item);
+ this.source.push(item);
+
+ // Callbacks
+ this.onChange({ item });
+ this.onRemove({ item });
+ }
+ }
+
+ moveAllToSource () {
+ // Need to do a while for the callbacks
+ while (angular.isArray(this.source) && this.target.length) {
+ this.moveToSource(this.target[0]);
+ }
+ }
+
+ moveToTarget (item) {
+ if (angular.isArray(this.target)) {
+ remove(this.source, (source) => source === item);
+ this.target.push(item);
+
+ // Callbacks
+ this.onChange({ item });
+ this.onAdd({ item });
+ }
+ }
+
+ moveAllToTarget () {
+ // Need to do a while for the callbacks
+ while (angular.isArray(this.target) && this.source.length) {
+ this.moveToTarget(this.source[0]);
+ }
+ }
+}
diff --git a/packages/oui-dual-list/src/dual-list.provider.js b/packages/oui-dual-list/src/dual-list.provider.js
new file mode 100644
index 00000000..8f6023ad
--- /dev/null
+++ b/packages/oui-dual-list/src/dual-list.provider.js
@@ -0,0 +1,39 @@
+import merge from "lodash/merge";
+
+export default class {
+ constructor () {
+ this.translations = {
+ source: {
+ heading: "Unselected items",
+ placeholder: "No items to select",
+ move: "Add",
+ moveAll: "Add all",
+ search: "Search in source content"
+ },
+ target: {
+ heading: "Selected items",
+ placeholder: "No items are selected",
+ move: "Remove",
+ moveAll: "Remove all",
+ search: "Search in target content"
+
+ }
+ };
+ }
+
+ /**
+ * Set the translations
+ * @param {Object} translations a map of translations
+ */
+ setTranslations (translations) {
+ this.translations = merge(this.translations, translations);
+ return this;
+ }
+
+ $get () {
+ return {
+ translations: this.translations
+ };
+ }
+}
+
diff --git a/packages/oui-dual-list/src/index.js b/packages/oui-dual-list/src/index.js
new file mode 100644
index 00000000..09959fa3
--- /dev/null
+++ b/packages/oui-dual-list/src/index.js
@@ -0,0 +1,12 @@
+import DualList from "./dual-list.component";
+import DualListProvider from "./dual-list.provider";
+import DualListSource from "./source/dual-list-source.component";
+import DualListTarget from "./target/dual-list-target.component";
+
+export default angular
+ .module("oui.dual-list", [])
+ .component("ouiDualList", DualList)
+ .component("ouiDualListSource", DualListSource)
+ .component("ouiDualListTarget", DualListTarget)
+ .provider("ouiDualListConfiguration", DualListProvider)
+ .name;
diff --git a/packages/oui-dual-list/src/index.spec.data.json b/packages/oui-dual-list/src/index.spec.data.json
new file mode 100644
index 00000000..8126782e
--- /dev/null
+++ b/packages/oui-dual-list/src/index.spec.data.json
@@ -0,0 +1,65 @@
+{
+ "string": {
+ "source": ["Lorem"],
+ "target": ["Ipsum"]
+ },
+ "strings": {
+ "source": [
+ "Andrew",
+ "Red",
+ "Lila",
+ "Marie",
+ "Robert",
+ "Hope",
+ "Alexis",
+ "Madison",
+ "Vanessa"
+ ],
+ "target": [
+ "Kevin",
+ "Mark"
+ ]
+ },
+ "object": {
+ "source": [{ "name": "Lorem", "age": 15 }],
+ "target": [{ "name": "Ipsum", "age": 25 }]
+ },
+ "objects": {
+ "source" : [
+ { "name": "Andrew", "age": 15 },
+ { "name": "Red", "age": 25 },
+ { "name": "Lila", "age": 34 },
+ { "name": "Marie", "age": 12 },
+ { "name": "Robert", "age": 64 },
+ { "name": "Hope", "age": 15 },
+ { "name": "Alexis", "age": 25 },
+ { "name": "Madison", "age": 34 },
+ { "name": "Vanessa", "age": 12 }
+ ],
+ "target" : [
+ { "name": "Kevin", "age": 12 },
+ { "name": "Mark", "age": 64 }
+ ]
+ },
+ "nestedObject": {
+ "source": [{ "name": { "firstName": "Lorem" }, "age": 15 }],
+ "target": [{ "name": { "firstName": "Ipsum" }, "age": 25 }]
+ },
+ "nestedObjects" : {
+ "source": [
+ { "name": { "firstName": "Andrew" }, "age": 15 },
+ { "name": { "firstName": "Red" }, "age": 25 },
+ { "name": { "firstName": "Lila" }, "age": 34 },
+ { "name": { "firstName": "Marie" }, "age": 12 },
+ { "name": { "firstName": "Robert" }, "age": 64 },
+ { "name": { "firstName": "Hope" }, "age": 15 },
+ { "name": { "firstName": "Alexis" }, "age": 25 },
+ { "name": { "firstName": "Madison" }, "age": 34 },
+ { "name": { "firstName": "Vanessa" }, "age": 12 }
+ ],
+ "target" : [
+ { "name": { "firstName": "Kevin" }, "age": 12 },
+ { "name": { "firstName": "Mark" }, "age": 64 }
+ ]
+ }
+}
diff --git a/packages/oui-dual-list/src/index.spec.js b/packages/oui-dual-list/src/index.spec.js
new file mode 100644
index 00000000..9c9a5e34
--- /dev/null
+++ b/packages/oui-dual-list/src/index.spec.js
@@ -0,0 +1,256 @@
+import data from "./index.spec.data.json";
+import get from "lodash/get";
+
+describe("ouiDualList", () => {
+ let TestUtils;
+ let $timeout;
+ let configuration;
+
+ const sourceString = data.string.source;
+ const sourceStrings = data.strings.source;
+ const sourceObject = data.object.source;
+ const sourceNestedObject = data.nestedObject.source;
+
+ const targetString = data.string.target;
+ const targetStrings = data.strings.target;
+ const targetObject = data.object.target;
+ const targetNestedObject = data.nestedObject.target;
+
+ const getDualList = (source, target) => TestUtils.compileTemplate(`
+
+
+
+ `, {
+ source,
+ target
+ });
+
+ const getDualListWithProperty = (property, source, target) => TestUtils.compileTemplate(`
+
+
+
+ `, {
+ property,
+ source,
+ target
+ });
+
+ const getDualListWithEvents = (onAddSpy, onChangeSpy, onRemoveSpy, source, target) => TestUtils.compileTemplate(`
+
+
+
+ `, {
+ onAddSpy,
+ onChangeSpy,
+ onRemoveSpy,
+ source,
+ target
+ });
+
+ beforeEach(angular.mock.module("oui.button"));
+ beforeEach(angular.mock.module("oui.dual-list"));
+ beforeEach(angular.mock.module("oui.dual-list.configuration"));
+ beforeEach(angular.mock.module("oui.search"));
+ beforeEach(angular.mock.module("oui.spinner"));
+ beforeEach(angular.mock.module("oui.test-utils"));
+
+ beforeEach(inject((_$timeout_, _TestUtils_) => {
+ $timeout = _$timeout_;
+ TestUtils = _TestUtils_;
+ }));
+
+ describe("Provider", () => {
+ angular.module("oui.dual-list.configuration", [
+ "oui.dual-list"
+ ]).config(ouiDualListConfigurationProvider => {
+ ouiDualListConfigurationProvider.setTranslations({
+ foo: "bar"
+ });
+ });
+
+ beforeEach(inject(_ouiDualListConfiguration_ => {
+ configuration = _ouiDualListConfiguration_;
+ }));
+
+ it("should have custom options", () => {
+ expect(configuration.translations.foo).toEqual("bar");
+ });
+ });
+
+ describe("Component", () => {
+ describe("oui-inline-adder", () => {
+ it("should have a default classname", () => {
+ const element = getDualList(sourceStrings, targetStrings);
+
+ $timeout.flush();
+
+ expect(element.hasClass("oui-dual-list")).toBeTruthy();
+ });
+
+ it("should load the source and target data", () => {
+ const element = getDualList(sourceStrings, targetStrings);
+
+ const sourceLength = sourceStrings.length;
+ const sourceItemsLength = element.find("oui-dual-list-source").find("li").length;
+
+ expect(sourceItemsLength).toBe(sourceLength);
+
+ const targetLength = targetStrings.length;
+ const targetItemsLength = element.find("oui-dual-list-target").find("li").length;
+
+ expect(targetItemsLength).toBe(targetLength);
+ });
+
+ it("should display value from array of strings", () => {
+ const element = getDualList(sourceString, targetString);
+
+ const source = element.find("oui-dual-list-source")[0];
+ const sourceItem = angular.element(source.querySelector(".oui-dual-list-item:first-child .oui-dual-list-item__property"));
+
+ expect(sourceItem.text()).toBe(sourceString[0]);
+
+ const target = element.find("oui-dual-list-target")[0];
+ const targetItem = angular.element(target.querySelector(".oui-dual-list-item:first-child .oui-dual-list-item__property"));
+
+ expect(targetItem.text()).toBe(targetString[0]);
+ });
+
+ it("should display property value from array of objects", () => {
+ const property = "name";
+ const element = getDualListWithProperty(property, sourceObject, targetObject);
+
+ const source = element.find("oui-dual-list-source")[0];
+ const sourceItem = angular.element(source.querySelector(".oui-dual-list-item:first-child .oui-dual-list-item__property"));
+
+ expect(sourceItem.text()).toBe(get(sourceObject[0], property));
+
+ const target = element.find("oui-dual-list-target")[0];
+ const targetItem = angular.element(target.querySelector(".oui-dual-list-item:first-child .oui-dual-list-item__property"));
+
+ expect(targetItem.text()).toBe(get(targetObject[0], property));
+ });
+
+ it("should display property value from array of nested objects", () => {
+ const property = "name.firstName";
+ const element = getDualListWithProperty(property, sourceNestedObject, targetNestedObject);
+
+ const source = element.find("oui-dual-list-source")[0];
+ const sourceItem = angular.element(source.querySelector(".oui-dual-list-item:first-child .oui-dual-list-item__property"));
+
+ expect(sourceItem.text()).toBe(get(sourceNestedObject[0], property));
+
+ const target = element.find("oui-dual-list-target")[0];
+ const targetItem = angular.element(target.querySelector(".oui-dual-list-item:first-child .oui-dual-list-item__property"));
+
+ expect(targetItem.text()).toBe(get(targetNestedObject[0], property));
+ });
+
+ it("should move first source item to target", () => {
+ const element = getDualList(sourceStrings, targetStrings);
+ const controller = element.controller("ouiDualList");
+
+ const source = element.find("oui-dual-list-source")[0];
+ const sourceItem = angular.element(source.querySelector(".oui-dual-list-item:first-child button"));
+
+ sourceItem.triggerHandler("click");
+
+ const sourceLength = sourceStrings.length - 1;
+ const sourceItemsLength = controller.source.length;
+
+ expect(sourceItemsLength).toBe(sourceLength);
+
+ const targetLength = targetStrings.length + 1;
+ const targetItemsLength = controller.target.length;
+
+ expect(targetItemsLength).toBe(targetLength);
+ });
+
+ it("should move all source items to target", () => {
+ const element = getDualList(sourceStrings, targetStrings);
+ const controller = element.controller("ouiDualList");
+
+ const source = element.find("oui-dual-list-source")[0];
+ const sourceAction = angular.element(source.querySelector(".oui-dual-list__action button"));
+
+ sourceAction.triggerHandler("click");
+
+ const sourceLength = 0;
+ const sourceItemsLength = controller.source.length;
+
+ expect(sourceItemsLength).toBe(sourceLength);
+
+ const targetLength = sourceStrings.length + targetStrings.length;
+ const targetItemsLength = controller.target.length;
+
+ expect(targetItemsLength).toBe(targetLength);
+ });
+
+ it("should move first target item to source", () => {
+ const element = getDualList(sourceStrings, targetStrings);
+ const controller = element.controller("ouiDualList");
+
+ const target = element.find("oui-dual-list-target")[0];
+ const targetItem = angular.element(target.querySelector(".oui-dual-list-item:first-child button"));
+
+ targetItem.triggerHandler("click");
+
+ const sourceLength = sourceStrings.length + 1;
+ const sourceItemsLength = controller.source.length;
+
+ expect(sourceItemsLength).toBe(sourceLength);
+
+ const targetLength = targetStrings.length - 1;
+ const targetItemsLength = controller.target.length;
+
+ expect(targetItemsLength).toBe(targetLength);
+ });
+
+ it("should move all target items to source", () => {
+ const element = getDualList(sourceStrings, targetStrings);
+ const controller = element.controller("ouiDualList");
+
+ const target = element.find("oui-dual-list-target")[0];
+ const targetAction = angular.element(target.querySelector(".oui-dual-list__action button"));
+
+ targetAction.triggerHandler("click");
+
+ const sourceLength = sourceStrings.length + targetStrings.length;
+ const sourceItemsLength = controller.source.length;
+
+ expect(sourceItemsLength).toBe(sourceLength);
+
+ const targetLength = 0;
+ const targetItemsLength = controller.target.length;
+
+ expect(targetItemsLength).toBe(targetLength);
+ });
+
+ it("should call events callbacks", () => {
+ const onAddSpy = jasmine.createSpy("onAddSpy");
+ const onChangeSpy = jasmine.createSpy("onChangeSpy");
+ const onRemoveSpy = jasmine.createSpy("onRemoveSpy");
+
+ const element = getDualListWithEvents(onAddSpy, onChangeSpy, onRemoveSpy, sourceStrings, targetStrings);
+
+ // Source click
+ const source = element.find("oui-dual-list-source")[0];
+ const sourceItem = angular.element(source.querySelector(".oui-dual-list-item:first-child button"));
+ sourceItem.triggerHandler("click");
+
+ expect(onAddSpy).toHaveBeenCalled();
+ expect(onChangeSpy).toHaveBeenCalled();
+
+ // Target click
+ const target = element.find("oui-dual-list-target")[0];
+ const targetItem = angular.element(target.querySelector(".oui-dual-list-item:first-child button"));
+ targetItem.triggerHandler("click");
+
+ expect(onRemoveSpy).toHaveBeenCalled();
+ expect(onChangeSpy).toHaveBeenCalled();
+ });
+ });
+ });
+});
diff --git a/packages/oui-dual-list/src/source/dual-list-source.component.js b/packages/oui-dual-list/src/source/dual-list-source.component.js
new file mode 100644
index 00000000..633e977f
--- /dev/null
+++ b/packages/oui-dual-list/src/source/dual-list-source.component.js
@@ -0,0 +1,16 @@
+import controller from "./dual-list-source.controller";
+import template from "./dual-list-source.html";
+
+export default {
+ require: {
+ dualList: "^ouiDualList"
+ },
+ bindings: {
+ heading: "@?",
+ placeholder: "@?",
+ loading: "",
+ searchable: ""
+ },
+ controller,
+ template
+};
diff --git a/packages/oui-dual-list/src/source/dual-list-source.controller.js b/packages/oui-dual-list/src/source/dual-list-source.controller.js
new file mode 100644
index 00000000..84d1ea6b
--- /dev/null
+++ b/packages/oui-dual-list/src/source/dual-list-source.controller.js
@@ -0,0 +1,38 @@
+import { addBooleanParameter, addDefaultParameter } from "@ovh-ui/common/component-utils";
+
+export default class {
+ constructor ($attrs, $element, $filter, $scope, $timeout, ouiDualListConfiguration) {
+ "ngInject";
+
+ this.$attrs = $attrs;
+ this.$element = $element;
+ this.$filter = $filter;
+ this.$timeout = $timeout;
+ this.$scope = $scope;
+ this.translations = ouiDualListConfiguration.translations.source;
+ }
+
+ $onInit () {
+ addDefaultParameter(this, "heading", this.translations.heading);
+ addDefaultParameter(this, "placeholder", this.translations.placeholder);
+ addBooleanParameter(this, "loading");
+ addBooleanParameter(this, "searchable");
+
+ this.id = `ouiDualListSource${this.$scope.id}`;
+ this.expanded = true;
+ }
+
+ $postLink () {
+ this.$timeout(() =>
+ this.$element.addClass("oui-dual-list-source")
+ );
+ }
+
+ isLoading () {
+ return this.loading || angular.isUndefined(this.dualList.source);
+ }
+
+ toggle () {
+ this.expanded = !this.expanded;
+ }
+}
diff --git a/packages/oui-dual-list/src/source/dual-list-source.html b/packages/oui-dual-list/src/source/dual-list-source.html
new file mode 100644
index 00000000..ca067fa8
--- /dev/null
+++ b/packages/oui-dual-list/src/source/dual-list-source.html
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{::$ctrl.translations.moveAll}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/packages/oui-dual-list/src/target/dual-list-target.component.js b/packages/oui-dual-list/src/target/dual-list-target.component.js
new file mode 100644
index 00000000..7dd5a255
--- /dev/null
+++ b/packages/oui-dual-list/src/target/dual-list-target.component.js
@@ -0,0 +1,16 @@
+import controller from "./dual-list-target.controller";
+import template from "./dual-list-target.html";
+
+export default {
+ require: {
+ dualList: "^ouiDualList"
+ },
+ bindings: {
+ heading: "@?",
+ placeholder: "@?",
+ loading: "",
+ searchable: ""
+ },
+ controller,
+ template
+};
diff --git a/packages/oui-dual-list/src/target/dual-list-target.controller.js b/packages/oui-dual-list/src/target/dual-list-target.controller.js
new file mode 100644
index 00000000..5e833c41
--- /dev/null
+++ b/packages/oui-dual-list/src/target/dual-list-target.controller.js
@@ -0,0 +1,38 @@
+import { addBooleanParameter, addDefaultParameter } from "@ovh-ui/common/component-utils";
+
+export default class {
+ constructor ($attrs, $element, $filter, $scope, $timeout, ouiDualListConfiguration) {
+ "ngInject";
+
+ this.$attrs = $attrs;
+ this.$element = $element;
+ this.$filter = $filter;
+ this.$timeout = $timeout;
+ this.$scope = $scope;
+ this.translations = ouiDualListConfiguration.translations.target;
+ }
+
+ $onInit () {
+ addDefaultParameter(this, "heading", this.translations.heading);
+ addDefaultParameter(this, "placeholder", this.translations.placeholder);
+ addBooleanParameter(this, "loading");
+ addBooleanParameter(this, "searchable");
+
+ this.id = `ouiDualListTarget${this.$scope.id}`;
+ this.expanded = false;
+ }
+
+ $postLink () {
+ this.$timeout(() =>
+ this.$element.addClass("oui-dual-list-target")
+ );
+ }
+
+ isLoading () {
+ return this.loading || angular.isUndefined(this.dualList.target);
+ }
+
+ toggle () {
+ this.expanded = !this.expanded;
+ }
+}
diff --git a/packages/oui-dual-list/src/target/dual-list-target.html b/packages/oui-dual-list/src/target/dual-list-target.html
new file mode 100644
index 00000000..69233b0f
--- /dev/null
+++ b/packages/oui-dual-list/src/target/dual-list-target.html
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{::$ctrl.translations.moveAll}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/packages/oui-inline-adder/src/inline-adder.provider.js b/packages/oui-inline-adder/src/inline-adder.provider.js
index c2810a0f..1441531d 100644
--- a/packages/oui-inline-adder/src/inline-adder.provider.js
+++ b/packages/oui-inline-adder/src/inline-adder.provider.js
@@ -1,4 +1,4 @@
-import { merge } from "lodash";
+import merge from "lodash/merge";
export default class {
constructor () {
diff --git a/packages/oui-navbar/src/navbar.provider.js b/packages/oui-navbar/src/navbar.provider.js
index c9b4b4e9..108130f3 100644
--- a/packages/oui-navbar/src/navbar.provider.js
+++ b/packages/oui-navbar/src/navbar.provider.js
@@ -1,4 +1,4 @@
-import { merge } from "lodash";
+import merge from "lodash/merge";
export default class {
constructor () {
From ad07be1f05522d10028c2f1d26c76615532386e7 Mon Sep 17 00:00:00 2001
From: Axel Peter
Date: Fri, 9 Nov 2018 16:25:45 +0100
Subject: [PATCH 2/5] feat(oui-dual-list): add bulk action for filtered list
---
.../oui-dual-list/src/dual-list.controller.js | 15 ++++++++-------
.../src/source/dual-list-source.html | 6 +++---
.../src/target/dual-list-target.html | 6 +++---
3 files changed, 14 insertions(+), 13 deletions(-)
diff --git a/packages/oui-dual-list/src/dual-list.controller.js b/packages/oui-dual-list/src/dual-list.controller.js
index 73f65bb6..c4cd4e29 100644
--- a/packages/oui-dual-list/src/dual-list.controller.js
+++ b/packages/oui-dual-list/src/dual-list.controller.js
@@ -2,11 +2,12 @@ import get from "lodash/get";
import remove from "lodash/remove";
export default class {
- constructor ($attrs, $element, $timeout) {
+ constructor ($attrs, $element, $filter, $timeout) {
"ngInject";
this.$attrs = $attrs;
this.$element = $element;
+ this.$filter = $filter;
this.$timeout = $timeout;
}
@@ -31,10 +32,10 @@ export default class {
}
}
- moveAllToSource () {
+ moveAllToSource (searchQuery) {
// Need to do a while for the callbacks
- while (angular.isArray(this.source) && this.target.length) {
- this.moveToSource(this.target[0]);
+ while (angular.isArray(this.source) && this.$filter("filter")(this.target, searchQuery).length) {
+ this.moveToSource(this.$filter("filter")(this.target, searchQuery)[0]);
}
}
@@ -49,10 +50,10 @@ export default class {
}
}
- moveAllToTarget () {
+ moveAllToTarget (searchQuery) {
// Need to do a while for the callbacks
- while (angular.isArray(this.target) && this.source.length) {
- this.moveToTarget(this.source[0]);
+ while (angular.isArray(this.target) && this.$filter("filter")(this.source, searchQuery).length) {
+ this.moveToTarget(this.$filter("filter")(this.source, searchQuery)[0]);
}
}
}
diff --git a/packages/oui-dual-list/src/source/dual-list-source.html b/packages/oui-dual-list/src/source/dual-list-source.html
index ca067fa8..d5842c1f 100644
--- a/packages/oui-dual-list/src/source/dual-list-source.html
+++ b/packages/oui-dual-list/src/source/dual-list-source.html
@@ -31,7 +31,7 @@
ng-if="$ctrl.searchable"
arial-label="{{::$ctrl.translations.search}}"
placeholder="{{::$ctrl.translations.search}}"
- model="$ctrl.search">
+ model="$ctrl.searchQuery">
@@ -40,7 +40,7 @@
ng-if="$ctrl.dualList.source.length > 1"
ng-show="!$ctrl.isLoading()">
+ on-click="$ctrl.dualList.moveAllToTarget($ctrl.searchQuery)">
{{::$ctrl.translations.moveAll}}
@@ -63,7 +63,7 @@
+ ng-repeat="item in $ctrl.dualList.source | filter:$ctrl.searchQuery | orderBy:$ctrl.dualList.property">
diff --git a/packages/oui-inline-adder/src/inline-adder.html b/packages/oui-inline-adder/src/inline-adder.html
index 9502e156..8a7bb8eb 100644
--- a/packages/oui-inline-adder/src/inline-adder.html
+++ b/packages/oui-inline-adder/src/inline-adder.html
@@ -12,13 +12,13 @@
-
+
-
+
diff --git a/packages/oui-pagination/src/pagination.html b/packages/oui-pagination/src/pagination.html
index db8204ed..225321bb 100644
--- a/packages/oui-pagination/src/pagination.html
+++ b/packages/oui-pagination/src/pagination.html
@@ -32,7 +32,7 @@
ng-attr-aria-label="{{ ::$ctrl.translations.previousPage }}"
ng-disabled="$ctrl.currentPage === 1"
ng-click="$ctrl.onPageChange($ctrl.getCurrentPage() - 1)">
-
+
@@ -53,7 +53,7 @@
ng-attr-aria-label="{{ $ctrl.translations.currentPageOfPageCount }}"
class="oui-button oui-button_dropdown oui-dropdown__trigger">
-
+
diff --git a/packages/oui-search/src/search.html b/packages/oui-search/src/search.html
index 94e4922e..2f1e5ba8 100644
--- a/packages/oui-search/src/search.html
+++ b/packages/oui-search/src/search.html
@@ -13,10 +13,10 @@
ng-click="$ctrl.onSearchReset()"
ng-show="$ctrl.model.length"
ng-disabled="$ctrl.disabled">
-
+
-
+
From 338e6680b6de7fcf624e484df09934df3d99b6fa Mon Sep 17 00:00:00 2001
From: Axel Peter
Date: Mon, 12 Nov 2018 10:12:53 +0100
Subject: [PATCH 5/5] fix: code review
---
packages/oui-dual-list/src/dual-list.provider.js | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/packages/oui-dual-list/src/dual-list.provider.js b/packages/oui-dual-list/src/dual-list.provider.js
index 8f6023ad..b80244b7 100644
--- a/packages/oui-dual-list/src/dual-list.provider.js
+++ b/packages/oui-dual-list/src/dual-list.provider.js
@@ -4,19 +4,18 @@ export default class {
constructor () {
this.translations = {
source: {
- heading: "Unselected items",
- placeholder: "No items to select",
+ heading: "Items to select",
+ placeholder: "No item to select",
move: "Add",
moveAll: "Add all",
search: "Search in source content"
},
target: {
heading: "Selected items",
- placeholder: "No items are selected",
+ placeholder: "No selected item",
move: "Remove",
moveAll: "Remove all",
search: "Search in target content"
-
}
};
}