diff --git a/index.html b/index.html index d8ed569d0d..638b3f2402 100644 --- a/index.html +++ b/index.html @@ -30,6 +30,7 @@

Components:

Button Cascading list CheckBox + Condition Flow HTML Fragment List diff --git a/test/mocks/data/models/employee-list-ui-descriptor.mjson b/test/mocks/data/models/employee-list-ui-descriptor.mjson new file mode 100644 index 0000000000..671c088be6 --- /dev/null +++ b/test/mocks/data/models/employee-list-ui-descriptor.mjson @@ -0,0 +1,9 @@ +{ + "root": { + "prototype": "core/meta/user-interface-descriptor", + "values": { + "listIgnoreSelectionAfterLongPress": true, + "listDispatchLongPress": true + } + } +} diff --git a/test/mocks/data/models/employee.mjson b/test/mocks/data/models/employee.mjson index 0246fad4cf..8c8183ccb1 100644 --- a/test/mocks/data/models/employee.mjson +++ b/test/mocks/data/models/employee.mjson @@ -25,6 +25,9 @@ }, "montage/ui/cascading-list.reel": { "%": "test/mocks/data/models/employee-cascading-list-ui-descriptor.mjson" + }, + "montage/ui/list.reel": { + "%": "test/mocks/data/models/employee-list-ui-descriptor.mjson" } } } diff --git a/test/mocks/data/services/mock-service.js b/test/mocks/data/services/mock-service.js index 397b7e088d..0033468ac3 100644 --- a/test/mocks/data/services/mock-service.js +++ b/test/mocks/data/services/mock-service.js @@ -127,9 +127,9 @@ exports.MockService = Montage.specialize({ new Store('store 2', 'Paris'), new Store('store 3', 'Montreal'), new Store('store 4', 'New York'), - new Store('store 4', 'London'), - new Store('store 4', 'Tokyo'), - new Store('store 4', 'Shanghai') + new Store('store 5', 'London'), + new Store('store 6', 'Tokyo'), + new Store('store 7', 'Shanghai') ]; } }, diff --git a/test/spec/ui/flow-spec.js b/test/spec/ui/flow-spec.js index db1c839b3f..ebf767a48a 100644 --- a/test/spec/ui/flow-spec.js +++ b/test/spec/ui/flow-spec.js @@ -13,10 +13,10 @@ TestPageLoader.queueTest("flow/flow", function (testPage) { flowRepetition, rangeController; - function waitForFlowRepetition() { + function waitForFlowRepetition(times) { var component = flow._repetition || flow; - return testPage.waitForComponentDraw(flow).then(function () { + return testPage.waitForComponentDraw(flow, times).then(function () { return flow._repetition; }); } @@ -61,9 +61,11 @@ TestPageLoader.queueTest("flow/flow", function (testPage) { } flow.content = content; - waitForFlowRepetition().then(function (flowRepetition) { - expect(cleanTextContent(flowRepetition.element)).toBe("0 1 2"); + waitForFlowRepetition(2).then(function () { + expect(cleanTextContent(flow._repetition.element)).toBe("0 1 2"); done(); + }).catch(function (error) { + done.fail(error); }); }); }); @@ -79,10 +81,12 @@ TestPageLoader.queueTest("flow/flow", function (testPage) { flow.content = content; - waitForFlowRepetition().then(function (flowRepetition) { + waitForFlowRepetition(2).then(function (flowRepetition) { expect(cleanTextContent(flowRepetition.element)).toBe("(0) (1)"); expect(flowRepetition.element.children.length).toBe(3); done(); + }).catch(function (error) { + done.fail(error); }); }); }); @@ -98,22 +102,24 @@ TestPageLoader.queueTest("flow/flow", function (testPage) { flow.scroll = 0; flow.content = content; - waitForFlowRepetition().then(function (flowRepetition) { + waitForFlowRepetition(2).then(function (flowRepetition) { expect(cleanTextContent(flowRepetition.element)).toBe("0 1 2"); flow.scroll = 3; - testPage.waitForComponentDraw(flowRepetition).then(function () { + return waitForFlowRepetition(2).then(function () { expect(cleanTextContent(flowRepetition.element)).toBe("3 1 2 4 5"); flow.scroll = 2; - testPage.waitForComponentDraw(flowRepetition).then(function () { + return waitForFlowRepetition(2).then(function () { expect(cleanTextContent(flowRepetition.element)).toBe("3 1 2 4 0"); flow.scroll = 0; - testPage.waitForComponentDraw(flow).then(function () { + return waitForFlowRepetition(2).then(function () { expect(cleanTextContent(flowRepetition.element)).toBe("3 1 2 4 0"); expect(flowRepetition.element.children.length).toBe(5); done(); }); }); }); + }).catch(function (error) { + done.fail(error); }); }); }); @@ -132,26 +138,31 @@ TestPageLoader.queueTest("flow/flow", function (testPage) { it("should regenerate iterations and trim the excess", function (done) { flow.handleResize(); - waitForFlowRepetition().then(function (flowRepetition) { + waitForFlowRepetition(2).then(function (flowRepetition) { expect(cleanTextContent(flowRepetition.element)).toBe("0 1 2"); expect(flowRepetition.element.children.length).toBe(3); done(); + }).catch(function (error) { + done.fail(error); }); }); it("after flow changes in size should update frustum culling and iterations", function (done) { flow.element.style.width = "300px"; flow.handleResize(); - waitForFlowRepetition().then(function (flowRepetition) { + waitForFlowRepetition(2).then(function (flowRepetition) { expect(cleanTextContent(flowRepetition.element)).toBe("0 1"); expect(flowRepetition.element.children.length).toBe(2); flow.element.style.width = "500px"; flow.handleResize(); - testPage.waitForComponentDraw(flowRepetition).then(function () { + + return waitForFlowRepetition(2).then(function () { expect(cleanTextContent(flowRepetition.element)).toBe("0 1 2"); expect(flowRepetition.element.children.length).toBe(3); done(); }); + }).catch(function (error) { + done.fail(error); }); }); }); @@ -169,10 +180,12 @@ TestPageLoader.queueTest("flow/flow", function (testPage) { rangeController.content = content; }); it("should update the visible iterations", function (done) { - waitForFlowRepetition().then(function (flowRepetition) { + waitForFlowRepetition(2).then(function (flowRepetition) { expect(cleanTextContent(flowRepetition.element)).toBe("(0) (1) (2)"); expect(flowRepetition.element.children.length).toBe(3); done(); + }).catch(function (error) { + done.fail(error); }); }); }); @@ -191,63 +204,88 @@ TestPageLoader.queueTest("flow/flow", function (testPage) { }); it("modifying controller's selection should update iterations", function (done) { + flow._repetition.isSelectionEnabled = true; rangeController.selection = ["(1)"]; - waitForFlowRepetition().then(function (flowRepetition) { + + waitForFlowRepetition(2).then(function (flowRepetition) { expect(cleanTextContent(flowRepetition.element)).toBe("(0) (1) (2)"); expect(flowRepetition.element.children[0].children[0].component.iteration.selected).toBeFalsy(); expect(flowRepetition.element.children[1].children[0].component.iteration.selected).toBeTruthy(); expect(flowRepetition.element.children[2].children[0].component.iteration.selected).toBeFalsy(); rangeController.selection = ["(2)"]; - expect(flowRepetition.element.children[0].children[0].component.iteration.selected).toBeFalsy(); - expect(flowRepetition.element.children[1].children[0].component.iteration.selected).toBeFalsy(); - expect(flowRepetition.element.children[2].children[0].component.iteration.selected).toBeTruthy(); - done(); + + return waitForFlowRepetition(2).then(function (flowRepetition) { + expect(flowRepetition.element.children[0].children[0].component.iteration.selected).toBeFalsy(); + expect(flowRepetition.element.children[1].children[0].component.iteration.selected).toBeFalsy(); + expect(flowRepetition.element.children[2].children[0].component.iteration.selected).toBeTruthy(); + flow._repetition.isSelectionEnabled = false; + + done(); + }); + }).catch(function (error) { + done.fail(error); }); }); it("modifying iterations's selected should update controller's selection", function (done) { - waitForFlowRepetition().then(function (flowRepetition) { + flow.isSelectionEnabled = true; + + waitForFlowRepetition(2).then(function (flowRepetition) { flowRepetition.element.children[0].children[0].component.iteration.selected = true; expect(rangeController.selection.toString()).toBe("(0)"); expect(flow.selection.toString()).toBe("(0)"); + flow.isSelectionEnabled = false; done(); - }); + }).catch(function (error) { + done.fail(error); + }); }); // it("changes in controller's selection should be reflected in flow's selection", function () { // }); it("changes in flow's selection should be reflected in controller's selection", function () { + flow.isSelectionEnabled = true; flow.selection = ["(1)"]; expect(rangeController.selection.toString()).toBe("(1)"); + flow.isSelectionEnabled = false; + }); it("after scrolling enough to hide selected iteration, no iteration should be selected", function (done) { + flow.isSelectionEnabled = true; flow.selection = ["(1)"]; flow.scroll = 50; - waitForFlowRepetition().then(function (flowRepetition) { + waitForFlowRepetition(2).then(function (flowRepetition) { expect(cleanTextContent(flowRepetition.element)).toBe("(48) (49) (50) (51) (52)"); expect(flowRepetition.element.children[0].children[0].component.iteration.selected).toBeFalsy(); expect(flowRepetition.element.children[1].children[0].component.iteration.selected).toBeFalsy(); expect(flowRepetition.element.children[2].children[0].component.iteration.selected).toBeFalsy(); expect(flowRepetition.element.children[3].children[0].component.iteration.selected).toBeFalsy(); expect(flowRepetition.element.children[4].children[0].component.iteration.selected).toBeFalsy(); + flow.isSelectionEnabled = false; done(); + }).catch(function (error) { + done.fail(error); }); }); it("iterations in selection should be selected after they enter in the frustum area", function (done) { + flow.isSelectionEnabled = true; flow.selection = ["(1)"]; flow.scroll = 50; - waitForFlowRepetition().then(function (flowRepetition) { + waitForFlowRepetition(2).then(function (flowRepetition) { expect(cleanTextContent(flowRepetition.element)).toBe("(48) (49) (50) (51) (52)"); flow.scroll = 0; - waitForFlowRepetition().then(function (flowRepetition) { + return waitForFlowRepetition(2).then(function (flowRepetition) { expect(cleanTextContent(flowRepetition.element)).toBe("(0) (1) (2) (51) (52)"); expect(flowRepetition.element.children[0].children[0].component.iteration.selected).toBeFalsy(); expect(flowRepetition.element.children[1].children[0].component.iteration.selected).toBeTruthy(); expect(flowRepetition.element.children[2].children[0].component.iteration.selected).toBeFalsy(); expect(flowRepetition.element.children[3].children[0].component.iteration.selected).toBeFalsy(); expect(flowRepetition.element.children[4].children[0].component.iteration.selected).toBeFalsy(); + flow.isSelectionEnabled = false; done(); }); + }).catch(function (error) { + done.fail(error); }); }); }); diff --git a/test/spec/ui/repetition-selection-spec.js b/test/spec/ui/repetition-selection-spec.js index 10aafe39b9..b6ed937277 100644 --- a/test/spec/ui/repetition-selection-spec.js +++ b/test/spec/ui/repetition-selection-spec.js @@ -47,11 +47,15 @@ TestPageLoader.queueTest("repetition/selection-test/selection-test", function (t var listElementToSelect = querySelectorAll("ul>li")[0]; nameController.selection = [nameController.organizedContent[0]]; - testPage.mouseEvent({target: listElementToSelect}, "mousedown", function () { - testPage.mouseEvent({target: listElementToSelect}, "mouseup", function () { - expect(nameController.selection[0]).toBe(nameController.organizedContent[0]); - expect(repetition.selectedIndexes).toEqual([0]); - done(); + testPage.mouseEvent({ target: listElementToSelect }, "mousedown", function () { + testPage.mouseEvent({ target: listElementToSelect }, "mouseup", function () { + return testPage.waitForDraw().then(function () { + expect(nameController.selection[0]).toBe(nameController.organizedContent[0]); + expect(repetition.selectedIndexes).toEqual([0]); + done(); + }).catch(function (error) { + done.fail(error); + }); }); }); }); @@ -60,10 +64,15 @@ TestPageLoader.queueTest("repetition/selection-test/selection-test", function (t var listElementToSelect = querySelectorAll("ul>li")[1]; nameController.selection = [nameController.organizedContent[1]]; testPage.mouseEvent({target: listElementToSelect}, "mousedown", function () { - testPage.mouseEvent({target: listElementToSelect}, "mouseup", function () { - expect(listElementToSelect.classList.contains("selected")).toBeTruthy(); - expect(repetition.selectedIndexes).toEqual([1]); - done(); + testPage.mouseEvent({ target: listElementToSelect }, "mouseup", function () { + return testPage.waitForDraw().then(function () { + + expect(listElementToSelect.classList.contains("selected")).toBeTruthy(); + expect(repetition.selectedIndexes).toEqual([1]); + done(); + }).catch(function (error) { + done.fail(error); + }); }); }); }); @@ -71,17 +80,22 @@ TestPageLoader.queueTest("repetition/selection-test/selection-test", function (t it("should not deselect the representative element if clicked again", function (done) { var listElementToSelect = querySelectorAll("ul>li")[1]; nameController.selection = [nameController.organizedContent[1]]; - testPage.mouseEvent({target: listElementToSelect}, "mousedown", function () { - testPage.mouseEvent({target: listElementToSelect}, "mouseup", function () { - expect(listElementToSelect.classList.contains("selected")).toBeTruthy(); - expect(repetition.selectedIndexes).toEqual([1]); - testPage.mouseEvent({target: listElementToSelect}, "mousedown", function () { - testPage.mouseEvent({target: listElementToSelect}, "mouseup", function () { - expect(listElementToSelect.classList.contains("selected")).toBeTruthy(); - expect(repetition.selectedIndexes).toEqual([1]); - done(); + testPage.mouseEvent({ target: listElementToSelect }, "mousedown", function () { + testPage.mouseEvent({ target: listElementToSelect }, "mouseup", function () { + return testPage.waitForDraw().then(function () { + expect(listElementToSelect.classList.contains("selected")).toBeTruthy(); + expect(repetition.selectedIndexes).toEqual([1]); + + testPage.mouseEvent({ target: listElementToSelect }, "mousedown", function () { + testPage.mouseEvent({ target: listElementToSelect }, "mouseup", function () { + expect(listElementToSelect.classList.contains("selected")).toBeTruthy(); + expect(repetition.selectedIndexes).toEqual([1]); + done(); + }); }); + }).catch(function (error) { + done.fail(error); }); }); }); @@ -181,21 +195,29 @@ TestPageLoader.queueTest("repetition/selection-test/selection-test", function (t } }); - it("should properly update the iterations selected property after disabling the selection", function () { + it("should properly update the iterations selected property after disabling the selection", function (done) { var i; repetition.contentController.selection = []; - for (i = 0; i < 6; i++) { + + for (i = 0; i < repetition.contentController.length; i++) { expect(repetition.iterations[i].selected).toBeFalsy(); } repetition.contentController.selection = [repetition.iterations[0].object]; expect(repetition.iterations[0].selected).toBeTruthy(); + repetition.isSelectionEnabled = false; - for (i = 0; i < 6; i++) { - expect(repetition.iterations[i].selected).toBeFalsy(); - } + + testPage.waitForDraw().then(function () { + for (i = 0; i < repetition.contentController.length; i++) { + expect(repetition.iterations[i].selected).toBeFalsy(); + } + done(); + }).catch(function (error) { + done.fail(error); + }); }); }); diff --git a/test/spec/ui/repetition-spec.js b/test/spec/ui/repetition-spec.js index 67997e02cd..81b44517f9 100644 --- a/test/spec/ui/repetition-spec.js +++ b/test/spec/ui/repetition-spec.js @@ -627,11 +627,18 @@ TestPageLoader.queueTest("repetition/repetition", function (testPage) { describe("Repetition of a component with a binding", function () { it("should draw the repetition with the correct duplicated bindings", function (done) { - var inputs = querySelectorAll(".list7 .textfield5"); - expect(inputs.length).toBe(2); - expect(inputs[0].value).toBe("Hello"); - expect(inputs[0].value).toBe("Hello"); - done(); + var list7 = querySelector(".list7").component; + + testPage.waitForComponentDraw(list7).then(function () { + var inputs = querySelectorAll(".list7 .textfield5"); + expect(inputs.length).toBe(2); + expect(inputs[0].value).toBe("Hello"); + expect(inputs[0].value).toBe("Hello"); + }).catch(function (error) { + done.fail(error); + }).then(function () { + done(); + }) }); }); diff --git a/test/spec/ui/repetition/componentx.reel/componentx.html b/test/spec/ui/repetition/componentx.reel/componentx.html index 778438077e..386418ae12 100644 --- a/test/spec/ui/repetition/componentx.reel/componentx.html +++ b/test/spec/ui/repetition/componentx.reel/componentx.html @@ -38,7 +38,7 @@ "values": { "element": {"#": "textfield2"}, "hasTemplate": false, - "domContent": {"<-": "@owner.domContent"} + "domContent": {"<-": "@owner.element"} } }, "owner": { diff --git a/test/spec/ui/substitution-spec.js b/test/spec/ui/substitution-spec.js index 42f3a9caf4..f460dadea4 100644 --- a/test/spec/ui/substitution-spec.js +++ b/test/spec/ui/substitution-spec.js @@ -149,7 +149,7 @@ TestPageLoader.queueTest("substitution-test/substitution-test", function (testPa }); }); - it("should not use switchElements to draw a switchValue that is currently drawn because it hasn't been updated", function (done) { + it("should not use switchElements to draw a switchValue that is currently drawn because it hasn't been updated", function (done, fail) { var substitution = templateObjects.substitution7; substitution.switchValue = "two"; @@ -157,7 +157,10 @@ TestPageLoader.queueTest("substitution-test/substitution-test", function (testPa testPage.waitForComponentDraw(substitution).then(function () { expect(substitution.element.children[0].className).toBe("Foo"); - }).finally(function () { + }).catch(function (e) { + done.fail(e); + }) + .then(function () { done(); }); }); diff --git a/ui/cascading-list.info/sample/index.html b/ui/cascading-list.info/sample/index.html index e3d77338fb..0fd26bb0e8 100644 --- a/ui/cascading-list.info/sample/index.html +++ b/ui/cascading-list.info/sample/index.html @@ -4,6 +4,7 @@ Cascading List Samples + diff --git a/ui/cascading-list.info/sample/ui/main.reel/main.css b/ui/cascading-list.info/sample/ui/main.reel/main.css index 9cfa0bba95..58e710ea28 100644 --- a/ui/cascading-list.info/sample/ui/main.reel/main.css +++ b/ui/cascading-list.info/sample/ui/main.reel/main.css @@ -38,8 +38,3 @@ section { flex: 1; display: flex; } - -.CascadingListItem { - width: 256px; - width: 16rem; -} diff --git a/ui/cascading-list.reel/cascading-list-item.reel/cascading-list-item.css b/ui/cascading-list.reel/cascading-list-item.reel/cascading-list-item.css index df1ca72c8b..d7e118e7f0 100755 --- a/ui/cascading-list.reel/cascading-list-item.reel/cascading-list-item.css +++ b/ui/cascading-list.reel/cascading-list-item.reel/cascading-list-item.css @@ -10,9 +10,7 @@ font-family: 'lato'; box-sizing: border-box; overflow: hidden; - border-bottom: 1px solid #c8c7cc; - border-top: 1px solid #c8c7cc; - border-right: 1px solid #c8c7cc; + width: 256px; } .CascadingListItem-header, @@ -40,6 +38,20 @@ border-bottom: 1px solid #c8c7cc; } +.CascadingList.is-shelf-opened .CascadingListItem-content, +.CascadingListItem-content.open-transition { + margin-top: 64px; +} + +.CascadingListItem-content.open-transition { + transition: .3s margin-top ease-in-out; +} + +.CascadingListItem-content.close-transition { + margin-top: 0px !important; + transition: .3s margin-top ease-in-out; +} + .CascadingListItem-footer { border-top: 1px solid #c8c7cc; } @@ -105,7 +117,6 @@ -ms-flex-direction: column; flex-direction: column; min-width: 256px; - min-width: 16rem; } .CascadingListItem-content .List { diff --git a/ui/cascading-list.reel/cascading-list-item.reel/cascading-list-item.html b/ui/cascading-list.reel/cascading-list-item.reel/cascading-list-item.html index b89ddadb34..aa9b0ae471 100755 --- a/ui/cascading-list.reel/cascading-list-item.reel/cascading-list-item.html +++ b/ui/cascading-list.reel/cascading-list-item.reel/cascading-list-item.html @@ -1,5 +1,6 @@ + +
-
+
@@ -115,4 +119,5 @@
+ diff --git a/ui/cascading-list.reel/cascading-list-item.reel/cascading-list-item.js b/ui/cascading-list.reel/cascading-list-item.reel/cascading-list-item.js index 9aa9867eb5..3992d2c6e8 100755 --- a/ui/cascading-list.reel/cascading-list-item.reel/cascading-list-item.js +++ b/ui/cascading-list.reel/cascading-list-item.reel/cascading-list-item.js @@ -155,18 +155,72 @@ var CascadingListItem = exports.CascadingListItem = Component.specialize({ value: null }, - selection: { + _selection: { value: null }, + _ignoreSelectionChange: { + value: false + }, + + selection: { + get: function () { + return this._selection; + }, + set: function (selection) { + if (selection !== this._selection) { + this._selection = selection; + + if ( + this.isCollection && selection && + this.context.columnIndex < this.cascadingList.currentColumnIndex && + !selection.length + ) { + var nextCascadingListItem = this.cascadingList.cascadingListItemAtIndex(this.context.columnIndex + 1); + this._ignoreSelectionChange = true; + this.selection.push(nextCascadingListItem.context.object); + } + } + } + }, + + enterDocument: { + value: function () { + this.context.cascadingList.registerComponentForBackRoute(this.backButton); + } + }, + + exitDocument: { + value: function () { + if (this.context) { + this.context.cascadingList.unregisterComponentForBackRoute(this.backButton); + } + } + }, + _handleSelectionChange: { value: function (plus, minus, index) { - if (plus && plus.length === 1) { + if ( + plus && plus.length === 1 && + !this._ignoreSelectionChange + ) { this.cascadingList.expand( plus[0], this.context.columnIndex + 1 ); } + + this._ignoreSelectionChange = false; + } + }, + + didDraw: { + value: function () { + if (this.context && this.content.component) { + // FIXME: Should be dispatched from the content placeholder. + // and then we dispatch a cascadingListItemLoaded event + this.dispatchEventNamed('cascadingListItemLoaded', true, true); + } } } diff --git a/ui/cascading-list.reel/cascading-list-shelf.reel/cascading-list-shelf-item.reel/cascading-list-shelf-item.css b/ui/cascading-list.reel/cascading-list-shelf.reel/cascading-list-shelf-item.reel/cascading-list-shelf-item.css new file mode 100644 index 0000000000..d02e534f89 --- /dev/null +++ b/ui/cascading-list.reel/cascading-list-shelf.reel/cascading-list-shelf-item.reel/cascading-list-shelf-item.css @@ -0,0 +1,36 @@ +.CascadingListShelfItem { + font-weight: 400; + color: #858585; + height: 18px; + display: flex; + align-items: center; +} + +.CascadingListShelfItem-label { + font-size: 16px; + padding: 0 8px; + padding-right: 8px; + margin: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + -ms-flex: 1; + flex: 1; +} + +.CascadingListShelfItem-delete { + padding-left: 8px; + width: 14px; + height: 14px; + background-position: center; + background-image: url("trash.svg"); + background-repeat: no-repeat; +} + +.CascadingListShelfItem-delete:hover { + cursor: pointer; +} + +.CascadingListShelfItem.montage-ghostImage .CascadingListShelfItem-delete { + visibility: hidden; +} diff --git a/ui/cascading-list.reel/cascading-list-shelf.reel/cascading-list-shelf-item.reel/cascading-list-shelf-item.html b/ui/cascading-list.reel/cascading-list-shelf.reel/cascading-list-shelf-item.reel/cascading-list-shelf-item.html new file mode 100644 index 0000000000..a584ae1318 --- /dev/null +++ b/ui/cascading-list.reel/cascading-list-shelf.reel/cascading-list-shelf-item.reel/cascading-list-shelf-item.html @@ -0,0 +1,37 @@ + + + + + + + +
+ + +
+ + diff --git a/ui/cascading-list.reel/cascading-list-shelf.reel/cascading-list-shelf-item.reel/cascading-list-shelf-item.js b/ui/cascading-list.reel/cascading-list-shelf.reel/cascading-list-shelf-item.reel/cascading-list-shelf-item.js new file mode 100644 index 0000000000..2c36206485 --- /dev/null +++ b/ui/cascading-list.reel/cascading-list-shelf.reel/cascading-list-shelf-item.reel/cascading-list-shelf-item.js @@ -0,0 +1,80 @@ +/** + * @module "ui/cascading-list-shelf-item.reel" + */ +var Component = require("../../../component").Component; + +/** + * @class CascadingListShelfItem + * @extends Component + */ +exports.CascadingListShelfItem = Component.specialize(/** @lends CascadingListShelfItem.prototype */{ + + _templateDidLoad: { + value: false + }, + + templateDidLoad: { + value: function () { + this.defineBindings({ + "_label": { + "<-": "data.defined() && userInterfaceDescriptor.defined() ? " + + "(data.path(userInterfaceDescriptor.nameExpression) || " + + "data) : null" + } + + }); + // FIXME: not safe! + // https://github.com/montagejs/montage/issues/1977 + this._templateDidLoad = true; + this._loadDataUserInterfaceDescriptorIfNeeded(); + } + }, + + _label: { + value: null + }, + + enterDocument: { + value: function () { + this.addEventListener("action", this); + } + }, + + exitDocument: { + value: function () { + this.removeEventListener("action", this); + } + }, + + userInterfaceDescriptor: { + value: null + }, + + handleDeleteAction: { + value: function () { + this.cascadingList.removeObjectFromShelf(this.data); + } + }, + + _loadDataUserInterfaceDescriptorIfNeeded: { + value: function () { + if (this.data && this._templateDidLoad) { + var self = this; + + return this.loadUserInterfaceDescriptor(this.data).then(function (UIDescriptor) { + self.userInterfaceDescriptor = UIDescriptor || self.userInterfaceDescriptor; // trigger biddings. + + self._label = self.callDelegateMethod( + "cascadingListShelfItemWillUseLabelForObject", + self, + self._label, + self.data, + self.rowIndex, + self.list + ) || self._label; // defined by a bidding expression + }); + } + } + } + +}); diff --git a/ui/cascading-list.reel/cascading-list-shelf.reel/cascading-list-shelf-item.reel/trash.svg b/ui/cascading-list.reel/cascading-list-shelf.reel/cascading-list-shelf-item.reel/trash.svg new file mode 100644 index 0000000000..b01d643cea --- /dev/null +++ b/ui/cascading-list.reel/cascading-list-shelf.reel/cascading-list-shelf-item.reel/trash.svg @@ -0,0 +1 @@ + diff --git a/ui/cascading-list.reel/cascading-list-shelf.reel/cascading-list-shelf.css b/ui/cascading-list.reel/cascading-list-shelf.reel/cascading-list-shelf.css new file mode 100644 index 0000000000..de54bf5f5d --- /dev/null +++ b/ui/cascading-list.reel/cascading-list-shelf.reel/cascading-list-shelf.css @@ -0,0 +1,116 @@ +.CascadingListShelf { + position: absolute; + left: 0; + right: 0; + z-index: 10; + background-color: #E8E8E8; + height: 0px; + overflow: hidden; + font-family: 'lato'; + box-sizing: border-box; +} + +.CascadingListShelf.is-open, +.CascadingListShelf.open-transition { + border-bottom: 1px solid #c8c7cc; + height: 64px; +} + +.CascadingListShelf.open-transition { + transition: .3s height ease-in-out; +} + +.CascadingListShelf.close-transition { + height: 0px; + transition: .3s height ease-in-out; +} + +.CascadingListShelf .CascadingListShelf-background { + display: flex; + justify-content: center; + align-items: center; + position: absolute; + z-index: 10; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +.CascadingListShelf .CascadingListShelf-background > svg { + display: none; +} + +.CascadingListShelf.is-empty .CascadingListShelf-background > svg, +.CascadingListShelf.accept-drop.will-drop .CascadingListShelf-background > svg { + display: block; +} + +.CascadingListShelf .CascadingListShelf-background .drop { + stroke: #858585; +} + +.CascadingList.is-dragging-item .CascadingListShelf.accept-drop .CascadingListShelf-background { + border: 2px dashed #0076FF; +} + +.CascadingList.is-dragging-item .CascadingListShelf.accept-drop.will-drop .CascadingListShelf-background { + display: flex; + border: 2px solid #0076FF; +} + +.CascadingListShelf.accept-drop.will-drop .CascadingListShelf-background .drop { + stroke: #0076FF; +} + +.CascadingListShelf.accept-drop.will-drop .CascadingListShelf-options, +.CascadingListShelf.accept-drop.will-drop .CascadingListShelf-items { + display: none; +} + +.CascadingListShelf-options { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 16px; + padding: 4px; + display: flex; + z-index: 11; +} + +.CascadingListShelf-title { + padding-right: 8px; + color: #0076FF; + font-weight: 600; +} + +.CascadingListShelf-close { + display: inline-block; + height: 16px; + width: 16px; + background-image: url("times.svg"); + background-repeat: no-repeat; + background-position: center; + background-size: contain; + outline: none; + margin-left: auto; +} + +.CascadingListShelf-close:hover { + cursor: pointer; +} + +.CascadingListShelf-items { + position: absolute; + top: 24px; + left: 0; + right: 0; + bottom: 0; + z-index: 11; +} + +.CascadingListShelf-repetition { + max-height: 100%; + overflow: auto; +} diff --git a/ui/cascading-list.reel/cascading-list-shelf.reel/cascading-list-shelf.html b/ui/cascading-list.reel/cascading-list-shelf.reel/cascading-list-shelf.html new file mode 100644 index 0000000000..be78392a91 --- /dev/null +++ b/ui/cascading-list.reel/cascading-list-shelf.reel/cascading-list-shelf.html @@ -0,0 +1,80 @@ + + + + + + + +
+
+ + + + + + + + + + + +
+
+ + +
+
+
+
+
+
+
+ + diff --git a/ui/cascading-list.reel/cascading-list-shelf.reel/cascading-list-shelf.js b/ui/cascading-list.reel/cascading-list-shelf.reel/cascading-list-shelf.js new file mode 100644 index 0000000000..efc28b2acf --- /dev/null +++ b/ui/cascading-list.reel/cascading-list-shelf.reel/cascading-list-shelf.js @@ -0,0 +1,160 @@ +/** + * @module "ui/cascading-list-dropzone.reel" + */ +var Component = require("../../component").Component; + +/** + * @class CascadingListShelf + * @extends Component + * + * TODO: + * + * - Add a visible drawer or not + * - swipe up to close it or down if there is a visible drawer + */ +exports.CascadingListShelf = Component.specialize({ + + enterDocument: { + value: function () { + this.element.addEventListener("transitionend", this); + } + }, + + exitDocument: { + value: function () { + this.element.removeEventListener("transitionend", this); + } + }, + + isOpened: { + value: false + }, + + acceptDrop: { + value: false + }, + + willDrop: { + value: false + }, + + open: { + value: function (noTransition) { + if (!this.isOpened) { + this._noTransition = !!noTransition; + this._shouldOpen = true; + this._shouldClose = false; + this.needsDraw = true; + } + } + }, + + close: { + value: function (noTransition) { + if (this.isOpened) { + this._noTransition = !!noTransition; + this._shouldClose = true; + this._shouldOpen = false; + this.needsDraw = true; + } + } + }, + + handleCloseAction: { + value: function () { + this.close(); + } + }, + + handleTransitionend: { + value: function (event) { + if (event.target === this.element) { + var currentCascadingListItem ; + + if (this._shouldClose) { + this.removeEventListener("action", this); + this._noTransition = false; + this._shouldClose = false; + this.isOpened = false; + this.cascadingList.clearShelfContent(); + this.dispatchEventNamed("cascadingListShelfClose", true, true, this); + currentCascadingListItem = this.cascadingList.getCurrentCascadingListItem(); + currentCascadingListItem.content.classList.remove('close-transition'); + this.needsDraw = true; + + } else if (this._shouldOpen) { + this.addEventListener("action", this); + this._noTransition = false; + this._shouldOpen = false; + this.isOpened = true; + this.dispatchEventNamed("cascadingListShelfOpen", true, true, this); + currentCascadingListItem = this.cascadingList.getCurrentCascadingListItem(); + currentCascadingListItem.content.classList.remove('open-transition'); + this.needsDraw = true; + } + } + } + }, + + willDraw: { + value: function () { + if (!this._anchorBoundingRect && this._shouldOpen) { + this._cascadingListHeaderElement = this.parentComponent.element.querySelector( + '[data-montage-id="cascading-list-header"]' + ); + + if (this._cascadingListHeaderElement) { + this._anchorBoundingRect = this._cascadingListHeaderElement.getBoundingClientRect(); + this.element.style.top = this._cascadingListHeaderElement.offsetHeight + "px"; + } + } + } + }, + + draw: { + value: function () { + var currentCascadingListItem; + + if (!this._noTransition) { + if (this._shouldOpen) { + this.classList.add('open-transition'); + currentCascadingListItem = this.cascadingList.getCurrentCascadingListItem(); + currentCascadingListItem.content.classList.add('open-transition'); + + } else { + this.classList.remove('open-transition'); + } + + if (this._shouldClose) { + this.classList.add('close-transition'); + currentCascadingListItem = this.cascadingList.getCurrentCascadingListItem(); + currentCascadingListItem.content.classList.add('close-transition'); + } else { + this.classList.remove('close-transition'); + } + } else { + this.classList.remove('open-transition'); + this.classList.remove('close-transition'); + + if (this._shouldOpen) { + this._shouldOpen = false; + this.isOpened = true; + this.addEventListener("action", this); + currentCascadingListItem = this.cascadingList.getCurrentCascadingListItem(); + currentCascadingListItem.content.classList.remove('close-transition'); + this.dispatchEventNamed("cascadingListShelfOpen", true, true, this); + + } else if (this._shouldClose) { + this.isOpened = false; + this._shouldClose = false; + currentCascadingListItem = this.cascadingList.getCurrentCascadingListItem(); + currentCascadingListItem.content.classList.remove('open-transition'); + this.cascadingList.clearShelfContent(); + this.removeEventListener("action", this); + this.dispatchEventNamed("cascadingListShelfClose", true, true, this); + } + } + } + } + +}); diff --git a/ui/cascading-list.reel/cascading-list-shelf.reel/times.svg b/ui/cascading-list.reel/cascading-list-shelf.reel/times.svg new file mode 100644 index 0000000000..f5b45d0df9 --- /dev/null +++ b/ui/cascading-list.reel/cascading-list-shelf.reel/times.svg @@ -0,0 +1 @@ + diff --git a/ui/cascading-list.reel/cascading-list.css b/ui/cascading-list.reel/cascading-list.css index 334739759f..70511d89ea 100644 --- a/ui/cascading-list.reel/cascading-list.css +++ b/ui/cascading-list.reel/cascading-list.css @@ -2,19 +2,19 @@ display: -webkit-box; display: -ms-flexbox; display: flex; - -webkit-box-flex: 1; - -ms-flex: 1; - flex: 1; -webkit-box-orient: vertical; -webkit-box-direction: normal; -ms-flex-direction: column; flex-direction: column; + position: relative; + overflow: hidden; + border: 1px solid #c8c7cc; + min-width: 256px; + max-width: 256px; overflow: hidden; - box-sizing: border-box; - border-left: 1px solid #c8c7cc; } -.CascadingList-repetition { +.CascadingList-succession { display: -webkit-box; display: -ms-flexbox; display: flex; @@ -23,4 +23,57 @@ -webkit-box-flex: 1; -ms-flex: 1; flex: 1; + width: 200%; +} + +.CascadingList.isFlat { + overflow: visible; + min-width: inherit; + max-width: inherit; +} + +.CascadingList.isFlat .CascadingListItem { + border-left: 1px solid #c8c7cc; +} + +.CascadingList.isFlat .CascadingListItem:first-of-type { + border-left: none; +} + +.CascadingList.isFlat .CascadingList-succession { + width: 100%; +} + +.CascadingList.animated .CascadingList-succession .buildInFrom { + transform: translateX(0%); +} + +.CascadingList.animated .CascadingList-succession .buildIn { + transition: transform .25s ease-in-out; + transform: translateX(-100%); +} + +.CascadingList.animated .CascadingList-succession .buildOut { + transition: transform .25s ease-in-out; +} + +.CascadingList.animated .CascadingList-succession .buildOutTo { + transform: translateX(-100%); +} + +.CascadingList.animated .CascadingList-succession.back-transition .buildInFrom { + transform: translateX(-200%); +} + +.CascadingList.animated .CascadingList-succession.back-transition .buildIn { + transition: transform .25s ease-in-out; + transform: translateX(-100%); +} + +.CascadingList.animated .CascadingList-succession.back-transition .buildOut { + transition: transform .25s ease-in-out; +} + +.CascadingList.animated .CascadingList-succession.back-transition .buildOutTo { + transform: translateX(100%); } diff --git a/ui/cascading-list.reel/cascading-list.html b/ui/cascading-list.reel/cascading-list.html index 88b85be9c1..9641d9f4cf 100644 --- a/ui/cascading-list.reel/cascading-list.html +++ b/ui/cascading-list.reel/cascading-list.html @@ -1,5 +1,6 @@ + +
-
-
-
+
+
+ diff --git a/ui/cascading-list.reel/cascading-list.js b/ui/cascading-list.reel/cascading-list.js index c1d06d280b..a2468d2b9e 100644 --- a/ui/cascading-list.reel/cascading-list.js +++ b/ui/cascading-list.reel/cascading-list.js @@ -1,6 +1,12 @@ -var Component = require("../component").Component, - Montage = require("../../core/core").Montage, - Promise = require('../../core/promise').Promise; +var CascadingListShelfItem = require("./cascading-list-shelf.reel/cascading-list-shelf-item.reel").CascadingListShelfItem, + TranslateComposer = require("../../composer/translate-composer").TranslateComposer, + CascadingListItem = require("./cascading-list-item.reel").CascadingListItem, + PressComposer = require("../../composer/press-composer").PressComposer, + ListItem = require("../list-item.reel").ListItem, + Promise = require('../../core/promise').Promise, + Component = require("../component").Component, + Montage = require("../../core/core").Montage; + var CascadingListContext = exports.CascadingListContext = Montage.specialize({ @@ -30,11 +36,60 @@ var CascadingListContext = exports.CascadingListContext = Montage.specialize({ }); -exports.CascadingList = Component.specialize({ +var CascadingList = exports.CascadingList = Component.specialize({ - constructor: { - value: function () { - this.history = []; + __pressComposer: { + value: null + }, + + _pressComposer: { + get: function () { + if (!this.__pressComposer) { + this.__pressComposer = new PressComposer(); + this.__pressComposer.delegate = this; + + this.addComposerForElement( + this.__pressComposer, + this.element.ownerDocument + ); + } + + return this.__pressComposer; + } + }, + + __translateComposer: { + value: null + }, + + _translateComposer: { + get: function () { + if (!this.__translateComposer) { + this.__translateComposer = new TranslateComposer(); + this.__translateComposer.hasMomentum = false; + this.__translateComposer.shouldCancelOnSroll = false; + this.__translateComposer.translateX = 0; + this.__translateComposer.translateY = 0; + this.addComposer(this.__translateComposer); + } + + return this.__translateComposer; + } + }, + + __registeredRoutesComponents: { + value: null + }, + + _registeredRoutesComponents: { + get: function () { + if (!this.__registeredRoutesComponents) { + this.__registeredRoutesComponents = new Map(); + this._registeredRoutesComponents.set("back", []); + this._registeredRoutesComponents.set("next", []); + } + + return this.__registeredRoutesComponents; } }, @@ -42,8 +97,43 @@ exports.CascadingList = Component.specialize({ value: 0 }, + currentColumnIndex: { + get: function () { + return this._currentColumnIndex; + } + }, + history: { - value: null + get: function () { + return this.succession ? + this.succession.history : []; + } + }, + + isFlat: { + value: false + }, + + isResponsive: { + value: true + }, + + shrinkForWidth: { + value: 768 // px + }, + + _contentBuildOutAnimation: { + value: { + cssClass: "buildOut", + toCssClass: "buildOutTo" + } + }, + + _contentBuildInAnimation: { + value: { + fromCssClass: "buildInFrom", + cssClass: "buildIn" + } }, _root: { @@ -58,7 +148,7 @@ exports.CascadingList = Component.specialize({ if (this._root !== root) { this._root = root; - if (root) { + if (root && !this.isDeserializing) { this.expand(root); } } @@ -69,12 +159,60 @@ exports.CascadingList = Component.specialize({ value: false }, + isShelfOpened: { + value: false + }, + + _shelfContent: { + value: null + }, + + shelfContent: { + get: function () { + return this._shelfContent || (this._shelfContent = []); + } + }, + + enterDocument: { + value: function () { + if (!CascadingList.cssTransform) { + if ("webkitTransform" in this._element.style) { + CascadingList.cssTransform = "webkitTransform"; + } else if ("MozTransform" in this._element.style) { + CascadingList.cssTransform = "MozTransform"; + } else if ("oTransform" in this._element.style) { + CascadingList.cssTransform = "oTransform"; + } else { + CascadingList.cssTransform = "transform"; + } + } + + window.addEventListener("resize", this); + this.addEventListener("cascadingListItemLoaded", this); + this.addEventListener("cascadingListShelfOpen", this); + this.addEventListener("cascadingListShelfClose", this); + this.addEventListener("listIterationLongPress", this); + + if (this._root) { + this.expand(this._root); + } + } + }, + exitDocument: { value: function () { + window.removeEventListener("resize", this); + this.removeEventListener("cascadingListItemLoaded", this); + this.removeEventListener("cascadingListShelfOpen", this); + this.removeEventListener("cascadingListShelfClose", this); + this.removeEventListener("listIterationLongPress", this); + + this.classList.remove('animated'); + this.popAll(); } }, - + _delegate: { value: null }, @@ -179,6 +317,8 @@ exports.CascadingList = Component.specialize({ this._currentColumnIndex = columnIndex; + this._closeShelfIfNeeded(); + return this._populateColumnWithObjectAndIndex( object, columnIndex, isEditing ); @@ -188,15 +328,21 @@ exports.CascadingList = Component.specialize({ cascadingListItemAtIndex: { value: function (index) { if (this.history[index]) { - return this.history[index].cascadingListItem; + return this.history[index]; } } }, + getCurrentCascadingListItem: { + value: function () { + return this.cascadingListItemAtIndex(this._currentColumnIndex); + } + }, + findIndexForObject: { value: function (object) { for (var i = this.history.length - 1; i > -1; i--) { - if (this.history[i] === object) { + if (this.history[i].context === object) { return i; } } @@ -205,31 +351,284 @@ exports.CascadingList = Component.specialize({ } }, + openShelf: { + value: function (noTransition) { + if (!this.shelf.isOpened && !this.isFlat) { + this.shelf.open(noTransition); + } + } + }, + + closeShelf: { + value: function (noTransition) { + if (this.shelf.isOpened && !this.isFlat) { + this.shelf.close(noTransition); + } + } + }, + + removeObjectFromShelf: { + value: function (object) { + var index; + + if ((index = this.shelfContent.indexOf(object)) !== -1) { + this.shelfContent.splice(index, 1); + } + } + }, + + addObjectToShelf: { + value: function (object) { + if (!this.shelfHasDataObject(object)) { + this.shelfContent.push(object); + } + } + }, + + clearShelfContent: { + value: function () { + this.shelfContent.clear(); + } + }, + + shelfHasDataObject: { + value: function (object) { + return this.shelfContent.indexOf(object) > -1; + } + }, + + registerComponentForBackRoute: { + value: function (component) { + if (component) { + var backRoutes = this._registeredRoutesComponents.get("back"); + + if (backRoutes.indexOf(component) === -1) { + backRoutes.push(component); + } + } + } + }, + + unregisterComponentForBackRoute: { + value: function (component) { + if (component) { + var backRoutes = this._registeredRoutesComponents.get("back"), + index; + + if ((index = backRoutes.indexOf(component)) > -1) { + backRoutes.splice(index, 1); + } + } + } + }, + + /** + * Event Handlers + */ + + handlePress: { + value: function (event) { + if (!this.element.contains(event.targetElement)) { + this.closeShelf(); + } + } + }, + + handleListIterationLongPress: { + value: function (event) { + if (!this.isFlat) { + this.openShelf(); + } + } + }, + + handleTranslateStart: { + value: function (event) { + var startPosition = this._translateComposer.pointerStartEventPosition, + dataObject = this._findDataObjectFromElement(startPosition.target); + + this._resetTranslateContext(); + + if (dataObject) { + var delegateResponse = this.callDelegateMethod( + 'cascadingListCanDragObject', this, dataObject, true + ), + canDrag = delegateResponse === void 0 ? + true : delegateResponse; + + if (canDrag) { + var shouldCascadingListShelfAcceptDrop = !this.shelfHasDataObject(dataObject); + delegateResponse = this.callDelegateMethod( + 'cascadingListShelfShouldAcceptDataObject', + this, + dataObject, + shouldCascadingListShelfAcceptDrop + ); + + this._startPositionX = startPosition.pageX; + this._startPositionY = startPosition.pageY; + + // Add delegate method + this._draggingComponent = this._findDraggingComponentFromElement(startPosition.target); + this._draggingDataObject = dataObject; + this._isDragging = true; + this._shouldShelfAcceptDrop = delegateResponse === void 0 ? + shouldCascadingListShelfAcceptDrop : delegateResponse; + + this._addDragEventListeners(); + } + } + } + }, + + handleTranslate: { + value: function (event) { + this._translateX = event.translateX; + this._translateY = event.translateY; + + var positionX = this._startPositionX + this._translateX, + positionY = this._startPositionY + this._translateY; + + this._isShelfWillAcceptDrop = this._isDraggingComponentOverShelf(); + + //FIXME: need to used a radius before canceling the back navigation + clearTimeout(this._timeout); + + if (!this._isShelfWillAcceptDrop && !this._isBackTransition) { + this._shouldNaviguateBack = this._isDraggingComponentOverBackRoute(positionX, positionY); + + if (this._shouldNaviguateBack) { + var self = this; + + this._timeout = setTimeout(function () { + self._navigateBack(); + }, 50); + } + } + + + this.needsDraw = true; + } + }, + + handleTranslateEnd: { + value: function () { + if (this._isShelfWillAcceptDrop) { + this.addObjectToShelf(this._draggingDataObject); + } + + this._resetTranslateContext(); + } + }, + + + handleTranslateCancel: { + value: function () { + this._resetTranslateContext(); + } + }, + + handleCascadingListShelfOpen: { + value: function (event) { + this.isShelfOpened = true; + this._pressComposer.addEventListener("press", this); + this._translateComposer.addEventListener("translateStart", this); + } + }, + + handleCascadingListShelfClose: { + value: function (event) { + this.isShelfOpened = false; + this._pressComposer.removeEventListener("press", this); + this._translateComposer.removeEventListener("translateStart", this); + } + }, + + handleBackAction: { + value: function () { + this._navigateBack(); + } + }, + + handleBuildOutEnd: { + value: function (event) { + this._isBackTransition = false; + } + }, + + handleCascadingListItemLoaded: { + value: function () { + this.classList.add('animated'); + this.removeEventListener('cascadingListItemLoaded', this); + } + }, + + handleResize: { + value: function () { + this.needsDraw = true; + } + }, + + /** + * Private Method + */ + + _navigateBack: { + value: function () { + this._pop(); + this._closeShelfIfNeeded(); + this._isBackTransition = true; + } + }, + + _closeShelfIfNeeded: { + value: function () { + if (this.shelf.isOpened && !this.shelfContent.length) { + this.closeShelf(true); + } + } + }, + _push: { value: function (context) { - this.history.splice(context.columnIndex, 1, context); - this.needsDraw = true; + var cascadingListItem = new CascadingListItem(); + cascadingListItem.element = document.createElement("div"); + cascadingListItem.cascadingList = this; + cascadingListItem.delegate = this.delegate; + cascadingListItem.context = context; + cascadingListItem.isFlat = this.isFlat; + cascadingListItem.needsDraw = true; + this.history.splice(context.columnIndex, 1, cascadingListItem); if (this.shouldDispatchCascadingListEvents) { - this.dispatchEventNamed('cascadingListPush', true, true, context); + this.dispatchEventNamed( + 'cascadingListPush', + true, + true, + cascadingListItem + ); } } }, _pop: { value: function () { - var cascadingListItem, - context = this.history.pop(); - + var cascadingListItem = this.history.pop(); + this._currentColumnIndex--; - context.isEditing = false; + cascadingListItem.context.isEditing = false; this.needsDraw = true; if (this.shouldDispatchCascadingListEvents) { - this.dispatchEventNamed('cascadingListPop', true, true, context); + this.dispatchEventNamed( + 'cascadingListPop', + true, + true, + cascadingListItem + ); } - return context; + return cascadingListItem; } }, @@ -237,7 +636,7 @@ exports.CascadingList = Component.specialize({ value: function (object, columnIndex, isEditing) { if (!this._populatePromise && object) { var self = this; - + this._populatePromise = this.loadUserInterfaceDescriptor(object).then(function (UIDescriptor) { var context = self._createCascadingListContextWithObjectAndColumnIndex( object, @@ -268,6 +667,180 @@ exports.CascadingList = Component.specialize({ return context; } + }, + + _findDraggingComponentFromElement: { + value: function (element) { + var component; + + while (element && !(component = element.component)) { + element = element.parentElement; + } + + while (component && !( + component instanceof ListItem || + component instanceof CascadingListShelfItem + )) { + component = component.parentComponent; + } + + return component; + } + }, + + _addDragEventListeners: { + value: function () { + this._translateComposer.addEventListener('translate', this, false); + this._translateComposer.addEventListener('translateEnd', this, false); + this._translateComposer.addEventListener('translateCancel', this, false); + } + }, + + _removeDragEventListeners: { + value: function () { + this._translateComposer.removeEventListener('translate', this, false); + this._translateComposer.removeEventListener('translateEnd', this, false); + this._translateComposer.removeEventListener('translateCancel', this, false); + } + }, + + + _resetTranslateContext: { + value: function () { + this._removeDragEventListeners(); + this._startPositionX = 0; + this._startPositionY = 0; + this._translateX = 0; + this._translateY = 0; + this._isDragging = false; + this.__translateComposer.translateX = 0; + this.__translateComposer.translateY = 0; + this._draggingElementBoundingRect = null; + this._isShelfWillAcceptDrop = false; + this._shouldShelfAcceptDrop = false; + this.needsDraw = true; + } + }, + + _findDataObjectFromElement: { + value: function (element) { + var component, dataObject; + + while (element && !(component = element.component)) { + element = element.parentElement; + } + + while (component && !(dataObject = component.data)) { + component = component.parentComponent; + } + + return dataObject; + } + }, + + _isDraggingComponentOverShelf: { + value: function () { + if (this._shelfBoundingRect && this.shelf.isOpened) { + var x = this._startPositionX + this._translateX, + y = this._startPositionY + this._translateY; + + if (x >= this._shelfBoundingRect.left && x <= this._shelfBoundingRect.right && + y >= this._shelfBoundingRect.top && y <= this._shelfBoundingRect.bottom + ) { + return true; + } + } + + return false; + } + }, + + _isDraggingComponentOverBackRoute: { + value: function (positionX, positionY) { + var backRoutes = this._registeredRoutesComponents.get("back"), + component, + rect; + + for (var i = 0, length = backRoutes.length; i < length; i++) { + component = backRoutes[i]; + //FIXME: need to be optimized + rect = component.element.getBoundingClientRect(); + + if (positionX > rect.x && positionX < (rect.x + rect.width) && + positionY > rect.y && positionY < (rect.y + rect.height) + ) { + return true; + } + } + + return false; + } + }, + + /** + * Draw Methods + */ + + willDraw: { + value: function () { + if (this.isResponsive) { + this.isFlat = window.innerWidth >= this.shrinkForWidth; + + for (var i = 0; i < this.history.length; i++) { + var item = this.history[i]; + item.isFlat = this.isFlat; + } + } + + if (this._isDragging) { + if (!this._shelfBoundingRect) { + this._shelfBoundingRect = this.shelf.element.getBoundingClientRect(); + } + + if (this._draggingComponent && !this._draggingElementBoundingRect) { + this._draggingElementBoundingRect = this._draggingComponent.element.getBoundingClientRect(); + } + } + } + }, + + draw: { + value: function () { + if (this._isDragging) { + this.element.classList.add('is-dragging-item'); + + if (!this._ghostElement) { + this._ghostElement = this._draggingComponent.element.cloneNode(true); + this._ghostElement.classList.add("montage-ghostImage"); + this._ghostElement.style.visibility = "hidden"; + this._ghostElement.style.position = "absolute"; + this._ghostElement.style.zIndex = 999999; + this._ghostElement.style.top = this._draggingElementBoundingRect.top + "px"; + this._ghostElement.style.left = this._draggingElementBoundingRect.left + "px"; + this._ghostElement.style.width = this._draggingElementBoundingRect.width + "px"; + document.body.appendChild(this._ghostElement); + this._needsToWaitforGhostElementBoundaries = true; + this.needsDraw = true; + } + + if (!this._needsToWaitforGhostElementBoundaries) { + // Delegate Method for ghost element positioning? + this._ghostElement.style.visibility = "visible"; + } else { + this._needsToWaitforGhostElementBoundaries = false; + } + + this._ghostElement.style[CascadingList.cssTransform] = "translate3d(" + + this._translateX + "px," + this._translateY + "px,0)"; + } else { + this.element.classList.remove('is-dragging-item'); + + if (this._ghostElement) { + document.body.removeChild(this._ghostElement); + this._ghostElement = null; + } + } + } } }); diff --git a/ui/component.js b/ui/component.js index 713877f54f..c36c32b614 100644 --- a/ui/component.js +++ b/ui/component.js @@ -1368,56 +1368,75 @@ var Component = exports.Component = Target.specialize(/** @lends Component.proto } }, set: function (value) { - var components, - componentsToAdd = [], - i, - component; + var componentsToAdd = [], + elementsToAppend = + this._elementsToAppend = [], + elementsToAppendBefore = + this._elementsToAppendBefore = [], + componentsPendingBuildOut = + this._componentsPendingBuildOut = [], + currentDomContent = this.domContent, + childComponents = this.childComponents, + isArray = false, index = -1, + i, length, childComponent, + component, element; - if (!this._elementsToAppend) { - this._elementsToAppend = []; - } - this._newDomContent = value; - this.needsDraw = true; + if (value) { + if (value instanceof Element) { + if (currentDomContent.length <= 1) { + if (currentDomContent.indexOf(value) === -1) { + elementsToAppend.push(value); + } + } else { + elementsToAppend.push(value); + } + } else if ((isArray = + (Array.isArray(value) || value instanceof NodeList) + )) { + if (currentDomContent.length === 1) { + index = Array.prototype.indexOf.call(value, currentDomContent[0]); + } - if (this._newDomContent === null) { - this._shouldClearDomContentOnNextDraw = true; - } + for (i = 0; i < value.length; i++) { + element = value[i]; - if (typeof this.contentWillChange === "function") { - this.contentWillChange(value); - } + if (currentDomContent.indexOf(element) === -1) { + if (i > index) { + elementsToAppend.push(element); + } else { + elementsToAppendBefore.push(element); + } - // cleanup current content - components = this.childComponents; - if (value) { - if (!this._componentsPendingBuildOut) { - this._componentsPendingBuildOut = []; + if ( // Clean up possible transplanted components + (component = element.component) && + component._addedToDrawList && + component.parentComponent + ) { + component.parentComponent._removeToDrawList(component); + } + } + } } - for (i = components.length - 1; i >= 0; i--) { - if (this._componentsPendingBuildOut.indexOf(components[i]) === -1) { - this._componentsPendingBuildOut.push(components[i]); + + for (i = childComponents.length - 1; i >= 0; i--) { + childComponent = childComponents[i]; + + if ( + (isArray && Array.prototype.indexOf.call(value, childComponent.element) === -1) || + (!isArray && elementsToAppend.length && elementsToAppend.indexOf(childComponent.element) === -1) + ) { + componentsPendingBuildOut.push(childComponent); } } } else { - this._componentsPendingBuildOut = []; - for (i = components.length - 1; i >= 0; i--) { - components[i]._shouldBuildOut = true; - } - } - if (value instanceof Element) { - this._elementsToAppend.push(value); - this._findAndDetachComponents(value, componentsToAdd); - } else if (value && value[0]) { - for (i = 0; i < value.length; i++) { - this._elementsToAppend.push(value[i]); - this._findAndDetachComponents(value[i], componentsToAdd); + for (i = childComponents.length - 1; i >= 0; i--) { + componentsPendingBuildOut.push(childComponents[i]); } } - // not sure if I can rely on _parentComponent to detach the nodes instead of doing one loop for dettach and another to attach... - for (i = 0; (component = componentsToAdd[i]); i++) { - this.addChildComponent(component); - } + this._newDomContent = value; + this._shouldClearDomContentOnNextDraw = this._newDomContent === null; + this.needsDraw = true; } }, @@ -1616,6 +1635,7 @@ var Component = exports.Component = Target.specialize(/** @lends Component.proto } self.canDrawGate.setField("componentTreeLoaded", true); + return self; }).catch(function (error) { console.error(error); }); @@ -1818,6 +1838,9 @@ var Component = exports.Component = Target.specialize(/** @lends Component.proto } instances.owner = self; + + // FIXME: should be set after the instantiateWithInstances call... + // https://github.com/montagejs/montage/issues/1977 self._isTemplateInstantiated = true; return template.instantiateWithInstances(instances, _document).then(function (documentPart) { @@ -2098,10 +2121,18 @@ var Component = exports.Component = Target.specialize(/** @lends Component.proto childComponent = oldDrawList[i]; childComponent._addedToDrawList = false; if (childComponent.canDraw()) { // TODO if canDraw is false when does needsDraw get reset? - childComponent._drawIfNeeded(level+1); + childComponent._drawIfNeeded(level + 1); } else if (drawLogger.isDebug) { drawLogger.debug(loggerToString(childComponent) + " can't draw."); } + + var componentsPendingBuildOut = childComponent._componentsPendingBuildOut; + + if (componentsPendingBuildOut) { + while (componentsPendingBuildOut.length) { + componentsPendingBuildOut.pop()._shouldBuildOut = true; + } + } } this._disposeArray(oldDrawList); } @@ -2465,13 +2496,16 @@ var Component = exports.Component = Target.specialize(/** @lends Component.proto _performDomContentChanges: { value: function () { var contents = this._newDomContent, - element, - elementToAppend, - i; + componentsToAdd, component, referenceNode, + element, elementToAppend, i; if (contents || this._shouldClearDomContentOnNextDraw) { element = this._element; + if (typeof this.contentWillChange === "function") { + this.contentWillChange(contents); + } + // Setting the innerHTML to clear the children will not work on // IE because it modifies the underlying child nodes. Here's the // test case that shows this issue: http://jsfiddle.net/89X6F/ @@ -2481,19 +2515,52 @@ var Component = exports.Component = Target.specialize(/** @lends Component.proto } } - if (this._elementsToAppend) { + if (this._elementsToAppendBefore && this._elementsToAppendBefore.length) { + componentsToAdd = []; + referenceNode = element.firstElementChild; + + while (this._elementsToAppendBefore.length) { + elementToAppend = this._elementsToAppendBefore.shift(); + + if (!element.contains(elementToAppend)) { + this._findAndDetachComponents(elementToAppend, componentsToAdd); + + if (referenceNode) { + element.insertBefore(elementToAppend, referenceNode); + } else { + element.appendChild(elementToAppend); + } + } + } + + for (i = 0; (component = componentsToAdd[i]); i++) { + this.addChildComponent(component); + } + } + + if (this._elementsToAppend && this._elementsToAppend.length) { + componentsToAdd = []; + while (this._elementsToAppend.length) { elementToAppend = this._elementsToAppend.shift(); + if (!element.contains(elementToAppend)) { + this._findAndDetachComponents(elementToAppend, componentsToAdd); element.appendChild(elementToAppend); } } + + for (i = 0; (component = componentsToAdd[i]); i++) { + this.addChildComponent(component); + } } this._newDomContent = null; + if (typeof this.contentDidChange === "function") { this.contentDidChange(); } + this._shouldClearDomContentOnNextDraw = false; } } @@ -2587,6 +2654,50 @@ var Component = exports.Component = Target.specialize(/** @lends Component.proto } }, + _removeToParentsDrawList: { + enumerable: false, + value: function () { + if (this._addedToDrawList) { + var parentComponent = this._parentComponent; + + if (parentComponent) { + parentComponent._removeToDrawList(this); + } + } + } + }, + + __removeToDrawList: { + enumerable: false, + value: function (childComponent) { + var index; + + if ( + this._drawList && + (index = this._drawList.indexOf(childComponent)) > -1 + ) { + this._drawList.splice(index, 1); + childComponent._addedToDrawList = false; + } + } + }, + + /** + * Adds the passed in child component to the drawList + * If the current instance isn't added to the drawList of its parentComponent, then it adds itself. + * @private + */ + _removeToDrawList: { + enumerable: false, + value: function (childComponent) { + this.__removeToDrawList(childComponent); + + if (this._drawList && !this._drawList.length) { + this._removeToParentsDrawList(); + } + } + }, + _needsDraw: { value: false }, @@ -3586,11 +3697,21 @@ var Component = exports.Component = Target.specialize(/** @lends Component.proto false ); - if (typeof object === "object" && - (constructor = object.constructor) && - constructor.objectDescriptorModuleId - ) { - objectDescriptorModuleId = constructor.objectDescriptorModuleId; + if (object && typeof object === "object") { + if ( + (constructor = object.constructor) && + constructor.objectDescriptorModuleId + ) { + objectDescriptorModuleId = constructor.objectDescriptorModuleId; + } + + if (!objectDescriptorModuleId && Array.isArray(object) && + object.length && object[0] && typeof object[0] === "object" && + !Array.isArray(object[0]) && (constructor = object[0].constructor) && + constructor.objectDescriptorModuleId + ) { // Try with the first object of the array. + objectDescriptorModuleId = constructor.objectDescriptorModuleId; + } } objectDescriptorModuleIdCandidate = this.callDelegateMethod( diff --git a/ui/condiction.info/sample/index.html b/ui/condiction.info/sample/index.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ui/condition.info/index.html b/ui/condition.info/index.html deleted file mode 100644 index 4a6fa9f7ab..0000000000 --- a/ui/condition.info/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - condition.info - - - - - - - \ No newline at end of file diff --git a/ui/condition.info/package.json b/ui/condition.info/package.json deleted file mode 100644 index 08a0a62625..0000000000 --- a/ui/condition.info/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "condition.info", - "private": true, - "dependencies": { - "montage": "../.." - } -} \ No newline at end of file diff --git a/ui/condition.info/sample/index.html b/ui/condition.info/sample/index.html new file mode 100644 index 0000000000..3feec77724 --- /dev/null +++ b/ui/condition.info/sample/index.html @@ -0,0 +1,19 @@ + + + + + + Condition Sample + + + + + + + diff --git a/ui/condition.info/sample/package.json b/ui/condition.info/sample/package.json new file mode 100644 index 0000000000..442aef85d7 --- /dev/null +++ b/ui/condition.info/sample/package.json @@ -0,0 +1,11 @@ +{ + "name": "condition-sample", + "version": "0.1.0", + "private": true, + "dependencies": { + "montage": "*" + }, + "mappings": { + "montage": "../../../" + } +} diff --git a/ui/condition.info/sample/ui/main.reel/main.css b/ui/condition.info/sample/ui/main.reel/main.css new file mode 100644 index 0000000000..21372df317 --- /dev/null +++ b/ui/condition.info/sample/ui/main.reel/main.css @@ -0,0 +1,62 @@ +html, body, .Main { + padding: 0; + margin: 0; + height: 100%; + width: 100%; + font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.Main { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +header { + padding: 60px 0 20px 0; + font-size: 2rem; + text-align: center; + color: #33495d; + height: 40px; +} + +.content { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + font-size: 0; +} + +h4 { + font-size: 1rem; + text-align: center; + color: #33495d; + margin: 40px; +} + +span { + display: inline-block; + font-size: 1rem; + padding: 0 8px; +} + +.montage-invisible { + visibility: hidden; +} + +.Condition { + text-align: center; +} diff --git a/ui/condition.info/sample/ui/main.reel/main.html b/ui/condition.info/sample/ui/main.reel/main.html new file mode 100644 index 0000000000..d3c4ffb0e5 --- /dev/null +++ b/ui/condition.info/sample/ui/main.reel/main.html @@ -0,0 +1,73 @@ + + + + + + + +
+
Condition Sample
+
+

Condition 2: [removalStrategy:remove] [condition=true]

+
+ Hello Montage +
+

Condition 2: [removalStrategy:remove] [condition=false]

+
+ Hello Montage +
+

Condition 3: [removalStrategy:hide] [condition=true]

+
+ Hello Montage +
+

Condition 4: [removalStrategy:hide] [condition=false]

+
+ Hello Montage +
+
+
+ + diff --git a/ui/condition.info/sample/ui/main.reel/main.js b/ui/condition.info/sample/ui/main.reel/main.js new file mode 100644 index 0000000000..18c7c32c6a --- /dev/null +++ b/ui/condition.info/sample/ui/main.reel/main.js @@ -0,0 +1,16 @@ +var Component = require("montage/ui/component").Component, + Promise = require('montage/core/promise').Promise; + +exports.Main = Component.specialize(/** @lends Main# */{ + + enterDocument: { + value: function () { + this.content = []; + + for (var i = 0; i < 10; i++) { + this.content.push('item ' + (i + 1)); + } + } + } + +}); diff --git a/ui/condition.reel/condition.js b/ui/condition.reel/condition.js index b6bf0639d1..3cf53eb56f 100644 --- a/ui/condition.reel/condition.js +++ b/ui/condition.reel/condition.js @@ -29,25 +29,17 @@ exports.Condition = Component.specialize( /** @lends Condition.prototype # */ { value: true }, - _contents: { + __content: { value: null }, - _needsClearDomContent: { - value: false - }, - - __contentDocumentFragment: { - value: null - }, - - _contentDocumentFragment: { + _content: { get: function () { - if (!this.__contentDocumentFragment && this.element) { - this.__contentDocumentFragment = document.createDocumentFragment(); + if (!this.__content && this.element) { + this.__content = Array.from(this.element.childNodes); } - return this.__contentDocumentFragment; + return this.__content; } }, @@ -61,22 +53,24 @@ exports.Condition = Component.specialize( /** @lends Condition.prototype # */ { */ condition: { set: function (value) { - + value = !!value; + if (value === this._condition) { return; } this._condition = value; - this.needsDraw = true; + // If it is being deserialized element might not been set yet if (!this.isDeserializing && this.removalStrategy === "remove") { if (value) { - this.domContent = this._contentDocumentFragment.childNodes; - + this.domContent = this._content; } else { - this._needsDraw = this._needsClearDomContent = true; + this._clearDomContent(); } } + + this.needsDraw = true; }, get: function () { return this._condition; @@ -85,19 +79,13 @@ exports.Condition = Component.specialize( /** @lends Condition.prototype # */ { _clearDomContent: { value: function () { - if (this.removalStrategy === "remove" && !this._condition) { - var childNodes = this.element.childNodes; - - while (childNodes.length) { - this._contentDocumentFragment.appendChild(childNodes[0]); - } - + if ( + this.removalStrategy === "remove" && + !this._condition && + this._content + ) { this.domContent = null; - this._shouldClearDomContentOnNextDraw = false; - this.needsDraw = false; } - - this._needsClearDomContent = false; } }, @@ -161,10 +149,6 @@ exports.Condition = Component.specialize( /** @lends Condition.prototype # */ { } else { this.element.classList.add("montage-invisible"); } - - if (this._needsClearDomContent) { - this._clearDomContent(); - } } } diff --git a/ui/list-item.info/sample/ui/main.reel/main.html b/ui/list-item.info/sample/ui/main.reel/main.html index a4e745cf30..784e33f0b6 100644 --- a/ui/list-item.info/sample/ui/main.reel/main.html +++ b/ui/list-item.info/sample/ui/main.reel/main.html @@ -352,6 +352,15 @@ "data": {"<-": "@owner.check"}, "delegate": {"=": "@owner"} } + }, + + "listItem41": { + "prototype": "montage/ui/list-item.reel", + "values": { + "element": {"#": "listItem41"}, + "label": "Item 28", + "showHandle": true + } } } @@ -388,6 +397,7 @@

list items

+

list items with object descriptor

diff --git a/ui/list-item.reel/list-item.css b/ui/list-item.reel/list-item.css index e8d69aea30..ac567c3699 100755 --- a/ui/list-item.reel/list-item.css +++ b/ui/list-item.reel/list-item.css @@ -59,6 +59,24 @@ margin-right: 35px; } +.ListItem.show-handle .ListItem-handle { + padding-top: 5px; +} + +.ListItem.show-handle .ListItem-handle:hover { + cursor: crosshair; + cursor: -webkit-grab; +} + +.ListItem.show-handle .ListItem-handle span { + display: block; + width: 18px; + height: 1px; + margin-bottom: 4px; + position: relative; + background: #C7C7CC; +} + /** Icon **/ .ListItem .ListItem-icon > * { diff --git a/ui/list-item.reel/list-item.html b/ui/list-item.reel/list-item.html index aa40439655..88132daf00 100755 --- a/ui/list-item.reel/list-item.html +++ b/ui/list-item.reel/list-item.html @@ -14,6 +14,7 @@ "classList.has('active')": {"<-": "@owner.active"}, "classList.has('selected')": {"<-": "@owner.selected"}, "classList.has('is-expandable')": {"<-": "@owner._isExpandable"}, + "classList.has('show-handle')": {"<-": "@owner.showHandle"}, "classList.has('description-bottom')": {"<-": "@owner._descriptionPosition == 'bottom'"} } }, @@ -57,6 +58,11 @@
+
+ + + +
diff --git a/ui/list-item.reel/list-item.js b/ui/list-item.reel/list-item.js index 24ab2579ad..784f0df346 100755 --- a/ui/list-item.reel/list-item.js +++ b/ui/list-item.reel/list-item.js @@ -62,7 +62,8 @@ exports.ListItem = Component.specialize({ "isExpandable) : isExpandable" } }); - //FIXME: not safe! + // FIXME: not safe! + // https://github.com/montagejs/montage/issues/1977 this._templateDidLoad = true; this._loadDataUserInterfaceDescriptorIfNeeded(); } @@ -163,6 +164,10 @@ exports.ListItem = Component.specialize({ userInterfaceDescriptor: { value: null }, + + showHandle: { + value: false + }, __pressComposer: { value: null diff --git a/ui/list.info/sample/ui/main.reel/main.html b/ui/list.info/sample/ui/main.reel/main.html index fc533daed5..8f516bc48f 100644 --- a/ui/list.info/sample/ui/main.reel/main.html +++ b/ui/list.info/sample/ui/main.reel/main.html @@ -31,12 +31,20 @@ } }, - "list1": { + "isReorderable": { + "prototype": "montage/ui/checkbox.reel", + "values": { + "element": {"#": "isReorderable"} + } + }, + + "list1": { "prototype": "montage/ui/list.reel", "values": { "element": {"#": "list1"}, "data": {"<-": "@owner.strings"}, "allowsMultipleSelection": {"<-": "!!@allowMultipeSelection.checked"}, + "enableReorderModeAfterLongPress": {"<-": "!!@isReorderable.checked"}, "isSelectionEnabled": {"<-": "!!@isSelectionEnabled.checked"}, "isExpandable": {"<-": "!!@isExpandable.checked"} } @@ -48,6 +56,7 @@ "element": {"#": "list2"}, "data": {"<-": "@owner.employees"}, "allowsMultipleSelection": {"<-": "!!@allowMultipeSelection.checked"}, + "enableReorderModeAfterLongPress": {"<-": "!!@isReorderable.checked"}, "isSelectionEnabled": {"<-": "!!@isSelectionEnabled.checked"}, "isExpandable": {"<-": "!!@isExpandable.checked"} } @@ -69,6 +78,7 @@ "element": {"#": "list3"}, "data": {"<-": "@owner.employees"}, "allowsMultipleSelection": {"<-": "!!@allowMultipeSelection.checked"}, + "enableReorderModeAfterLongPress": {"<-": "!!@isReorderable.checked"}, "isSelectionEnabled": {"<-": "!!@isSelectionEnabled.checked"}, "isExpandable": {"<-": "!!@isExpandable.checked"} } @@ -106,6 +116,10 @@

Options:

Allow Navigation:

+

+ Reorder after long press: + +

List with default list item

diff --git a/ui/list.reel/list.html b/ui/list.reel/list.html index 10d88621c5..bb7af84cd4 100755 --- a/ui/list.reel/list.html +++ b/ui/list.reel/list.html @@ -8,6 +8,7 @@ "owner": { "values": { "element": {"#": "list"}, + "repetition": {"@": "repetition"}, "selection": {"<->": "@repetition.selection"}, "montageListItemModule": { "%": "../list-item.reel"} } @@ -17,8 +18,9 @@ "prototype": "../repetition.reel", "values": { "element": {"#": "repetition"}, - "isSelectionEnabled": {"<-": "@owner.isSelectionEnabled"}, - "allowsMultipleSelection": {"<-": "@owner.allowsMultipleSelection"}, + "isSelectionEnabled": {"<-": "@owner._isSelectionEnabled"}, + "ignoreSelectionAfterLongPress": {"<-": "@owner._ignoreSelectionAfterLongPress"}, + "allowsMultipleSelection": {"<-": "@owner._allowsMultipleSelection"}, "content": {"<-": "@owner.data"} } }, @@ -36,10 +38,11 @@ "component.list": {"<-": "@owner"}, "component.delegate": {"<-": "@owner.delegate"}, "component.rowIndex": {"<-": "@repetition:iteration.index"}, - "component.isExpandable": {"<-": "@owner.isExpandable"}, + "component.isExpandable": {"<-": "@owner._isExpandable"}, "component.userInterfaceDescriptor": {"<-": "@owner.userInterfaceDescriptor"}, "component.selected": {"<-": "@repetition:iteration.selected"}, "component.active": {"<-": "@repetition:iteration.active"}, + "component.showHandle": {"<-": "@owner.isReorderModeEnabled"}, "component.classList.has('ListItem')": {"<-": "true"} } } diff --git a/ui/list.reel/list.js b/ui/list.reel/list.js index 920d6cb166..313f14bdec 100755 --- a/ui/list.reel/list.js +++ b/ui/list.reel/list.js @@ -2,53 +2,71 @@ var Component = require("../component").Component; exports.List = Component.specialize({ + _templateDidLoad: { + value: false + }, + templateDidLoad: { value: function () { - this.isExpandable = this.callDelegateMethod( - "shouldListBeExpandable", - this, - this.isExpandable, - this.data - ) || this.isExpandable; - - this.isSelectionEnabled = this.callDelegateMethod( - "shouldListEnableSelection", - this, - this.isSelectionEnabled, - this.data - ) || this.isSelectionEnabled; - - this.allowsMultipleSelection = this.callDelegateMethod( - "shouldListAllowMultipleSelectionn", - this, - this.allowsMultipleSelection, - this.data - ) || this.allowsMultipleSelection; + this._definesBindings(); + + // FIXME: not safe! + // https://github.com/montagejs/montage/issues/1977 + this._templateDidLoad = true; + this._loadDataUserInterfaceDescriptorIfNeeded(); + } + }, + + enterDocument: { + value: function () { + this.repetition._pressComposer.addEventListener("longPress", this); } }, + exitDocument: { + value: function () { + this.repetition._pressComposer.removeEventListener("longPress", this); + } + }, + + _data: { + value: null + }, + /** * Description TODO * @public */ - userInterfaceDescriptor: { - value: null + data: { + get: function () { + return this._data; + }, + set: function (data) { + if (this._data !== data) { + this._data = data; + this._loadDataUserInterfaceDescriptorIfNeeded(); + } + } + }, + + _isSelectionEnabled: { + value: false }, /** * Description TODO * @public */ - data: { - value: null + ignoreSelectionAfterLongPress: { + value: false }, /** * Description TODO * @public */ - isNavigationEnabled: { - value: false + userInterfaceDescriptor: { + value: null }, /** @@ -73,6 +91,132 @@ exports.List = Component.specialize({ */ delegate: { value: null + }, + + dispatchLongPress: { + value: false + }, + + handleLongPress: { + value: function (event) { + if (this.enableReorderModeAfterLongPress) { + this.isReorderModeEnabled = !this.isReorderModeEnabled; + } + + if (this._dispatchLongPress) { + var iteration = this.repetition._findIterationContainingElement(event.targetElement); + + if (iteration) { + this.dispatchEventNamed( + "listIterationLongPress", + true, + true, + iteration + ); + } + } + } + }, + + isReorderModeEnabled: { + value: false + }, + + _enableReorderModeAfterLongPress: { + value: false + }, + + enableReorderModeAfterLongPress: { + set: function (enableReorderModeAfterLongPress) { + enableReorderModeAfterLongPress = !!enableReorderModeAfterLongPress; + + if (this._enableReorderModeAfterLongPress !== enableReorderModeAfterLongPress) { + this._enableReorderModeAfterLongPress = enableReorderModeAfterLongPress; + this.repetition.ignoreSelectionAfterLongPress = enableReorderModeAfterLongPress; + } + }, + get: function () { + return this._enableReorderModeAfterLongPress; + } + }, + + _loadDataUserInterfaceDescriptorIfNeeded: { + value: function () { + if (this.data && this._templateDidLoad) { + var self = this; + + return this.loadUserInterfaceDescriptor(this.data).then(function (UIDescriptor) { + self.userInterfaceDescriptor = UIDescriptor || self.userInterfaceDescriptor; // trigger biddings. + + self._isExpandable = self.callDelegateMethod( + "shouldListBeExpandable", + self, + self._isExpandable, + self.data + ) || self._isExpandable; + + self._isSelectionEnabled = self.callDelegateMethod( + "shouldListEnableSelection", + self, + self._isSelectionEnabled, + self.data + ) || self._isSelectionEnabled; + + self._allowsMultipleSelection = self.callDelegateMethod( + "shouldListAllowMultipleSelection", + self, + self._allowsMultipleSelection, + self.data + ) || self._allowsMultipleSelection; + + self._ignoreSelectionAfterLongPress = self.callDelegateMethod( + "shouldListIgnoreSelectionAfterLongPress", + self, + self._ignoreSelectionAfterLongPress, + self.data + ) || self._ignoreSelectionAfterLongPress; + + self._dispatchLongPress = self.callDelegateMethod( + "shouldListDispatchLongPress", + self, + self._dispatchLongPress, + self.data + ) || self._dispatchLongPress; + }); + } + } + }, + + _definesBindings: { + value: function () { + this.defineBindings({ + "_ignoreSelectionAfterLongPress": { + "<-": "userInterfaceDescriptor.defined() ? " + + "(userInterfaceDescriptor.listIgnoreSelectionAfterLongPress || " + + "ignoreSelectionAfterLongPress) : ignoreSelectionAfterLongPress" + }, + "_isExpandable": { + "<-": "userInterfaceDescriptor.defined() ? " + + "(userInterfaceDescriptor.listIsExpandable || " + + "isExpandable) : isExpandable" + }, + "_isSelectionEnabled": { + "<-": "userInterfaceDescriptor.defined() ? " + + "(userInterfaceDescriptor.listIsSelectionEnabled ?? " + + "isSelectionEnabled) : isSelectionEnabled" + }, + "_allowsMultipleSelection": { + "<-": "userInterfaceDescriptor.defined() ? " + + "(userInterfaceDescriptor.listAllowsMultipleSelection || " + + "allowsMultipleSelection) : allowsMultipleSelection" + }, + "_dispatchLongPress": { + "<-": "userInterfaceDescriptor.defined() ? " + + "(userInterfaceDescriptor.listDispatchLongPress || " + + "dispatchLongPress) : dispatchLongPress" + } + }); + } } }); diff --git a/ui/repetition.reel/repetition.js b/ui/repetition.reel/repetition.js index debc101b2b..c6104bf87d 100644 --- a/ui/repetition.reel/repetition.js +++ b/ui/repetition.reel/repetition.js @@ -1879,6 +1879,10 @@ var Repetition = exports.Repetition = Component.specialize(/** @lends Repetition this._initialContentDrawn = true; } + if (!this.isSelectionEnabled && this.selection.length) { + this.selection.clear(); + } + // Synchronize iterations and _drawnIterations // Retract iterations that should no longer be visible @@ -2004,9 +2008,60 @@ var Repetition = exports.Repetition = Component.specialize(/** @lends Repetition if (selectionTracking) { this._enableSelectionTracking(); } else { - this.selection.clear(); this._disableSelectionTracking(); } + + if (!this.isDeserializing) { + this.needsDraw = true; + } + } + }, + + _ignoreSelectionAfterLongPress: { + value: false + }, + + ignoreSelectionAfterLongPress: { + set: function (ignoreSelectionAfterLongPress) { + ignoreSelectionAfterLongPress = !!ignoreSelectionAfterLongPress; + + if (this._ignoreSelectionAfterLongPress !== ignoreSelectionAfterLongPress) { + this._ignoreSelectionAfterLongPress = ignoreSelectionAfterLongPress; + + if (!this.listenToLongPress && ignoreSelectionAfterLongPress) { + this.listenToLongPress = true; + } + } + }, + get: function () { + return this._ignoreSelectionAfterLongPress; + } + }, + + _listenToLongPress: { + value: false + }, + + listenToLongPress: { + set: function (listenToLongPress) { + listenToLongPress = !!listenToLongPress; + + if (this._listenToLongPress !== listenToLongPress) { + this._listenToLongPress = listenToLongPress; + + if (listenToLongPress) { + this._pressComposer.addEventListener( + "longPress", this, false + ); + } else if (!this.ignoreSelectionAfterLongPress) { + this._pressComposer.removeEventListener( + "longPress", this, false + ); + } + } + }, + get: function () { + return this._listenToLongPress; } }, @@ -2018,6 +2073,10 @@ var Repetition = exports.Repetition = Component.specialize(/** @lends Repetition _enableSelectionTracking: { value: function () { this._pressComposer.addEventListener("pressStart", this, false); + + if (this.listenToLongPress) { + this._pressComposer.addEventListener("longPress", this, false); + } } }, @@ -2029,6 +2088,10 @@ var Repetition = exports.Repetition = Component.specialize(/** @lends Repetition _disableSelectionTracking: { value: function () { this._pressComposer.removeEventListener("pressStart", this, false); + + if (this.listenToLongPress) { + this._pressComposer.removeEventListener("longPress", this, false); + } } }, @@ -2049,6 +2112,14 @@ var Repetition = exports.Repetition = Component.specialize(/** @lends Repetition } }, + handleLongPress: { + value: function () { + if (this.ignoreSelectionAfterLongPress) { + this._ignoreSelection(); + this.selection.clear(); + } + } + }, /** * @private diff --git a/ui/substitution.reel/substitution.js b/ui/substitution.reel/substitution.js index 5b0def25ef..46c1ddd470 100644 --- a/ui/substitution.reel/substitution.js +++ b/ui/substitution.reel/substitution.js @@ -137,10 +137,6 @@ exports.Substitution = Slot.specialize( /** @lends Substitution.prototype # */ { } }, - _drawnSwitchValue: { - value: null - }, - _switchValue: { value: null }, @@ -193,6 +189,7 @@ exports.Substitution = Slot.specialize( /** @lends Substitution.prototype # */ { // In the future the DrawManager will handle adding and // removing nodes from the DOM at any time before draw(). this._updateComponentDom(); + this.addEventListener("firstDraw", this); } } }, @@ -208,19 +205,6 @@ exports.Substitution = Slot.specialize( /** @lends Substitution.prototype # */ { } }, - contentDidChange: { - value: function (newContent, oldContent) { - Slot.prototype.contentDidChange.call(this, newContent, oldContent); - - if (this._drawnSwitchValue) { - if (this._switchComponents[this._drawnSwitchValue]) { - this._switchElements[this._drawnSwitchValue] = this._switchComponents[this._drawnSwitchValue].element; - } - } - this._drawnSwitchValue = this._switchValue; - } - }, - _loadSwitchComponentTree: { value: function (value) { var self = this, @@ -258,7 +242,7 @@ exports.Substitution = Slot.specialize( /** @lends Substitution.prototype # */ { if (promises.length > 0) { canDrawGate.setField(value + "ComponentTreeLoaded", false); - Promise.all(promises).then(function () { + Promise.all(promises).then(function (components) { self._switchComponentTreeLoaded[value] = true; canDrawGate.setField(value + "ComponentTreeLoaded", true); self._canDraw = true; @@ -336,5 +320,22 @@ exports.Substitution = Slot.specialize( /** @lends Substitution.prototype # */ { transition: { value: null + }, + + handleFirstDraw: { + value: function (event) { + if ( + this._allChildComponents.indexOf(event.target) > -1 && + this._switchValue && + this._switchComponents[this._switchValue] + ) { + this._switchElements[this._switchValue] = + this._switchComponents[this._switchValue].element; + + if (this._content !== this._switchElements[this._switchValue]) { + this.content = this._switchElements[this._switchValue]; + } + } + } } }); diff --git a/ui/succession.info/sample/ui/main.reel/main.js b/ui/succession.info/sample/ui/main.reel/main.js index 30c38c30a0..948f20704a 100644 --- a/ui/succession.info/sample/ui/main.reel/main.js +++ b/ui/succession.info/sample/ui/main.reel/main.js @@ -165,7 +165,7 @@ exports.Main = Component.specialize({ handlePushx5Action: { value: function () { - var numberOfPushes = 10, + var numberOfPushes = 5, self = this; var pushInterval = setInterval(function () { @@ -176,7 +176,7 @@ exports.Main = Component.specialize({ if (numberOfPushes === 0) { clearInterval(pushInterval); } - }, 50); + }, 750); } }, diff --git a/ui/succession.reel/succession.js b/ui/succession.reel/succession.js index 57e28368f7..c3406ecf03 100644 --- a/ui/succession.reel/succession.js +++ b/ui/succession.reel/succession.js @@ -1,7 +1,7 @@ /** * @module "montage/ui/succession.reel" */ -var Component = require("ui/component").Component; +var Component = require("../component").Component; /** * Subclasses Component for its `domContent` behavior. @@ -103,6 +103,30 @@ exports.Succession = Component.specialize(/** @lends Succession.prototype */{ } }, + _isFlat: { + value: false + }, + + isFlat: { + get: function () { + return this._isFlat; + }, + set: function (isFlat) { + isFlat = !!isFlat; + + if (this._isFlat !== isFlat) { + this._isFlat = isFlat; + + if (!this.isDeserializing) { + this._updateDomContentWith( + isFlat ? + this.history : this.history[this.history.length - 1] + ); + } + } + } + }, + /** * Ensure components generated by instantiating in JavaScript instead of * declaring in template serialization has an element. @@ -113,19 +137,53 @@ exports.Succession = Component.specialize(/** @lends Succession.prototype */{ */ _updateDomContentWith: { value: function (content) { - if (content) { - var element; - if (!content.element) { - element = document.createElement("div"); - element.id = content.identifier || "appendDiv"; - content.element = element; + var element; + + if (!this.isFlat) { + if (content) { + if (!content.element) { + element = document.createElement("div"); + element.id = content.identifier || "appendDiv"; + content.element = element; + } else { + element = content.element; + } + this.domContent = element; + content.needsDraw = true; } else { - element = content.element; + this.domContent = null; } - this.domContent = element; - content.needsDraw = true; } else { - this.domContent = null; + if (content) { + var isArray = Array.isArray(content), + domContent = [], i, index, component; + + if (isArray) { + for (i = 0; i < content.length; i++) { + component = content[i]; + + if (!component.element) { + element = document.createElement("div"); + element.id = content.identifier; + content.element = element; + } + } + } else { + if (!content.element) { + element = document.createElement("div"); + element.id = content.identifier; + content.element = element; + } + } + + for (i = 0; i < this.history.length; i++) { + domContent.push(this.history[i].element); + } + + this.domContent = domContent; + } else { + this.domContent = null; + } } } }, @@ -155,20 +213,29 @@ exports.Succession = Component.specialize(/** @lends Succession.prototype */{ var length = this.history ? this.history.length : 0, isChanged = plus.length || minus.length, isChangeVisible = isChanged && index + plus.length === length, - isPush = isChangeVisible && !minus.length && index, + isPush = isChangeVisible && !minus.length && index >= 0, isPop = isChangeVisible && !plus.length && length, isReplace = isChangeVisible && !isPush && !isPop && length, isClear = isChangeVisible && !length; - // Set appropriate classes and update the succession if necessary. - if (isChangeVisible) { - this.classList[isPush ? "add" : "remove"]("montage-Succession--push"); - this.classList[isPop ? "add" : "remove"]("montage-Succession--pop"); - this.classList[isReplace ? "add" : "remove"]("montage-Succession--replace"); - this.classList[isClear ? "add" : "remove"]("montage-Succession--clear"); - this._prepareForBuild(this.content); - this.dispatchBeforeOwnPropertyChange("content", this.content); - this._updateDomContentWith(this.content); - this.dispatchOwnPropertyChange("content", this.content); + + if (!this.isFlat) { + // Set appropriate classes and update the succession if necessary. + if (isChangeVisible) { + this.classList[isPush ? "add" : "remove"]("montage-Succession--push"); + this.classList[isPop ? "add" : "remove"]("montage-Succession--pop"); + this.classList[isReplace ? "add" : "remove"]("montage-Succession--replace"); + this.classList[isClear ? "add" : "remove"]("montage-Succession--clear"); + this._prepareForBuild(this.content); + this.dispatchBeforeOwnPropertyChange("content", this.content); + this._updateDomContentWith(this.content); + this.dispatchOwnPropertyChange("content", this.content); + } + } else { + if (isChangeVisible) { + this.dispatchBeforeOwnPropertyChange("content", this.content); + this._updateDomContentWith(!!isPush ? plus : minus); + this.dispatchOwnPropertyChange("content", this.content); + } } } }, diff --git a/ui/virtual-list.reel/virtual-list.html b/ui/virtual-list.reel/virtual-list.html index 7af8fb38ab..7c6603f505 100755 --- a/ui/virtual-list.reel/virtual-list.html +++ b/ui/virtual-list.reel/virtual-list.html @@ -8,6 +8,7 @@ "values": { "element": {"#": "virtual-list"}, "flow": {"@": "flow"}, + "repetition": {"<-": "@flow._repetition"}, "selection": {"<->": "@flow.selection"}, "montageListItemModule": { "%": "../list-item.reel"}, "_scrollBars": {"@": "scrollbars"},