how to set editBox focus after panel/page refresh - xpages

On my xPage I have just a panel and editBox inside.
<xp:panel id="panel1">
<xp:inputText id="inputText1">
<xp:eventHandler event="onkeydown" submit="true"
refreshMode="partial" refreshId="panel1">
</xp:eventHandler></xp:inputText>
</xp:panel>
by pressing Enter key in editBox I want to refresh my panel1 and then return focus to my edit box inputText1.
P.S. it's supposed there are other components on that panel this is why I want to refresh it once user enter something into editbox and press Enter key. Any focus() set code doesn't work once you refresh the panel either into onkeydown event or in onComplete of eventhandler. But you can set the focus if you ouside of that panel (e.g. new button with onclick event "...focus())"

Pressing the enter key is a normal function to receive via a JavaScript listener. Triggering a partial refresh can also be done from JavaScript via the client-side XSP object. Here's how a basic implementation would look.
<xp:panel
id="panel1">
<xp:inputText
id="inputText1">
<xp:eventHandler
event="onkeydown"
submit="false"
id="eventHandler1"
execMode="partial">
<xp:this.script><![CDATA[if(event.keyCode == 13){
event.preventDefault();
XSP.partialRefreshPost("#{id:panel1}",{
onComplete: function(){
document.getElementById("#{id:inputText1}").focus();
}
});
}]]></xp:this.script>
</xp:eventHandler>
</xp:inputText>
</xp:panel>
Edit:
I forgot to use event.preventDefault() on the enter action. I confirmed this as working in a sample XPage, shown here. Alternatively, in place of the focus call, you could use a select to highlight existing text, or do something else to put the cursor at the end of the field.

I wrote a general purpose snippet a while back that tries to counteract the effect of partial refresh on focus states. If I understand your issue correctly, this would remove the need to "hard code" which field you want to have focus after the refresh. You will also need the snippet for hijacking partial refreshes.
I think the only thing you need to to to the field after you've added the two snippets is to make sure that the event handler/partial refresh only fires on the enter key.
In client script: return ( thisEvent.keyCode === 13 );
Code snippet for hijacking partial refreshes:
function hijackAndPublishPartialRefresh(){
// Hijack the partial refresh
XSP._inheritedPartialRefresh = XSP._partialRefresh;
XSP._partialRefresh = function( method, form, refreshId, options ){
// Publish init
dojo.publish( 'partialrefresh-init', [ method, form, refreshId, options ]);
this._inheritedPartialRefresh( method, form, refreshId, options );
}
// Publish start, complete and error states
dojo.subscribe( 'partialrefresh-init', function( method, form, refreshId, options ){
if( options ){ // Store original event handlers
var eventOnStart = options.onStart;
var eventOnComplete = options.onComplete;
var eventOnError = options.onError;
}
options = options || {};
options.onStart = function(){
dojo.publish( 'partialrefresh-start', [ method, form, refreshId, options ]);
if( eventOnStart ){
if( typeof eventOnStart === 'string' ){
eval( eventOnStart );
} else {
eventOnStart();
}
}
};
options.onComplete = function(){
dojo.publish( 'partialrefresh-complete', [ method, form, refreshId, options ]);
if( eventOnComplete ){
if( typeof eventOnComplete === 'string' ){
eval( eventOnComplete );
} else {
eventOnComplete();
}
}
};
options.onError = function(){
dojo.publish( 'partialrefresh-error', [ method, form, refreshId, options ]);
if( eventOnError ){
if( typeof eventOnError === 'string' ){
eval( eventOnError );
} else {
eventOnError();
}
}
};
});
}
Code snippet for remembering focus states:
dojo.addOnLoad(function(){
dojo.subscribe( 'partialrefresh-init', function(){
// setTimeout needed to make it work in Firefox
setTimeout(function(){
var activeElementId = document.activeElement.id;
var focusSubscription = dojo.subscribe( 'partialrefresh-complete', function(){
// Only set focus if field hasn't been overwritten/lost focus
if( document.activeElement.id !== activeElementId ){
var activeElement = dojo.byId(activeElementId);
if( activeElement && /INPUT|SELECT|TEXTAREA/.test( activeElement.nodeName ) ){
// Set focus to element/select text
activeElement.focus();
if( activeElement.nodeName !== 'SELECT' ){
activeElement.select();
}
}
}
// Unsubscribe after focus attempt is done
dojo.unsubscribe( focusSubscription );
});
// In case of error -> remove subscription
var errorSubscription = dojo.subscribe( 'partialrefresh-error', function(){
dojo.unsubscribe( focusSubscription );
});
}, 0 );
} );
});

Related

AddEventListener to SharePoint Modal window, so that parent window can execute a function, declared inside the Modal

The following code, included in $(document).ready of the modal window, does not work. Apparently the iframe of the SharePoint modal window has not yet been loaded into DOM, when the addEventListener fires out.
What would be the correct approach to handle this?
window.addEventListener("message", function(event) {
if(event.data == "openpi");{
alert(1)
}
});
Thank you!
There is dialogReturnValueCallback option in SP.UI.ModalDialog.showModalDialog, you could get the value from dialog and then used in parent window.
<script type="text/javascript">
//******** Dialog with Data from Pop Up Starts Here ***********/
function openDialogAndReceiveData(tUrl, tTitle) {
var options = {
url: tUrl,
title: tTitle,
dialogReturnValueCallback: onPopUpCloseCallBackWithData
};
SP.UI.ModalDialog.showModalDialog(options);
}
function onPopUpCloseCallBackWithData(result, returnValue) {
if(result== SP.UI.DialogResult.OK)
{
SP.UI.Status.removeAllStatus(true);
var sId = SP.UI.Status.addStatus("Data successfully populated to text boxes from Pop-up");
SP.UI.Status.setStatusPriColor(sId, 'green');
document.getElementById('<%= txtData1.ClientID %>').value = returnValue[0];
document.getElementById('<%= txtData2.ClientID %>').value = returnValue[1];
}else if(result== SP.UI.DialogResult.cancel)
{
SP.UI.Status.removeAllStatus(true);
var sId = SP.UI.Status.addStatus("You have cancelled the Operation !!!");
SP.UI.Status.setStatusPriColor(sId, 'yellow');
}
}
//******** Dialog with Data from Pop Up Ends Here ***********/
</script>
Check here for details

Input multiple with tags without autoCompletion

I have two inputs.
I want the two inputs to have the same look and feel see below:
The first input use autocomplete and allows the user to select a list of terms => I use p:autocomplete (see Primefaces documentation on autocomplete)
This input works fine.
For the second input, I would like to have the same display but without any autocompletion : the user just enter a list of terms with no autocompletion at all.
I tried to have a fake autocomplete that return the value given by the user but it is too slow and the behaviour is not correct when the user quit the input.
Any idea is welcome.
After a quick look at the PrimeFaces javascript code of the autoComplete and a few hours experimenting with it, I came up with a solution. It involves overriding the bindKeyEvents and in it deciding to call the original one or not, adding detection for the space key ('selecting a tag') and when pressed, add the tag and fire the selectionEvent (if ajax is used). Place the following code in your page or in an external javascript file
<script>
//<![CDATA[
if(PrimeFaces.widget.AutoComplete) {
PrimeFaces.widget.AutoComplete = PrimeFaces.widget.AutoComplete.extend ( {
bindKeyEvents: function() {
if (this.input.attr('data-justTags')) {
var $this = this;
this.input.on('keyup.autoComplete', function(e) {
var keyCode = $.ui.keyCode,
key = e.which;
}).on('keydown.autoComplete', function(e) {
var keyCode = $.ui.keyCode;
$this.suppressInput = false;
switch(e.which) {
case keyCode.BACKSPACE:
if ($this.cfg.multiple && !$this.input.val().length) {
$this.removeItem(e, $(this).parent().prev());
e.preventDefault();
}
break;
case keyCode.SPACE:
if($this.cfg.multiple) {
var itemValue = $this.input.val();
var itemDisplayMarkup = '<li data-token-value="' +itemValue + '"class="ui-autocomplete-token ui-state-active ui-corner-all ui-helper-hidden">';
itemDisplayMarkup += '<span class="ui-autocomplete-token-icon ui-icon ui-icon-close" />';
itemDisplayMarkup += '<span class="ui-autocomplete-token-label">' + itemValue + '</span></li>';
$this.inputContainer.before(itemDisplayMarkup);
$this.multiItemContainer.children('.ui-helper-hidden').fadeIn();
$this.input.val('').focus();
$this.hinput.append('<option value="' + itemValue + '" selected="selected"></option>');
if($this.multiItemContainer.children('li.ui-autocomplete-token').length >= $this.cfg.selectLimit) {
$this.input.css('display', 'none').blur();
$this.disableDropdown();
}
$this.invokeItemSelectBehavior(e, itemValue);
}
break;
};
});
} else {
//console.log("Original bindEvents");
this._super();
}
}
});
}
//]]>
</script>
For deciding on when to call the original one or not, I decided to use a passThrough attribute with a data-justTags name. e.g. pt:data-justTags="true" (value does not matter, so pt:data-justTags="false" is identical to pt:data-justTags="true"). A small html snippet of this is:
<p:autoComplete pt:data-justTags="true" multiple="true" value="#{myBean.selectedValues}">
And do not forget to add the xmlns:pt="http://xmlns.jcp.org/jsf/passthrough" namespace declaration.
I found a component that could do the job : http://www.butterfaces.org/tags.jsf

Display Error Control works only once

I've one required combobox "PriceList" associated with display error control.
I've anothe field "Price" with OnChange event to set required property of "Pricelist". If price is entered the "Pricelist" combobox required property set to false else on blank, "Pricelist" is enabled.
Both fields and xpage have disabled client validation off.
The combox displays error message in the beginning when document is created. If I change and blanked the value of "price", "pricelist" 's error control does not display message though required property is true.
What is issue here?
Pricelist code:
<xp:comboBox
id="comboBox7"
value="#{document1.PList1}"
style="width:99.0px"
disableClientSideValidation="true">
<xp:this.validators>
<xp:validateRequired
message="Required">
</xp:validateRequired>
</xp:this.validators>
<xp:this.required><![CDATA[#{javascript:var price11:com.ibm.xsp.component.xp.XspInputText = getComponent("price11");
var a=price11.getValueAsString()
if (a == ""){
return true;
}else{
return false;
}}]]></xp:this.required>
<xp:this.disabled><![CDATA[#{javascript:var price11:com.ibm.xsp.component.xp.XspInputText = getComponent("price11");
var a=price11.getValueAsString();
if ( a==""){
return false;
} else {
return true;
}}]]></xp:this.disabled>
<xp:selectItems>
<xp:this.value><![CDATA[#{javascript:var result = [];
var pricelist = #DbLookup("" , "Keywords","Price List", 2)
result.push("")
for (var i = 0; i < pricelist.length; i++) {
var eachName = pricelist[i];
result.push(eachName);
}
return result;}]]></xp:this.value>
</xp:selectItems>
</xp:comboBox>
Price code:
<xp:inputText
value="#{document1.Price1}"
id="price11"
style="width:80px"
required="true"
disableClientSideValidation="true">
<xp:this.validators>
<xp:validateRequired
message="Required field">
</xp:validateRequired>
</xp:this.validators>
<xp:eventHandler event="onchange" submit="true" refreshMode="complete">
<xp:this.action>
<xp:executeScript>
<xp:this.script><![CDATA[#{javascript:var comboBox7:com.ibm.xsp.component.xp.XspSelectOneMenu = getComponent("comboBox7");
var price11:com.ibm.xsp.component.xp.XspInputText = getComponent("price11");
var a=price11.getValueAsString();
if(a !=="" ){
//if(comboBox7.isRequired()==true){
//comboBox7.setRequired(false);
//}
//var result = [];
//var pricelist = #DbLookup("" , "Keywords","Price List", 2)
//result.push("")
//for (var i = 0; i < pricelist.length; i++) {
//var eachName = pricelist[i];
//result.push(eachName);
//}
//comboBox7.setValue(result);
comboBox7.setRequired(false);
comboBox7.setDisabled(true);
} else {
comboBox7.setDisabled(false);
comboBox7.setRequired(true);
}
}]]></xp:this.script>
</xp:executeScript>
</xp:this.action></xp:eventHandler></xp:inputText>
I suggest using the SSJS debugger or print statements to work out what's happening here. The code seems very convoluted. First of all, you have code that does setRequired on the ComboBox to true or false. But that can only run if validation passes. It also does a full refresh, so it appears you're reloading the page. Furthermore, you are calculating the required property on the combo box, so even if you do a partial refresh and you set it as required or not, the RenderResponse phase will recalculate the required property and override whatever state is set by the onChange event. The same is true for the disabled property.
If you set a component as required, validation for that refresh will already have occurred. So it will only be required the next time the page is submitted by the user. At which point validation will fail, so unless you're disabling validators, you cannot set it to not disabled. But disabling validators is done server-side, before values are entered by the user.
My recommendation would be to move your validation to your save method and work from the underlying datasource rather than the components. If you want to mark components as valid or not, then there is a setValid() method and there are blog posts about adding a FacesMessage to indicate an error and binding it to a component.
For a more advanced method, Tim Tripcony did a NotesIn9 about using the binding property to link components for this kind of related validation. But that will require Java.

Trying to open another xPage from a basicContainerNode

I have a navigation custom control that I want to link to specific documents in a database. I tried using a pageTreeNode but that opened the link when the page loaded. I was told to use a basicContainerNode which allows me to execute code to build the URL but I don't know the code to open the xPage. Can someone tell me how to open the xPage once I have a URL to a document?
<xp:eventHandler event="onItemClick" submit="true"
refreshMode="partial" refreshId="navigator1">
<xp:this.action><![CDATA[#{javascript:
if( context.getSubmittedValue() == "ArchitecturalChangeForm" )
{
// Open Page with queryString
var docUNID = eStarService.fetchDocLibraryDocumentUNID( sessionScope.get( "PropertyNox" ), "Architectural Change Form" );
if( isEmpty( docUNID ) )
{
sessionScope.put( "dialogOopsTitle", "Oopps!" );
sessionScope.put( "dialogOopsMessage", "\nUn-able to locate Architecture File! Please review My reference Library!" );
var dialogOops = getComponent( "dialogOops" );
dialogOops.show();
return "";
}
// WHAT GOES HERE FOR THE URL??
return "OpenDocument&docunid=" + docUNID;
}
You can use context.redirectToPage():
context.redirectToPage( "yourxpage.xsp?action=openDocument&docunid=" + docUNID);

AngularJS : How to say to a directive to clone scope?

I have this fiddle, and can not make this work. I believe that the reason resides in that two li elements with a custom directive edit-in-place share scope.
The solution would be to say to the directive to create a copy of the scope that binds on the parent - can transclude help?
angular.module('bla', [])
.directive('editInPlace', ['$parse','$compile', function($parse, $compile) {
return {
restrict: 'A',
scope: true,
link: function (scope, element, attribs) {
var inputStart = '<input style="border: 2 solid black" name="inPlaceInput" style="display:none" value="';
var inputEnd = '">';
scope.editModeAccessor = $parse(attribs.editInPlace);
scope.modelAccessor = $parse(attribs.ngBind);
scope.$watch(attribs.editInPlace, function(newValue, oldValue){
if (newValue){
console.debug("click");
console.debug("value: " + scope.modelAccessor(scope));
var inputHtml = inputStart + scope.modelAccessor(scope) + inputEnd;
element.after(inputHtml);
jQuery(element).hide();
scope.inputElement = jQuery("input[name=inPlaceInput]");
scope.inputElement.show();
scope.inputElement.focus();
scope.inputElement.bind("blur", function() {
blur();
});
} else {
blur();
}
});
function blur(){
console.debug("blur secondary");
if (scope.inputElement){
console.debug("blur secondary inputElement found");
var value = scope.inputElement.val();
console.debug("input value: "+ value);
scope.inputElement.remove();
jQuery(element).show();
scope.editModeAccessor.assign(scope, false);
scope.modelAccessor.assign(scope, value);
}
}
}
}
}]);
function ContactsCtrl($scope, $timeout){
$scope.contacts = [{number:'+25480989333', name:'sharon'},{number:'+42079872232', name:''}];
$scope.editMode = false;
var editedId;
$scope.edit = function(id){
$scope.editMode = true;
jQuery("#"+id).hide();
editedId = id;
//TODO show delete button
}
$scope.$watch('editMode', function(newValue, oldValue){
if (!newValue && editedId){
jQuery("#"+editedId).show();
}
});
}
<div ng-app="bla">
<div ng-controller="ContactsCtrl">
<h4>Contacts</h4>
<ul>
<li ng-repeat="contact in contacts">
<span edit-in-place="editMode" ng-bind="contact.number"></span>
<span edit-in-place="editMode" ng-bind="contact.name"></span>
<span id="{{$index}}" ng-click="edit($index)"><i class="icon-edit">CLICKtoEDIT</i></span>
</li>
</ul>
</div></div>
I think cloning the scope is not the best solution.
When creating a directive in angular, you should encapsulate all the functionality within the directive. You should also avoid mixing jQuery in when you don't have to. Most of the time (as in this case) you're just introducing unnecessary complexity. Lastly, classes are the best way of controlling display, rather than the style attribute on an element.
I took the liberty of rewriting your directive in a more "angular" way - with no jQuery. As you can see from the updated jsFiddle, it is simpler and cleaner. Also, it works!
This directive can be easily modified to add lots of additional awesome functionality.
app.directive( 'editInPlace', function() {
return {
restrict: 'E',
scope: { value: '=' },
template: '<span ng-click="edit()" ng-bind="value"></span><input ng-model="value"></input>',
link: function ( $scope, element, attrs ) {
// Let's get a reference to the input element, as we'll want to reference it.
var inputElement = angular.element( element.children()[1] );
// This directive should have a set class so we can style it.
element.addClass( 'edit-in-place' );
// Initially, we're not editing.
$scope.editing = false;
// ng-click handler to activate edit-in-place
$scope.edit = function () {
$scope.editing = true;
// We control display through a class on the directive itself. See the CSS.
element.addClass( 'active' );
// And we must focus the element.
// `angular.element()` provides a chainable array, like jQuery so to access a native DOM function,
// we have to reference the first element in the array.
inputElement[0].focus();
};
// When we leave the input, we're done editing.
inputElement.prop( 'onblur', function() {
$scope.editing = false;
element.removeClass( 'active' );
});
}
};
});

Resources