EXTJS in XPages #1 – Starting from scratch (the first grid)

In this article I will demonstrate how to create a basic EXTJS grid on your XPages website. I will go through each step of the process and once complete this will be the reference for all following articles on how to do it.

Introduction

EXTJS is a JavaScript library. Well ok to be clear it is MANY JavaScript libraries which can all be added to your XPages in the same way.  I will show you where to find the files, how to add them to your Domino database and then how to use the Extension Library REST service to put your first grid on your XPage.

Sencha

Originally built as an add-on library extension of YUI by Jack Slocum, Ext JS includes interoperability with jQuery and Prototype. Beginning with version 1.1, Ext JS retains no dependencies on external libraries, instead making their use optional.

Licensing

Sencha has two licenses commercial and GPL. For the sake of this blog I am working under the GPL license because everything is being made public. If you want to use the grid for your work you will need to pay for it. A developer pack is $600 each and at a commercial XPages developers charge out rate that is less than a day….I think it will make you more productive!

Downloading

Go to the download page and download the appropriate copy of the whole library (45M-ish).

Getting it into your Database

Once downloaded, unzip the file and get the contents on your machine. That is 210M of unreal goodness and excessive volume and we REALLY don’t need to bloat our database with all that. So delete all the folders except resource and src and remove all the js files until you are left with the extjs files and the license….

ext9

That has got us down to about 30M of files which is still a LOT but they are not going to be loaded into your webpage all at the same time so don’t fret about it

Then drag and drop the root folder into your databases WebContents folder. This can be found in the Package Explorer via  the XPage perspective in 8.5.3 or Window/Show Eclipse Views/Java in <8.5.3.
ext10

Asynchronous Module Definition

As you may know with the dojo libraries there is a methodology called “Asynchronous Module Definition (AMD)” which allows a JavaScript application to load in the modules that it needs – and this rocks! What is also means is that (and I know some of you were thinking it) you are not going to load “30M” worth of files onto your webpage at any time.

Building an XPage

On your XPage you will need to reference the EXTJS library to make sure that is included in your webpage resources. This is done by simply adding the following code to the XPage:

<script type="text/javascript" src="ext-4.1.1/ext-all-debug.js"></script>	
<link href="ext-4.1.1/resources/css/ext-all.css" rel="stylesheet" type="text/css" /> 

This also adds the basic stylesheet to the grid as well (more on that in a later blog post) Once that is added to the XPage then we are all ready to start……

Looking at the examples

As I mentioned in my first article – you really should go look at the examples page in this case specifically the grids section. In there you should poke around and look at the code, terrifying as it may be it really shouldn’t be and as we go through this blog series I will explain and simply all the examples. In this case I am going to look at the RESTful Store example  Clicking on the restful.js link you will see all the code used int his example. I am not going to go into the code itself in this example, suffice as to say you just need to copy and paste 🙂

Creating a grid from a REST service
We are going to start our XPage with Dave Leedy’s fakenames first name view ext1 from that we are going to look at the view in Designer, specifically at the programmatic names of the columns. ext4 As you can see from the image above, each of the columns has a programmatic use name. This is important to remember and necessary to change sometimes.

The REST service.

The REST service is added to an XPage in the following manner – (you must have the Extension Library installed on the client and the server to make this happen). This is the code on my xRestService XPage for the ByFirstName feed. Very simple and sends 100 documents out in JSON format

<xe:restService id="by-FirstName" pathInfo="byFirstName">
	<xe:this.service>
	<xe:viewItemFileService viewName="ByName-First"
		count="100" defaultColumns="true" systemColumns="132">
	</xe:viewItemFileService>
	</xe:this.service>
</xe:restService>

Looking at the REST service through a URL call we see the following format for the output:

{
    "@timestamp":"2013-02-23T06:53:34Z",
    "@toplevelentries":1300,
    "items":
    [
      {
          "@entryid":"1-66F3A5FE2B2A38CC85257B1B00254E6D",
          "@unid":"66F3A5FE2B2A38CC85257B1B00254E6D",
          "@noteid":"688E",
          "@position":"1",
          "@siblings":1298,
          "firstname":"Adam",
          "lastname":"Saenz",
          "address":"2519 Custer Street",
          "city":"ROCKWOOD (SOMERSET)",
          "state":"PA",
          "zip":15557,
          and so on,
      },
      {
          "@entryid":"2-3857882D6726A78F85257B1B00254F1C",
          "@unid":"3857882D6726A78F85257B1B00254F1C",
          "@noteid":"6B4A",
          "@position":"2",
          and so on,

Adding the Grid to the XPage I have simplified the example given on the Sencha site so that we can just see the bare bones of the grid. As we go through th series I will add more and more complication into the grids so that we can see how they work. The grid code is built up of multiple components:

  • Require the necessary EXTJS libraries
  • Defining a People Object
  • Creating the data store
  • Getting the grid data from the REST service
  • Applying the data to the grid in columns

So let’s look at the code The AMD part

Ext.require(['Ext.data.*', 'Ext.grid.*']);

The Person Object In here we define the fields ‘firstname’, ‘lastname’, ‘address’, ‘city’, ‘state’. These field names relate to the names of the fields coming in through the REST service JSON string and therefore directly to the programmatic column name in the view (told you it was important)

Ext.define('Person', {
    extend: 'Ext.data.Model',
    fields: [{
         'firstname', 'lastname', 'address', 'city', 'state']
});

The Data Store In this section the data Store is created which is used to populate the grid. The store: defines the Person Object (above) as the model to display the information sets up the connection to get the REST service from the ‘xRestService.xsp/byFirstName’ URL

    var store = Ext.create('Ext.data.Store', {
        autoLoad: true,
        autoSync: true,
        model: 'Person',
        proxy: {
            type: 'rest',
            url: 'xRestService.xsp/byFirstName',
            reader: {
                type: 'json',
                root: 'items'
            },
            writer: {
                type: 'json'
            }
        }
    });

The Grid itself Finally we build the grid – and the grid parameters define what it looks like – I have added comments to the code so that you can see in place what each section is doing

    var grid = Ext.create('Ext.grid.Panel', {
        renderTo: 'gridHere',		//render to the id named gridHere
        width: 600,			//width of the grid
        height: 300,			//height of the grid
        frame: true,
        title: 'Users',			//Grid title at the top
        store: store,			//Store object
        iconCls: 'icon-user',		//icon for the title
        columns: [{			//for each column in the grid define the attributes of the column
            header: 'First',		//Column Header Title
            width: 80,			//Column width
            sortable: true,		//Is the column sortable?
            dataIndex: 'firstname',	//what is the data field in the JSON used to fill this column
            field: {			//what type of field is this?
                xtype: 'textfield'
            }
        }, {
            text: 'Last',
            width: 80,
            sortable: true,
            dataIndex: 'lastname',
            field: {
                xtype: 'textfield'
            }
        }, {
            text: 'Address',
            width: 80,
            sortable: true,
            dataIndex: 'address',
            field: {
                xtype: 'textfield'
            }
        }, {
            text: 'City',
            width: 80,
            sortable: true,
            dataIndex: 'city',
            field: {
                xtype: 'textfield'
            }
        },{
            text: 'State',
            width: 80,
            sortable: true,
            dataIndex: 'state',
            field: {
                xtype: 'textfield'
            }
        }]
    });
});

And there it is – you add all the above in a SCRIPT tag on the XPage the code for you first grid. We also add a little CSS to show the icon for the image and a div as a container. This is not the “best” way to do it, and we will discuss that in a later article but for the moment it is the easiest to understand being in the same XPage.

<script type="text/javascript" src="ext-4.1.1/ext-all-debug.js"></script>
	<link href="ext-4.1.1/resources/css/ext-all.css" rel="stylesheet" type="text/css" /></pre>
<style><!--
.icon-user {
    background-image: url(images/user.png) !important;
}

.icon-add {
    background-image: url(images/add.png) !important;
}

.icon-delete {
    background-image: url(images/delete.png) !important;
}
--></style>
<pre>
<script type="text/javascript">// <![CDATA[
Ext.require(['Ext.data.*', 'Ext.grid.*']);

Ext.define('Person', {
    extend: 'Ext.data.Model',
    fields: [{
        name: 'id',
        type: 'int',
        useNull: true
    },  'firstname', 'lastname', 'address', 'city', 'state']
});

Ext.onReady(function(){

    var store = Ext.create('Ext.data.Store', {
        autoLoad: true,
        autoSync: true,
        model: 'Person',
        proxy: {
            type: 'rest',
            url: 'xRestService.xsp/byFirstName',
            reader: {
                type: 'json',
                root: 'items'
            },
            writer: {
                type: 'json'
            }
        }
    });

    var grid = Ext.create('Ext.grid.Panel', {
        renderTo: 'gridHere',
        width: 600,
        height: 300,
        frame: true,
        title: 'Users',
        store: store,
        iconCls: 'icon-user',
        columns: [{
            header: 'First',
            width: 80,
            sortable: true,
            dataIndex: 'firstname',
            field: {
                xtype: 'textfield'
            }
        }, {
            text: 'Last',
            width: 80,
            sortable: true,
            dataIndex: 'lastname',
            field: {
                xtype: 'textfield'
            }
        }, {
            text: 'Address',
            width: 80,
            sortable: true,
            dataIndex: 'address',
            field: {
                xtype: 'textfield'
            }
        }, {
            text: 'City',
            width: 80,
            sortable: true,
            dataIndex: 'city',
            field: {
                xtype: 'textfield'
            }
        },{
            text: 'State',
            width: 80,
            sortable: true,
            dataIndex: 'state',
            field: {
                xtype: 'textfield'
            }
        }]
    });
});
// ]]></script></pre>
<div id="gridHere"></div>
<pre>

ext5

Demonstration

The EXTJS in XPages demonstration database can be found at http://demo.xomino.com/xomino/extjs.nsf

You will note that the menu does not look right – I know – one thing to discuss with the grids – their stylesheets…..we will see 🙂

This was tested in IE9, Safari, Chrome, Firefox, Chrome on Android and iPhone/iPad

Play with the columns and see what they can do – I will post again soon to discuss the capabilities of the basic grid further.

Download

The sample database that will accompany this series will always be available from the menu in the live demo database from this link – http://demo.xomino.com/xomino/Extjs.nsf

Conclusion

I think it is safe to say that this is a simple, fantastic, aesthetically pleasing, way of showing data on the screen to the users and hopefully this was a simple enough process to follow along with. This is a simple grid with not much functionality but as we will come to see – there are w whole world of different capabilities within the EXT grids and their plugins 🙂

Advertisements

EXTJS in XPages – the grid series

In this article I will introduce the blog series EXTJS in XPages and discuss why this important to your XPages development knowledge

Introduction

For ever and a day it has been the dream of Lotus Notes developers to be able to “modernize” their notes client applications and provide their clients an interactive user interface which is similar enough to the notes view interface to seem “familiar” but it different and “cool” enough to help them make the decision to modernize. I believe EXTJS used correctly, provides that user experience and compelling business demands to upgrade.

I have spent the last year introducing the world to the possibilities of jQuery in XPages and my next challenge/learning experience is EXTJS grids. I am on a learning curve with this JavaScript library and we can follow along together.

 

Recreating the notes view of data

Ahhhh the notes view – we have all used it, probably most of us for longer than the age of our oldest children – but the notes view still rocks………..until you want to display it on the web. In XPages we have the View Panel which is a great and simple way to get data onto the webpage in no time at all and have the users at least navigate to their desired documents. View Panels are good, and repeat controls are better, giving the developer more control over the format and layout of the data displayed while still serving the data from the trusty view on the back end. But do the user’s “dig it”? Maybe…I guess it depends on how well you have managed their high expectations 🙂 My guess is not really…

The view Panel?

A view panel does not look like a notes view. It does not function like a notes view. It really doesn’t provide the user with a simple way to find their data in a large set.

Score: Effort for the developer to create – 2 – 2 Experience for the user

The repeat control

A repeat control is better….well it should be from the user’s perspective, but is it really? It is actually harder for the developer to create a repeat control, but at the same time is more flexible than a view control to create a custom layout which meets the users requirements. But does it make a better user interface for the user ?- not unless the developer is also a graphics designer.

Score: Effort for the developer to create – 5 – 3 Experience for the user

 EXTJS Grids

Just take a quick look at grids in the http://dev.sencha.com/deploy/ext-4.0.0/examples/ library, I will wait for you………don’t get distracted by the cool charts, tabs, windows, layout managers and all that stuff your users don’t care about (riiight) let’s at least stay focused on the grids.

If you came back from that not being impressed by the examples then you need to either look again stupid, or walk away now.

EXTJS is more than a JavaScript library, it is a JavaScript Library collection which has been developed over many years and has grown into a one stop shop bonanza of amazing web interfaces.

In this series of articles I am going to show how some of the EXTJS grids can be easily used in XPages applications by simply looking at the examples and then applying what we know about them and XPages to implement a grid within the XPages environment.

Turn this

ext1

Into this

ext2

Score: Effort for the developer to create (the first time) – 7 – 9 Experience for the user

In about 60 minutes (give or take)

This is going to be a fun ride and I hope you enjoy it with me 🙂

 

Thank you

I have to say thank you to a great team of guys at PSC who I came to work with this past summer and introduced me to what they were doing with EXTJS:- Imran Bhaidani, Steve “Slobo” Lohja, Andrew Barickman and Troy Reimer. Imran was the grids genius and spent many many hours learning their capabilities. I know a thing or two now about EXTJS but I jumped into the project in the middle and I wanted to go back and learn it properly from scratch myself.  I am not using any of their code, this is all me, learning it as I go along 🙂

Where did my wordpress blog go? And how can I fix it?

Problem

This blog site was not coming up this morning – and it took me a while to figure out why not but here it is.

The blog is technically xomino.wordpress.com and that is redirected to xomino.com automagically by wordpress.

I set this up originally through GoDaddy by pointing my zone file at the xomino.wordpress.com site. Well guess what happened over the weekend – the IP address of the wordpress server changed!!

Solution

I saw this, this morning:

word1

My IP address for the site was not pointing at the xomino.wordpress.com IP address

A quick change to the GoDaddy Zone file et voila I have a blog again – woo hoo!!

word2

So if your wordpress site has gone down this weekend – you need to go through your registrar and change the IP address of the domain back to the new IP address.

Love it when a plan comes together – annoyed that the IP address of the wordpress server was changed with no notification

Searching a REST service – when the default is not everything

I have wasted too much time over the last couple of days figuring out why my REST service will only return 32 entries….

Problem
This would seam like a simple search using the viewJSONService and I would expect in my case to see 198 results

<xe:restService id="marky" pathInfo="marky">
	<xe:this.service>
		<xe:viewJsonService defaultColumns="true" viewName="ByName-First">
			<xe:this.search><![CDATA["Avenue"]]></xe:this.search>
		</xe:viewJsonService>
	</xe:this.service>
</xe:restService>

But I only get 32 – why???

Solution

Something simple, small and unassuming – searchMaxDocs. When it is not set the default is not All Matching Documents as I had assumed…

search1

If you do NOT set searchMaxDocs to ZERO you will never get more than 32 documents in your search results.

<xe:restService id="marky" pathInfo="marky">
	<xe:this.service>
		<xe:viewJsonService defaultColumns="true" viewName="ByName-First" searchMaxDocs="0">
			<xe:this.search><![CDATA["Avenue"]]></xe:this.search>
		</xe:viewJsonService>
	</xe:this.service>
</xe:restService>

Conclusion

The default number of results when searching a REST service is 32

Always set your searchMaxDocs=”0″

A couple of Tips on how to optimize your XPages REST Service

In this article will demonstrate how simple planning and insight can make your XPages EXTLib REST service more efficient.

Problem

Whatever you are using your REST service for, wither it is to feed charts, grids, other application or whatever it is, they take up data bandwidth and processing time.

Consider the following notes view

rest1

If I display that in a simple REST service one of the entries looks like this:


  {
      "@entryid":"1-45995DDEB92AA70C882579F0008280CB",
      "@unid":"45995DDEB92AA70C882579F0008280CB",
      "@noteid":"1A12",
      "@position":"1",
      "@siblings":200,
      "@form":"Contact",
      "$13":"Adriana Kerr",
      "FirstName":"Adriana",
      "LastName":"Kerr",
      "EMail":"adriana_kerr@renovations.com",
      "City":"Norwalk",
      "State":"CA",
      "Created":"2012-04-30T23:45:26Z",
      "$10":"Adriana Kerr"
  },

This batch of data takes up 462 bytes. The first 6 lines the attributes @entryid to @form you have no control over. But you do have controll over the others.

The field name displayed comes from the computed column value in the view. If you have a column which is just a single field the programmatic name will reflect the field Name.

rest2

But if you have a column value which not just a fieldname then you will have a programmatic id assigned

rest3

1) Taking control of the column names

The point is that these values can be changed

rest4

And in doing this your rest service changes:


  {
      "@entryid":"1-45995DDEB92AA70C882579F0008280CB",
      "@unid":"45995DDEB92AA70C882579F0008280CB",
      "@noteid":"1A12",
      "@position":"1",
      "@siblings":200,
      "@form":"Contact",
      "1":"Adriana Kerr",
      "2":"Adriana",
      "3":"Kerr",
      "4":"adriana_kerr@renovations.com",
      "5":"Norwalk",
      "6":"CA",
      "7":"2012-04-30T23:45:26Z",
      "8":"Adriana Kerr"
  },

And this batch of data takes up 426 bytes (as opposed to 462 before). This right there represents a saving of 8% which might not seem like much but this is only a small view with only a few columns with only short field name.

If you scale this up to a large view with 25 columns and large field names that can make 2000 bytes per entry – I just performed a quick test on one of my larger REST services and saved over 25% of the data (which is what inspired me to write the article).

“Soooo what” I hear you say – well consider 500 data entries in my world. That ramps up very quickly to a 1M data transfer – saving 25% on that is a significant bandwidth saving – not to mention the reduction in load on your browser having to parse that amount of data.

XPiNC memory 

This is especially pertinent in the XPiNC world which I have the unfortunate pleasure of playing with – it is Firefox 3.5 and has memory issues. I have nothing quantifiable to back this up but there is no doubt about it in my mind – a large data volume causes issues in the browser memory management. This is highlighted by a slowdown of the interface – you know the things we USED to have a problem with but don’t any more with Chrome and Firefox browsers.

2) Better control and only send what you need….

This should be common sense but if you are upgrading an existing database and want to take advantage of existing views, might not always be apparent.

By default the view send all the columns when you select  defaultColumns=”true”

<xe:restService id="restService3" pathInfo="contactjson">
	<xe:this.service>
		<xe:viewJsonService viewName="AllContacts"
				var="entry" contentType="text/plain"
				count="10" defaultColumns="true">
		</xe:viewJsonService>
	</xe:this.service>
</xe:restService>

By going into the all properties and selecting the columns you want you have more control and save bandwidth/memory management

In this case I put the column names back and programmatically added the columns to the REST service

rest5

And then I added the id and firstName column programmatically to the REST service – but as you can see I forced the name of the field in this case to be 1 and 2

<xe:restService id="restService3" pathInfo="contactjson">
	<xe:this.service>
		<xe:viewJsonService viewName="AllContacts"
				var="entry" contentType="text/plain"
				count="10" defaultColumns="false">
			<xe:this.columns>
				<xe:restViewColumn name="1" columnName="id"></xe:restViewColumn>
				<xe:restViewColumn name="2" columnName="firstName"></xe:restViewColumn>
			</xe:this.columns>
		</xe:viewJsonService>
	</xe:this.service>
</xe:restService>

This creates the optimized JSON feed with the smaller field name, without changing it at the column.

  {
      "@entryid":"1-45995DDEB92AA70C882579F0008280CB",
      "@unid":"45995DDEB92AA70C882579F0008280CB",
      "@noteid":"1A12",
      "@position":"1",
      "@siblings":200,
      "@form":"Contact",
      "1":"Adriana Kerr",
      "2":"Adriana",
  },

This code is (for example) all that is needed from this view and at 263 Bytes it is 39% smaller than the original
Caveats

  • If the bulk of your data is in the field value and not in the field names you will not see such a large improvement.
  • Changing the column names in a view needs to be done carefully as those names could be referenced elsewhere.
  • When you have a $10 programmatic column name and you change the formula in the column the name is recomputed – be aware that this can change

XPiNC browser url format – and why my demo failed at IBMConnect (part 2)

So this is article is to expand a little on the post I shared on Friday about my demo failure at IBMConnect. I will demonstrate why the problem occurred and what you can do about it. ALL thanks go to Simon Reid who pointed me at the reason once I had found the fix.

The Problem

I had a demonstration http://demo.xomino.com database which shows how some jQuery plugins can be used in XPages. The links within the menu “were” constructed as follows:

isExternal:=@If(@Right(Link; "http")!=""; @True; @False);

theLink:="<a href=\""+@If(isExternal; Link; "/"+@WebDbName+Link)+"\">"+LinkTitle+"</a>";

"<p>" + theLink+"</p>"+@NewLine

The point of this was to open up a link to an external site correctly and point a link to this database appropriately. The use of “/”+@WebDbName is to ensure that the database would still work when it was download and put on someone else’s server.

Works like a charm in the web
FAILED in XPinc

The Reason

As Simon Stated in his reply to my previous post – the XPiNC browser adds a /xsp/ to the front of the url.

To prove this I created a very simple link in an XPage:

<a href="javascript: alert(location.href)>Click Me</a>

And when I clicked the link in the XPiNC browser I got this…

XSPnooooo

And look at that “/xsp/xomino/jQinX.nsf” – there it is the xsp added to the URL

that is NOT what @WebDBName returns (/xomino/jQinX.nsf) and that is the root cause of the fail.

The Solution

There are a number of solutions and it really depends on what works for you – the fact that you are reading this you know about the issue and you need to work out which solution is best for you – here are some options if you need XPiNC support:

  1. Don’t user @WebDbName to get the database path – plain and simple.
  2. If you are able to – compute the links to the other XPages using the link control. I personally despise the link control because it is limiting and does not slot well into jQuery plugins – but it is an option.

The following code creates a link to the LongPress demo page – this works in XPiNC and on the Web

	<xp:link escape="true" text="Long Press Link" id="link2" value="/xLongPress.xsp" title="Long Press Link"></xp:link>

The link created looks like this in XPiNC

	<a id="view:_id1:_id2:_id52:link2" href="/xsp/xomino%5CjQinX.nsf/xLongPress.xsp" class="xspLink" title="Long Press Link">Long Press Link</a>

And looks like this in a browser

	<a id="view:_id1:_id2:_id52:link2" href="/xomino/jQinX.nsf/xLongPress.xsp" class="xspLink" title="Long Press Link">Long Press Link</a><br>

Unfortunately I want my links in documents in a view so that I can look them up – so I can’t use the xp:link

Here are some other options

  1. Use links which are not relative to the databasePath on the server. In my case I changed the menu links to be relative to each other “xPageSlide.xsp” rather than /xomino/jQinX.nsf/xPageSlide.xsp
  2. Use jQuery or dojo to do some form of XPiNC detection and manipulate the links after they have been sent to the page. You could do this by detecting if /xsp/ was the start of your location.path variable and adding it to all the link you cared about – this is dangerous and risky – not favorite which is also why I have no example.

 

Conclusion

The XPiNC browser adds /xsp/ to the front of all URLs – be aware of it

The solution depends on your needs – but for normal links to other XPages use the xp:link control which will do the link figuring out on it’s own

If I had not presented this and FAILED at IBMConnect I would not have had the conversation about it – and would not have learned something very important like this – AWESOME!

 

General Comment

How you deal with failure says a lot about your personality – if it drives you to be better then good for you.

This failure is a learning experience which was handled on stage by laughter, and I hope by this blog post as a learning experience for all 🙂

 

jQuery in XPages @IBMConnect demo fixed – and the problem explained

So I am in the middle of doing my presentation @ IBMConnect 2013 and everything is going relatively well…..and someone asks a question

Question: “Hey Marky – does jQuery work in XPiNC?”

Marky: “sure it does watch this”

#SmugWithConfidence
Click Click
#FailWhale
404 – proxyServer Error

WTF ????

#PresenterPanic??!!
#CrowdLaughter (mostly from my loyal friends)….
#OhTheShame

WTF??? 😦

I told the audience it was because I had obviously broken it and I thought I knew the problem – i apologized profusely and said that I would fix it ASAP. Well today is ASAP 😛

It now works – you can download the sample database from http://demo.xomino.com and run it in XPiNC

The Problem

Some genius…..had messed with the menu for IBMConnect and changed the menu links so they looked like this

	<a href="/xomino/jQinX.nsf/xPageSlide.xsp">PageSlide</a>

And that works perfectly fine in a web browser – but locally on the XPiNC webserver causes the ProxyServer error above!! The fix is simple – change it so that it is not relative to the server.

The issue is the /xomino link which is interpreted by the browser as http://demo.xomino.com, SLASH xomino. This is good technique because it doesn’t name the server and is therefore portable to other servers. But apparently fails in the local XPiNC server.

Why? I have no idea unfortunately but I am more concerned with the solution than the cause. Changing the links as follows and we have a working demo database again

	<a href="xPageSlide.xsp">PageSlide</a>

as all the demo pages are at the same level in the URL this is not a problem. Weird eh!

I will talk to some people smarter than I and see if I can find out why this problem is caused by the  XPiNC server – but be aware that you should not use links relative to the XPiNC server….weird!

I am very sorry this screwed up in the demo but hopefully enough people will forgive me and we can all carry on 🙂 🙂