If you want to create summary link data using JavaScript and found out that the required format is a bit complex, then I might be able to help you here.
On Codeplex I’ve shared a JavaScript module called SummaryLinkStore.js. It enables developers to create or edit SummaryLink data in SharePoint using JavaScript.
Introduction
You can find SummaryLink data at a few places in SharePoint. It is a field type, typically used on the welcome page of a publishing site, and it’s a property on the Summary Link web part. The datatype allows you to configure a collection of links practically in any way you need. You can group them, sort them, style them (using XSL), and configure other properties.
Unfortunately, you may have found out that the same field does not work really well with other platforms. Server side you can use the SummaryLinkFieldValue object to manage this data. However, when the SummaryLink value is read, you get the deserialized value of the SummaryLinkFieldValue instance. This XML value quickly becomes too large to export to Excel and to manipulate it, without the actual serialized SummaryLinkFieldValue available, requires a lot of coding and investigation into what the XML the format actually means.
Here my library comes in. It is capable of serializing the SummaryLink value into an object structure that you can use my functions on to manipulate the data. Similarly you can deserialize the value back so you can store your new or modified value into the field.
For example, if you read a SummaryLink field in SharePoint that only has one link, it would return the following string in JavaScript (this is about as simple as a SummaryLinks value can be):
1 |
'<div title="_schemaversion" id="_3"><div title="_links"><div title="_link"><span title="_title">codeplex</span><span title="_order">1</span><span title="_begincolumn">True</span><span title="_linkurl"><a href="https://summarylinkstore.codeplex.com/documentation"> https://summarylinkstore.codeplex.com/documentation</a></span></div></div><div title="_view"><span title="_columns">1</span><span title="_linkstyle"></span><span title="_groupstyle"></span></div></div>' |
With SummaryLinkStore.js, you do not need to worry so much about the behavior of group headers, columns, XSL styles and other settings, because the module will read and write the xml value for you:
1 2 3 4 |
var stringValue = '<div title="_schemaversion" id="_3"><div title="_links"><div title="_link"><span title="_title">codeplex</span><span title="_order">1</span><span title="_begincolumn">True</span><span title="_linkurl"><a href="https://summarylinkstore.codeplex.com/documentation"> https://summarylinkstore.codeplex.com/documentation</a></span></div></div><div title="_view"><span title="_columns">1</span><span title="_linkstyle"></span><span title="_groupstyle"></span></div></div>'; var links = new oPB.SummaryLinkStore(stringValue); var codeplexLink = links.summaryLinks({title: 'codeplex'})[0]; var codeplexUrl = codeplexLink.linkUrl(); |
The above code will search for a link with the title “codeplex” and assigns the first match to the codeplexLink variable. From this link the url is retrieved by using the linkUrl accessor. codeplexUrl will get the value ‘https://summarylinkstore.codeplex.com/documentation’, which is indeed the url of the link with the title ‘codeplex’ in stringValue.
We could go further by changing the title of the link we found into ‘SummaryLinkStore.js documentation’ and then getting the string value to store in the SummaryLinks field:
1 2 |
codeplexLink.title('SummaryLinkStore.js documentation'); stringValue = links.serialize(); |
If you would store stringValue back in the field it came from, you would see that the title of the link has been updated.
Using web part property value
If you wish to use the data from an existing web part on the page. You can use JavaScript to read the web part properties, as you would for any other web part. What you need for oPB.SummaryLinkStore is the property named “SummaryLinkStore”:
1 2 3 4 5 6 7 8 9 10 11 |
var summaryLinkStore = new oPB.SummaryLinkStore(); //I assume you already have the webPart instance, using the LimitedWebPartManager. var webPartProperties = webPart.get_properties(); ctx.load(webPartProperties); ctx.ExecuteQueryAsync(function propertiesLoaded() { summaryLinkStore.deserialize({ replace: true, value: webPartProperties.get_fieldValues()["SummaryLinkStore"] }); //Do stuff with summaryLinkStore. }, failHandler); |
The example above shows how you can deserialize the string data from the web part into JavaScript objects. This basically means that the summaryLinkStore variable will have references to objects and functions that help you understand and manipulate this data.
When you call summaryLinkStore.serialize();, you’ll notice the exact same string should be returned. Or if the original input could not be interpreted as such, the serialized string would represent what the module understood from it. And this should be similar, within reason, to the behavior of the Summary Link web part.
Often if you wish to write this data to a web part, you would compose the webpart xml file to import using the LimitedWebPartManager. If this is the case, you might want to escape the summaryLinkStore value for XML insertion. This will be done for you, if you cast the oPB.SummaryLinkStore instance back to a string:
1 2 3 4 5 |
var sls = new oPB.SummaryLinkStore(); sls.deserialize({value: valueFromVariable}); var originalValue = sls.serialize(); var escapedForXMLInsertion = "" + sls; |
Demonstration
To explain how the oPB.SummaryLinkStore module can be useful, I’ve made a simple demonstration for this blog. Obviously it does not showcase the full potential, but I hope it will help you understand and inspire you to use it creatively.
Import your data into this demo
If you have no SummaryLinkStore value ready to paste, you can generate your own data using the form further down below. Otherwise, you can paste a SummaryLinkStore string value into the text area below. Then make sure the onchange event is fired by blurring the field (clicking outside of the field):
The onchange event will trigger the deserialize function that was explained earlier. This demonstration uses one instance of oPB.SummaryLinkStore, so the same data will be used in the rest of the demo.
The user interface is written for this demonstration, oPB.SummaryLinkStore empowers you to write your own user interface, but does not come with any user interface by itself.
Building a User Interface
The instance of oPB.SummaryLinkStore has a function called “build”. You can provide this build function with a builder function. In the builder function you can compose a string to represent the data in the SummaryLinkStore. The build function will iterate over this data and call the builder function whenever that might be relevant.
So if you wish to build a HTML representation of the SummaryLinkStore data, you need to write a builder function that can compose this HTML. The builder function should take an object as an argument. This object will have two properties: “current” and “phase”.
The oPB.SummaryLinkStore build function will then iterate over the data and call the builder function for each data “node” once or twice. Based on the provided values for “current” and “phase”, the builder function should be able to append the appropriate html to the result.
I can imagine that I’ve lost you here, so let me clarify this with an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
var context = { builder: function builder(params) { //The this value in the builder function is equal to the context object: var context = this; //The data-object for which the builder function is called: var current = params.current; //What part of the object should be added to the string representation: var phase = params.phase; //As an output mechanism, we choose to build an array of strings, which we will join into one string in the end: var strings = context.strings = context.strings || []; //The current instance, can be one of three types: The SummaryLinkStore instance itself, a collection of SummaryLinks or a SummaryLink. //This is how you can check which type is used: var isSummaryLinkStore = (current instanceof oPB.SummaryLinkStore); var isCollection = oPB.SummaryLinkStore.isSummaryLinkCollection(current); var isSummaryLink = (current instanceof oPB.SummaryLinkStore.SummaryLink); //The phase can be one of three values: open, close or leaf: var isOpen = phase === 'open'; //If builder will be called twice on the current instance, first time phase is open, last time, phase is close. var isClose = phase === 'close'; //If builder was already called for 'open' on current instance, it will also be called with 'close' var isLeaf = phase === 'leaf'; //If instance has no collection of content, the instance is called as a leaf. if(isSummaryLinkStore && isOpen) { strings.push("Content of the summary link store:"); } if(isSummaryLink) { var textValue = (current.isGroupHeader() ? "Group: " : "Link: ") + current.title(); if(isLeaf) { //For leaf instances, the builder function is called only once, so the full HTML needs to be appended here: strings.push('<div>'+textValue+'</div>'); } if(isOpen) { //We can trust phase close will also be called, so we can leave our element opened to close it later. strings.push('<div>'+textValue); } if(isClose) { //Obviously we should close our element however when phase is close. strings.push('</div>'); } } if(isCollection) { //To clarify where in the data structure collections are used, we give them styling if(isOpen) { //We can trust that if phase is open, builder will also be called (after collection content has been build) with phase is close. // This means we do not have to close our HTML here: strings.push('<div style="padding-left: 2em; border: 1px dotted white; margin-left: 1px;">') } else if(isClose) { //Called after content of the collection has been build. Closing the HTML element that was opened by phase is open. strings.push('</div>'); } } } }; summaryLinkStore.build(context); var resultHTML = context.strings.length ? context.strings.join('') : "empty"; |
As you can see, “current” is the instance representing a “node” in the data hierarchy, while phase is a string explaining what part of the object should be added to the string representation. phase can be one of three values: “open”, “close” or “leaf”.
The example above, should result in the following HTML (corresponding to the SummaryLinkStore data you pasted in the text area above, or created using the form further down below):
To go further with this example, we can build HTML that outputs controls to interact with the same data from which the HTML is generated. I’ve not included the sourcecode for the example below, but the idea is similar to the code for the previous example:
To use generated HTML, is of course just one of the options to interact with the data. A more sober, but effective approach would be to just add a static form:
After using the example above to manipulate the data, the result can be found here:
To get the data, escaped for XML insertion, all you need to do is cast the SummaryLinkStore object to a string. If you do not want to escape the XML for insertion into for instance a web part definition, then you can call the serialize function.
1 2 |
var escapedXMLString = "" + summaryLinkStore; var unescapedXMLString = summaryLinkStore.serialize(); |
Catching Errors
I throw errors (exceptions) intentionally, for example: when I need to protect my code from doing something unexpected otherwise. So you need to catch my errors, to ensure stability, especially if user input is involved.
1 2 |
var sls = new oPB.SummaryLinkStore; sls.columns("string"); //This will throw an error! |
When you are troubleshooting the library, typically you’d want to have a little bit more information about what is happening. The following line of script can help with that (to some extend):
1 |
oPB.SummaryLinkStore.addErrorListener(function listener(message){console.log(message);}); |
Finally
The example in this blog is just to get you started. There are functions available to you to do all kinds of things. So for the full documentation, have a look at https://summarylinkstore.codeplex.com/documentation.
I’d like to hear what you think! (related to this blog and JavaScript module I mean)