Updated Branches: refs/heads/master f02a205eb -> 53e056052
Add events to Palette component so that client-side can choose to accept or veto a change to the selection Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/53e05605 Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/53e05605 Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/53e05605 Branch: refs/heads/master Commit: 53e056052e17d6e719b40815e5fd0be3a394ef0a Parents: f02a205 Author: Howard M. Lewis Ship <[email protected]> Authored: Mon Jun 10 17:38:29 2013 -0700 Committer: Howard M. Lewis Ship <[email protected]> Committed: Mon Jun 10 17:38:29 2013 -0700 ---------------------------------------------------------------------- 54_RELEASE_NOTES.txt | 7 + .../META-INF/modules/t5/core/events.coffee | 11 ++ .../META-INF/modules/t5/core/palette.coffee | 181 +++++++++++++------ .../tapestry5/corelib/components/Palette.java | 8 +- .../resources/META-INF/assets/core/Palette.css | 18 +- .../tapestry5/corelib/components/Palette.tml | 14 +- tapestry-core/src/test/app1/PaletteDemo.tml | 7 +- .../META-INF/modules/palette-demo.coffee | 5 + .../tapestry5/integration/app1/FormTests.java | 8 +- .../integration/app1/PaletteTests.java | 28 +-- .../integration/app1/pages/PaletteDemo.java | 4 +- 11 files changed, 197 insertions(+), 94 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/53e05605/54_RELEASE_NOTES.txt ---------------------------------------------------------------------- diff --git a/54_RELEASE_NOTES.txt b/54_RELEASE_NOTES.txt index f05b58c..28f7e40 100644 --- a/54_RELEASE_NOTES.txt +++ b/54_RELEASE_NOTES.txt @@ -268,3 +268,10 @@ org.apache.tapestry5.test.TapestryRunnerConstants in the new tapestry-runner mod ## AbstractValidator base class The constructor for AbstractValidator has changed to include an instance of JavaScriptSupport. + +## CSS Changes + +Where Tapestry-specific CSS still exists (in support of the Palette component and the Tree component), the "t-" prefix +has been removed. This may affect applications that overrode the Tapestry CSS rules to adapt Tapestry to the application +look and feel. + http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/53e05605/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/events.coffee ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/events.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/events.coffee index bc9ca33..4054520 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/events.coffee +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/events.coffee @@ -124,6 +124,17 @@ define # * If the field's container has class "input-append" or "input-prepend", then the block is inserted after the container showValidationError: "t5:field:show-validation-error" + # Events triggered by the Palette component. + palette: + # Event triggered when the selection is about to change. The memo object has these properties: + # * selectedValues - list of selected values (if change is allowed) + # * reorder - if true, then the event represents changing the ordrer of the selections only + # * cancel - function to invoke to prevent the change to the Palette from occurring + willChange: "t5:palette:willChange" + # Event triggered after the selection has changed. The memo object has one property: + # * selectedValues - list of selected values + didChange: "t5:palette:didChange" + # Defines a number of event names specific to Tapestry Zones. Zones are Tapestry components that are structured # to correctly support dynamic updates from the server via an Ajax request, and a standard response # (the partial page render reponse). More details are available in the `t5/core/zone` module. http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/53e05605/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/palette.coffee ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/palette.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/palette.coffee index b03163e..9f8cc4c 100644 --- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/palette.coffee +++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/palette.coffee @@ -1,4 +1,4 @@ -# Copyright 2012 The Apache Software Foundation +# Copyright 2012-2013 The Apache Software Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,14 +15,17 @@ # ## t5/core/palette # # Support for the `core/Palette` component. -define ["./dom", "underscore"], - (dom, _) -> +define ["./dom", "underscore", "./events"], + (dom, _, events) -> + + isSelected = (option) -> option.selected + class PaletteController constructor: (id) -> @selected = (dom id) - @container = @selected.findParent ".t-palette" - @available = @container.findFirst ".t-palette-available select" + @container = @selected.findParent ".palette" + @available = @container.findFirst ".palette-available select" @hidden = @container.findFirst "input[type=hidden]" @select = @container.findFirst "[data-action=select]" @@ -69,6 +72,8 @@ define ["./dom", "underscore"], for option in movers @selected.element.add option + # Invoked after any change to the selections list to update the hidden field as well as the + # buttons' state. updateAfterChange: -> @updateHidden() @updateButtons() @@ -107,6 +112,8 @@ define ["./dom", "underscore"], @doMoveDown() return false + # Invoked whenever the selections in either list changes or after an updates; figures out which buttons + # should be enabled and which disabled. updateButtons: -> @select.element.disabled = @available.element.selectedIndex < 0 @@ -123,78 +130,139 @@ define ["./dom", "underscore"], doDeselect: -> @transferOptions @selected, @available, false doMoveUp: -> - e = @selected.element - pos = e.selectedIndex - 1 - movers = @removeSelectedOptions @selected - before = e.options[if pos < 0 then 0 else pos] + options = _.toArray @selected.element.options + + movers = _.filter options, isSelected + + # The element before the first selected element is the pivot; all the selected elements will + # move before the pivot. If there is no pivot, the elements are shifted to the front of the list. + firstMoverIndex= _.first(movers).index + pivot = options[firstMoverIndex - 1] + + options = _.reject options, isSelected + + splicePos = if pivot then _.indexOf options, pivot else 0 + + movers.reverse() + + for o in movers + options.splice splicePos, 0, o + + @reorderSelected options - @reorderOptions movers, before doMoveDown: -> - e = @selected.element - lastSelected = _.chain(e.options).toArray().reverse().find((o) -> o.selected).value() + options = _.toArray @selected.element.options - lastPos = lastSelected.index - before = e.options[lastPos + 2] + movers = _.filter options, isSelected - movers = @removeSelectedOptions @selected + # The element after the last selected element is the pivot; all the selected elements will + # move after the pivot. If there is no pivot, the elements are shifted to the end of the list. + lastMoverIndex = _.last(movers).index + pivot = options[lastMoverIndex + 1] - @reorderOptions movers, before + options = _.reject options, isSelected - reorderOptions: (movers, before) -> - for mover in movers - @addOption @selected, mover, before - @updateAfterChange() + splicePos = if pivot then _.indexOf(options, pivot) + 1 else options.length + + movers.reverse() + + for o in movers + options.splice splicePos, 0, o + + @reorderSelected options + + # Reorders the selected options to the provided list of options; handles triggering the willUpdate and + # didUpdate events. + reorderSelected: (options) -> + + canceled = false + + memo = + selectedValues: _.pluck options, "value" + reorder: true + cancel: -> canceled = true + + @selected.trigger events.palette.willChange, memo + + unless canceled + @deleteOptions @selected + + for o in options + @selected.element.add o, null + + @selected.trigger events.palette.didChange, memo + + @updateAfterChange() + + # Deletes all options from a select (an ElementWrapper), prior to new options being populated in. + deleteOptions: (select) -> + + e = select.element + + for i in [(e.length - 1)..0] by -1 + e.remove i + + # Moves options between the available and selected lists, including event notifiations before and after. transferOptions: (from, to, atEnd) -> + if from.element.selectedIndex is -1 return - _(to.element.options).each (o) -> o.selected = false + # This could be done in a single pass, but: + movers = _.filter from.element.options, isSelected + fromOptions = _.reject from.element.options, isSelected - movers = @removeSelectedOptions from + toOptions = _.toArray to.element.options - @moveOptions movers, to, atEnd + for o in movers + @insertOption toOptions, o, atEnd - removeSelectedOptions: (select) -> - movers = [] - e = select.element - options = e.options + selectedOptions = if to is @selected then toOptions else fromOptions - for i in [(e.length - 1)..(e.selectedIndex)] by -1 - o = options[i] - if o.selected - e.remove i - movers.unshift o + canceled = false + + memo = + selectedValues: _.pluck selectedOptions, "value" + reorder: false + cancel: -> canceled = true + + @selected.trigger events.palette.willChange, memo + + return if canceled + + # Remove the movers (the selected from elements): + for i in [(from.element.length - 1)..0] by -1 + if from.element.options[i].selected + from.element.remove i + + # A bit ugly: update the to select by removing all, then adding back in. - return movers + for i in [(to.element.length - 1)..0] by -1 + to.element.options[i].selected = false + to.element.remove i - moveOptions: (movers, to, atEnd) -> - _.each movers, (o) => - @moveOption o, to, atEnd + for o in toOptions + to.element.add o, null + + @selected.trigger events.palette.didChange, memo @updateAfterChange() - moveOption: (option, to, atEnd) -> - before = null + + insertOption: (options, option, atEnd) -> unless atEnd optionOrder = @valueToOrderIndex[option.value] - candidate = _.find to.element.options, (o) => @valueToOrderIndex[o.value] > optionOrder - if candidate - before = candidate - - @addOption to, option, before - - addOption: (to, option, before) -> - try - to.element.add option, before - catch ex - if before is null - # IE throws an exception about type mismatch; here's the fix: - to.add option - else - to.add option, before.index + before = _.find options, (o) => @valueToOrderIndex[o.value] > optionOrder + + if before + i = _.indexOf options, before + options.splice i, 0, option + else + options.push option + indexOfLastSelection: (select) -> e = select.element @@ -221,8 +289,5 @@ define ["./dom", "underscore"], _(options[last..]).all (o) -> o.selected - initialize = (id) -> - new PaletteController(id) - - # Export just the initialize function - return initialize \ No newline at end of file + # Export just the initializer function + (id) -> new PaletteController(id) \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/53e05605/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Palette.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Palette.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Palette.java index d82dcad..5ea4fba 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Palette.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Palette.java @@ -1,4 +1,4 @@ -// Copyright 2007, 2008, 2009, 2010, 2011, 2012 The Apache Software Foundation +// Copyright 2007-2013 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -47,7 +47,7 @@ import java.util.Collection; * <p/> * <pre> * <style> - * DIV.t-palette SELECT { width: 300px; } + * DIV.palette SELECT { width: 300px; } * </style> * </pre> * <p/> @@ -59,6 +59,10 @@ import java.util.Collection; * <p/> * For an alternative component that can be used for similar purposes, see * {@link Checklist}. + * <p>Starting in 5.4, the selected parameter may be any kind of collection, but is typically a List if the Palette is configured for re-ordering, + * and a Set if order does not matter (though it is common to use a List in the latter case as well). Also, starting in 5.4, + * the Palette is compatible with the {@link org.apache.tapestry5.validator.Required} validator (on both client and server-side), and + * triggers new events that allows the application to veto a proposed changed to the selection (see the {@code t5/core/events} module). * * @tapestrydoc * @see Form http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/53e05605/tapestry-core/src/main/resources/META-INF/assets/core/Palette.css ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/resources/META-INF/assets/core/Palette.css b/tapestry-core/src/main/resources/META-INF/assets/core/Palette.css index b2aa921..fe8b62f 100644 --- a/tapestry-core/src/main/resources/META-INF/assets/core/Palette.css +++ b/tapestry-core/src/main/resources/META-INF/assets/core/Palette.css @@ -1,13 +1,13 @@ -DIV.t-palette { +DIV.palette { display: inline; } -DIV.t-palette SELECT { +DIV.palette SELECT { margin-bottom: 2px; width: 200px; } -DIV.t-palette-title { +DIV.palette-title { color: white; background-color: black; text-align: center; @@ -17,28 +17,28 @@ DIV.t-palette-title { width: 200px; } -DIV.t-palette-available { +DIV.palette-available { float: left; } -DIV.t-palette-controls { +DIV.palette-controls { margin: 5px 5px; float: left; } -DIV.t-palette-controls > DIV { +DIV.palette-controls > DIV { margin-top: 5px; } -DIV.t-palette-controls > DIV:first-child { +DIV.palette-controls > DIV:first-child { margin-top: 0; } -DIV.t-palette-selected { +DIV.palette-selected { float: left; clear: right; } -DIV.t-palette-spacer { +DIV.palette-spacer { clear: left; } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/53e05605/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/Palette.tml ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/Palette.tml b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/Palette.tml index 7512185..8b3b196 100644 --- a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/Palette.tml +++ b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/Palette.tml @@ -1,9 +1,9 @@ -<div class="t-palette" xml:space="default" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"> +<div class="palette" xml:space="default" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"> <t:remove>Contains the array of option ids for selected elements:</t:remove> <input type="hidden" name="${controlName}" value="${initialJSON}" disabled="${disabledValue}"/> - <div class="t-palette-available"> - <div class="t-palette-title"> + <div class="palette-available"> + <div class="palette-title"> <t:delegate to="availableLabel"/> </div> <select multiple="multiple" size="${size}" disabled="${disabledValue}"> @@ -15,7 +15,7 @@ </select> </div> - <div class="t-palette-controls"> + <div class="palette-controls"> <div> <button data-action="select" class="btn" disabled="${disabledValue}"> <img src="${select}" alt="${message:core-palette-select-label}"/> @@ -39,12 +39,12 @@ </div> </t:if> </div> - <div class="t-palette-selected"> - <div class="t-palette-title"> + <div class="palette-selected"> + <div class="palette-title"> <t:delegate to="selectedLabel"/> </div> <t:remove> - data-value=mode="options" is a bit of hack to inform the client side that the value for the field is all options, selected or not. + data-value=mode="options" is a bit of hack to inform the client side that the value for the field is all options, selected or not. This is used by validation logic. </t:remove> <select t:type="any" t:id="selected" id="${clientId}" multiple="multiple" size="${size}" disabled="${disabledValue}" data-value-mode="options" t:mixins="rendernotification"> <t:remove>Starts empty, populated on the client side.</t:remove> http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/53e05605/tapestry-core/src/test/app1/PaletteDemo.tml ---------------------------------------------------------------------- diff --git a/tapestry-core/src/test/app1/PaletteDemo.tml b/tapestry-core/src/test/app1/PaletteDemo.tml index 3023c3a..7e516ce 100644 --- a/tapestry-core/src/test/app1/PaletteDemo.tml +++ b/tapestry-core/src/test/app1/PaletteDemo.tml @@ -39,6 +39,11 @@ </t:form> -<p>Selected Languages: ${languages}</p> +<dl class="dl-horizontal"> + <dt>Languages:</dt> + <dd id="selected-languages">${languages}</dd> + <dt>Selected Values</dt> + <dd id="event-selection"/> +</dl> </html> http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/53e05605/tapestry-core/src/test/coffeescript/META-INF/modules/palette-demo.coffee ---------------------------------------------------------------------- diff --git a/tapestry-core/src/test/coffeescript/META-INF/modules/palette-demo.coffee b/tapestry-core/src/test/coffeescript/META-INF/modules/palette-demo.coffee new file mode 100644 index 0000000..226e062 --- /dev/null +++ b/tapestry-core/src/test/coffeescript/META-INF/modules/palette-demo.coffee @@ -0,0 +1,5 @@ +define ["t5/core/dom", "t5/core/events"], + (dom, events) -> + + dom.onDocument events.palette.willChange, (event, memo) -> + (dom "event-selection").update JSON.stringify memo.selectedValues \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/53e05605/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/FormTests.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/FormTests.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/FormTests.java index e8cf44d..24558d2 100644 --- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/FormTests.java +++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/FormTests.java @@ -471,13 +471,13 @@ public class FormTests extends TapestryCoreTestCase "//input[@id='datefield']", - "//div[@class='t-palette']//input[@type='hidden']", + "//div[@class='palette']//input[@type='hidden']", - "//div[@class='t-palette-available']//select", + "//div[@class='palette-available']//select", - "//div[@class='t-palette-selected']//select", + "//div[@class='palette-selected']//select", - "//div[@class='t-palette-controls']//button", + "//div[@class='palette-controls']//button", "//input[@id='submit_0']"}; http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/53e05605/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/PaletteTests.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/PaletteTests.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/PaletteTests.java index e9b0889..03d87fc 100644 --- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/PaletteTests.java +++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/PaletteTests.java @@ -1,4 +1,4 @@ -// Copyright 2009, 2011 The Apache Software Foundation +// Copyright 2009-2013 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,17 +24,17 @@ import org.testng.annotations.Test; public class PaletteTests extends TapestryCoreTestCase { - public static final String AVAILABLE_OPTIONS = "css=.t-palette-available select"; + public static final String AVAILABLE_OPTIONS = "css=.palette-available select"; - public static final String SELECTED_OPTIONS = "css=.t-palette-selected select"; + public static final String SELECTED_OPTIONS = "css=.palette-selected select"; - public static final String SELECT_BUTTON = "css=.t-palette [data-action=select]"; + public static final String SELECT_BUTTON = "css=.palette [data-action=select]"; - public static final String DESELECT_BUTTON = "css=.t-palette [data-action=deselect]"; + public static final String DESELECT_BUTTON = "css=.palette [data-action=deselect]"; - public static final String MOVE_UP_BUTTON = "css=.t-palette [data-action=move-up]"; + public static final String MOVE_UP_BUTTON = "css=.palette [data-action=move-up]"; - public static final String MOVE_DOWN_BUTTON = "css=.t-palette [data-action=move-down]"; + public static final String MOVE_DOWN_BUTTON = "css=.palette [data-action=move-down]"; @Test public void palette_component() @@ -43,18 +43,22 @@ public class PaletteTests extends TapestryCoreTestCase waitForPageInitialized(); - assertText("css=.t-palette-available .t-palette-title", + assertText("css=.palette-available .palette-title", "Languages Offered"); - assertText("css=.t-palette-selected .t-palette-title", + assertText("css=.palette-selected .palette-title", "Selected Languages"); addSelection(AVAILABLE_OPTIONS, "label=Haskell"); addSelection(AVAILABLE_OPTIONS, "label=Javascript"); click(SELECT_BUTTON); + // What a listener on the events.palette.willChange event would see in memo.selectdValues: + assertText("id=event-selection", "[\"HASKELL\",\"JAVASCRIPT\"]"); + + clickAndWait(SUBMIT); - assertTextPresent("Selected Languages: [HASKELL, JAVASCRIPT]"); + assertText("id=selected-languages", "[HASKELL, JAVASCRIPT]"); waitForPageInitialized(); @@ -75,7 +79,7 @@ public class PaletteTests extends TapestryCoreTestCase clickAndWait(SUBMIT); - assertTextPresent("[ERLANG, HASKELL, JAVA, LISP, ML, PERL, PYTHON, RUBY]"); + assertText("id=selected-languages", "[ERLANG, HASKELL, JAVA, LISP, ML, PERL, PYTHON, RUBY]"); check("reorder"); @@ -97,7 +101,7 @@ public class PaletteTests extends TapestryCoreTestCase clickAndWait(SUBMIT); - assertTextPresent("[ERLANG, RUBY, HASKELL, JAVA, LISP, ML, PYTHON, PERL]"); + assertText("id=selected-languages", "[ERLANG, RUBY, HASKELL, JAVA, LISP, ML, PYTHON, PERL]"); } /** http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/53e05605/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PaletteDemo.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PaletteDemo.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PaletteDemo.java index 8ed2e3f..2f92fc6 100644 --- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PaletteDemo.java +++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PaletteDemo.java @@ -1,4 +1,4 @@ -// Copyright 2007, 2012 The Apache Software Foundation +// Copyright 2007-2013 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.apache.tapestry5.integration.app1.pages; import org.apache.tapestry5.ComponentResources; import org.apache.tapestry5.SelectModel; import org.apache.tapestry5.ValueEncoder; +import org.apache.tapestry5.annotations.Import; import org.apache.tapestry5.annotations.Persist; import org.apache.tapestry5.annotations.Property; import org.apache.tapestry5.integration.app1.data.ProgrammingLanguage; @@ -28,6 +29,7 @@ import org.apache.tapestry5.util.EnumValueEncoder; import java.util.ArrayList; import java.util.List; +@Import(module="palette-demo") public class PaletteDemo { @Inject
