10 Replies Latest reply on Apr 18, 2007 9:40 AM by avbentem

    HOWTO: override ThemeSelector (valueChangeListener, cookie n

      Though in fact quite easy, it took me some time to get this working (for example as at some point I forgot the #${ .. } to surround the EL statement...) -- so a mini HOWTO:

      First of all: for the localeSelector auto-submit seems to be very easy:

      <h:form>
       <h:selectOneMenu
       value="#{localeSelector.localeString}"
       onchange="submit();"
       valueChangeListener="#{localeSelector.select}">
       <f:selectItems value="#{localeSelector.supportedLocales}" />
       </h:selectOneMenu>
      </h:form>


      However, I guess this works by accident; in fact org.jboss.seam.core.LocaleSelector#select() does not accept ValueChangeEvent as a parameter; so the above just works given the current implementation of select -- no guarantees for future releases. Any comment on that anyone?

      And: the above does not work for ThemeSelector, as ThemeSelector#select() apparently does not know of any new value. So, an example of an extended ThemeSelector to implement a value change handler:
      package my.package;
      import static org.jboss.seam.InterceptionType.NEVER;
      import static org.jboss.seam.annotations.Install.APPLICATION;
      import javax.faces.event.ValueChangeEvent;
      import org.jboss.seam.ScopeType;
      import org.jboss.seam.annotations.Install;
      import org.jboss.seam.annotations.Intercept;
      import org.jboss.seam.annotations.Name;
      import org.jboss.seam.annotations.Scope;
      import org.jboss.seam.theme.ThemeSelector;
      
      // Use the same @Name as the built-in selector
      @Name("org.jboss.seam.theme.themeSelector")
      @Scope(ScopeType.SESSION)
      @Intercept(NEVER)
      @Install(precedence = APPLICATION)
      public class ThemeOnChangeSelector extends ThemeSelector {
      
       private String cookieName = "theme";
      
       // When overridden then this should NOT have @Create defined
       // again; it will inherit it.
       @Override
       public void initDefaultTheme() {
       // whatever, if needed.
       super.initDefaultTheme();
       }
      
       @Override
       public String getCookieName() {
       return cookieName;
       }
      
       public void setCookieName(String cookieName) {
       this.cookieName = cookieName;
       }
      
       public void valueChanged(ValueChangeEvent event) {
       setTheme((String) event.getNewValue());
       select();
       }
      }


      Having used the very same value for @Name above, the logs will show:

      org.jboss.seam.init.Initialization
      two components with same name, higher precedence wins: org.jboss.seam.theme.themeSelector


      And, having used the very same value for @Name above, one can use the very same setup in components.xml:

      <?xml version="1.0" encoding="UTF-8"?>
      <components xmlns="http://jboss.com/products/seam/components"
       xmlns:theme="http://jboss.com/products/seam/theme"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://jboss.com/products/seam/components http://jboss.com/products/seam/components-1.1.xsd
       http://jboss.com/products/seam/theme http://jboss.com/products/seam/theme-1.1.xsd">
      
       <theme:theme-selector cookie-enabled="true">
       <theme:available-themes>
       <value>default</value>
       <value>accessible</value>
       <value>printable</value>
       </theme:available-themes>
       </theme:theme-selector>
      </components>

      ...or, when one wants to use the additional setter for a different cookie name, then one cannot use the existing namespace, so:
      <component name="org.jboss.seam.theme.themeSelector">
       <property name="availableThemes">
       <value>default</value>
       <value>accessible</value>
       <value>printable</value>
       </property>
       <property name="cookieEnabled">true</property>
       <property name="cookieName">skin</property>
      </component>


      Now, the following works, still referring to the default #{themeSelector}:
      <h:form>
       <h:selectOneMenu
       immediate="true"
       value="#{themeSelector.theme}"
       onchange="submit();"
       valueChangeListener="#{themeSelector.valueChanged}">
       <f:selectItems value="#{themeSelector.themes}" />
       </h:selectOneMenu>
      
       <h:selectOneMenu
       immediate="true"
       value="#{localeSelector.localeString}"
       onchange="submit();"
       valueChangeListener="#{localeSelector.select}">
       <f:selectItems value="#{localeSelector.supportedLocales}" />
       </h:selectOneMenu>
      </h:form>


      Note that the immediate="true" are not actually required when these dropdowns are the only elements within the form. And note that any other form contents might be lost when the user selects another theme or language. See also: http://wiki.apache.org/myfaces/SubmitPageOnValueChange

      In the pages, no changes are required:
      <link href="#{theme.css}" rel="stylesheet" type="text/css" />

      and
      template="#{theme.template}"


      Finally, when using both themes and localization then add the translations to, for example, messages_nl.properties:
      org.jboss.seam.theme.default=Standaard
      org.jboss.seam.theme.accessible=Groot
      org.jboss.seam.theme.printable=Afdrukken


      Enjoy,
      Arjan.

        • 1. Re: HOWTO: override ThemeSelector (valueChangeListener, cook
          pmuir

          Can you put this on the wiki as a new page and link it off the main Seam page? Thanks

          • 2. Re: HOWTO: override ThemeSelector (valueChangeListener, cook

             

            "petemuir" wrote:
            Can you put this on the wiki

            Yes, but awaiting thoughts on why valueChangeListener works in this situation:
            <h:form>
             <h:selectOneMenu
             value="#{localeSelector.localeString}"
             onchange="submit();"
             valueChangeListener="#{localeSelector.select}">
             <f:selectItems value="#{localeSelector.supportedLocales}" />
             </h:selectOneMenu>
            </h:form>


            • 3. Re: HOWTO: override ThemeSelector (valueChangeListener, cook

              And when overriding the ThemeSelector, why not add a method such as LocaleSelector#selectLanguage(String)

              /**
               * Set the theme and force it to load, useful for quick action links: <br>
               * <code>
               * <h:commandLink value="#{messages['org.jboss.seam.theme.default']}"
               * action="#{themeSelector.selectTheme('default')}"/>
               * </code>
               *
               * @param theme the name of the theme to be loaded
               */
              public void selectTheme(String theme) {
               setTheme(theme);
               select();
              }


              • 4. Re: HOWTO: override ThemeSelector (valueChangeListener, cook


                I've submitted a feature request for the above functionality: http://jira.jboss.org/jira/browse/JBSEAM-1124

                Arjan.

                • 5. Re: HOWTO: override ThemeSelector (valueChangeListener, cook
                  gavin.king

                  I agree that this functionality should be added to Seam.

                  • 6. Re: HOWTO: override ThemeSelector (valueChangeListener, cook
                    gavin.king

                    done in cvs

                    • 7. Re: HOWTO: override ThemeSelector (valueChangeListener, cook

                       

                      "gavin.king@jboss.com" wrote:
                      done in cvs

                      Errr, wow, that was fast :-)

                      I noticed that you've overloaded ThemeSelector#select(..) and LocaleSelector#select(..). This might very well work, but when I used my own component (that extended ThemeSelector) then I could not get it to work when using the same method name. I do not recall the details but maybe I have some notes about that in the office. But I am not a JavaServer Faces expert, so maybe I was just messing up within the EL within the HTML.

                      I'll download and test at some later time. By the way: I wouldn't mind having the Javadoc included as well. That was how I learned about LocaleSelector#selectLanguage to start with!

                      Thanks,
                      Arjan.

                      • 8. Re: HOWTO: override ThemeSelector (valueChangeListener, cook

                        ...and you also changed TimeZoneSelector that I didn't even know about. Great!

                        Arjan.

                        • 9. Re: HOWTO: override ThemeSelector (valueChangeListener, cook
                          gavin.king

                           

                          I'll download and test at some later time


                          Yes please, I don't see why overloading should be a problem, but if it is, please let me know.

                          • 10. Re: HOWTO: override ThemeSelector (valueChangeListener, cook

                            I tested with yesterday's CVS head, and it seems to work fine.

                            Please note that the current LocaleSelector#setLocale(Locale) and setLocaleString(String) do not call the setter methods, which invoke isDirty. I can't find any problems with this, but don't know if the isDirty is important here.

                            No problems at all using the new ThemeSelector#selectTheme(String).

                            As for the auto-submit:

                            First, for the archives, taken from MyFaces wiki: note that for checkboxes and radio buttons you will want to use onclick instead of onchange as IE6 fires the onchange during 'blur' instead of the actual change of these input controls whereas onclick is fired when the value is actually changed for all browsers.

                            Secondly:

                            "gavin.king@jboss.com" wrote:
                            I don't see why overloading should be a problem, but if it is, please let me know.

                            I guess I got confused due to (for testing purposes) using BOTH <h:commandButton> and onchange="submit()" within the very same <h:form>. So, I had two dropdowns, one with the button and another with the onchange handler:

                            <h:form ...>
                             <span class="menuItem">
                             <h:selectOneMenu value="#{themeSelector.theme}">
                             <f:selectItems value="#{themeSelector.themes}"/>
                             </h:selectOneMenu>
                             <h:commandButton action="#{themeSelector.select}"
                             value="Select Theme"/>
                             </span>
                             <span class="menuItem">
                             <h:selectOneMenu value="#{themeSelector.theme}"
                             onchange="submit();"
                             valueChangeListener="#{themeSelector.select}">
                             <f:selectItems value="#{themeSelector.themes}" />
                             </h:selectOneMenu>
                             </span>
                            </h:form>

                            which yields:
                            <form id="j_id3" name="j_id3" method="post" ...>
                             <span class="menuItem">
                             <select name="j_id3:j_id8" size="1">
                             <option value="default" selected="selected">Default</option>
                             <option value="accessible">Accessible</option>
                             <option value="printable">Print</option>
                             </select>
                             <input type="submit" name="j_id3:j_id10" value="Select Theme" />
                             </span>
                             <span class="menuItem">
                             <select name="j_id3:j_id12" size="1" onchange="submit();">
                             <option value="default" selected="selected">Default</option>
                             <option value="accessible">Accessible</option>
                             <option value="printable">Print</option>
                             </select>
                             </span>
                            </form>


                            Seeing no Javascript above to somehow register which input the user touched last, I guess using two dropdowns (or three, ..., within the same form) to achieve the very same thing is not supposed to be supported (probably the reason why Apache Trinidad supports autoSubmit). Ok, no problem, ready.

                            But if it is supposed to be supported, then I can investigate further. I surely am no JSF expert though. First impression, using Firefox: JSF seems to know which method to invoke, probably because Firefox does not send the button name when using submit() to post the form. However, it sometimes seems to use the wrong value (like the value from another dropdown) to determine what the user has selected. In some quick tests I can simultaneously use both the <h:commandButton> and onchange="submit()" for ThemeSelector, but only if the onchange-version is the first. This also applies to the LocaleSelector, but then the dropdown would still show the previous language as being the selected choice, even though the page would be rendered using the new language. When defining the onchange-version as the second dropdown, then the version with the submit button no longer has any effect.

                            Changing the method names makes no difference. Also the value for immediate does not seem to change the behavior.

                            I also noticed that, when changing the theme, LocaleSelector#setLocaleString is invoked as well, even twice. But when changing the locale, ThemeSelector does not get called. If that rings any alarm bells then please let me know and I can debug some more. But I guess it's not an issue at all. Obviously, only using a single dropdown in the form solves all issues.

                            Thanks,
                            Arjan.