JavaScript Errors in SharePoint Application Pages
Today was a day packed with investigation and troubleshooting weird JavaScript errors on a customer premise. I put this information on my blog so to help anyone who might have found in a similar journey.
SYMPTOMS
Problem 1: Views
You have a custom SharePoint 2010 master page. You assign that master page as the master page for a site, both for content and system pages. You face the following errors when dealing with list or library views:- You can't change the current view in the Ribbon view selector. The combo box doesn't drop down. Additionally, you do not have the view selector on the page title.
- You edit the current view (or any other view). A JavaScript error is displayed in Internet Explorer and you can't save the changes. However, in Firefox you can modify the view without any problems.
Problem 2: File Upload page
You have a custom SharePoint 2010 application page. The page inherits from the default SharePoint file upload page. You want to replace the file upload control with a custom control. However, you have to maintain the original control because the inherited code relies on the presence of those controls. You hide the controls but you still face JavaScript errors due to the hidden control validation scripts.CAUSES
Problem 1: the culprits are the new SharePoint 2010 master page placeholders and delegate controls.In order to have view selector working, you have to add the following DelegateControl to the master page (as found on this thread):
<SharePoint:DelegateControl runat="server" ControlId="TreeViewAndDataSource">
</SharePoint:DelegateControl>
</SharePoint:DelegateControl>
</form>
<asp:ContentPlaceHolder id="PlaceHolderUtilityContent" runat="server"/>
<asp:ContentPlaceHolder id="PlaceHolderUtilityContent" runat="server"/>
Problem 2: the culprit was the JavaScript emitted by the default validation controls for the file upload selector. The solution was to embed the default control inside an ASP.NET Panel control set to Visible="false". In this way the controls are present on the server but nothing is rendered back to the page, preventing JavaScript errors.
NumberField and getElementById
Just a quick lesson learnt today in field:
SCENARIO
You want to use JavaScript to validate certain fields when the SharePoint page is in Edit Mode. You use Control.ClientID ASP.NET property to emit the control ID from the server-side code using Page.ClientScript (usually in PreRender event). You’d usually use this ID to access the control on the client side using document.getElementById.PROBLEM
I had issues with NumberField (the server-side control that is used to render a numeric column in SharePoint). Its ClientID property is not the same as the one that is finally rendered back. In fact, it’s a child control, two levels down from the NumberField. The reason for this behaviour is that NumberField is a composite templated control, with multiple child controls that compose it.SOLUTION
Instead of using:numberFieldCtrl.ClientIDuse
numberFieldCtrl.Controls[0].FindControl("TextField").ClientIDto get your correct control ID for JavaScript validation code on the page.
Hiding List View Group Headers
It’s really annoying that SharePoint displays those pesky column names followed by a colon when you group by a column. In this example I use the the columns “Categoria” and “Titulo” to group a bunch of pages.
In addition, the gray header row is ugly, too.
We can hide it with some Content Editor Web Part (CEWP) and JavaScript magic.
The offending elements can be identified:
We have to hide the following elements:
- a TR element with class name ms-viewheadertr (the header row)
- the fourth and the fifth child of a TD element with class names ms-gb (the column name of the first group by and a colon)
- the fourth and the fifth child of a TD element with class names ms-gb2 (the column name of the second group by and a colon)
This is the code to be pasted in a CEWP at the same page where you have the “ugly” list view. Please note that the text node which contains the colon must be removed instead of being hidden.
<script type="text/javascript" language="javascript">
_spBodyOnLoadFunctionNames.push("HideHeaders");
function HideHeaders()
{
var elements = getElementsByClassName(document, "td", "ms-gb");
var elem;
for(var i=0;i<elements.length;i++)
{
elem = elements[i];
elem.childNodes[3].style.display = "none";
elem.removeChild(elem.childNodes[4]);
}
elements = getElementsByClassName(document, "td", "ms-gb2");
for(var i=0;i<elements.length;i++)
{
elem = elements[i];
elem.childNodes[3].style.display = "none";
elem.removeChild(elem.childNodes[4]);
}
elements = getElementsByClassName(document, "tr", "ms-viewheadertr");
for(var i=0;i<elements.length;i++)
{
elem = elements[i];
elem.style.display = "none";
}
}
/*
Written by Jonathan Snook, http://www.snook.ca/jonathan
Add-ons by Robert Nyman, http://www.robertnyman.com
*/
function getElementsByClassName(oElm, strTagName, strClassName){
var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
var arrReturnElements = new Array();
strClassName = strClassName.replace(/\-/g, "\\-");
var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
var oElement;
for(var i=0; i<arrElements.length; i++){
oElement = arrElements[i];
if(oRegExp.test(oElement.className)){
arrReturnElements.push(oElement);
}
}
return (arrReturnElements)
}
</script>
This is the result of this code:
IE and The Mysterious ‘The page took too long to save’ Message
Tags: error, javascript, moss, sharepoint
Another one of those “oh my god” moments in SharePoint programming, although it had nothing to do with SharePoint proper.
The Background
I want to dynamically add a DIV alongside a SharePoint field in Publishing page. The intended purpose is to extend the built-in control without any server-side code. I want the DIV to hold a simple hand-crafted AJAX call to add new items.Everything is okay, I add my code to the SharePoint _spBodyOnLoadFunctionNames.push function, I find the control to be extended and I yank the DIV from its previous location to the parentNode object of the control. It nicely shows the new DIV below the original control:
The DIV contains an anchor element that has href=”#” and onclick=”SomeFunction();”. It’s created dynamically when the page loads:
var link = document.createElement("a");
link.href="#";
link.innerHTML = "Agregar nuevo elemento";
link.onclick = "SomeFunction();";
div.parentNode.appendChild(link);
The Symptoms
When you click the anchor in the added DIV, there’s a 30-second pause during which IE responds to no clicks, after which the following message appears:Are you sure you want to navigate away from this page?
The page took too long to save. You can click “Cancel”, and then try to save the page again. If you click “OK”, you might lose unsaved data.
Press OK to continue, or Cancel to stay away on the current page.
After you click OK, the anchor works well, calling the SomeFunction flawlessly.
The First Suspect
I though that the issue was caused by another function I called, that uses “AJAX” call to owssvr.dll to filter cascading dropdown controls (more on that in next posts). It might leave the HTTP request in a undefined state so that the IE thinks it’s still not loaded.I commented it out but the error persisted.
The Second Suspect
Then I thought that it has something to do with the appendChild method I use to move the DIV from its placeholder to the new position below the control it’s extending.I changed the call to dynamically create a new DIV instead of moving the old one to a new position in DOM tree. But, the same error persisted.
The Real Culprit
After David GutiĆ©rrez, one of my colleagues (whose mileage in JavaScript is much greater than mine) revised the code, and after a perfunctory “brain processing” period (which lasted about an hour, in this case), he appeared and outlined the malfunction.The error message is caused by IE trying to “execute” the new onClick event, which is set to a JavaScript function in this case. But, the syntax is wrong, wrapped inside double-quotes, like in inline HTML.
link.onclick = "SomeFunction();";The correct syntax is written just like a method call, using only the function name:
link.onclick = SomeFunction;
IE seems to “compile” the string into a method call, but only after you confirm that you want to navigate away. Weird enough.
Add a Submit Button Confirmation to an ASP.NET Web Form
I've been asked many times how to add a confirmation dialog for a simple (or complex, why not?) ASP.NET web form. It's simple:
- Find the OnClientClick property of the button control
- Type return confirm('are you sure?') as the value of the property
"I Need To..." Web Part and JavaScript error
If you try to customize the "I Need To..." web part, as outlined by Gary in his blog post, you might experience a JavaScript error, just like this one:
Line: 1This error is launched by the TATWP_jumpMenu JavaScript function, because it attempts to execute a function defined in another .js file that hasn't been loaded.
Char: 1
Error: Object expected
Code: 0
Workaround:
Add a hidden Content Editor Web Part with the following HTML source text:
<script language="javascript" src="/_layouts/portal.js?rev=INhSs9mWTnUTqdwwIXYMaQ%3D%3D"></script>This will ensure that the referenced script file loads correctly.
Hide a field from NewForm.aspx
I was faced with this problem lately: you have a optional field in your list that you'd like to leave hidden from the user (it's used for workflow status keeping).
The first solution is to customize NewForm.aspx from SharePoint Designer and replace the default list form with Custom List Form, in which you can remove the fields you don't need. The inconvenience is that you lose the ability to attach files to the list item. As I had to attach files to the list, this option was unacceptable.
Fortunately, there is another way to hide a field. You can customize NewForm.aspx and add a JavaScript code that hides the whole row the field is placed. I used the code snippet from SharePoint Designer blog and I came out with this small code block, to be placed on NewForm.aspx (or EditForm.aspx, as you wish).
You have to replace these fields (see the SPD blog entry for more information):
- TAGNAME: HTML element that is being rendered ("SELECT", "INPUT"...)
- IDENTIFIER: SharePoint field type identifier ("TextField", "DropDownChoice"...)
- FIELD NAME: Display name of the field (e.g. "Status", "Customer Name"...)
<script language="javascript" type="text/javascript">
_spBodyOnLoadFunctionNames.push("hideFields");
function hideFields() {
var control = getTagFromIdentifierAndTitle("TAGNAME","IDENTIFIER","FIELD NAME");
control.parentNode.parentNode.parentNode.style.display="none";
}
function getTagFromIdentifierAndTitle(tagName, identifier, title) {
var len = identifier.length;
var tags = document.getElementsByTagName(tagName);
for (var i=0; i < tags.length; i++) {
var tempString = tags[i].id;
if (tags[i].title == title && (identifier == "" || tempString.indexOf(identifier) == tempString.length - len)) {
return tags[i];
}
}
return null;
}
</script>
Add JavaScript Date Validation into List Item forms
I want to validate two fields on a new list item form by invoking JavaScript custom function. They are two date fields and I want to ensure that the end date can't happen before the start date. My first idea was to attach a validation function on the onclick event of the Submit button.
I started by inspecting the generated HTML of the form. The Submit button already has a onclick() code which is:
if (!PreSaveItem()_) return false;WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions("ctl00$ctl13$g_740c1963_b0da_4b45_9b71_0dcca5d082b0$ctl00$toolBarTbl$RightRptControls$ctl00$ctl00$diidIOSaveItem", "", true, "", "", false, true))
Searching in the SharePoint JavaScript files in the LAYOUT folder, I found the definition of PreSaveItem function in FORMS.JS file. It simply invokes PreSaveAction function, if defined.
Finally, it was just a matter of inserting a custom function named PreSaveAction in a <SCRIPT> block of the NewForm.aspx (and EditForm.aspx). I also used the date parse code from this forum.
The code I put in NewItem.aspx is like this
function PreSaveAction()
{
var date1 = getTagFromIdentifierAndTitle("INPUT","DateTimeFieldDate","Contract Date");
var date2 = getTagFromIdentifierAndTitle("INPUT","DateTimeFieldDate","Contract End Date");
var arrDate1 = date1.value.split("/");
var useDate1 = new Date(arrDate1[2], arrDate1[1]-1, arrDate1[0]);
var arrDate2 = date2.value.split("/");
var useDate2 = new Date(arrDate2[2], arrDate2[1]-1, arrDate2[0]);
if(useDate1 > useDate2)
{
alert("The end date cannot happen earlier than the start date");
return false; // Cancel the item save process
}
return true; // OK to proceed with the save item }
{
var date1 = getTagFromIdentifierAndTitle("INPUT","DateTimeFieldDate","Contract Date");
var date2 = getTagFromIdentifierAndTitle("INPUT","DateTimeFieldDate","Contract End Date");
var arrDate1 = date1.value.split("/");
var useDate1 = new Date(arrDate1[2], arrDate1[1]-1, arrDate1[0]);
var arrDate2 = date2.value.split("/");
var useDate2 = new Date(arrDate2[2], arrDate2[1]-1, arrDate2[0]);
if(useDate1 > useDate2)
{
alert("The end date cannot happen earlier than the start date");
return false; // Cancel the item save process
}
return true; // OK to proceed with the save item }
// getTagFromIdentifierAndTitle: Find a form field object using its tagName, identifier, and title to find it in the page
// Arguments:
// tagName: The type of input field (input, select, etc.)
// identifier: The identifier for the instance of the fieldName (ff1, ff2, etc.)
// title: The title of the list column
//
function getTagFromIdentifierAndTitle(tagName, identifier, title) {
var len = identifier.length;
var tags = document.getElementsByTagName(tagName);
for (var i=0; i < tags.length; i++) {
var tempString = tags[i].id;
if (tags[i].title == title && (identifier == "" || tempString.indexOf(identifier) == tempString.length - len)) {
return tags[i];
}
}
return null;
}
</script>
Awesome article ..keep updating
ReplyDeleteSharePoint Training