I have found a very interesting website: You might not need jQuery. It contains a lot of usefull solutions for the different IE versions.
Posts Tagged: ‘JQuery’
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.
XPages: Dojo 1.8.1 & jQuery Mobile 1.3.1
As David Leedy got into trouble with Dojo 1.8.1 and jQuery Mobile 1.3.1 and after reading the follow up from Ulrich Krause with the analysis of the problem, I thought that this problem is caused from the AMD loader of Dojo. That’s why I changed the loading order (by forcing the jQuery libraries to get loaded before Dojo), and this seems to work.
I have tested it in IE 8, FF 20.0 and Chrome 26.0.
Here is my test XPage:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view
xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.properties>
<xp:parameter
name="xsp.resources.aggregate"
value="true" />
</xp:this.properties>
<xp:this.resources>
<xp:styleSheet
href="http://code.jquery.com/mobile/1.3.1/jquery.mobile-1.3.1.min.css" />
<xp:headTag
tagName="script">
<xp:this.attributes>
<xp:parameter
name="type"
value="text/javascript" />
<xp:parameter
name="src"
value="http://code.jquery.com/jquery-1.9.1.min.js" />
</xp:this.attributes>
</xp:headTag>
<xp:headTag
tagName="script">
<xp:this.attributes>
<xp:parameter
name="type"
value="text/javascript" />
<xp:parameter
name="src"
value="http://code.jquery.com/mobile/1.3.1/jquery.mobile-1.3.1.min.js" />
</xp:this.attributes>
</xp:headTag>
</xp:this.resources>
<div
data-role="page"
class="jqm-demos"
data-quicklinks="true">
<div
data-role="content"
class="jqm-content"
id="contentRefresh">
<h2
id="accordion-markup">
<script>document.write(dojo.version)</script>
</h2>
<div>
<div
data-role="collapsible-set"
data-theme="c"
data-content-theme="d">
<div
data-role="collapsible">
<h3>Section</h3>
<p>
<xp:label
value="#{javascript:java.lang.System.currentTimeMillis()}"
id="labelRefresh" />
</p>
</div>
</div>
</div>
<a
href="#"
onclick="XSP.partialRefreshGet('#{id:labelRefresh}')"
data-role="button">Refresh Label</a>
</div>
</div>
</xp:view>
And this is the result:
The XSP object is loaded correctly and Partial Refreshs are executed correctly:
It is required that the resource aggregation is enabled. More details can be found here.
XPages: Capture Signatures with the jQuery-Plugin ‘jSignature’
In one of my current projects it is one of the goals that the members of the field staff have the possibility to sign a report directly on their iPad. After some research I found the very cool jQuery plugin jSignature, which easily allows to add a signature capture field to a XPage.
The plugin is very easy to use: Just add a <div> to your XPage and initialize the plugin with the .jSignature() constructor and that’s it! The API is very simple, but provides everything needed: The captured signatures can be stored in different formats like PNG or SVG or as native Vector. Additionally they can be encoded as BASE64 or BASE30. The data can restored as easy as they can be saved: Just one API call and it’s done.
To save the signatures to a Notes document, the resulting data can be copied to a hidden input field. In the provied example above I additionally added the format of the signature. For a better handling of the generated data I decided to store the data in the SVG format, because this allows to save it directly in standard notes text field (without having problems because of the 32 K limit). This works well and the data can be displayed in standard browsers without any problems. Only in XPiNC this will not work, because the SVG format is not supported. PDFs doesn’t support SVG too, that’s why I created a converted agent using the Apache Batik framework.
I will add a demo database asap. A description of parameters to customize the plugin can be found here.
P.S. The compressed Version is not running on teamstudio Unplugged. You have to use the uncompressed Version. The example uses the x$ function of Mark Roden.
XPage example
<?xml version="1.0" encoding="UTF-8"?> <xp:view xmlns:xp="http://www.ibm.com/xsp/core"> <xp:this.resources> <xp:script src="/x$.js" clientSide="true"></xp:script> <xp:script src="/jSignatureHandler.js" clientSide="true" /> <xp:styleSheet href="/jSignature.css"></xp:styleSheet> </xp:this.resources> <xp:this.data> <xp:dominoDocument var="documentSig" formName="frmSignature" /> </xp:this.data> <script src="js/jquery-1.8.1.min.js" /> <script src="js/jSignature/jSignature.min.js" /> <xp:div id="jSignature" style="width:400.0px"></xp:div> <xp:scriptBlock id="scriptBlockSignature"> <xp:this.value><![CDATA[ $(document).ready(function() { sigHandler.init( "#{id:jSignature}", "#{id:inputHiddenSignatureData}", "#{id:inputHiddenSignatureFormat}" ); } )]]></xp:this.value> </xp:scriptBlock> <xp:button id="button1" value="Reset"> <xp:eventHandler event="onclick" submit="false"> <xp:this.script><![CDATA[ sigHandler.reset(); ]]></xp:this.script> </xp:eventHandler> </xp:button> <xp:button id="button2" value="Save"> <xp:eventHandler event="onclick" submit="true" refreshMode="complete"> <xp:this.script><![CDATA[ return sigHandler.save(); ]]></xp:this.script> <xp:this.action> <xp:saveDocument var="documentSig" /> </xp:this.action> </xp:eventHandler> </xp:button> <xp:inputHidden id="inputHiddenSignatureData" value="#{documentSignature.SignatureData}" /> <xp:inputHidden id="inputHiddenSignatureFormat" value="#{documentSignature.SignatureFormat}" /> </xp:view>
CSJS library “jSignatureHandler.js”
/** * signatureHandler * JS Object for handling the signature data * Requires jSignature jQuery plugin & special * function "x$" from Mark Roden * * @author Sven Hasselbach * @category JavaScript * @category jQuery * @category UI * @version 1.0 */ var signatureHandler = function() { _module = "signatureHandler"; _idJSignature: null; // DOM id of JSignatue DIV _idItemData: null; // DOM id of INPUT for signature data _idItemFormat: null; // DOM id of INPUT for signature format _objDOMJSignature: null; // handle to DOM object _objDOMItemData: null; // handle to DOM object _objDOMItemFormat: null; // handle to DOM object _maxSize = 32000; // max characters to store (32K limit!) _format = "svg"; // format used /** * set DOM id of JSignature DIV * @param String id */ this.setJSignatureId = function( id ){ this._idJSignature = id; } /** * get DOM id of JSignature DIV * @return String id */ this.getJSignatureId = function(){ return this._idJSignature; } /** * set DOM id of data item * @param String id */ this.setItemDataId = function( id ){ this._idItemData = id; } /** * get DOM id of data item * @return String id */ this.getItemDataId = function(){ return this._idItemData; } /** * set DOM id of format item * @param String id */ this.setItemFormatId = function( id ){ this._idItemFormat = id; } /** * get DOM id of format item * @return String id */ this.getItemFormatId = function(){ return this._idItemFormat; } /** * get handle to DOM object of JSignature DIV * @return Object */ this.getJSignatureDOMObj = function(){ return this._objDOMJSignature; } /** * set handle to DOM object of JSignature DIV * @param Object */ this.setJSignatureDOMObj = function( obj ){ this._objDOMSignature = obj; } /** * get handle to DOM object of data item * @return Object */ this.getItemDataDOMObj = function(){ return this._objDOMItemData; } /** * set handle to DOM object of data item * @param Object */ this.setItemDataDOMObj = function( obj ){ this._objDOMItemData = obj; } /** * get handle to DOM object of format item * @return Object */ this.getItemFormatDOMObj = function(){ return this._objDOMItemFormat; } /** * set handle to DOM object of format item * @param Object */ this.setItemFormatDOMObj = function( obj ){ this._objDOMItemFormat = obj; } /** * initialize object * * @param String id of jSignature DIV * @param String id of data item INPUT * @param String id of format item INPUT * */ this.init = function( idJSig, idItemData, idItemFormat ){ try{ // init jSignature this._idJSignature = idJSig; this._objDOMSignature = x$( this._idJSignature ) ; this._objDOMSignature.jSignature(); // init data item this._idItemData = idItemData; this._objDOMItemData = x$( this._idItemData ); // init format item this._idItemFormat = idItemFormat; this._objDOMItemFormat = x$( this._idItemFormat ); return true; }catch(e){ var errMsg = _module + "::" + arguments.callee.name + "\n"; for( p in e ){ errMsg += p + ": '" + e[p] + "'\n"; } console.error( "Error!\n\n" + errMsg ); return false; } } /** * reset jSignature */ this.reset = function(){ try{ this._objDOMSignature.jSignature("reset"); return true; }catch(e){ var errMsg = _module + "::" + arguments.callee.name + "\n"; for( p in e ){ errMsg += p + ": '" + e[p] + "'\n"; } console.error( "Error!\n\n" + errMsg ); return false; } } /** * saves the data from jSignature * */ this.save = function(){ try{ var datapair = this._objDOMSignature.jSignature( "getData", _format ); var format = "data:" + datapair[0]; var data = datapair[1]; // check max size! if( data.length > _maxSize){ alert( "The size of the signature is too large. Please retry!" ); return false; } this._objDOMItemData.val( data ); this._objDOMItemFormat.val( format ) return true; }catch(e){ var errMsg = _module + "::" + arguments.callee.name + "\n"; for( p in e ){ errMsg += p + ": '" + e[p] + "'\n"; } console.error( "Error!\n\n" + errMsg ); return false; } } } // init JS instance sigHandler = new signatureHandler();
XPages: JQuery Gantt Module
In the past weeks I was trying something new, at least new for me…
I realised that using jQuery in Xpages is a wonderful idea. Mark Roden has a nice blog with great things you can do with jQuery in XPages. You can find it here: Xomino Also check out his demo site: Xomino JQinX Demo
In one project I wanted to use a gantt chart. I want to write this article to show you how it’s done and warn you about the problems I had.
Let’s start.
To use jQuery you have to download it and integrate it in your database. Build your own version of jQuery here: JQuery Download It is the download page of JQuery UI, a set of many nice plugins and modules for jQuery. A version of the base jQuery is included. You can also configure a custom theme for your jQuery modules. It is on the right menu when you follow the link “design a custom theme“
For the gantt chart I am using this module: Taitems JQuery Gantt Chart Module
So, now you have to integrate these files into your database. You can do this either via the resource category in the database, or via the PackageExplorer (it is an Eclipse View) I recommend using the package explorer, because you can manage your files more easily. Simply drag and drop your files and create some folders to organize them.
Now you have to load them into your application. You can do this easily via a theme with the following statement:
<resource> <content-type>application/x-javascript</content-type> <href>js/jquery-1.7.min.js</href> </resource>
Or you load them directly into your code via the resource properties of the XPage/ Custom Control
<xp:script src="js/jquery-ui-1.8.23.custom.min.js" clientSide="true"/>
You have to do this for the jQuery base module, the UI module and the gantt module, also you have to load the CSS files.
First problem I had to face: The gantt module doesn’t work with every jQuery version. I was using jQuery 1.8.0. But when using this module, I had an error message which complained about some undefined variables (TypeError: m is undefined) Using the 1.7 version was a solution to this.
Now we can get started. On the page of the gantt module is an easy manual how to use this module. It is quite self explaining.
To implement a gantt chart, you only have to put in these few lines of code (client side of course)
<xp:scriptBlock id="scriptBlock1">
<xp:this.value>
// + "/ganttJSONBuilder?OpenAgent";
// + "/js/data.js";
jQuery( function() {
var dataPath = location.href.substring(0,
location.href.lastIndexOf('/') + 1)
+ "/ganttJSONBuilder?OpenAgent;
$(".gantt").gantt( {
source : dataPath
});
});]]>
<<!--xp:this.value>
<<!--xp:scriptBlock>
As data source the module uses JSON data. Use this site to validate your JSON data easily. The structure of your data is explained in the manual site of the module.
Be aware, the module expects a URL as data source. That means you can’t use server side Javascript in that scriptblock to generate the data source. So, we have two options on how to load the data into the module, either via a file which you can put into the WebContent folder in your database, or you call an agent. The agent is probably the better option because you sure want to load data dynamically.
Using an agent to achieve this is basically easy, you only have to write your JSON string into a variable and then you do this:
PrintWriter pw = getAgentOutput(); pw.println(output);
The difficulty of generating the JSON string itself depends on your data. In my case it was a simple project management, so some tasks, projects and editors. I used a view. I looped the entries and parsed the information into a JSON String.
There are some nice little things you should know when doing so.
The first line of your JSON string has to be the content type of the output, for JSON it is: Content-type: application/json; charset=utf-8\n
Also, it is very important that you use line breaks in the string, simply by using \n. If you aren’t doing that, your JSON string will not be properly interpreted and the module shows nothing but a nice grey bar.
Easily to overlook, but also important, in JSON you have to use a comma to separate your entries. Be sure you have no comma behind your last entry or parameter. It will result in a misinterpretion of the data.
The next important thing is, in the JSON data, you have to provide all entries defined in the manual. That means, if you forgot to write one of the parameters into the string, it is valid, but not recognized by the gantt module and isn’t doing anything. All of them need a value. providing nothing will have the same result: Nothing =)
I wanted to have my projects and tasks separated. In one line the project and then in the other lines the related tasks. This is possible by writing an extra entry to your JSON string, in which you only write the information about the project. When writing the tasks of the project, you probably don’t want to write the project in front of each task. If you think you can define an empty string for the name parameter of the JSON entry, you are wrong. The module needs a value of at least one character. Using an empty string results in not displaying the chart and some nice error messages. Using a space (” “) is the solution.
Also, you have to provide a start and end date for each entry. I wanted to have no duration for the project, so I didn’t provide the “from” and “to” parameter, it rewarded me with some other javascript errors. So I used the whole duration of the project for the project entry, it’s a good feature anyway.
A nice feature: You can use HTML in your JSON parameters, so, if you use
tags for example in your desc parameter and some formatting tags, you can make the mouseOver of the entries look more beautiful and put some more information into it.
If you think the styleClasses available for your entries in the chart (ganttRed, ganttGreen, ganttOrange) are not that beautiful, or you want to use some more, you can easily define some. There is a css file in the module. Looks like this:
.fn-gantt .ganttOrange { background-color: #FCD29A; } .fn-gantt .ganttOrange .fn-label { color: #714715 !important; }
Copy those entries and do your own styles, the class at the end of the definition is recognized by the module, so you can use it directly in the “customClass” parameter of your JSON.
But there is one big problem when using this module. Above the chart, I have some buttons. I wanted to load different charts with those buttons. Easily done I thought, just firing a partial execute on the chart container to set another data source or something. But, nothing happens, in fact really nothing, not even an event which I could see in my FireBug. Some other buttons with only clientside code are working fine. All other buttons on the whole page are working fine, only those I have directly above my chart didn’t wanted to work. I haven’t figured out what’s the problem, if I find it, I will let you know.
Another problem is that the gantt chart is not working in the Notes Client. If I open the Xpage in my Notes client, the chart isn’t loaded, only a nice grey bar. Other jQuery modules are working fine, for example the accordion or flip is working, but not the module. If you want to use it in the NotesClient you should think about another solution… or wait until I find a solution for this problem =)
Another thing you should consider are loading times. The loading time is increasing dramatically proportional on how many entries you display (rowsPerPage parameter in the gantt function) and how many years they cover. If you want to display some tasks from a period of 10 years or more, you have to wait a bit. I tried a period of 30 years… not a good idea, my browser crashed… after about 5 minutes of loading.
So, I think that’s all I know about the module and how to use it. Despite all the small and big problems I have a nice application with some really nice gantt charts. If you are interested in such an application, you should visit this page (Cobalt Software GmbH), the new version should be available soon. Ok, sorry for the advertisement…
If you have any questions or solutions for my unsolved problems, the comment section is all yours =)
Hope it was useful for some of you.
Filed under: Notes & XPages Tagged: Chart, Cobalt Software GmbH, CSS, Datasource, Gantt, Javascript, JQuery, JSON, Mark Roden, Notes, Parameter, Plugin, Serverside Javascript, Taitem, Tools, Xomino, XPages, Xpages in NotesClient, XpInc
