SharePoint Tips & Code 1.Here is a bit of code to retrieve the list items from a list: SPList list = web.Lists["MyLibrary"]; if (list != null) { var results = from SPListItem listItem in list.Items select new { xxx = (string)listItem["FieldName"]), yyy = (string)listItem["AnotherField"], zzz = (string)listItem["Field"] }; } To retrieve a file you could also use this method on SPWeb: GetFileAsString Without linq: int itemId = getItemId(); SPWeb currentWeb = SPContext.Current.Web; SPList list = currentWeb.Lists["MyList"]; if ( list != null ) { SPListItem theItem = list.Items.GetItemById(itemId); doWork(theItem); } The SPWeb can be retrieved in numerous ways, using the SPContext will work if the code is called from SharePoint. To get an SPWeb object from a URL you can use SPSite object i.e. using ( SPSite site = new SPSite(urlToWeb) ) { using (SPWeb web = site.OpenWeb()) { doWork(web); } } the 'using' statement ensures non-managed resources are reclaimed in a timely manner, by calling 'Dispose()' on the relevant objects. Effective as that may be, you should really look into best practices as they relate to storing documents in the 12 hive versus the content database. private static string getXsl() { string xslString = null; using (StreamReader streamReader = new StreamReader(File.Open(HttpContext.Current.Server.MapPath(@"~_layouts\theXSL.xslt"), FileMode.Open))) { xslString = streamReader.ReadToEnd(); } Page | 1
SharePoint Tips & Code return xslString; }
2.Using linq with Sharepoint and disposing of objects
The technical answer to your original question is a qualified "No": all SPWeb objects opened from an SPSite are automatically disposed when the SPSite is disposed. However, in practice it is a good idea to dispose an SPWeb as soon as you're done with it to reduce memory pressure, especially when working with code like this that opens several SPWeb objects. Implementing this dispose-safe behavior for LINQ is actually quite simple in C#. You can find full details in this post, but the short version is that a C# iterator can handle disposal for you. Using my AsSafeEnumerable() extension method, your code is relatively safe written like this: using (SPSite spSite = Utility.GetElevatedSite(_rootUrl)) { var sw = from SPWeb web in spSite.AllWebs.AsSafeEnumerable() where web.ServerRelativeUrl.ToLower() == path from SPWeb subWeb in web.Webs.AsSafeEnumerable() select subWeb; foreach(SPWeb aSubWeb in sw) { // Do something } } Now the result of your query, which I've assigned to sw, is a lazy iterator of type IEnumerable<SPWeb>. As you enumerate over that result, each SPWeb will be disposed when the enumerator moves to the next item. This means that it is not safe to use that SPWeb reference, or any SP* object created from it (SPList, etc), outside of your foreach loop. Also sw would not be safe to use outside of that using block because the iterator's SPWebCollections will be tied to the now-disposed SPSite. That said, code like this that enumerates over all webs (twice!) is extremely expensive. There is almost certainly a more efficient way that this could be implemented, if only by using spSite.AllWebs[path] instead of your from/where. Regarding garbage collection, these objects require disposal because of unmanaged memory allocated that the GC doesn't even know about. Finally, a word of caution about your 'GetElevatedSite' utility. If you're using RunWithElevatedPrivileges in your helper method to get your elevated SPSite, there are a number of issues you could run into by returning your SPSite out of that elevated context. If possible, I would suggest using SPSite impersonation instead - my preferred method is described here. Q: When should you dispose SPWeb and SPSite objects? And even more important, when not?
Page | 2
SharePoint Tips & Code A: You should always dispose them if you created them yourself, but not otherwise. You should never dispose SPContext.Current.Web/Site and you should normally not dispose SPWeb if IsRootWeb is true. More tricky constructs are things along the line of SPList.ParentWeb.
3.Sharepoint API - How to Upload files to Sharepoint Doc Library from ASP.NET Web Application You can write some custom code to do it. You could use the SharePoint API if you are on the same server or use WebServices Here is the sample code assuming that you know the url of the document library and you are uploading the document to the root folder. You will have to add Microsoft.SharePoint.dll as reference to your ASP.NET project using (SPSite siteCollection = new SPSite(url)) { using (SPWeb spWeb = siteCollection.OpenWeb()) { SPList spList = spWeb.GetList(url); string fileName = "XXXX"; FileStream fileStream = null; Byte[] fileContent = null; try { string docPath = XXXX; //physical location of the file fileStream = File.OpenRead(docPath + fileName); fileContent = new byte[Convert.ToInt32(fileStream.Length)]; fileStream.Read(fileContent, 0, Convert.ToInt32(fileStream.Length)); spList.RootFolder.Files.Add(spList.RootFolder.Url + "/" + fileName, fileContent, true); spList.Update(); } catch(Exception ex) { } finally { if (fileStream != null) { fileStream.Close(); } } } } Page | 3
SharePoint Tips & Code
4.SPSite, SPWeb and SPWebCollection http://www.sharepointblogs.com/mingssn/archive/2007/10/10/sharepoint-programming.aspx Sharepoint come with many class. Some class name is confusing. SPSite: Represents a collection of sites on a virtual server, including a top-level site and all its subsites. Each SPSite object, or site collection, is represented within an SPSiteCollection object that consists of the collection of all site collections on the virtual server. Note: SPSite is NOT a Collection, in another words, you can not use foreach to loop SPSite. To do this, you have to use SPWebCollection. SPWebCollection: Represents a collection of SPWeb objects. SPWeb: Represents a SharePoint Web site. SPFolder: Represents a folder on a SharePoint Web site. The following snipet is copy from a class: #region properties /// <summary> /// site collection url address /// public string _RootSiteUrl { get { return RootSiteUrl; } set { RootSiteUrl = value; } } /// <summary> /// Site collection, get SPSite object repsent a "collection" /// public SPSite _Sites { get { return new SPSite(this._RootSiteUrl); } set { Sites = value; } } /// <summary> /// Web collection, SPWebCollection, this is the real Collection /// public SPWebCollection _Webs { Page | 4
SharePoint Tips & Code get { return this._Sites.AllWebs; } } public string _PPMAspxPageSiteUrl { get { return this.PPMTargetSiteUrl; } set { PPMTargetSiteUrl = value; } } public SPWeb this[string SiteName] // indexer, represent a SPWeb { get { int i = 0; foreach (SPWeb web in this._Webs) { if (web.Url == SiteName) return this._Webs[ i ]; i++; } return null; } } #endregion The indexer is quite useful, to get a SPWeb object, simply use mySharePointClass["your site or subsite url"] to access!
Sharepoint tips 5.Enable Sharepoint server side script
By default, Sharepoint does not allow server side script for security reason. To get around this, you can modify your web.config to specify which page(s) you allow server side script. For example, you can add following code to your Sharepoint application web.config. ( I am using /en-ca/pages/* as example ) <PageParserPaths> <PageParserPath VirtualPath=”/en-ca/pages/*” CompilationMode=”Always” AllowServerSideScript=”true” /> I found this is very useful during development if you are using in-line script for your aspx pages. Let’s say you have a SP project including some customrized aspx pages. You’ve created feature and a wsp file to install in your Sharepoint. Now you noticed you have to do some change to the in-line script of the aspx file. Of course, you could re-deploy the entire wsp again, but as a developer, you don’t want do this because most likely you have to modify the aspx again and again …Following is the steps I am using Page | 5
SharePoint Tips & Code 1. Load aspx pages from Sharepoint UI and publish it 2. Reset IIS 3. Load this aspx page from your browser Sharepoint will give you error message saying server script is not allowed. OK, that’s fine, modify web.config add <PageParserPaths> and reload the page. Yeah, everything is working and you can see your changes. Someting you should also know 1. It is better using code behind for the aspx pages in your SP project. By using code behind, when you change any logic of your aspx pages, you only rep-deploy dlls, not the page itself. You don’t need modify web.config. 2. Why this does not happen before you change and load the aspx page to the SP? That page also contains script, but SP did not give any error at that time. I will explain this in my next post “Sharepoint ghost”
6.Backup/restore sharepoint content database
I’ve read some articles of how to backup/restore Sharepoint application. Of course you can use stsadm -o backup&restore to do the job but you can also backup/restore content database from sql server directly. Even this is not recommended by some Sharepoint guru but I believeit is good to know and it might work in your scenario (for example, move production/staging/QA envirnment to your dev envirnment). Ok, here are the steps. 1. Backup your sharepoint content database from sql server 2. Go to Sharepoint Centre Admin->Application Management->Content databases. Locate your sharepoint application and remove the content database. This will basically break the link between your sharepoint application and your content database. If you don’t do this, the link always exists and you will not be able to restore the content database later because sql server thinks the content database is still in use, and you will find you can’t kill that process in the sql server. 3. Delete and recreate you local sharepoint content database ( what? r u crazy? ) 4. Restore the content database you did in step 1 5. Go to same place as step 2 and attach the content database. Note, you might get a error message saying the sql database collation is not correct, it is because your sql server default collation is different than what sharepoint required. Go to step 3 and create the database as request by choosing collation Latin1_General_CI_AS_KS_WS. 6. Reset IIS 7. It’s better re-deploy your sharepoint solution but this may not necessary 8. Don’t forget reset sharepoint site collection administrator Few problems you might notice when you access your sharepoint application. 1. Make sure disgard checked out files, otherwise site collection adm will get error when visiting those pages. 2. Any search settings such as scope, managed property, keywords will not restore Page | 6
SharePoint Tips & Code 3. I don’t have a chance to try this on multiple content database scenario, it might not work properly if that is the case. That’s it; this is just an alternative way to backup/restore your sharepoint content. 7.How to programmatically enable scheduling (part I) One of my current task is to enable scheduling for existing 44 document/list libraries. All of these document/list library has only one content type associated with and it was inherited from a base content type. However, Sharepoint default site column Scheduling Start Date and Scheduling End Date are not included in the base content type. To accomplete the task, you can use Sharepoint to configure this manually, steps as following 1. add Scheduling Start Date and Scheduling End Date to the library 2. enable versioning 3. enable scheduling 4. repeat 44 times Apparently, this is not a good idea. If we need rebuild the site from scratch, that means you have to repeat step 1-4 again. So, put on developer’s hat: My first thought is to add Sharepoint default Scheduling Start Date and Scheduling End Date site column to the base content type. I have my content type xml already, so I added the following
The only trick is how to find the default guid. Go to site setting->content type->click any of the site content type include above fields (for example: Generic Content->Scheduling Start Date). The browser address bar ends like Fid=%7B51d39414%2D03dc%2D4bd0%2Db777%2Dd3e20cb350f7%7D, replace %7B with “{”, %2D with “-”, %7D with “}”, the guid is {51d39414-03dc-4bd0-b777-d3e20cb350f7}. Well, build and deploy, those 2 fields successfully added to my base content type. But wait….this does not solve my problem yet. Oops, those 44 document/list library does not change at all because they are there ALREADY….my changing of base content type only work for those NEW document/lists, not for the existing one. Rather than write your own code, Sharepoint actually has a option to let you update all inherited content types when you add/delete site column from its parent content type. You could go to site setting->modify site setting -> content type -> select the base content type -> add fields from existing site column. Make sure “update inheritate content type” check box is checked. Also, do these manual steps before you install the solution. Ok, the 2 extra fields were added to the entire document library after this… (In my case, all content library inherited from the base content type). But, to enable scheduling, you have to enable versioning and scheduling. Instead of do it manually, I decide create a feature to loop through all document library and enable scheduling. Page | 7
SharePoint Tips & Code 8.Organize your Sharepoint solution If you using Visual Studio to design/develop SharePoint solution, keep in mind you suppose using only ONE solution for your entire SharePoint project, that is, only ONE .wsp file will be created for your SharePoint deployment later. By doing this, usually more work for your team is required but will make your deployment much easier and you could also automate your Sharepoint deployment in the future. This article is about how to organize your Sharepoint solution based on my experience. Organize your SharePoint solution Sharepoint solution usually include A: Sharepoint “base project” Your Sharepoint solution is based on this project. This project will handle all the Sharepoint envirnoment. For example: Site column, content type, page layout, master page, list, document library, event handler, managed property, search scope, site definition. Be patient and be careful because this is the backbone your application will establish on. B: Sharepoint “UI project” This should include web parts, ASP.NET control, AJAX controls, delegate controls, css and resource files. I would also include any third party controls into this project if there is any. C: Sharepoint “BI project” This is business interface and logic project, including your business UI, logic and all your .aspx pages. In visual studio, you probably need separate this into several projects. For example: a database layer project, a business logic layer project, a user interface layer project, a workflow layer project C: Sharepoint “support project” This project is to support your solution. This maybe vary depend on what you need. For example, you may need a feature/function to import some data to the SharePoint list a function to help you manage users. a page to allow your client upload XML files to the server a function to allow you import/export your SharePoint keywords a unit testing project to test your custom build function
9.Size of SPWeb based on its Folders and Files private long GetWebSize(SPWeb web) { long total = 0; foreach (SPFolder folder in web.Folders) { total += GetFolderSize(folder); } Page | 8
SharePoint Tips & Code foreach (SPWeb subweb in web.Webs) { total += GetWebSize(subweb); subweb.Dispose(); } return total; }
Sharepoint List Definition that binds only to my Custom Content Type Q:Hi. I am developing a Sharepoint Solution, that implements a new list. This list has an event receiver attached to a Custom Content type. I am using VSeWSS 1.3 for this task and it's going ok (the content type gets created, a list is created and bound to the content type, the event receiver triggers successfully. My only concern is that in the created list, it always show the base Content Type (Item CT with Title field). Through the Web GUI I can hide this content type, but I can't find where to do that in my XML definitions, or make it on the solution to avoid double tasks when deploying. Any suggestions?? A: You will have to edit the Schema.xml for your custom list. Find the
tag and remove any you do not wish to be shown. Your list definition will have a guid (eg. <Elements Id="0a8594c8-5cf1-492e-88ce-df943830c88c") that will specify the list from the schema xml (e.g.) I am not sure what the implementation is for, usually there is a feature.xml to combine the previous xml files together (e.g.<ElementManifests><ElementManifest Location="MyFeature\ListDefinition.xml" /><ElementFile Location="MyFeature\schema.xml" />) A: In the schema.xml you need to make 2 changes in the element add the following attribute: EnableContentTypes="TRUE" the element should contain a element that specifes your custom Content type. for example:
SharePoint Tips & Code FolderCreation="FALSE" Direction="$Resources:Direction;" Url="Lists/List_Title" BaseType="0" Name="List_Title" Id="51D716AC-DF9D-4ebb-9F8E-9134EEBB7C39" Type="100" xmlns="http://schemas.microsoft.com/sharepoint/" EnableContentTypes="TRUE" > <MetaData> Should I use a workflow or event receiver? Q:I want to build a custom content type that will be the basis of a list item that will have multiple states. The various states will determine which list will instantiate this item. It will move between the states, and therefore the lists, based on user actions. I have a few choices to implement this: 1. Create workflows on each list that handle the specific functions related to that list. Move an item to another list when necessary (copy item to new list, delete orig item), and let that workflow kick off. 2. Create a workflow on the custom content type we will be using, and let that move the item between the various lists. Not sure if a workflow on a content type can move from list to list, let alone across site collections. 3. Use the event receivers on the custom content type to manage state. User acts on an item, changing its state, so the event receiver creates a copy of itself on the other list and then deletes itself on the current list. I know this works across site collections. Which way is best, and why? Anything that absolutely will not work? Any method I've overlooked? A: Generally speaking: In SharePoint workflows and event-receivers are related (if you look at the events on a list with an attached workflow you will find a event-receiver starting the workflow..) The advantage of workflows is the possibility for the user to check the log (given that you use the LogActivity) The advantage of event-receivers is the greater number of events; they are more flexible than workflows. From what you describe i would probably choose workflows, so the users can check if their item was processed correct. Export a SharePoint Custom Content Type That Inherits from Contact to Outlook Q: I've created a custom content type that inherits from the built in Contact content type in WSS 3.0. When I create a list instance and assign my custom content type, there is no "Connect to Outlook" option like there would be if an end user created a new "Contact" directly. Am I doing something wrong or is this a limitation of SharePoint? The relevant part of my feature looks like this:
SharePoint Tips & Code A: The "connect to Outlook" is a function of the list, not the content type. I.e. you can have multiple content types for a Contact list, but you will need to create a list based on a Contact list to get the additional features you require. For a test to see what I mean, create a contact list, go to the advanced settings for the list and Allow management of content types. Add your custom content type and remove the default contact. Now for giggles, delete your custom content type and add the Announcement content type. You will still be able to "Connect to Outlook". Hopefully that demonstrates that the content type is mainly about the fields available to the listitem stored as that particular content type.
Customizing default.master in WSS 3.0 Q: Does anyone know how to customize the master page (i.e. default.master) for a WSS 3.0 site? Actually, I already know how to customize the master page (i.e. via SharePoint Designer, or a text editor), I'm more interested in learning how to tell WSS 3.0 to use my customized master page. I've renamed the default.master with my customized version and that works, but that sets it for all WSS sites. I want to be able to use version A of a master page for site collection A, and a separate master page for other site collections. Note, I'm NOT referring to MOSS 2007. I already know how to set the master page for MOSS 2007. I can't seem to figure out how to do it for WSS 3.0 though. A: WSS pages use Master Pages just like regular web apps. However, the value if the MasterPageFile attribute is a token, and is set to "~default.master". This token gets replaced with the actual reference to the master page in the page's PreInit method. You can change the value of ~default.master to anything you like. But a better solution is to do the same type of thing that SharePoint does. I added an HttpHandler to my SharePoint site. The handler attaches to the PreRequestHandlerExecute method, and changes the value of the master page file attribute before SharePoint starts rendering the page. Add a line to the httpModules section of the web.config that looks like this: Then create a class like this: namespace MyClassLibrary.Utility { public class MasterPageHttpModule : IHttpModule { public void Init(HttpApplication context) { context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute); } void context_PreRequestHandlerExecute(object sender, EventArgs e) { bool useCustomMasterPages = Convert.ToBoolean(ConfigurationManager.AppSettings["ShowCustomMasterPages"].ToString()); if (useCustomMasterPages) { Page page = HttpContext.Current.CurrentHandler as Page; if (page != null) Page | 11
SharePoint Tips & Code { page.PreInit += new EventHandler(page_PreInit); } } } void page_PreInit(object sender, EventArgs e) { bool useThreeColumnMaster = Convert.ToBoolean(ConfigurationManager.AppSettings["UseThreeColumnMasterOnDefaultPage"].ToStr ing()); try { Page page = sender as Page; if (page != null && SPContext.Current != null) { string url = page.Request.Url.AbsolutePath.ToLower(); if (url.IndexOf("/public/") > -1) { if (url.IndexOf("sitemap.aspx") == -1) { page.MasterPageFile = "~/_catalogs/masterpage/edge_con.master"; } else { page.MasterPageFile = ""; } } else if (url.IndexOf("default.aspx") > -1) { if (useThreeColumnMaster) { page.MasterPageFile = "~/_catalogs/masterpage/edge_con.master"; } else { page.MasterPageFile = "~/_catalogs/masterpage/edge.master"; } } else if (url.IndexOf("sitemap.aspx") > -1) { // // Sitemap pages should not have a master page // page.MasterPageFile = ""; page.Controls.Clear(); } else if (url.IndexOf("/admin/") > -1) { page.MasterPageFile = "~/_catalogs/masterpage/edge.master"; Page | 12
SharePoint Tips & Code } else if (url.IndexOf("/member/") > -1) { page.MasterPageFile = "~/_catalogs/masterpage/edge.master"; } else if (url.IndexOf("/supplier/") > -1) { page.MasterPageFile = "~/_catalogs/masterpage/edge.master"; } else if (page.MasterPageFile == "~masterurl/default.master") { page.MasterPageFile = "~/_catalogs/masterpage/edge.master"; } } } catch (Exception exception) { LogWriter logWriter = new LogWriter(); logWriter.WriteToLog("Could not set master page: " + exception.Message, LogType.MasterPage, DateTime.Now); } } public void Dispose() { } } } Now you are dynamically choosing a mater page. A: You need to change the MasterUrl property on the SPWeb object representing the site. A good way to do this is to create a SharePoint feature that when activated sets the property, and when deactivated restores the original value. The Feature.xml could look like this: <ElementManifests> <ElementManifest Location="Masterpage.xml"/> <ElementFile Location="my.master"/> Page | 13
SharePoint Tips & Code The file Masterpage.xml uploads your master page to the gallery: <Elements xmlns="http://schemas.microsoft.com/sharepoint/"> <Module Name="UploadMaster" Url="_catalogs/masterpage" > Include this feature in a WSP solution along with the MyAssembly.dll containing the MyFeatureReceiver class, which goes like this: public class MyFeatureReceiver : SPFeatureReceiver { public override void FeatureActivated(SPFeatureReceiverProperties properties) { SPWeb web = (SPWeb)properties.Feature.Parent; web.MasterUrl = properties.Definition.Properties["MyMaster"].Value; web.Update(); } public override void FeatureDeactivating(SPFeatureReceiverProperties properties) { SPWeb web = (SPWeb)properties.Feature.Parent; web.MasterUrl = "default.master"; web.Update(); } public override void FeatureInstalled(SPFeatureReceiverProperties properties) { } public override void FeatureUninstalling(SPFeatureReceiverProperties properties) { } } Finally, deploy the solution and activate the feature on your site(s).
SharePoint - How can I customize NewForm.aspx in custom feature? Q:I have created a custom list in a SharePoint site and generated a Visual Studio 2008 project using SharePoint Solution Generator. I can package this as a feature and install it. It runs fine on my server. After testing this out, I've been able to add a custom masterpage to the feature which is deployed to the _catalogs/masterpage folder. Here it is: <Elements Id="2196306F-3F37-40b5-99CF-42E89B93888A" xmlns="http://schemas.microsoft.com/sharepoint/"> Page | 14
SharePoint Tips & Code <Module Name="DefaultMasterPage" Url="_catalogs/masterpage" RootWebOnly="FALSE"> Now that I have a custom masterpage in my site, I would like to have this used for the creation of new items. But I don't want to have to set the master from SharePoint Designer. Going back to the generated solution, it has NewForm.aspx etc. with the list schema. How do I... Customize the form that is displayed for new items, and have it redirect to a thankyou.aspx page rather than showing all the list items? Set the url to the master page correctly? I'm lost on point number 1. Do I need to create a custom webpart and embed that in NewForm.aspx? On point 2 I have made some headway but have run into an issue. If I set the master like this in my NewForm.aspx... MasterPageFile="~masterurl/gcmaster.master" It will install OK, If I use _catalogs/masterpage in the directive, it will not find the master because the URL is relative. Only this code seems to work: MasterPageFile="../../_catalogs/masterpage/gcmaster.master" What's the best practice way of setting the master page file in SharePoint, when deploying a custom feature/solution? A: Best way to do it is open up NewForm.aspx in SharePoint Designer, change: MasterPageFile="~masterurl/default.master" to MasterPageFile="~masterurl/custom.master" This will obviously edit the current instance but if you want to deploy this with a site def or feature then you need to create and NewForm.aspx page within the same folder as schema.xml in your list instance folder. Hope that helps.
How do I programmatically retrieve previous versions of master pages from a SharePoint site? A: The SPFileVersion class was indeed the way forward. Here's a snippet of code that will export previous versions of the default.master page out to the file system. One thing to note though is that exporting versions[0] doesn't work - it produces an exception when trying calling ver.OpenBinary. I suspect that this has something to do with the whole ghosted/unghosted issue in SharePoint, that the original version of the file is stored differently to subsequent versions. Running this code for other files that were added to the master page gallery, works when retrieving versions[0]. This seems to be an issue only for files that were in the original uncustomised SharePoint site. Page | 15
SharePoint Tips & Code SPFile file; SPFileVersionCollection versions; SPFileVersion ver; byte[] content; FileStream fs; SPSite site = new SPSite("http://localhost:6000"); file = site.RootWeb.GetFile("_catalogs/masterpage/default.master"); Console.WriteLine(file.Url + ", " + file.Length); versions = file.Versions; Console.WriteLine(versions.Count); for (int i = 1; i < versions.Count; i++) { ver = versions[i]; Console.WriteLine(ver.VersionLabel + ", " + ver.Size); content = ver.OpenBinary(); fs = new FileStream("c:\\temp\\" + ver.VersionLabel + "-default.master",ileMode.Create); fs.Write(content, 0, content.Length); fs.Close(); } How to update default value for “Title” field in “Document” content type or types inherited from it This is what we have found for "Title": • The “Title” field is not sealed in fieldwss.xml (12\TEMPLATE\FEATURES\fields) • It’s not sealed under “Document” content type in ctypeswss.xml (12\TEMPLATE\FEATURES\ctypes) • It is sealed in Document library definition in the schema.xml (12\TEMPLATE\FEATURES\DocumentLibrary\DocLib) Setting default value through OM would reset the value to NULL on content type .Update(). This is because "Title" is sealed in the DL definition. Common (and simple) coding tasks in sharepoint Well, everyone went to teched last week and I was left at work stuck with a project with a strict deadline. So out of boredom I decided that I will compile a small snippet database for beginning SharePoint developers. I see a lot of people in the microsoft public sharepoint developer forums asking these questions over and over again, so I thought I will save everyone some time by answering these right here. Some people may say these are RTFM questions, since the SDK contains all of these examples, but apperantly people dont bother with the SDK, or find it confusing to navigate through. So here is my 2 cents. The purpose here is to give some code samples to common tasks like: •getting a reference to a site •Iterating over all lists in a site Page | 16
SharePoint Tips & Code •getting a reference to a list •getting a reference to an item in a list •getting a reference to the item's properties •getting a reference to a document and its properties •adding a new item to a list •modifying an item in a list If you feel I should add to this list, let me know and I will write some sample code. However, please remember this is for simple, common tasks and I wont start giving application samples here. So, lets get down to it! Getting a reference to a site Ok, you have a sharepoint site in a URL "http://server/sites/site" and you want to get the object of the site from its URL. What do you do? using(SPSite mySite = new SPSite("http://server/sites/site")) { using(SPWeb myWeb = mySite.OpenWeb()) { } } Now you have the SPWeb object, allowing you to get information about the site you used in the URL. For example, iterating all lists in the site: using(SPSite mySite = new SPSite("http://server/sites/site")) { using(SPWeb myWeb = mySite.OpenWeb()) { string linksHtml = ""; foreach(SPList list in myWeb.Lists) { string listLink = "" + list.Title +
The above example also showed how to get a reference to a list by iterating over the lists in a site. But what if you want a specific list called "contacts"? The following will show you how to get the object for a list that you know the name of: using(SPSite mySite = new SPSite("http://server/sites/site")) { using(SPWeb myWeb = mySite.OpenWeb()) { SPList contactsList = myWeb.Lists["Contacts"]; } } Now lets iterate through the items in the list and get item's properties: Page | 17
SharePoint Tips & Code using(SPSite mySite = new SPSite("http://server/sites/site")) { using(SPWeb myWeb = mySite.OpenWeb()) { SPList contactsList = myWeb.Lists["Contacts"]; foreach (SPListItem contact in contactsList.Items) { string contactLastName = contact["Last Name"].ToString(); } } } Now some of you are saying - "what about documents?", to which I answer that document libraries are the same as lists (ok, there are some differences but we will leave that for future articles). To get to a document in a document library you can either use the code above to iterate through the library and its files, or, if you know the file's URL, you can do this: using(SPSite mySite = new SPSite("http://server/sites/site")) { using(SPWeb myWeb = mySite.OpenWeb()) { SPFile file = myWeb.GetFile("http://server/sites/site/library/folder/file"); } } File properties are available through its Item property. If you have for example a text field in the document library called "My Custom String Property", and you want to know the value for that field in a specific file, use the following code: using(SPSite mySite = new SPSite("http://server/sites/site")) { using(SPWeb myWeb = mySite.OpenWeb()) { SPFile file = myWeb.GetFile("http://server/sites/site/library/folder/file"); string filePropertyValue = file.Item["My Custom String Property"].ToString(); } } We now want to add a new item to a list: using(SPSite mySite = new SPSite("http://server/sites/site")) { using(SPWeb myWeb = mySite.OpenWeb()) { SPList contactsList = myWeb.Lists["Contacts"]; SPListItem newItem = contactsList.Items.Add(); newItem["First Name"] = "Ishai"; newItem["Last Name"] = "Sagi"; newItem.Update(); } } Page | 18
SharePoint Tips & Code Let’s change all items in the list to build the email address based on the first and last name (in the format of '[email protected]'): using(SPSite mySite = new SPSite("http://server/sites/site")) { using(SPWeb myWeb = mySite.OpenWeb()) { SPList contactsList = myWeb.Lists["Contacts"]; foreach(SPListItem existingItem in contactsList.Items) { existingItem["E-mail Address"] = existingItem["First Name"] + "." + existingItem["Last Name"] + "@testing.com"; newItem.Update(); } } } Well, that's it. If you think of more snippets like that, let me know! Server side controls and data binding in web parts One of the most common mistakes (or questions) that developers encounter when starting with web part development is how to use server side controls such as buttons, data grids or drop-down boxes in a webpart. This is because web part development is not the usual, easy (lazy) drag-and-drop development that most of us MS developers take for granted. Unless you use smartpart (which you shouldn't - article coming soon), there is no way to develop a web part in a drag-and-drop environment, and you are stuck with writing manual server side code. The drag-and-drop environment usually takes care of the declaration of the objects we are drag-anddropping, and the instantiation of the objects in the right time in the life cycle of the web page. With web parts, you don't have access to page events, just control (web part) events, and you have to know yourself how, when and where to do what with the server control you want to use in your web part. This leads to several common mistakes: •Controls are rendered, but do not respond to events (you click on a button, the page posts-back but nothing happens) •Controls are rendered, but the data they are supposed to show is not showing •Controls are rendered with data in them, but if you change a property value in the web part, the data is not changed in the control until I refresh the page (or click "apply" twice) All of these are easy to avoid, and should not baffle you as a developer. In this article I will show how to use a server side control, with data binding and event triggering in a web part. My button is not triggering the click event This is always for the same reason. You created the button in the wrong place in the web part life cycle. Your code probably looks like this: Page | 19
SharePoint Tips & Code
public class MyFirstWebPart : WebPart { Label userName = new Label(); Button changeUserButton = new Button(); //this is wrong! protected override void CreateChildControls() { userName.Text = "Ishai"; changeUserButton.Text = "Change Label Text To 'Sagi'"; changeUserButton.Click+=new EventHandler(changeUserButton_Click); this.Controls.Add(userName); this.Controls.Add(changeUserButton); } void changeUserButton_Click(object sender, EventArgs e) { userName.Text = "Sagi"; } } Now - why doesn't this code work? you get a label with "Ishai", you get a button with some text, and when you click on the button the page does a post back but the label's text doesn't change. Basically the changeUserButton_Click event isn't getting triggered. The answer is - the button is getting recreated every time you load the page. This is because the "= new Button();" instantiation is done in the wrong place. It is supposed to be done in the CreateChildControls event, so that the control doesnt get recreated every time the page is loaded. If the control gets recreated, it looses its connection to the event handler function, and the event never gets triggered. A bigger mistake I see often in the forums is when developers create the button in the web part in the render() event, which reconnects the control every time the web part is rendered. The correct way to do the same thing is using the following code: public class MyFirstWebPart : WebPart { Label userName = new Label(); Button changeUserButton; protected override void CreateChildControls() { changeUserButton = new Button(); userName.Text = "Ishai"; changeUserButton.Text = "Change Label Text To 'Sagi'"; changeUserButton.Click+=new EventHandler(changeUserButton_Click); this.Controls.Add(userName); this.Controls.Add(changeUserButton); } void changeUserButton_Click(object sender, EventArgs e) { userName.Text = "Sagi"; } Page | 20
SharePoint Tips & Code } Notice the difference (marked in bold)? The button is instantiated in the CreateChildControls event, and will not get rebuilt each time the page is loaded. This means the click event will work when the button is clicked. ------------------------------------------------------------------------------------------------------------------------------Controls are rendered, but data isn't rendered. The major differance between the drag-and-drop interface and the code-your-control interface that we use for web parts, is that you don't get visual studio to automagically configure your data connection and binding for you. I know a lot of people who get really confused when tasked with using a data control manually, because they are not aware what should be done to connect the control to the data. They are used to doing it using the menus that come out of the box with visual studio when you design a page. If you are such a developer, you need to learn the minimum of what automatic actions visual studio takes when you drag-and-drop a data control on the page, and then use menus to connect it. In a web part you do it manually. The following example shows how to connect a data grid to a data table and show them on a web part. While the following code works, it is still not the best practice this article wants to point out - since it still has one little thing that can be done better - and avoid the last common mistake. But I will tackle that after I explain this code: public class MyFirstWebPart : WebPart { DataGrid grid; protected override void CreateChildControls() { DataTable dt = null; using (SPSite site = new SPSite(Page.Request.Url.ToString())) { using (SPWeb web = site.OpenWeb()) { SPList list = web.Lists["Contacts"]; string q = @" <Where> 0 "; SPQuery query = new SPQuery(); query.Query = q; SPListItemCollection items = list.GetItems(query); dt = items.GetDataTable(); } } grid = new DataGrid(); grid.DataSource = dt; grid.AllowPaging = true; Page | 21
SharePoint Tips & Code grid.AllowSorting = true; grid.GridLines = GridLines.None; grid.PageSize = 5; grid.PagerStyle.NextPageText = "Next"; grid.PagerStyle.PrevPageText = "Previous"; grid.AutoGenerateColumns = true; grid.DataBind(); this.Controls.Add(grid); base.CreateChildControls(); } } So, what is going on here? Like in the first example, most of my code is in the CreateChildControls event. As we are going to see later, this is not always the right place to put all of the code. 1.Using the code from my common coding tasks article, I connect to the sharepoint site that the web part is hosted in, and connect to the "Contacts" list. 2.Using a simple CAML query to return all items in the contacts list (this is a sample on how to use queries), I get a datatable of all contacts in the list. (this is on the line that says dt = items.GetDataTable();) 3.I create the datagrid object ("grid") in the createchildcontrols event. This is (as I mentioned before) the correct place to create server side controls. 4.I set the datasource property of the DataGrid to point to the DataTable. This is where we define the connection between the two (this is what you used to do using a wizard in the drag-and-drop .net applications). 5.I also set some default settings for the grid - like paging, sorting and that is should Auto Generate Columns. This is where I see a lot of mistakes in the forums - people assuming that just connecting the datasource to the datagrid and calling DataBind() should be enough to get something on the page, and then don't understand why there is nothing rendered. If you don't set AutoGenerateColumns to true, you will have to define (manually) which columns from the data table to display. 6.Finally, I call the DataBind() method which is another thing people tend to forget. Without this call, you will not get information in the grid!. Changing web part properties require me to click apply twice for the data to change! This is a usual mistake that I have sinned my self in doing and has everything to do with where\when you get the data. If the data should change based on something that happens like changing the web part properties (or getting input from a connected web part), if you get the data before the change in the property is registered, then you will have to refresh the page to see the changes! So how do we do it? The answer may be complicated. It all depends on the control you are using and how you want to use it. In the following example I will still use the same datagrid code I used above, but will add a property to the web part to allow the user to specify which list to render. If I only add the property and change the Page | 22
SharePoint Tips & Code code in the "SPList list = web.Lists["Contacts"];" line, the user will change the value of the property to another list, but still see the same values from the contacts list, until he refreshes the page. So I will move the code that builds the DataTable into the OnPreRender event. public class MyFirstWebPart : WebPart { DataGrid grid; DataTable dt = null; private const string c_Default_List_Name = "Contacts"; private string listName = c_Default_List_Name; [WebBrowsable(true), Personalizable(true), Category("Web Part List Connection"), DisplayName("List Name"), WebDisplayName("List Name"), Description("Defines the list the web part will read from."), DefaultValue(c_Default_List_Name)] public string ListName { get { return listName; } set { listName = value; } } protected override void CreateChildControls() { grid = new DataGrid(); grid.AllowPaging = true; grid.AllowSorting = true; grid.GridLines = GridLines.None; grid.PageSize = 5; grid.PagerStyle.NextPageText = "Next"; grid.PagerStyle.PrevPageText = "Previous"; grid.AutoGenerateColumns = true; this.Controls.Add(grid); base.CreateChildControls(); } protected override void OnPreRender(EventArgs e) { using (SPSite site = new SPSite(Page.Request.Url.ToString())) { using (SPWeb web = site.OpenWeb()) { SPList list = web.Lists[this.ListName]; string q = @" <Where> 0 "; Page | 23
SharePoint Tips & Code SPQuery query = new SPQuery(); query.Query = q; SPListItemCollection items = list.GetItems(query); dt = items.GetDataTable(); } } grid.DataSource = dt; grid.DataBind(); base.OnPreRender(e); } } What is happening here? The web part now has a property called ListName, which allows the user to select a different list to display in the grid. The major change is that while the DataGrid is still getting created and added to the controls collection in the CreateChildContorls event, which is where you should do that, it is only getting connected to the DataTable in the OnPreRender event - just before the page starts rendering the html. This means that changes made to the web part like setting the ListName property have already taken place. Finally, although it is not part of this article's original scope, I will give a final example on how to specify to the web part which fields to display (since I know I will get questions on that). Basically, what you need to do is to remove the code line that says AutoGenerateColumns = true and manually add columns. The problem is that columns may or may not have internal names. What I do is use the AutoGenerateColumns to see the field names, and then specify the column names as they are returned. The following code sample will allow the user to select a list, and specify which fields to display: public class MyFirstWebPart : WebPart { DataGrid grid; DataTable dt = null; Label errLabel = new Label(); #region properties #region ListName private const string c_Default_List_Name = "Contacts"; private string listName = c_Default_List_Name; [WebBrowsable(true), Personalizable(true), Category("Web Part List Connection"), DisplayName("List Name"), WebDisplayName("List Name"), Description("Defines the list the web part will read from."), DefaultValue(c_Default_List_Name)] public string ListName { get { return listName; } Page | 24
SharePoint Tips & Code set { listName = value; } } #endregion #region AutoGenerateColumns private const bool c_Default_AutoGenerateColumns = true; private bool autoGenerateColumns = c_Default_AutoGenerateColumns; [WebBrowsable(true), Personalizable(true), Category("Web Part List Connection"), DisplayName("Show All Columns?"), WebDisplayName("Show All Columns?"), Description("Defines if the web part will display all the columns. If false, the columns have to be defined in the 'Columns To Display' property."), DefaultValue(c_Default_AutoGenerateColumns)] public bool AutoGenerateColumns { get { return autoGenerateColumns; } set { autoGenerateColumns = value; } } #endregion #region ColumnsToDisplay private const string c_Default_Columns_List = ""; private string columnsToDisplay = c_Default_Columns_List; [WebBrowsable(true), Personalizable(true), Category("Web Part List Connection"), DisplayName("Columns To Display (CSV)"), WebDisplayName("Columns To Display (CSV)"), Description("Defines a Comma Seperated Value list of the list's fields that will be displayed, if 'Show All Columns' is turned off."), DefaultValue(c_Default_Columns_List)] public string ColumnsToDisplay { get { return columnsToDisplay; } set { columnsToDisplay = value; } } #endregion #endregion #region events protected override void CreateChildControls() { grid = new DataGrid(); grid.AllowPaging = true; grid.AllowSorting = true; grid.GridLines = GridLines.None; Page | 25
SharePoint Tips & Code grid.PageSize = 5; grid.PagerStyle.NextPageText = "Next"; grid.PagerStyle.PrevPageText = "Previous"; this.Controls.Add(grid); base.CreateChildControls(); errLabel.Text = ""; this.Controls.Add(errLabel); } protected override void OnPreRender(EventArgs e) { errLabel.Text = ""; try { using (SPSite site = new SPSite(Page.Request.Url.ToString())) { using (SPWeb web = site.OpenWeb()) { SPList list = web.Lists[this.ListName]; string q = @" <Where> 0 "; SPQuery query = new SPQuery(); query.Query = q; SPListItemCollection items = list.GetItems(query); dt = items.GetDataTable(); if (this.AutoGenerateColumns) { grid.AutoGenerateColumns = true; } else if (this.ColumnsToDisplay.Length > 0) { grid.AutoGenerateColumns = false; string[] columnNames = this.ColumnsToDisplay.Split(','); foreach (string columnName in columnNames) { if (list.Fields.ContainsField(columnName)) { SPField field = GetField(list, columnName); if (field != null) { System.Web.UI.WebControls.BoundColumn col = new BoundColumn(); col.DataField = field.InternalName; col.HeaderText = field.Title; grid.Columns.Add(col); } } Page | 26
SharePoint Tips & Code } } } } grid.DataSource = dt; grid.DataBind(); } catch (Exception ex) { errLabel.Text = ex.ToString(); } base.OnPreRender(e); } #endregion /// <summary> /// A function to get a field from a list, supports both internal names and display names /// /// <param name="list">The list that contains the field /// <param name="name">The name of the field (internal or display names supported) /// An SPField object if the field is found, null if not found. private SPField GetField(SPList list, string name) { SPField field = null; try { field = list.Fields.GetFieldByInternalName(name); } catch { } try { field = list.Fields.GetField(name); } catch { } return field; } } The final code has two more properties - allowing you to select if the web part should Auto Generate Columns, or, if not, which columns to display. A custom function I wrote allows you to get a referance to the fields specified, even if the user used an internal name or a display name. Finaly, I moved the column creating code into the OnPreRender event as well, so that if you change the properties and define columns, you dont have to refresh to see the changes. I hope this answers any unanswered questions you may have had on how to use server controls and data binding in sharepoint web parts.
Page | 27
SharePoint Tips & Code How to add a site column to a list by code A colleague just asked me how to add a site column defined in the root web to a list somewhere in the site, so here is the small snippet: private void AddSiteColumnToList(string sitePath, string listName, string columnName) { using (SPSite site = new SPSite(sitePath)) { using (SPWeb web = site.OpenWeb()) { //Since the site column is on the root web, //we need to get the root web //If it was'nt on the root web, but instead on the same site as the //list, we could have just used the "web" object using (SPWeb root = site.RootWeb) { //get the site column from the root web SPField fld = root.Fields[columnName]; //get the list from the site SPList list = web.Lists[listName]; //add the column list.Fields.Add(fld); //update the list list.Update(); } } } } Introduction This multipart series of articles is intended to help you get ramped up with SharePoint customization. It's about modifying the default SharePoint user experience, list forms customization, branding, skinning SharePoint portals, etc. In Part 1, we introduced a generic function that can be used to hide the list view toolbar menu items (e.g., New Item, Upload, Edit in Grid view, etc.). If you haven't read it yet, I would encourage you to do that first. Here, I'll show you two other tricks for customizing the list form toolbar. Trick #1 : Hiding the List View Toolbar Menu Items!
Page | 28
SharePoint Tips & Code I've gone through SharePoint projects on different scales; a common requirement among most of these projects is hiding some menu items that are implemented by default within the framework of SharePoint. The obvious choice from the SDK is HideCustomAction! After digging through the web, I found out the following: • •
•
The "HideCustomAction" feature can merely hide the items which have been rendered through the "CustomAction" framework features such as Site Actions and Site Setting etc. ECB (Context Menu) items are rendered by JavaScript from the Core.js file so we can't hide them via the "HideCustomAction" feature. However, you can add a new menu item in the ECB menu through the "CustomAction" feature and hide it again through the "HideCustomAction" feature. In other words, the "HideCustomAction" feature can be used to hide the ECB menu items that you created via CustomAction, but can't be used to hide the out of the box menu items. The ListViewWebPart menu items (New menu, Upload menu, Actions menu, … etc.) are rendered through a class library as a web control from the Microsoft.SharePoint.dll, so they can't be hidden through the "HideCustomAction" feature.
Hmm, I thought of delving back into the world of JavaScript, and I came up with some generic functions that can be used to hide any menu item in SharePoint, and I decided to share them with the community. hideListViewToolbarItems("Edit in Datasheet", "export to Spreadsheet", "view rss feed","settings:create view"); function hideListViewToolbarItems() { /// <summary> /// By : Ayman M. El-Hattab ( [email protected] ) /// http://ayman-elhattab.blogspot.com /// var menuItem; var menuItemName; var menuItemIndex=-1; var menuItemNames=new Array("edit in datasheet", "open with windows explorer", "connect to outlook",'export to spreadsheet','view rss feed','alert me' ,"create column","settings:create view","list settings", "document library settings","explorer view","all documents", "all items","modify this view", "view:create view","new document", "new item","new folder","upload document", "upload multiple documents"); var menuItems = new Array("EditInGridButton", "OpenInExplorer","OfflineButton", "ExportToSpreadsheet","ViewRSS", "SubscribeButton","AddColumn", "AddView","ListSettings","ListSettings", "View1","DefaultView", "DefaultView","ModifyView","CreateView", "New0","New0", "NewFolder","Upload","MultipleUpload"); Page | 29
SharePoint Tips & Code
var allMenuItems = document.getElementsByTagName('ie:menuitem'); for(var i = 0; i < hideListViewToolbarItems.arguments.length; i++ ) { menuItemName= hideListViewToolbarItems.arguments[i].toLowerCase(); for (j=0; j < menuItemNames.length; j++) { if(menuItemNames[j]==menuItemName) { menuItemIndex = j; break; } } menuItem=menuItems[menuItemIndex]; for (var l = 0; l < allMenuItems.length; l++) { if(menuItemName.indexOf(":")!=-1) { menuItemName = menuItemName.split(":")[1]; } if (allMenuItems[l].id.indexOf(menuItem)!=-1 && allMenuItems[l].text.toLowerCase() == menuItemName) { // For FireFox Compatibility var parentNodeOfMenuItem = allMenuItems[l].parentNode; parentNodeOfMenuItem.removeChild(allMenuItems[l]); break; } } } } You can use this function to hide any menu item rendered in the ListViewWebPart toolbar which is used in the list view pages. Just call the function and pass the menu item names (comma separated) as they appear in the toolbar, ignoring the case. Only one exception to that: when you need to hide "Create View" which appears twice, once in "List Settings", and then in the view selector. In order to resolve this conflict, just call the function as follows: hideListViewToolbarItems("settings:create view") or hideListViewToolbarItems("view:create view").
Page | 30
SharePoint Tips & Code
Trick #2: Hiding List Form Toolbar Menu Items!
Sometimes, you need to remove some items from the toolbar that appears at the top of DispForm.aspx. Unfortunately, the "HideCustomAction" feature can merely hide the items which have been rendered through the "Custom Action" feature framework such as Site Actions and Site Setting, so let's take the same approach we took in Part 1, which is delving back into the world of JavaScript, which I really find very handy when it comes to SharePoint customization. The following function can be used to hide any toolbar item in dispform.aspx. Just call the function passing the names of the items comma separated (e.g., New Items, Alert Me, etc.). The function removes the items, and of course, the images rendered beside them (if found). Trick #3 will deal with where and how you can add the function to your list form. hideFormMenuItems("New Item","Alert Me"); function hideFormMenuItems() { var titleToHide=""; var anchorTag; var allAnchorTags = document.getElementsByTagName('a'); for(var i = 0; i < hideFormMenuItems.arguments.length; i++ ) { titleToHide = hideFormMenuItems.arguments[i]; if(titleToHide!='Alert Me') { for (var j = 0; j < allAnchorTags.length; j++) { anchorTag= allAnchorTags[j]; if (anchorTag.title.indexOf(titleToHide)!=-1) { anchorTag.parentNode.parentNode.parentNode.parentNode. parentNode.style.display="none"; anchorTag.parentNode.parentNode.parentNode.parentNode. parentNode.nextSibling.style.display="none"; break; } } Page | 31
SharePoint Tips & Code } else { for (var k=0; k < allAnchorTags.length;k++) { anchorTag= allAnchorTags[k]; if (anchorTag.id.indexOf("SubscribeButton")!=-1) { anchorTag.parentNode.parentNode.parentNode.parentNode. parentNode.style.display="none"; break; } } } } var allSpanTags = document.getElementsByTagName("span"); var spanTag; var toolbarRow; var lastCell; for(var m=0; m < allSpanTags.length;m++) { spanTag = allSpanTags[m]; if(spanTag.id=='part1') { toolbarRow = spanTag.childNodes[2].firstChild.firstChild; lastCell = toolbarRow.lastChild.previousSibling; while(lastCell.style.display=='none') { lastCell = lastCell.previousSibling; } if(lastCell.innerText == '|') { lastCell.style.display='none'; break; } } } Kindly note that the function is case sensitive, so take care of the case the toolbar item names are rendered with. Trick #3: Adding JavaScript Through CEWP! The content editor Web Part gives you the ability to add any valid HTML on the Web Part page you place it in, and this surely includes <script> elements. The major advantage when using the content editor Web Part is that anyone with Designer permissions can do it quickly and easily. Page | 32
SharePoint Tips & Code Nice and easy, let's do it, just go to Site Actions, Edit Page!!
Where the heck is the Edit Page menu item?!! Just like the old version, it's not available, I’m still not sure why they did that! Trick #3: Just append "&ToolPaneView=2" to the URL, and you will have DispForm.aspx in Edit Mode in your browser.
Add a content editor Web Part just below the Web Part that renders the list form, and insert the JavaScript of Trick #2 as shown in the figure below. (It's preferable to mark the content editor Web Part as hidden.) Page | 33
SharePoint Tips & Code
Exit the edit mode…
Page | 34
SharePoint Tips & Code
Tremendous, we managed to hide "New Item" and "Alert Me". What about renaming "Edit Item" to "Edit Program" and removing "Delete Item" but leaving the small image (x)? That is what I'm going to cover in Trick #4 in the next part!
Trick #4 : Renaming List Form Toolbar Items! Sometimes, you need to rename some items in the toolbar rendered at the top of DispForm.aspx to match the List Name. For instance, you have a custom list named "Programs" that stores the courses offered by a university. It would be better to have "New Program" rather than the default "New Item" and "Edit Program" rather than "Edit Item". Sometimes, you just need to totally delete the text and just leave the images with a tooltip that explains the action performed on clicking them. Before :
After :
Page | 35
SharePoint Tips & Code The following function can be used to achieve both tasks, just call the function passing the old and new names of the item. For instance if you need to rename "Add Item" to "Add Program", use renameFormMenuItems("Add Item","Add Program");. In case you need to remove the text and leave the image, call the function passing an empty string as the new name as follows: renameFormMenuItems("Delete Item","");. renameFormMenuItems("New Item","New Program"); renameFormMenuItems("Edit Item","Edit Program"); renameFormMenuItems("Delete Item",""); function renameFormMenuItems(oldName,newName) { var anchorTag; var allAnchorTags = document.getElementsByTagName('a'); if(oldName.length!=0) { for (var j = 0; j < allAnchorTags.length; j++) { anchorTag= allAnchorTags[j]; if (anchorTag.innerText.indexOf(oldName)!=-1) { anchorTag.innerText=newName; try { if(newName.length!=0) { anchorTag.parentNode.previousSibling.firstChild. firstChild.alt=newName; } else { anchorTag.parentNode.previousSibling.firstChild. firstChild.alt=oldName; } } catch(err) { } } } } Page | 36
SharePoint Tips & Code } Kindly note that the function is case sensitive so take care of the case the toolbar item names are rendered.
Page | 37