| Paul's profileAutoSpongePhotosBlogLists | Help |
AutoSpongeA Non-developer's Blog About Administering SharePoint --by Paul Grenier |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
list of SharePoint-related articles I find helpful for reference
|
August 29 FF and IE just can't get along: javascript event madnessI tried helping someone on the Mastering SharePoint forum today. shalinparmar was trying to get a simple form button to fire off a redirect based on a text control's value and stumbled into a hornet's nest of FF vs. IE junk. I stayed with it because I really wanted to learn why this wasn't working and what the fix would be for both browsers. One thing I learned was that IE and FF handle events like keypress, keydown, and keyup differently. IE especially makes it difficult when it auto-submits the form (and everything in MOSS content areas belongs to a form) on ENTER (char 13). The result can be a long ddddiiiiiinnnngggggg as the event fires off--far from ideal. For these reasons, I used keyup to fire the next javascript function and keypress was used to supress IE's auto-submit. One browser passes event.which while the other uses event.keycode. So there's a special case for detecting which DOM element exists before you can check which key was pressed. Another difference lies in the way FF and IE handle redirects. Windows.navigate simply doesn't exist in FF's DOM, so we use window.location. Lastly, a button tag will auto-submit in FF so you need to use an input tag with type="button". At any rate, the final working version is below. Don't ask what doIdNumSearch() does, I didn't write that. <table border="0"> <tr> <td class="ms-advsrchHeadingText" colspan="2"> <h3 class="ms-standardheader" style="font-size: 1em"> Search by number...</h3> </td> </tr> <tr> <td class="ms-advsrchText"> <label for="my_list_idnum"> <b>Number</b>:</label> </td> <td class="ms-advsrchText"> <input name="my_list_idnum" type="text" maxlength="200" id="my_list_idnum" onkeypress="return killEnter(event);" onkeyup="doSearch(event);"> </td> </tr> <tr> <td> </td> <td> <input type="button" value="Search" onclick="doIdNumSearch();" /> </td> </tr> </table>
<script type="text/javascript"> function doIdNumSearch() { var k = document.getElementById('my_list_idnum').value; var loc = 'results.aspx?k=' + k + 'contentclass:STS_ListItem_GenericList&s=my list'; window.location = loc; }
function doSearch(e) { //must fire on keyup because of IE var characterCode; if(e && e.which){ e = e; characterCode = e.which; //for most browsers }else{ e = event; characterCode = e.keyCode; //for IE } if(characterCode == 13){ doIdNumSearch(); } }
function killEnter(e){ //this stops IE from auto-submitting on ENTER if(e.keyCode == 13 || e.which == 13){ return false; } return true; } </script>
August 28 Conditional Validaton with JavascriptWhile I try to stay away from javascript validation, sometimes security concerns get trumped by "quick and dirty." Such is the case when you need simple, conditional validation on forms in SharePoint. Logic like "if Due Date is not NULL then make Justification required." This was the example used in the question from STP today. As usual, I was onto the solution when someone's blog straight up answered it for me. Thanks to Edin Kapic for posting his date logic soluton. So far this only works while the "Justification" field is a Plain text field. I had too much trouble getting the RTF field to properly identify any attributes I could use the logic portion. If someone else can get it to work with an RTF field, please drop me a line or post on EndUserSharePoint.com. <script type="text/javascript">
function PreSaveAction() { var findDate = getTagFromIdentifierAndTitle("input","DateTimeFieldDate","Due Date"); var findText = getTagFromIdentifierAndTitle("textarea","","Test"); if(findDate.value != "" && findText.value == "") { alert("You must provide a Justification for the Due Date"); return false; // Cancel the item save process } return true; // OK to proceed with the save item }
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> August 20 Print a Web Part with JavascriptAdd a content editor web part to your page with the following inside: <script type="text/javascript"> _spBodyOnLoadFunctionNames.push("printWebPart");
function printWebPart(wpNum) { if (wpNum && document.getElementById != null) { var html = '<HTML>\n<HEAD>\n'; if (document.getElementsByTagName != null) { var headTags = document.getElementsByTagName("head"); if (headTags.length > 0) html += headTags[0].innerHTML; } html += '\n</HEAD>\n<BODY>\n'; var printDiv = 'WebPartWPQ'; printDiv += wpNum; var printWPElem = document.getElementById(printDiv); if (printWPElem != null){ html += printWPElem.innerHTML; }else{ alert("Could not find the printDiv div"); return; } html += '\n</BODY>\n</HTML>'; var printWP = window.open("","printWebPart"); printWP.document.open(); printWP.document.write(html); printWP.document.close(); printWP.print(); } } </script>
Now place a link anywhere you want that looks like this: <a href="javascript:void(printWebPart(3))">Print
this WebPart</a>
The number (3) represents the 3rd web part on the page. You have to count the CEWP we added and the default Search Box in the title area. So these two together will print your "main" web part. August 18 EndUserSharePoint.com: The Fundamentals of Libraries and ListsAnyone getting their feet wet with SharePoint this year should look into Mark Miller's online workshop. Most users will get access and a little training from their project team but a lot of project teams struggle with end user training before rollout. I'm also concerned where I've seen end user training that doesn't follow best practices for even the simplest processes. Mark will help you understand the basics of "Contributor-level" access and at this price point, may be a great replacement for the hurried and spotty training some project teams produce when they're under the gun. Maybe if you tell Mark that AutoSponge sent you, you'll get a free cookie.
August 13 Fixing ExpGroupBy in Core.js - International MOSS Community Comes ThroughI'll say it again, I love the MOSS community. By adopting high standards and documenting our successes, we all win. Case in point: I made a data view web part for administering a human "state machine" workflow (article coming soon) that used grouping, but the expand/collapse groups did not work in FireFox. Thanks to MOSS community awesomeness, I fixed the issue in just a few minutes. The javascript in core.js that expands and collapses the grouped sections did not work in FireFox. I opened Firebug to have a look. <a onclick="javascript:ExpGroupBy(this);return false;"
href="javascript:"> function ExpGroupBy(formObj) { if (browseris.nav) return; ... The first line of the script stops FF from running the script but there's no real reason. "Google will save me from this insanity!" search: sharepoint expgroupby firefox First result: sharepoint + Firefox - SWiKI found a link to an Italian page: Fix per ExpGroupBy di WSS3 su FireFox search: site:www.marcobellinaso.com/blog-ita/post/Fix-per-ExpGroupBy-di-WSS3-su-FireFox.aspx click [Translate] On the translated page, I could see that Marco noticed the same problem with ExpGroupBy. He fixed the function and provided a download. That was awesome. Problem fixed in minutes instead of hours. I'm reposting the script here for my documentation. I suggest, for a quick fix on a single page, add a Content Editor Web Part with the new function like I have below; set the web part to "hidden." <script type="text/javascript"> function ExpGroupBy(formObj) { if ((browseris.w3c) && (!browseris.ie)) { document.all=document.getElementsByTagName("*"); } docElts=document.all; numElts=docElts.length; images=formObj.getElementsByTagName("IMG"); img=images[0]; srcPath=img.src; index=srcPath.lastIndexOf("/"); imgName=srcPath.slice(index+1); if (imgName=='plus.gif') { fOpen=true; displayStr=""; img.src='/_layouts/images/minus.gif'; } else { fOpen=false; displayStr="none"; img.src='/_layouts/images/plus.gif'; } oldName=img.name; img.name=img.alt; img.alt=oldName; spanNode=img; while(spanNode !=null) { spanNode=spanNode.parentNode; if (spanNode !=null && spanNode.id !=null && spanNode.id.length > 5 && spanNode.id.substr(0, 5)=="group") break; } parentNode=spanNode; while(parentNode !=null) { parentNode=parentNode.parentNode; if (parentNode !=null && parentNode.tagName=="TABLE") break; } lastNode=null; if (parentNode !=null) { lastNode=parentNode.lastChild; if (lastNode !=null && lastNode.tagName=="TBODY") lastNode=lastNode.lastChild; if (lastNode !=null && lastNode.tagName=="TR" && lastNode.lastChild !=null) lastNode=lastNode.lastChild; } for(var i=0;i<numElts;i++) { var childObj=docElts[i]; if (childObj==spanNode) break; } ID=spanNode.id.slice(5); for(var j=i+1; j<numElts; j++) { var childObj=docElts[j]; if (childObj.id.length > 5 && childObj.id.substr(0, 5)=="group") { curID=childObj.id.slice(5); if (curID <=ID) return; } parentNode=childObj; while(parentNode !=null) { parentNode=parentNode.parentElement; if (parentNode==spanNode) break; } if (parentNode==spanNode) continue; if (childObj !=img && childObj.tagName=="IMG" && childObj.src && childObj.src.slice(childObj.src.length - 25)=='/_layouts/images/plus.gif') { childObj.src='/_layouts/images/minus.gif'; oldName=childObj.name; childObj.name=childObj.alt; childObj.alt=oldName; } if (childObj.tagName==spanNode.tagName && childObj.id !="footer") { childObj.style.display=displayStr; } if ((childObj.tagName=="TABLE" && lastNode==null) || childObj==lastNode) break; } } //--> </script>
July 28 Important Surveys Don't Like "Save"While building a survey, I noticed that the "Save" and "Next" buttons appear on every segment of the survey except the final page where "Finish" appears. I don't want my users to get confused and "Save" their survey, assuming they're done, when they really need to use "Next" to complete the remaining questions. So, I opened the Edit page option on both the NewForm.aspx and EditForm.aspx using the URL trick (?PageView=Shared&ToolPaneView=2) and added the following javascript using a Content Editor Web Part: <script type="text/javascript"> _spBodyOnLoadFunctionNames.push("setValue");
function setValue() { hideButton("Save"); }
//This function hides a button on the page function hideButton(valueDef){ var frm = document.forms[0]; for (i=0;i<frm.elements.length;i++) { if (frm.elements[i].type == "button" && frm.elements[i].value == valueDef) { frm.elements[i].style.display = "none"; } } } //--> </script>
July 22 Advanced Search Form and Preselected ScopesAdvanced Search pages don't seem to do what they're told. If you want to narrow the Advanced Search results to a specific scope:
Now you can check the box to keep advanced results in the scope(s) you choose. To preselect all check boxes:
<script type="text/javascript"> _spBodyOnLoadFunctionNames.push("checkAll");
function checkAll() { var frm = document.forms[0]; for (i=0;i<frm.elements.length;i++) { if (frm.elements[i].type == "checkbox") { frm.elements[i].checked = true; } } } //--> </script> July 15 Pop Quiz - Content Types and FoldersSharePoint Quiz! Scenario:
A. HR Policy, what you picked from the New Item menu. B. Company Document, the same as the template. C. Company Document when opened then HR Policy once saved to the library. D. None of the above. Answer (spoiler) . . . . . . . . . . D. None of the Above Explanation: Normally, when you open a template with the Company Document Content Type and save it to a library with only one Content Type, the system changes the document's Content Type to match the library. However, in this case, even though you chose "New HR Policy" from the menu, the result is different. The template has the Company Document Content Type which does not exist in the target HR Document Library so the system uses the default Content Type for the target library, HR Document. HR Document became the default Content Type for the library when we removed HR Policy from visibility. Lesson: If your target library has more than one Content Type allowed, make sure to type your template. July 02 Cleanup Hyperlinks in Calculated ColumnsA common request from end users: "Can we make a calculated column of hyperlinks that don't display the whole url?" Because MOSS filters "dangerous code" from metadata columns, calculated columns can not resolve to something with code in it and the Excel function "HYPERLINK" does not work in MOSS. So, what can we do? Admins and power users can clean this up with SPD or custom web parts. However, the end user has fewer options. If the end user has the Edit Page permission and access to the Content Editor Web Part, they can use some javascript to clean up the links. If you don't normally allow that, you may want to add the second technique's script to your default.master as it will clean up any ugly links on your page. Technique 1 Let's say, you have a bunch of document names in a list and you want to create a url to another site using a calculated column like this: ="http://google.com/"&Title We know the "base URL" and we want to dynamically separate the appended portion from the base (e.g., displaying http://www.google.com/labs as labs), so we add this script to the list view page using a CEWP: <script type="text/javascript"> _spBodyOnLoadFunctionNames.push("cleanLinks");
function cleanLinks(){ var baseURL = "http://google.com/"; var len = baseURL.length; var links = document.getElementsByTagName("a"); for (var i=0; i < links.length; i++) { if (links[i].href && links[i].href.substring(0,len) == baseURL){ if (links[i].innerText==undefined) { links[i].textContent = links[i].textContent.substring(len); } else{ links[i].innerText = links[i].innerText.substring(len); //for IE } } } }
//--> </script>
<script type="text/javascript"> _spBodyOnLoadFunctionNames.push("cleanLinks");
function cleanLinks(){ var links = document.getElementsByTagName("a"); for (var i=0; i < links.length; i++) { if (links[i].href && links[i].href == links[i].innerText){ links[i].innerText = "link"; }else{ if (links[i].href && links[i].href == links[i].textContent){ links[i].textContent = "link"; } } } }
//--> </script>
A slightly fancier version of that technique adds an image using some embedded CSS:
<STYLE TYPE="text/css" MEDIA=screen> <!-- .mylink { background-image: url(/_layouts/images/link.gif); background-repeat: no-repeat; font-family: tahoma,sans-serif; font-size: 8pt; padding-left: 25px; } --> </STYLE> <script type="text/javascript"> _spBodyOnLoadFunctionNames.push("cleanLinks");
function cleanLinks(){ var links = document.getElementsByTagName("a"); for (var i=0; i < links.length; i++) { if (links[i].href && links[i].href == links[i].innerText){ links[i].parentNode.className="mylink"; links[i].innerText = "link"; }else{ if (links[i].href && links[i].href == links[i].textContent){ links[i].parentNode.className="mylink"; links[i].textContent = "link"; } } } } //--> </script>
June 26 Road to Document Management pt4 -- Configuring Content TypesAfter researching our options and discussing the benefits of each alternative, out team set out to start configuring the document content types for the system. That puts us around Step 5 of our original outline: Step 5 - Convert document metadata to policy
That description assumed we would convert a lot of legacy documents--which still remains to be seen. Our main goal aims at creating the best possible system to manage future documents. To start, we created a document content type based on Document and named it Company Document. This gives us total control over any document Content Type without modifying the OOB Document Content Type. We then replaced the Content Type in the top-level Site Collection's document library with Company Document.
The top-level Site Collection's library will hold our document templates used by other Content Types. So now we need to identify what metadata will apply to all Company Documents (if any) and add that to the Content Type as a Site Column. We created a list called Records Retention Schedules (these are mandated for us by the State). We added the schedules that pertained to our organization to the list then added a new Lookup site column to the Company Document Content Type. Next, we created a Request content type and a child type called New Site Request. Although most sites include this feature as a Custom List, we chose documents because of the integration with Outlook and the ability to create Document Workspaces that could help our administrators collaborate with new teams to develop their requirements. The Request type inherits from Company Document which now has a Site Column called Retention Schedule. By adding the New Site Request template to the Site Collection's document library and setting it as the template in the New Site Request Content Type, we can pre-select the Retention Schedule used for all New Site Requests. Usually, lookups don't work too well in the bod y of Word documents because the Content Control displays the integer ID value of the target lookup object instead of the column specified (or the column shown in the DIP--Document Information Panel). However, when setting a Lookup value in the template, new instances of documents based on that template will display the proper Lookup value in the document body. In our case, we wanted to note the document retention schedule in the document control footer. In addition, because we used a Lookup data type for the document retention schedule, users can drill down into the definition of that schedule. By clicking the document in the library and selecting View Properties, the user can see the Retention Schedule identifier (e.g., S1-020). This code links to the Retention Schedule record. That record references the disposition method (by another Lookup column). By clicking that, the user can see exactly what process to follow for record disposition (e.g., destroy with permission). We now know that we will need to design the architecture of our system in two ways: Information Architecture (how the Content Types and metadata work to organize information) and Storage Architecture (how the lists and libraries aid in managing workflow, security permissions, and reporting). I identified three models of Storage Architecture which I documented for an article appearing on EndUserSharePoint.com soon. I'll address how these models come into play for Document Management in the next installment. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|