Is this a bug in the Tab Container? - xpages

I think this is a bug in the Tab Container...
Opening a new tab using Java or the Server Side JavaScript createTab method when the new tab contains a panel that is set to be an iframe pointing to another XPage in the same database will always cause the XPage to get reloaded after loading about five or six tabs (in Chrome, IE does the same but it takes more tabs...)
If the tab contains an iframe that points to another database that holds the XPage it works fine.
The SSJS code is:
getComponent("djTabContainer1").createTab({title:"New tab"});;
The Java code is
public static boolean createTab(UIDojoTabContainer tabContainer) {
try {
if (tabContainer == null) {
tabContainer = (UIDojoTabContainer) Utils.findComponent("TabContainer");
if (tabContainer == null) {
return false;
}
}
String tabTitle = null;
String url = null;
String unid = null;
UIDojoTabPane newTab = null;
// get default number from current project preferences
tabTitle = "My Tabpage";
url = Utils.GetXpageURL("tabpage.xsp");
// create a new Tab
newTab = new UIDojoTabPane();
newTab.setTitle(tabTitle);
newTab.setTabUniqueKey(new Random().toString());
newTab.setClosable(true);
newTab.setId("TabContainer_" + unid);
newTab.setStyleClass("myTabContainer");
Utils.WriteToConsole("setting style class on " + newTab.getTitle());
// newTab.setStyle("height:auto;width:auto; overflow-y: auto;border: 0px;");
// create new Panel
UIPanelEx newPanel = new UIPanelEx();
newPanel.setId("IFrame" + unid);
newPanel.setStyleClass("iframeClass");
// make an iFrame of this panel with our URL as src
newPanel.setTagName("iframe");
Attr property = new Attr();
property.setName("src");
property.setValue(url);
newPanel.addAttr(property);
// add Panel to our new Tab
newTab.getChildren().add(newPanel);
// add the new tab to our tab container
tabContainer.getChildren().add(newTab);
tabContainer.setSelectedTab(unid);
return true;
} catch (Exception ex) {
Utils.WriteToConsole("Unable to add a new Tab Page to the Tab Container (com.tlcc.Main.createTab)", ex);
return false;
}
}
The XPage that is referenced in the src property of the iframe is very basic...
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:button
value="Label"
id="button1">
</xp:button>
<xp:inputText id="inputText1"></xp:inputText></xp:view>
When the XPage reloads it has no more tabs (except the first tab that was created in the XPage at the start) and is not responsive.
Howard

It could be a server page persistence/component tree limit problem. If you use the default server page persistence, the server only stores 4 pages in memory (per user).
When you create new tabs on the page that loads another XPage, the server is filling up the server page persistence queue and when you hit the 5th tab, the current page is no longer part of the server page persistence (the component tree is no longer in memory) leading to a reload of the current page.
You can increase the number of pages stored (and also move the disk persistence instead of memory persistence). You can also set viewState to "nostate" on the XPage that you load in the iframe (at least to test out my theory).
See Toby Samples blog post on state and this answer on a similar state issue: https://stackoverflow.com/a/31431917/785061

So, to finalize this... the setting for viewstate=nostate allows more tabs but then does not keep the components in memory. So, it works well for readonly XPages but not when the document on the XPage is in edit mode. The setting Maximum Pages on disk in the Xsp properties on the persistence tab will allow more tabs with iframes that have XPages but still will crash at some point.
Net is, don't use multiple iframes that pull in XPages from the same nsf...

Related

SharePoint WebPart Context Menu is going inside div (please see picture)

I have created MasterPage and page layout. Then I created a page using page layout. Then by editing the page, I added content editor web part but there was one issue that web part context menu was going inside div. Kindly see attached image.
your response would be highly appreacited.
Thanksenter image description here
it may helped somebody that I managed it programmatically using javascript.
when icon will be clicked then javascript function will be fired which will check the height of context menu and if the height is greater than the height of the web part then it will increase the height of the zone height. please see code below.
var menulistheight;
var zoneid;
jQuery('.js-webpart-menuCell').click(function (e) {
if (!menulistheight) {
var dd = jQuery('.ms-core-menu-list');
dd = dd.height();
menulistheight = dd;
}
var zone = $(e.target).closest("#MSOZone")[0];
var id = jQuery(zone).attr("zoneid");
var zHeight = jQuery(zone).height();
if (zHeight < menulistheight) {
jQuery(zone).css("height", zHeight + menulistheight - 100 + "px");
}
});

Error setting sessionScope variable in KendoUI grid to open document

My app uses KendoUI Grids for user views, but Xpages with custom controls for form pages (at least for now).
The Xpage looks for a sessionScope variable to determine if it is a new doc (nothing in the scope Var) or and update (unid is in the var).
<xp:this.data>
<xe:objectData
saveObject="#{javascript:PCModel.save()}"
var="PCModel">
<xe:this.createObject><![CDATA[#{javascript:var pc = new com.scoular.model.PC();
var unid = sessionScope.get("key");
if (unid != null) {
pc.loadByUnid(unid);
sessionScope.put("key","");
viewScope.put("readOnly","Yes");
} else {
pc.create();
viewScope.put("readOnly","No");
}
viewScope.status = pc.status;
return pc;}]]></xe:this.createObject>
</xe:objectData>
</xp:this.data>
This worked for a completely Xpage app. I just put a value in the sessionScope key and called the Xpage.
In the Kendo UI code I am using client-side javascript and I don't see a way to set the sessionScope.
I can control the URL, so I could switch gears and use that, however the data for my app is in a different DB than my code.
Any help would be greatly appreciated.
The easiest way is indeed using the URL:
call the XPage with a parameter ?key=... and change your CSJS code line to
var unid = param.key;
In case you really need to set a sessionScope variable from client side
then add this empty computed text field
<xp:text
escape="true"
id="setSessionScope"
value="#{javascript: if (param.key) {sessionScope.key = param.key} ''}" />
to your XPage and set the sessionScope variable in CSJS code with
XSP.partialRefreshGet("#{id:setSessionScope}", {params: {'key': 'your key'}})

Scroll to the selected tab if the selected tab is hidden in p:tabview on initial tabview load

Assume that there are many tabs are there in p:tabview and scroll is being shown to navigate to all the tabs.
If the active tab whose contents are shown is the right most tab then it is not visible to the user on initial p:tabview load. If we navigate to the right most using scroll button then we could see the active tab highlighted indicating it is selected tab.
How to scroll to the active tab on load so that user able to see the selected tab always.
This can be done in the following way by overriding initScrolling method in primefaces p:tabview component JavaScript file i.e. tabview.js file.
initScrolling: function() {
if(this.jq.is(':visible')) {
var overflown = (this.lastTab.position().left - this.firstTab.position().left) >
this.navscroller.innerWidth();
if(overflown) {
this.navscroller.css('padding-left', '18px');
this.navcrollerLeft.show();
this.navcrollerRight.show();
activeTab = this.navContainer.children('.ui-tabs-selected');
viewportWidth = this.navscroller.innerWidth();
activeTabPosition = activeTab.position().left + parseInt(activeTab.innerWidth());
if(activeTabPosition > viewportWidth) {
var scrollStep = activeTabPosition - viewportWidth;
scrollStep = Math.ceil(scrollStep/100) * -100;
this.scroll(scrollStep);
} else {
this.restoreScrollState();
}
}
return true;
}
else {
return false;
}
}
I would have wished to override this single method by extending tabview component widget but it is not supported. So we should have our own tabview.js file with the above method modified.
This is done in primefaces 4.0 version, more, or less the changes will be same higher versions also.

xpages computed onClick open forms

I'm trying and trying for some time to resolve a viewPanel functionality - var property set to rowData.
Depending on the form name, I want to open the docs. ( which are listed in my viewPanel ) in a normal way and into a <xe:dialog> control. I did found this question Xpages Dynamic dojo dialog control and I'm trying to make it works in my case. the docs which I want to be open in the <xe:dialog>, are also created inside the dialog. By this viewpanel, I want to show/open them using this viewPanel control.
Here is the code from the onClick column event:
var formName = rowData.getDocument().getItemValueString("Form");
var docUNID = rowData.getDocument().getUniversalID();
var href = facesContext.getExternalContext().getRequest().getContextPath();
var pe:NotesViewEntry = rowData
if ( formName == "fmCompanie") // in this case, it works OK.
{ href + "/doc.xsp?documentId=" + docUNID + "&action=openDocument"; }
else if ( formName == "fmPersContact" ) // hmm... Still trying...
{ viewScope.put("dlgDocUnid", pe.getUniversalID())
getComponent("exampleDialog").show(); }
So, by this event I'm trying to set a viewScope variable which uses the UNID for the datasource in my exampleDialog control.
Also: the dialog control ( which lays on the same custom control as the viewPanel) has the documentId:
<xp:this.data>
<xp:dominoDocument var="Pdoc" formName="fmPersContact"
ignoreRequestParams="true" scope="request">
<xp:this.documentId><![CDATA[#{javascript:viewScope.get("dlgDocUnid");}]]></xp:this.documentId>
</xp:dominoDocument>
</xp:this.data>
Still, when I'm trying to open a doc. ( using form == "fmPersContact") the dialog has all fields empty, even if the doc. is already fill with some field values.
I appreciate your help. Thanks for your time.
The data source in the dialog does not contain action attribute. Therefore it does not respect the documentId parameter and creating a new document within the dialog.
Add action="editDocument" attribute and it will work.
Also, check what you are refreshing with the onclick event. You should partially refresh an area that contains your data (e.g. dialog or the panel in your dialog, etc.)

SharePoint: commonModalDialogClose does not close cross-domain dialog

I have a page hosted in 'virtualcasa1' domain opening a modal dialog:
var options = {
title: "Repro",
width: 400,
height: 600,
url: http://domain2:999/sites/blank/_layouts/XDomainTest/XDomainTestTarget.aspx //[1]
//url: http://virtualcasa1/sites/blank/_layouts/XDomainTest/XDomainTestTarget.aspx [2]
};
SP.UI.ModalDialog.showModalDialog(options);
And I have this code to close it:
alert(document.domain);
SP.UI.ModalDialog.commonModalDialogClose(SP.UI.DialogResult.cancel, 'Cancelled clicked');
If both are in the same domain (case [2] above), the dialog closes well, no issues.
But - if target page hosted in the dialog (case [1] above), dialog does NOT close :-(
document.domain above shows the correct domain where page exists.
I suspect I'm facing a cross-domain issue here (duh), but how to fix it? Or am I wrong and issue is not XDomain-related?
Thanks much!
HTML5's postMessage is your answer.
https://developer.mozilla.org/en-US/docs/Web/API/Window.postMessage
Your parent window that initiates the dialog must have the following javascript:
function listener(event) {
//alert(event.data);
if (event.data == 'Cancel') {
SP.UI.ModalDialog.commonModalDialogClose(SP.UI.DialogResult.cancel, 'Cancel clicked');
}
else {
SP.UI.ModalDialog.commonModalDialogClose(SP.UI.DialogResult.OK, event.data);
}
}
if (window.addEventListener) {
addEventListener("message", listener, false)
} else {
attachEvent("onmessage", listener)
}
Javascript for OK and Cancel buttons in your popup:
<input type="button" value="OK" onclick="parent.postMessage('Message to be displayed by the parent', '*');" class="ms-ButtonHeightWidth" />
<input type="button" value="Cancel" onclick="parent.postMessage('Cancel', '*');" class="ms-ButtonHeightWidth" />
Ajay's answer from the 1st of August 2014 is good, but it needs a bit more explanation. The reason for the failure to close the dialog is simple. Cross site scripting security features of modern browsers disallow a few things, one of which is the use of window.frameElement from within the framed window. This is a read-only property on the window object and it becomes set to null (or with IE, it actually throws an exception when you try to access it). The ordinary Cancel event handlers in the modal dialog conclude with a call to window.frameElement.cancelPopup(). This will fail of course. The ordinary Save handler where the Save worked on the server side results in SharePoint sending back a single line as the replacement document, which is a scriptlet to call window.frameElement.commitPopup(). This also will not work, and it's a real pain to overcome because the page has been reloaded and there is no script available to handle anything. XSS won't give us access to the framed DOM from the calling page.
In order to make a cross domain hosted form work seamlessly, you need to add script to both the page that opens the dialog and the framed page. In the page that opens the dialog, you set the message listener as suggested by Ajay. In the framed form page, you need something like below:
(function() {
$(document).ready(function() {
var frameElement = null;
// Try/catch to overcome IE Access Denied exception on window.frameElement
try {
frameElement = window.frameElement;
} catch (Exception) {}
// Determine that the page is hosted in a dialog from a different domain
if (window.parent && !frameElement) {
// Set the correct height for #s4-workspace
var frameHeight = $(window).height();
var ribbonHeight = $('#s4-ribbonrow').height();
$('#s4-workspace').height(frameHeight - ribbonHeight);
// Finds the Save and Cancel buttons and hijacks the onclick
function applyClickHandlers(theDocument) {
$(theDocument).find('input[value="Cancel"]').removeAttr('onclick').on('click', doTheClose);
$(theDocument).find('a[id="Ribbon.ListForm.Edit.Commit.Cancel-Large"]').removeAttr('onclick').removeAttr('href').on('click', doTheClose);
$(theDocument).find('input[value="Save"]').removeAttr('onclick').on('click', doTheCommit);
$(theDocument).find('a[id="Ribbon.ListForm.Edit.Commit.Publish-Large"]').removeAttr('onclick').removeAttr('href').on('click', doTheCommit);
}
// Function to perform onclick for Cancel
function doTheClose(evt) {
evt.preventDefault();
parent.postMessage('Cancel', '*');
}
// Function to perform onclick for Save
function doTheCommit(evt) {
evt.preventDefault();
if (!PreSaveItem()) return false;
var targetName = $('input[value="Save"]').attr('name');
var oldOnSubmit = WebForm_OnSubmit;
WebForm_OnSubmit = function() {
var retVal = oldOnSubmit.call(this);
if (retVal) {
var theForm = $('#aspnetForm');
// not sure whether following line is needed,
// but doesn't hurt
$('#__EVENTTARGET').val(targetName);
var formData = new FormData(theForm[0]);
$.ajax(
{
url: theForm.attr('action'),
data: formData,
cache: false,
contentType: false,
processData: false,
method: 'POST',
type: 'POST', // For jQuery < 1.9
success: function(data, status, transport) {
console.log(arguments);
// hijack the response if it's just script to
// commit the popup (which will break)
if (data.startsWith('<script') &&
data.indexOf('.commitPopup()') > -1)
{
parent.postMessage('OK', '*');
return;
}
// popup not being committed, so actually
// submit the form and replace the page.
theForm.submit();
}
}).fail(function() {
console.log('Ajax post failed.');
console.log(arguments);
});
}
return false;
}
WebForm_DoPostBackWithOptions(
new WebForm_PostBackOptions(targetName,
"",
true,
"",
"",
false,
true)
);
WebForm_OnSubmit = oldOnSubmit;
}
applyClickHandlers(document);
}
});
})();
This solution makes use of the jQuery library, which our organization uses extensively. It is our preferred framework (chosen by me). I'm sure someone very clever could rewrite this without that dependency, but this is a good starting point. I hope someone finds it useful, as it represents a good two days work. Some things to note:
SharePoint does a postback on all sorts of events on the page, including putting the page into edit mode. Because of this, it makes more sense to trap the specific button clicks, both on the form and in the ribbon, rather than wholesale redefinition of, for example, the global WebForm_OnSubmit function. We briefly override that on a Save click and then set it back.
On any Save click event, we defeat the normal posting of the form and replace that with an identical POST request using AJAX. This allows us to discard the returned scriptlet when the form was successfully posted. When the form submission was not successful, perhaps because of blank required values, we just post the form properly to allow the page to be updated. This is fine, since the form will not have been processed. An earlier version of this solution took the resulting HTML document and replaced all of the page contents, but Internet Explorer doesn't like this.
The FormData api allows us to post the form as multipart-mime. This api has at least basic support in all modern browsers, and there are workarounds for older ones.
Another thing that seems to fail in the cross domain hosted dialog is the scrolling of the content window. For whatever reason, the height is not set correctly on the div with id s4-workspace, so we also set that in the solution.
EDIT:
Almost forgot. You may also need to add this control to your framed ASPX page, which can be done with SharePoint Designer:
<WebPartPages:AllowFraming runat="server"/>
I have exactly the same issue - a dialog opening a view page for an item works fine when opened from a site collection on the same web app/domain, but the Close button fails to work when opening the same item from a site collection hosted in a separate web application. I'm assuming it is a cross-domain thing so I've altered the solution to accomodate this restriction, however, I'm not 100% happy about it as it does make the overall solution a little awkward to use from a user-perspective. I've put the issue to one side for now due to project timescales, but I'm still curious as to why. The only things I can think of is the whole cross-domain thing causing it and that maybe it is there by design to prevent XSS security holes.

Resources