Posts Tagged: ‘web’

XPages: WebContent Files (3) – Create a Minimizer Servlet

26. November 2014 Posted by Sven Hasselbach

Tschüß RFC 2616! War nett mit Dir!

8. Juni 2014 Posted by Sven Hasselbach

Wie auf Heise berichtet, ist das RFC 2616 zu seinem 15ten Geburtstag in Rente geschickt worden, und sollte keine Verwendung mehr finden. Bei Fragen rund um die HTTP/1.1 Spezifikation gelten von nun an folgende RFCs:

In jedem der RFCs findet sich ein Abschnitt “Changes from RFC 2616“, indem die grundlegensten Änderungen gegenüber der ursprünglichen Spezifikation erläutert werden.

XPages & Angular.js: AngScope for Firebug

6. Juni 2014 Posted by Sven Hasselbach

AngScope is a

“Simple Firebug extension that allows you to inspect the AngularJS scope that a DOM element binds to.”

Just do a right click on the DOM element you want to inspect and select “Inspect Angular Scope“:

This gives you a direct access to all elements of the scopes of your Angular.js application:

You can find the extension here.

XPages & Angular.js: Accessing Rich Text (1)

5. Juni 2014 Posted by Sven Hasselbach

WordPress 3.9.1: Fix für Bug mit Copy & Paste von Bildern aus Zwischenablage

5. Juni 2014 Posted by Sven Hasselbach

Um das Problem zu beheben, muss der Konfigurationsparameter paste_data_images für den TinyMCE Editor auf true gesetzt werden. Dazu kann man folgendes tun:

In der Datei /wp-includes/class-wp-editor.php folgende Zeile ergänzen:

init["paste_data_images"] = true;

Zu finden ist die richtige Stelle dafür in diesem Code-Block (ca. Zeile 1135):

.....

/**
* Fires after tinymce.js is loaded, but before any TinyMCE editor
* instances are created.
*
* @since 3.9.0
*
* @param array $mce_settings TinyMCE settings array.
*/
     do_action( 'wp_tiny_mce_init', self::$mce_settings );

?>
<script type="text/javascript">
<?php

     if ( self::$ext_plugins )
          echo self::$ext_plugins . "\n";

     if ( ! is_admin() )
          echo 'var ajaxurl = "' . admin_url( 'admin-ajax.php', 'relative' ) . '";';

?>

( function() {
     var init, edId, qtId, firstInit, wrapper;

     if ( typeof tinymce !== 'undefined' ) {
          for ( edId in tinyMCEPreInit.mceInit ) {
               if ( firstInit ) {
                    init = tinyMCEPreInit.mceInit[edId] = tinymce.extend( {}, firstInit, tinyMCEPreInit.mceInit[edId] );
               } else {
                    init = firstInit = tinyMCEPreInit.mceInit[edId];
               }
               init["paste_data_images"] = true;

              wrapper = tinymce.DOM.select( '#wp-' + edId + '-wrap' )[0];

.....

Danach kann man wieder Bilder aus der Zwischenablage einfügen.

WordPress 3.9.1: Bug mit Copy & Paste von Bildern aus Zwischenablage

5. Juni 2014 Posted by Sven Hasselbach

Anscheinden hat das heutige Update des Blog eines meiner liebsten Features gekillt: Das direkte Copy & Pasten von Bildern aus der Zwischenablage geht nicht mehr. Tolle Sache.

Siehe dazu: https://core.trac.wordpress.org/ticket/27970

XPages & Angular.js: Fileuploads

2. Juni 2014 Posted by Sven Hasselbach

When using Angular.js you sooner or later want to upload a file to your Domino server. But to do this, you not only need some nice looking frontend, you also need some code in the backend. For the frontend you can use one of the already exsiting modules which are available for Angular.js, for example the angular-file-upload. For a quick start, I have choosen to modify the provided Simple example.

After stripping down the example files (just saved the whole website with Firefox), you can import all the files into a new database. Mark Roden has written a very good tutorial about the details for this step.

The WebContent folder should look like this:

Now you have to modify the index.html and make all pathes relative:

When you open the index.html in the browser you will see some errors in the console, because of some missing fonts, but we will ignore this issue in this quick example.

Instead, we are adding a link for a download of an uploaded file:

&#160;<a download="{{ item.file.name }}" ng-href="{{ item.file.dlPath }}">download</a></td>

The download link will be filled with the URL of the uploaded attachment in the target database after a successfull upload.

Now it’s time to tell the frontend where to store the file in the backend. For this, you have to modify the controller.js and define the target of the upload process. In this example, the target is a XPage named “upload.xsp“. After uploading a file to the server, the XPage returns the URL of the attachment as a JSON string. To update the link in the frontend, we bind a function to the event “success” which adds the URL to the current file item:

angular.module('app', ['angularFileUpload'])

.controller('TestController', function ($scope, $fileUploader) {
    'use strict';

    // create a uploader with options
    var uploader = $scope.uploader = $fileUploader.create({
        scope: $scope,   
        url: 'upload.xsp'
    });

    uploader.bind('success', function (event, xhr, item, response) {
        // add the response url to the file item 
        item.file.dlPath = response.url;
    });

});

 [This is the complete controler.js which replaces the file from the module's example.]

The XPage handles the uploaded files them directly without a file upload control. Every uploaded file will be attached to a single document, and embedded to the Richtext item “Body“. If the upload was successfull, the XPages returns the JSON data containing the URL to the newly created attachment.

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" rendered="false">
    <xp:this.data>
        <xp:dominoDocument var="documentFile" action="editDocument" concurrencyMode="force" />
    </xp:this.data>

    <xp:this.afterRenderResponse>
        <![CDATA[#{javascript:

            /**
             * saves a uploaded attachment to a datasource
             *
             * @param ds
             *        the datasource
             * @param rtItemName
             *        the richtext item to save the file
             * @return
             *        the name of the uploaded file
             * @author Sven Hasselbach
             */
            function saveAttachment( ds:NotesXspDocument, rtItemName:String ):String {

                // get the file data
                var con = facesContext.getExternalContext();
                var request:com.sun.faces.context.MyHttpServletRequestWrapper = con.getRequest();
                var map:java.util.Map = request.getParameterMap();
                var fileData:com.ibm.xsp.http.UploadedFile = map.get( "file" );

                if( fileData == null )
                      return;

                // get the file
                var tempFile:java.io.File = fileData.getServerFile();
                var correctedFile = new java.io.File( tempFile.getParentFile().getAbsolutePath() +
                java.io.File.separator + fileData.getClientFileName() );
                var success = tempFile.renameTo(correctedFile);

                // create or use an existing RT item
                var rtFiles:NotesRichTextItem = null;
                if(!(ds.getDocument().hasItem( rtItemName ))){
                    rtFiles = ds.getDocument().createRichTextItem( rtItemName )
                }else{
                    rtFiles = ds.getDocument().getFirstItem( rtItemName );
                }

                // embed the file
                rtFiles.embedObject(lotus.domino.local.EmbeddedObject.EMBED_ATTACHMENT, "",
                      correctedFile.getAbsolutePath(), null);

                  // rename the file back for server processing
                  correctedFile.renameTo(tempFile);

                  // save the datasource
                ds.save();

                // return the filenam
                return fileData.getClientFileName();

            }

            // save the doc
            var file = saveAttachment( documentFile, "Body" );

            // create the response
            var res = facesContext.getExternalContext().getResponse();
            res.setContentType( "application/json" );
            res.setCharacterEncoding( "UTF-8" );
            var writer = res.getWriter();

            if( file != null ){
                // send a JSON url string back to the client
                var url = documentFile.getDocument().getHttpURL();
                var fileUrl = url.replace("?OpenDocument","/$File/"+file+"?OpenElement");
                writer.write( '{"url": "' + fileUrl + '"}' );    
            }else{
                // otherwise send empty JSON data
                writer.write( '{}' );
            }
            writer.flush();
            facesContext.responseComplete();
        }]]>
    </xp:this.afterRenderResponse>
</xp:view>

After uploading a file, you can see that the file was uploaded successfully in the firebug console:

When clicking the “download” link, the file is available to download:

And the attachment was uploaded to the database:

That’s it!

You might not need jQuery

8. Mai 2014 Posted by Sven Hasselbach

I have found a very interesting website: You might not need jQuery. It contains a lot of usefull solutions for the different IE versions.

XPages: Bootstrap File Input

26. März 2014 Posted by Sven Hasselbach

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.

XPages: A Bootstrap Skin for CKEditor

17. März 2014 Posted by Sven Hasselbach

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>

XPages: Set a Theme for a single XPage

14. März 2014 Posted by Sven Hasselbach

… 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:

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

10. März 2014 Posted by Sven Hasselbach

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:

XPages: Optimized Partial Refreshs

7. März 2014 Posted by Sven Hasselbach

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:

But wait, 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.

XPages: Modify the File Selection Button

24. Januar 2014 Posted by Sven Hasselbach

With Dojo, you can easily customize the file upload button, f.e. to change the label, add additional style sheets or use HTML5 or Flash plugin for uploading.

Here is basic example which adds a label and a blue border around the button:

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

    <xp:this.data>
        <xp:dominoDocument var="document1" formName="File" />
    </xp:this.data>

    <xp:scriptBlock id="scriptBlockFileUpload">
        <xp:this.value>
            <![CDATA[
                require(['dojo/parser',
                         'dojox/form/Uploader',
                         'dojo/domReady!'], 
                         function(parser, ready){
                             parser.parse().then(function(){
                                uploader = new dojox.form.Uploader({
                                    name:'#{id:fileUpload1}', 
                                    label:'Select Some Files',
                                    multiple:false, 
                                    style: 'border:5px solid blue;'
                                }, '#{id:fileUpload1}');

                                uploader.startup();
                             });
                         });
            ]]>
        </xp:this.value>
    </xp:scriptBlock>

    <xp:fileUpload id="fileUpload1"
        value="#{document1.Body}">
    </xp:fileUpload>

    <xp:button value="Submit" id="buttonSubmit" >
        <xp:eventHandler event="onclick" submit="true"
            refreshMode="complete" immediate="false" save="true" />
    </xp:button>

</xp:view>

Have a look at dojox.form.Uploader for more details.

[EN] Customer Experience: Instead of traditional Campaigns – On the Web, wait for the customer to make the first move

1. Oktober 2013 Posted by StefanP.

I strongly believe, that there is a lot of truth in this posting. But it is hard, to change behavior and wait for the customer to make the first move, as Gerry McGovern writes.

Traditional communications and marketing are built on a belief that if you can be creative enough with the message you can grab customer attention. Proponents of this ethos are used to getting out in front of the customer and placing big messages in front of them. They love campaigns in which they bombard customers with specific messages for a specific period of time. And then they move on to the next campaign.

They think they can change the customer’s journey. They think they can change the customer’s mind with marketing magic. In the age of the empowered customer — who is cynical and skeptical towards brands and organizations — these marketing and communications campaigns will increasingly end in failure.

Today, we are more likely to be successful if we wait for the customer to make the first move. Searching is like advertising in reverse. The words that customers place in the search box are ads — their ads. On the Web, the customer is the communicator and marketer. There is a reversal of roles.

Help the customer on their journey. Don’t try to change their mind, help them expand it. Expand their horizon based on the choices they already intend to make. Give them alternatives that are directly connected with what they want to do. More than anything else: be helpful.

via Customer Experience: On the Web, Habits are Expensive to Change.


Filed under: English Tagged: CustomerExperience, DigitalExperience, Marketing, Web