XPages: Bootstrap File Input

When using the default file upload control in a Bootstrap application, the default file upload button does not fit anymore to the design:

To fix this issue, you can use a small jQuery plugin named Twitter Bootstrap File Input. When this plugin is added to your XPage, the button will look like this:

 

To initialize the jQuery plugin, you have to call it with a selector which selects all DOM elements of type file:

<xp:scriptBlock
    id="scriptBlockInitFile">
    <xp:this.value>
        <![CDATA[
           $(document).ready( 
               function() {
                   $('input[type=file]').bootstrapFileInput();
               }
           );
        ]]>
    </xp:this.value>
</xp:scriptBlock>

The description of the button can be changed by setting the title attribute. Additionally, you can choose if the file name will be displayed inside or outside of the button:

To place it inside, you need to add the attribute data-filename-placement to the file upload control:

<xp:fileUpload
    id="fileUploadControl"
    value="#{document.Body}"
    title="Datei auswählen">
    <xp:this.attrs>
        <xp:attr
            name="data-filename-placement"
            value="inside" />
    </xp:this.attrs>
</xp:fileUpload>

Because I have added it to a Custom Control and use it multiple times on a XPage, I have changed the original code and added a flag to prevent multiple calls, otherwise all file elements are modified over and over again:

Here is the modified code:

/*
  Bootstrap - File Input
  ======================

  This is meant to convert all file input tags into a set of elements that displays consistently in all browsers.

  Converts all
  <input type="file">
  into Bootstrap buttons
  <a>Browse</a>

  Sven Hasselbach, 26.03.2014:
  Added a fix to prevent multiple wrapping 

*/
(function($) {

$.fn.bootstrapFileInput = function() {

  this.each(function(i,elem){

    var $elem = $(elem);

    // Maybe some fields don't need to be standardized.
    if (typeof $elem.attr('data-bfi-disabled') != 'undefined') {
      return;
    }

    // --- Fix to prevent multiple wrapping 
    // Sven Hasselbach, 26.03.2014

    // check for an existing 'wrapped' attribute'
    if(!!$elem.attr('wrapped'))
        return;

    // add the 'wrapped' attribute
    $elem.attr('wrapped', 'true');
    // --- End of Fix    
    // Set the word to be displayed on the button
    var buttonWord = 'Browse';

    if (typeof $elem.attr('title') != 'undefined') {
      buttonWord = $elem.attr('title');
    }

    var className = '';

    if (!!$elem.attr('class')) {
      className = ' ' + $elem.attr('class');
    }

    // Now we're going to wrap that input field with a Bootstrap button.
    // The input will actually still be there, it will just be float above and transparent (done with the CSS).
    $elem.wrap('<a></a>').parent().prepend($('<span></span>').html(buttonWord));
  })

  // After we have found all of the file inputs let's apply a listener for tracking the mouse movement.
  // This is important because the in order to give the illusion that this is a button in FF we actually need to move the button from the file input under the cursor. Ugh.
  .promise().done( function(){

    // As the cursor moves over our new Bootstrap button we need to adjust the position of the invisible file input Browse button to be under the cursor.
    // This gives us the pointer cursor that FF denies us
    $('.file-input-wrapper').mousemove(function(cursor) {

      var input, wrapper,
        wrapperX, wrapperY,
        inputWidth, inputHeight,
        cursorX, cursorY;

      // This wrapper element (the button surround this file input)
      wrapper = $(this);
      // The invisible file input element
      input = wrapper.find("input");
      // The left-most position of the wrapper
      wrapperX = wrapper.offset().left;
      // The top-most position of the wrapper
      wrapperY = wrapper.offset().top;
      // The with of the browsers input field
      inputWidth= input.width();
      // The height of the browsers input field
      inputHeight= input.height();
      //The position of the cursor in the wrapper
      cursorX = cursor.pageX;
      cursorY = cursor.pageY;

      //The positions we are to move the invisible file input
      // The 20 at the end is an arbitrary number of pixels that we can shift the input such that cursor is not pointing at the end of the Browse button but somewhere nearer the middle
      moveInputX = cursorX - wrapperX - inputWidth + 20;
      // Slides the invisible input Browse button to be positioned middle under the cursor
      moveInputY = cursorY- wrapperY - (inputHeight/2);

      // Apply the positioning styles to actually move the invisible file input
      input.css({
        left:moveInputX,
        top:moveInputY
      });
    });

    $('body').on('change', '.file-input-wrapper input[type=file]', function(){

      var fileName;
      fileName = $(this).val();

      // Remove any previous file names
      $(this).parent().next('.file-input-name').remove();
      if (!!$(this).prop('files') && $(this).prop('files').length > 1) {
        fileName = $(this)[0].files.length+' files';
      }
      else {
        fileName = fileName.substring(fileName.lastIndexOf('\\') + 1, fileName.length);
      }

      // Don't try to show the name if there is none
      if (!fileName) {
        return;
      }

      var selectedFileNamePlacement = $(this).data('filename-placement');
      if (selectedFileNamePlacement === 'inside') {
        // Print the fileName inside
        $(this).siblings('span').html(fileName);
        $(this).attr('title', fileName);
      } else {
        // Print the fileName aside (right after the the button)
        $(this).parent().after('<span>'+fileName+'</span>');
      }
    });

  });

};

// Add the styles before the first stylesheet
// This ensures they can be easily overridden with developer styles
var cssHtml = '<style>'+
  '.file-input-wrapper { overflow: hidden; position: relative; cursor: pointer; z-index: 1; }'+
  '.file-input-wrapper input[type=file], .file-input-wrapper input[type=file]:focus, .file-input-wrapper input[type=file]:hover { position: absolute; top: 0; left: 0; cursor: pointer; opacity: 0; filter: alpha(opacity=0); z-index: 99; outline: 0; }'+
  '.file-input-name { margin-left: 8px; }'+
  '</style>';
$('link[rel=stylesheet]').eq(0).before(cssHtml);

})(jQuery);

Thanks to Gregory Pike for his good work. The jQuery plugin is distributed under Apache License.

Veröffentlicht unter Java Script, Web, XPages | Verschlagwortet mit , , , , , , , , | 1 Kommentar

XPages: A Bootstrap Skin for CKEditor

I have found a very nice skin for CKEditor, the “BootstrapCK-Skin”. It gives a bootstrap look and feel to the Editor:

The dialogs are also skinned:

You can find and download the skin here http://kunstmaan.github.io/BootstrapCK-Skin/

To use the skin in one of your applications, you have to import the unzipped files into your NSF…

… and add your “own” declaration of a xspCKEditor instance:

<xp:scriptBlock id="scriptBlockCKEditor">
   <xp:this.value>
      <![CDATA[
         require( ['dojo/_base/declare', 'ibm/xsp/widget/layout/xspCKEditor'], function( declare, xspCKEditor ){
            return declare( 'ch.hasselba.xpages.CKEDITOR', xspCKEditor, {
               constructor: function ckew_ctor(/*Object*/options){
                  CKEDITOR.timestamp = '';
               }
            });    
         });
      ]]>
   </xp:this.value>
</xp:scriptBlock>

This is required to remove an URL parameter, which is added automatically and will break the references. Then you have to overwrite the dojoType of your RichText control and add a dojoAttribute for the Skin. The path has to be appended after the name of the skin.

<xp:inputRichText
   id="inputRichTextBody"
   value="#{documentInfo.Body}"
   dojoType="ch.hasselba.xpages.CKEDITOR">
   <xp:this.dojoAttributes>
      <xp:dojoAttribute
         name="skin"
         value="BootstrapCK-Skin,/path/to/your/db.nsf/BootstrapCK-Skin/">
      </xp:dojoAttribute>
   </xp:this.dojoAttributes>
</xp:inputRichText>

EDIT:
Oliver Busse found a problem when using the style in an XPiNC application and added a hotfix for the issue on his blog: http://mardou.dyndns.org/hp.nsf/blogpost.xsp?documentId=BC2

Veröffentlicht unter Java Script, Web, XSP | Verschlagwortet mit , , , | 3 Kommentare

Quick-n-Dirty: Use “isDocEditable” in an old school Java Agent

If you want to check if a document is editable, you can do this in an old school Java agent with the NAPI function isDocEditable provided by the XSPNative class.

First you have to add the required JARs to your agent. Then, you have to call XSPNative.isDocEditable with the document you want to test:

import lotus.domino.AgentBase;
import lotus.domino.Database;
import lotus.domino.Document;
import lotus.domino.Session;

import com.ibm.domino.napi.c.xsp.XSPNative;

public class JavaAgent extends AgentBase {

    public void NotesMain() {

      try {
          Session session = getSession();
          Database db = session.getCurrentDatabase();
          Document doc = db.getAllDocuments().getFirstDocument();
          System.out.println( "Is Editable: " + XSPNative.isDocEditable( doc ) );
      } catch(Exception e) {
          e.printStackTrace();
       }
   }
}

P.S. Keep in mind that this article has been posted in the “Quick-n-Dirty” category.

Veröffentlicht unter Agenten, Java | Verschlagwortet mit , , , , | Hinterlasse einen Kommentar

XPages: Set a Theme for a single XPage

… or how you can use your own FacesContext implementation.

What we need first is our own FacesContext implementation with new methods to set the StlyeKitId (which is the name of the Theme) for initializing the StyleKit instance:

package ch.hasselba.xpages;

import javax.faces.context.FacesContext;
import com.ibm.xsp.application.ApplicationExImpl;
import com.ibm.xsp.context.FacesContextExImpl;
import com.ibm.xsp.stylekit.StyleKit;

/**
 * ThemeSwitcherFacesContext
 * allows to switch the theme during runtime
 * 
 * @author Sven Hasselbach
 * @version 0.1
 */
public class ThemeSwitcherFacesContext extends FacesContextExImpl {

    private StyleKit styleKit;
    private String styleKitId;
    private FacesContext delegated;
    /**
     * constructor
     * 
     * @param fc
     *     delegated javax.faces.context.FacesContext
     */
    public ThemeSwitcherFacesContext(FacesContext fc) {
        super(fc);
        this.delegated = fc;
    }

    /**
     * returns current StyleKit
     * 
     * @return com.ibm.xsp.stylekit.StyleKit
     *      the StyleKit used for rendering the view
     */
    public StyleKit getStyleKit() {
        if (this.styleKit == null) {
            this.styleKit = super.getStyleKit();
        }
        return this.styleKit;
    }

    /**
     * sets the StyleKit
     * 
     * @param stlyeKit
     *     the com.ibm.xsp.stylekit.StyleKit to use 
     */
    public void setStyleKit(final StyleKit styleKit) {
        this.styleKit = styleKit;
    }

    /**
     * returns the current StyleKitId (aka Theme name)
     * 
     * @return String
     *     the id of the StyleKit
     */
    @Override
    public String getStyleKitId() {
        if (this.styleKitId == null) {
            this.styleKitId = super.getStyleKitId();
        }
        return styleKitId;
    }

    /**
     * sets the StyleKitId
     * 
     * @param styleKitId
     *     the id of the StyleKit
     */
    public void setStyleKitId(final String styleKitId) {
        this.styleKitId = styleKitId;
    }

    /**
     * initializes the StyleKit for the current view
     */
    public void loadStyleKit() {
        this.styleKit = ((ApplicationExImpl) getApplication())
                .getStyleKit(this.styleKitId);
    }
}

But you cannot register the FacesContext implementation directly. That’s why we have to build our own FacesContextFactory to inject our FacesContext instance this:

package ch.hasselba.xpages;

import javax.faces.FacesException;
import javax.faces.context.FacesContext;
import javax.faces.context.FacesContextFactory;
import javax.faces.lifecycle.Lifecycle;
import com.ibm.xsp.FacesExceptionEx;
import com.ibm.xsp.context.FacesContextFactoryImpl;

/**
 * ThemeSwitcherFacesContextFactory
 * 
 * @author Sven Hasselbach
 * @version 0.1
 */
public class ThemeSwitcherFacesContextFactory extends FacesContextFactory {

    private FacesContextFactory delegate;

    @SuppressWarnings("unchecked")
    public ThemeSwitcherFacesContextFactory(){
        try{
            Class clazz = Class.forName( "com.sun.faces.context.FacesContextFactoryImpl" );
            this.delegate = (FacesContextFactory) clazz.newInstance();
        }
        catch (Exception e){
          throw new FacesExceptionEx(e);
        }
    }

    public ThemeSwitcherFacesContextFactory(FacesContextFactory fcFactory){
        this.delegate = fcFactory;
        if ((this.delegate instanceof FacesContextFactoryImpl)) {
            this.delegate = ((FacesContextFactoryImpl)this.delegate).getDelegate();
        }
    }

    public FacesContext getFacesContext(Object param1, Object param2, 
        Object param3, Lifecycle paramLC) throws FacesException {
        FacesContext fc = this.delegate.getFacesContext(param1, param2, param3, paramLC);
        return new ThemeSwitcherFacesContext(fc);
    }
}

Now, you have to add the FacesContextFactory to the faces-config.xml to activate it:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config>
  <factory>
    <faces-context-factory>
        ch.hasselba.xpages.ThemeSwitcherFacesContextFactory
    </faces-context-factory>
  </factory>
</faces-config>

If you open the XPage, an error will occur:

This happens because there are some java security restrictions with the class loader of the delegated classes. You have to modify the java security in the java.policy file (you can limit the the grant to specific database if you want).

grant {
    permission java.security.AllPermission;
};

Then you can add a Theme to your XPage, f.e. this one:

<theme
    extends="webstandard" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:noNamespaceSchemaLocation="platform:/plugin/com.ibm.designer.domino.stylekits/schema/stylekit.xsd" >
    <control>
        <name>ViewRoot</name>
        <property>
            <name>style</name>
            <value>background-color:rgb(255,255,0)</value>
        </property>
    </control>
</theme>

To use the ThemeSwitcher, you have to add some code in the beforePageLoad event:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

    <xp:this.beforePageLoad>
        <![CDATA[#{javascript:
            importPackage( ch.hasselba.xpages );
            var fc:ch.hasselba.xpages.ThemeSwitcherFacesContext = facesContext;
            fc.setStyleKitId( "ThemeA");
            fc.loadStyleKit();}
        ]]>
    </xp:this.beforePageLoad>

    Switched Theme

</xp:view>

When the XPage is opened in the browser, the Theme is changed:

All other pages in the NSF are stil using the default Theme:

Veröffentlicht unter Java, JSF, Web, XPages | Verschlagwortet mit , , , | 1 Kommentar

Quick-n-Dirty: A Hotfix for CKEditor 4

Russell Maher wrote a very interesting article about using CKEditor 4 in XPages, but the current solution requires to change to HTML files directly on the server.

But with this little Hotfix you can use CKEditor form a NSF an don’t need to change the HTML files on the domino server:

1. Switch to package explorer perspective
2. Open the file ckeditor.js

3. Search for the variable timestamp…

4. …and remove the 4 characters

5. Save it. That’s it!

Now, CKEditor 4 can be loaded directly from the NSF.

Veröffentlicht unter Java Script, XPages | Verschlagwortet mit , , , | 3 Kommentare

XPages: Use async / defer option for external CSJS Script Libraries

When adding CSJS libraries to your XPage, try to use the defer or the async option for a better user experience. When not using this options, the Page might be blocked during page load.

Have a look at this example XPage which contains two external CSJS scripts (for demonstration purposes they are computed to get a remote script out of nowhere):

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
    Foo!
    <xp:this.resources>
        <xp:script clientSide="true">
            <xp:this.src>
               <![CDATA[#{javascript:"http://" + @Unique() + ".null";}]]>
            </xp:this.src>
        </xp:script>
    </xp:this.resources>
    <xp:scriptBlock id="scriptBlock1">
        <xp:this.src>
           <![CDATA[#{javascript:"http://" + @Unique() + ".null";}]]>
        </xp:this.src>
    </xp:scriptBlock>
    Bar!
</xp:view>

When opening the XPage, the DOM is blocked, until the operation times out:

The best you can do is to use the async or the defer option of external CSJS scripts. For script blocks, there is an option in the DDE available:

The async option can be set with an attribute:

<xp:this.attrs>
    <xp:attr name="async" value="async" minimized="true" />
</xp:this.attrs>

To use the option for a resource, you must add them as an attribute for both options:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
    Foo!
    <xp:this.resources>
        <xp:script clientSide="true">
            <xp:this.src>
               <![CDATA[#{javascript:"http://" + @Unique() + ".null";}]]>
            </xp:this.src>
            <xp:this.attrs>
                <xp:attr name="async" value="async" minimized="true" />
            </xp:this.attrs>
        </xp:script>
    </xp:this.resources>
    <xp:scriptBlock id="scriptBlock1" defer="true">
        <xp:this.src>
           <![CDATA[#{javascript:"http://" + @Unique() + ".null";}]]>
        </xp:this.src>
    </xp:scriptBlock>
    Bar!
</xp:view>

There are some other techniques, but this is the simplest way and supported in most browsers:

Veröffentlicht unter HTML5, Java Script, Performance, Web, XPages | Verschlagwortet mit , , , , , , , , , | 1 Kommentar

XPages: Add an attribute to the BODY-Element

Today I wanted to add an attribute to the <BODY> element of my XPage. My goal was to generate HTML code like this:

<body role="document">

After some testing I found a solution by overwriting the method encodeHtmlBodyStart. To do this, you have to extend the class ViewRootRendererEx2:

package ch.hasselba.xpages;

import java.io.IOException;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import com.ibm.xsp.component.UIViewRootEx;
import com.ibm.xsp.renderkit.html_basic.ViewRootRendererEx2;

public class MyViewRootRenderer extends ViewRootRendererEx2 {

    @Override
    protected void encodeHtmlBodyStart(FacesContext fc, UIViewRootEx uiRoot,
            ResponseWriter writer) throws IOException {

        writer.startElement("body", uiRoot);
        writer.writeAttribute("role", "document", "role");
        writeln(writer);
    }

}

To activate it you have to change the renderer class in the faces-config.xml:

<faces-config>  
  <render-kit>
    <renderer>
      <component-family>javax.faces.ViewRoot</component-family>
      <renderer-type>com.ibm.xsp.ViewRootEx</renderer-type>
      <renderer-class>ch.hasselba.xpages.MyViewRootRenderer</renderer-class>
    </renderer>
  </render-kit>
</faces-config>

Now, the attribute is added correctly to my HTML code:

Veröffentlicht unter Java, JSF, XPages | Verschlagwortet mit , , , , , | Hinterlasse einen Kommentar

XPages: Use a Method Binding as Converter

I accidentally found a way to add a method binding as a converter to a component, because I have added a managed bean as a converter directly in the source code. The DDE does not support this.

If you go to the converter property of a component, you can only add one of the predefined converters:

But you can go to the source and add a method binding to the option, in this case my bean which implements my converter functionality.

<xp:inputText
    id="inputText1"
    value="#{sessionScope.inputText1}"
    converter="#{myConverterBean}" />

If you now reopen the saved XPage, the converter property is filled in, but cannot edited / changed anymore.

You must remove the property in the source code to get the old behaviour back.

Tested in 8.5.2, 8.5.3 & ND 9

Veröffentlicht unter Java, JSF, XPages | Verschlagwortet mit , , , , , , , | 3 Kommentare

XPages: Optimized Partial Refreshs (2)

With the Optimized Partial Refresh you can do a lot of nice things: If only a part of the form is sent to the server, only this part of the components in the JSF component tree will be processed. This means that only submitted values are applied, coverted and validated, which can result in less server usage and a better performance.

Normally, if you have two required fields on you XPage and do a Partial Refresh, both fields will have a validation error:

But with this technique, you can choose which component should be refreshed. When clicking of Button Refresh 1, only the first field is marked as a required field:

When filling in the first field, but clicking on Refresh 2

… the value of the first field gets lost, because it was not sent to the server (if a value was already stored there, it won’t get lost). And the second field is marked as required:

This is the example XPage, the CSJS code is moved to the Custom Control ccOptimizedPR.

<?xml version="1.0" encoding="UTF-8"?>
<xp:view
    xmlns:xp="http://www.ibm.com/xsp/core"
    xmlns:xc="http://www.ibm.com/xsp/custom">

    <xc:ccOptimizedPR />

    <xp:div id="refreshMe">
        <xp:messages id="messagesError" />
        <xp:inputText
            id="inputText1"
            value="#{sessionScope.inputText1}">
            <xp:this.validators>
                <xp:validateRequired message="Field 1 is empty!" />
            </xp:this.validators>
        </xp:inputText>
        <xp:inputText
            id="inputText2"
            value="#{sessionScope.inputText2}">
            <xp:this.validators>
                <xp:validateRequired message="Field 2 is empty!" />
            </xp:this.validators>
        </xp:inputText>
    </xp:div>

    <xp:button
        value="Normal Refresh"
        id="buttonNormal">
        <xp:eventHandler
            event="onclick"
            submit="true"
            refreshMode="partial"
            refreshId="refreshMe">
        </xp:eventHandler>
    </xp:button>

    <xp:button
        value="Refresh 1"
        id="buttonCleaned1">
        <xp:eventHandler
            event="onclick"
            submit="false">
            <xp:this.script>
                <![CDATA[
                    XSP.partialRefreshPost(
                        '#{id:refreshMe}',{
                            clearForm: true,
                            additionalFields: ['#{id:inputText1}' ]
                        }
                    );]]>
            </xp:this.script>
        </xp:eventHandler>
    </xp:button>
    <xp:button
        value="Refresh 2"
        id="buttonCleaned2">
        <xp:eventHandler
            event="onclick"
            submit="false">
            <xp:this.script>
                <![CDATA[
                    XSP.partialRefreshPost(
                        '#{id:refreshMe}',{
                            clearForm: true,
                            additionalFields: ['#{id:inputText2}' ]
                        }
                    );]]>
            </xp:this.script>
        </xp:eventHandler>
    </xp:button>

</xp:view>
Veröffentlicht unter Dojo Toolkit, Java, JSF, Performance, XPages, XSP | Verschlagwortet mit , , , , , , , , , | Hinterlasse einen Kommentar

XPages: Optimized Partial Refreshs

Inspired by the last post of Mark, I have created a small CSJS snippet which allows to optimize the behaviour of a Partial Refresh. Normally, if you execute a Partial Refresh, all elements of a form are sent to the server. Take a look at this XPage:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

    <xp:inputText
        id="inputText01"
        value="#{sessionScope.inputText01}" />
    <xp:inputText
        id="inputText02"
        value="#{sessionScope.inputText02}" />
    <xp:inputText
        id="inputText03"
        value="#{sessionScope.inputText03}" />
    <xp:inputText
        id="inputText04"
        value="#{sessionScope.inputText04}" />
    <xp:inputText
        id="inputText05"
        value="#{sessionScope.inputText05}" />
    <xp:inputText
        id="inputText06"
        value="#{sessionScope.inputText06}" />
    <xp:inputText
        id="inputText07"
        value="#{sessionScope.inputText07}" />
    <xp:inputText
        id="inputText08"
        value="#{sessionScope.inputText08}" />
    <xp:inputText
        id="inputText09"
        value="#{sessionScope.inputText09}" />
    <xp:inputText
        id="inputText10"
        value="#{sessionScope.inputText10}" >
    </xp:inputText>

    <xp:div id="refreshMe">
        <xp:label
            value="#{javascript:java.lang.System.currentTimeMillis()}"
            id="labelNow" />
    </xp:div>

    <xp:button
        value="Normal Refresh"
        id="buttonNormal">
        <xp:eventHandler
            event="onclick"
            submit="true"
            refreshMode="partial" refreshId="refreshMe">
        </xp:eventHandler>
    </xp:button>

</xp:view>

The button refreshes only a small portion of the XPage, the DIV element refreshMe. It does not require any of the posted field values, it just refreshes the DOM element in the frontend. But when clicking the button, the posted data to the server contain all the fields and their values, which is not necessary in this case.

This is how the request looks like in Firebug (I have prefilled all fields with the values 1..9):

The response of the server is – as expected – the actual HTML code for the refreshed DOM element:

The optimized  version adds the option clearForm to partial refreshs. When using this option, only the XPages internal fields are sent to the server, but DOM will be refreshed correctly:

<xp:button
        value="Cleaned Refresh"
        id="buttonCleaned">
        <xp:eventHandler
            event="onclick"
            submit="false">
            <xp:this.script>
                <![CDATA[
                    XSP.partialRefreshPost(
                        '#{id:refreshMe}',{
                            clearForm: true,
                        }
                    );]]>
            </xp:this.script>
        </xp:eventHandler>
    </xp:button>

Now, the POST looks like this:

You may think “This is useless, you can do a XSP.partialRefreshGet instead!”

That’s why there is the second option additionalFields. This allows to define all fields you want to update during the refresh:

<xp:button
        value="Cleaned Refresh"
        id="buttonCleaned">
        <xp:eventHandler
            event="onclick"
            submit="false">
            <xp:this.script>
                <![CDATA[
                    XSP.partialRefreshPost(
                        '#{id:refreshMe}',{
                            clearForm: true,
                            additionalFields: ['#{id:inputText01}',
                                              '#{id:inputText02}' ],
                        }
                    );]]>
            </xp:this.script>
        </xp:eventHandler>
    </xp:button>

When clicking the button now, the specified fields are added to the POST request (and will be updated in the JSF tree:

Here is the snippet (Tested on IE 11, Chrome 33 & FF 27 with ND9 & ND 8.5.3 )

<xp:scriptBlock id="scriptBlockPROptimized">
        <xp:this.value><![CDATA[
XSP.addOnLoad(function(){

    // hijack the existing partial refresh method
    if( !XSP.__partialRefresh ){
        XSP.__partialRefresh = XSP._partialRefresh;
    }

    // add the new one to the XSP object
    XSP._partialRefresh = function x_prfh(method, form, refreshId, options){

        // clear the form?
        if( options.clearForm ){

            // create a new HTML form...
            var newForm = document.createElement( "form" );
            newForm.setAttribute( "method", form.method );
            newForm.setAttribute( "action", form.action );

            // ... and loop all existing fields
            for( var i = 0; i<form.length; i++ ){
                var field = form[i];
                var fieldName = field.name;
                var includeField = false;

                try{

                    // check for addition fields
                    if( options.additionalFields ){
                        includeField = dojo.indexOf(options.additionalFields, fieldName)!=(-1)?true:false;
                    }

                    // only add XPages relevant fields and addtional fields
                    if( fieldName == form.id || fieldName.substr(0,2) == '$$' || includeField ){

                        var newField = null;
                        if( field.options ){
                            // special handling for fields with options
                            for( var j=0; j<field.length; j++ ){
                                if( field.options[j].selected ){
                                    newField = document.createElement( "input" );
                                    newField.setAttribute( "type", "hidden" );
                                    newField.setAttribute( "name", fieldName );
                                    newField.setAttribute( "value", field.options[j].value );
                                    newForm.appendChild( newField );
                                }
                            }
                        }else{
                            // default field handling: just clone the DOM element
                            newField = field.cloneNode( true );
                            newForm.appendChild( newField );
                        }
                    }
                }catch(e){
                    console.log(e);
                }
            }

            // call the original refresh method with the new form
            return XSP.__partialRefresh(method, newForm, refreshId, options);
        }

        XSP.__partialRefresh(method, form, refreshId, options);
    };
});]]></xp:this.value>
    </xp:scriptBlock>

Just add the script block above to your XPage, or move it into a CSJS library. But keep in mind that you have to think twice when removing data from the request. It can lead in an inconsistence between the data in the client and the data stored in the component tree on the server.

Veröffentlicht unter Dojo Toolkit, Java Script, Performance, Web, XPages, XSP | Verschlagwortet mit , , , , , , , , , | 3 Kommentare