Hello kind readers…
A good friend of mine, Vardhaman wrote a post about adding web part through COM some time back,
here. I would like to take it a step further on how it be a wonderful thing in making your sandboxed solutions more fun and limit free :). To understand what I am talking about, let's look at a scenario,
Scenario:
You have a sandbox solution and have a hierarchy of sites created. Every site has a landing page, mainly default.aspx and you have a particular web part as part of each site landing page. Now you want to automate this task.
Approach:
Normally a direct approach would be to write a feature .. Some server code to loop through all webs and add the web part on the page using the SPLimitedWebPartManager class.. BUT.. This class in not available to the Sandboxed solution. That’s a bummer .. Enters Client Object Model we dearly call COM. Now using the COM you can add a web part to a page even in a sandbox solution and is pretty cool!! Lets see how we can use the magical COM to get what we want… step by step ...
$(document).ready(function () {
ExecuteOrDelayUntilScriptLoaded(addWebpartForMe, "sp.js"); //Self explanatory
});
function addWebpartForMe() {
// you can get the client context using the server relative URL of the current site, you can use the
//SharePoint js variable to read from the current page or can pass it dynamically to your function
//it works with get_current() as well, no need to pass absolute URL to get the context
var clientContext = new SP.ClientContext(_spPageContextInfo.siteServerRelativeUrl );
var web = clientContext.get_web();
//to get the page give the server relative URL of the page which again you can hard code or get using get listems
// via the COM only
var oFile = web.getFileByServerRelativeUrl(_spPageContextInfo.siteServerRelativeUrl +'/Pages/default.aspx');
//Once you have the file, using the file you get the limited web part manage class giving the scope of webpart, //shared brings all data that’s shared across users. (you can use scope user to get personalization related stuff)
var limitedWebPartManager = oFile.getLimitedWebPartManager(SP.WebParts.PersonalizationScope.shared);
//The complete webpart XML, now this can be done in number of ways, you can either hard code it or if you want //a dynamic approach for this, just use the COM to get the Catalogs gallery and read from the webpart file you
//want to and use in the code for adding it to page
var webPartXml = '<webParts>' +
' <webPart xmlns=\"http://schemas.microsoft.com/WebPart/v3\">' +
….
'</webPart>' +
'</webParts>';
//Using the web part manager you import the web part XML on the page
var oWebPartDefinition = limitedWebPartManager.importWebPart(webPartXml);
//Once the xml is loaded on the page you get reference to the web part based on the imported definition
var oWebPart = oWebPartDefinition.get_webPart();
//Get all the child webs of the current web
this.allWebs = web.get_webs();
//store the xml in reference variable for this call
this.webpartXml = webPartXml;
//Now when you have the reference to the web part loaded, add it to the page with specified zone id and index of web part.
limitedWebPartManager.addWebPart(oWebPart, 'LeftColumn', 1);
//load the web webpart instance for the context, also for all child webs
clientContext.load(oWebPart);
clientContext.load(allWebs);
clientContext.executeQueryAsync(Function.createDelegate(this, this.onQuerySucceededWebPart), Function.createDelegate(this, this.onQueryFailedWebPart));
}
//when you webpart is added the success method is called
function onQuerySucceededWebPart() {
var Enumerator = this.allWebs.getEnumerator(); // to loop in all child webs
//get the webpart xml coming from parent function call
var xmlWp = this.webPartXml;
//Loop webs
while (Enumerator.moveNext()) {
var subWeb = Enumerator.get_current(); //get the reference to specific web item
//Now again you need to create a client context which is using the server relative URL of the child web, you
//need to create a fresh context of the site to which you are doing the web part operation on else it wont work
var ctx = new SP.ClientContext(subWeb.get_serverRelativeUrl());
//so though you have reference to the child web in subWeb variable you still need to open the context and get
//the web again, so baiscally you need the client context object created from the URL of the site that you want
//to do operation to, to make things work
var childWeb = ctx.get_web();
//Now you have the child web object using which you get the aspx page
var oFile = childWeb.getFileByServerRelativeUrl(subWeb.get_serverRelativeUrl() + '/Pages/default.aspx');
//same as before you get the webpart manager, import web part and add it
var limitedWebPartManager = oFile.getLimitedWebPartManager(SP.WebParts.PersonalizationScope.shared);
var oWebPartDefinition = limitedWebPartManager.importWebPart(xmlWp);
var nWebPart = oWebPartDefinition.get_webPart();
limitedWebPartManager.addWebPart(nWebPart, 'LeftColumn', 1);
// load and execute the query for this web part
ctx.load(nWebPart);
ctx.executeQueryAsync(Function.createDelegate(this, this.onQuerySucceededSubWebPart), Function.createDelegate(this, this.onQueryFailedWebPart));
}
//This will happen for all the sub webs calling client context specific to that web and would add web parts to all the sub webs for you.
alert('Web parts added to all');
}
function onQueryFailedWebPart(sender, args) {
alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}
function onQuerySucceededSubWebPart(sender, args) {
alert('Added successfully to all sub sites'); //will be called recursively, this is just for demo don't have alert statement here
}
This a veryyyyy basic example on how you can use the ECMA script to over come limitations in the sandboxed solution, of course you can modify the above to make it more complex and powerful or simple or whatever you like… there are SO MANY ways this could help in a sandbox solution…. its all up to you, possibilities are countless.
Point to Note:
A point to note here is that with the COM the looping through webs is not possible beyond one level, so you get only the immediate children of any web, I did talk about this limitation in my last post as well, so then could there be a way around it.. YES there can be… there can be many ways but I will just quickly talk about one that I can think of…
What you can do is on the parent site add a content query webpart and set it to bring all items from page content type, this will bring all the default.aspx from you site collection, now with the item you get the link url of the page and also site relative url can be deduced from it.. There you go you have the whole site collection webs url which you can use to open the context and do add remove to web parts.. A little combination of Jquery CQWP and COM.. Recipe to great solutions.
Now this makes a very flexible and NO-CODE solution which means you don’t even have to touch the server code and deploy it you can directly upload it to the doc library and add a reference to master page and you are done, that means everything can be done client side only and you can do modifications or anything any day without having to boot up your heavy VM :)
Hope this helps.
-"T"