Previously, we took a look at how to create interactive mashups with Visio diagrams without writing any code. If you need more flexibility in creating rich diagram mashups than offered out of the box by web part connections, you can use the Visio Services JavaScript Mashup API.
In this post we’ll review the breadth of possibilities by the Visio Services Mashup API and show you how to get started coding with a few simple examples.
Overview of the Visio Services JavaScript Mashup API
In a nutshell, the Visio Services JavaScript Mashup API enables developers to access and manipulate the Visio web drawing, its pages and shapes. Some key scenarios enabled by the Mashup API are:
- Interacting with web page elements that don’t expose web part connections.
- Creating visual markup on the Visio drawing canvas.
- Writing custom handlers for mouse events within the drawing.
- Exposing diagram data, such as shape name and shape data, to your solution.
- Combining data from inside and outside a diagram to build dynamic composite mashups.
Objects
The class diagram below summarizes the available objects in the API. Notice that related class members are collected into functional groups – each group is discussed in more detail in the API in the appendix of this article.
Events
Like many JavaScript APIs, it’s important to note that the Visio Services JavaScript API is event-based. To program against the Visio Web Access web part, you must catch the events it exposes by writing event handler functions. The diagram below is the state chart of VwaControl events:
Typically, when the onApplicationLoad event fires you’ll want to get a reference to the vwaControl object and set-up handlers for ondiagramcomplete. Note that vwaControl is the only valid object in the object model until ondiagramcomplete fires. Then, when the ondiagramcomplete event fires you’ll want to get a reference to the currently loaded page and set-up handlers for the various mouse events.
Getting Started
Now that you understand the object and event model of the API, let’s get started coding. Before you can interact programmatically with a Visio Web drawing you must add a Visio Web Access web part to a web part page, load a VDW file into it and add a Content Editor Web part to the page to contain your JavaScript.
To do so, assuming you have “Contribute”, “Design” or “Full Control” permissions to a page in SharePoint Server 2010, you should:
- Create a drawing in Visio and publish it to SharePoint document library as a VDW file.
- Create a .JS file in any JavaScript editor and copy paste the following contents into it. Once you’re done save it to the same document library as the drawing in step #1.1: <script language=”javascript”> 2: // A hook into AJAX’s Sys.Application.load event, raised after all scripts 3: // have been loaded and the objects in the application have been created 4: // and initialized. 5: Sys.Application.add_load(onApplicationLoad) 6: 7: // Global variables for the application 8: var webPartElementID = “WebPartWPQ3”; //Change this to the appropriate web part ID. 9: var vwaControl; 10: var vwaPage; 11: var vwaShapes; 12: 13: // Function Name: onApplicationLoad() 14: // Parameters: None 15: // Description: This function handles the AJAX’s Sys.Application.load event 16: // When this event fires the function captures references to 17: // the Visio Web Access web part object and registers 18: // the following Visio Web Access specific event handlers: 19: // 20: // 1. diagramcomplete: raised when the request for a web 21: // drawing page finishes. 22: function onApplicationLoad() { 23: try{ 24: vwaControl= new Vwa.VwaControl(webPartElementID) 25: vwaControl.addHandler(“diagramcomplete”, onDiagramComplete); 26: } 27: catch(err){ 28: } 29: } 30: 31: // Function Name: onDiagramComplete 32: // Parameters: None 33: // Description: This function handles the onDiagramComplete event which is 34: // raised when a request for a web drawing page completes. 35: // When the event fires, this function captures references 36: // to script’s global variables such as the current page, the 37: // collection of shapes on the page. It also sets the page zoom to 38: // “fit page to view” to give a full view of the page to 39: // users and registers the following Visio Web Access specific 40: // event handlers: 41: // 42: // 1. shapemouseleave: raised when the mouse enters a shape. 43: // 44: // 2. shapemouseenter: raised when the mouse leaves a shape. 45: // 46: // 3. shapeselectionchanged: raised when the active selection 47: // is changed from one shape 48: // to another. 49: function onDiagramComplete() { 50: try { 51: vwaPage = vwaControl.getActivePage(); 52: vwaPage.setZoom(-1); 53: vwaShapes = vwaPage.getShapes(); 54: vwaControl.addHandler(“shapeselectionchanged”, onShapeSelectionChanged); 55: vwaControl.addHandler(“shapemouseenter” , onShapeMouseEnter); 56: vwaControl.addHandler(“shapemouseleave”, onShapeMouseLeave); 57: } 58: catch(err) { 59: } 60: } 61: 62: // Put the sample code discussed later in the blog below… 63: </script>
- Edit the SharePoint page that will host the mash-up and add a Visio Web Access web part and configure it to load the web drawing published in step #1.
- On the same page, add a Content Editor Web Part and configure it to load the .JS file you created in step #2. This process is similar to step 3, however the Content Editor web part is in the Media and Content category of SharePoint web part picker and you need to set the “URL” property of the web part.
- Get out of page edit mode. Check the bottom left corner of your browser. If there is no JavaScript error, move to step 8. If there is however, you need to update the second line of the code snippet above which reads “var webPartElementID = “WebPartWPQ3”;” with the ID that SharePoint gave to the Visio Web Access web part.
- Open the SharePoint web part page in source mode (in IE, right click and “View Source”) and search for class=”VisioWebAccess”,then back track to the immediate parent <div> tag and look for an attribute of the form id=”WebPartWPQ?“.
- Once you have this ID, replace it in the JS file and save it back to the document library
- Reload your mash-up page.
Tada! You have the basics of a mash-up. That said, it doesn’t do much except get references to a few basic objects and setup some event handlers. The next section gives you a few useful examples that build on this base.
Example #1: Overlays on hover
In this example we will explore how to create an overlay that appears when a user hovers over a shape. In this scenario, the manager of a grocery store visualizes sales data on a floor plan of his store: sales figures are represented as colors on a store shelf. Now, let’s say we want to show a picture of the best-selling product of the day on each shelf as a user hovers his mouse over a shelf. This product changes daily and is calculated at night by running database queries; this data is then exposed through shape data when the drawing is refreshed.
Key JavaScript API Capabilities Used in this Solution
Capability | In this example… |
Creating dynamic visual markup on the Visio drawing canvas. | Product pictures take up a lot of screen real-estate — you can’t show all of them on the drawing without hiding the floor plan. Using overlays let you “fade them in” and “fade them out” as needed. |
Writing custom handlers for mouse events within the drawing. | The fade-in and fade-out feature discussed above requires you to handle the onmouseenter and onmouseleave events. |
Combining data from inside and outside a diagram to build dynamic composite mashups | The best-selling product on a shelf changes daily and is stored in the document. The product is stored outside the diagram. The mash-up must be able to pull these two sources of visual data into one diagram. |
Code
To solve this problem, you’ll want to draw an overlay on the shape when the mouse enters the shape, and remove the overlay when the mouse leaves the shape. Let’s take a look at one possible implementation:
1: // Function Name: shapeMouseEnter 2: // Parameters: 3: // source — a reference to the base HTML element of the vwaControl. 4: // args — the shape ID of the shape the mouse entered. 5: // Description: 6: // On entering a shelf shape (a shape that contains a “Best Seller” shape 7: // data), this function extracts that shape data then overlays the 8: // corresponding image on the shape. 9: shapeMouseEnter = function(source, args) 10: { 11: //Get the name of the shape that was just entered 12: var shape = vwaShapes.getItemById(args); 13: 14: //Determine the best seller for that shelf. This information is stored in a shape data //named “Best Seller” item that updates over time. 15: var bestSeller; 16: var shapeData = shape.getShapeData(); 17: for (var j=0; j<shapeData.length; j++) { 18: if (shapeData[j].label = “Best Seller”) { 19: bestSeller = shapeData[j].value; 20: } 21: } 22: 23: //Depending on which shape this is, draw the correct overlay. 24: switch(bestSeller) 25: { 26: case “productA”: 27: shape.addOverlay( 28: “myOverlay” , 29: “<Image Source=\”productA.jpg\” Width=\”50\” Height=\”70\”><\/Image>”, 30: vwaVerticalAlignment.Top, 31: vwaHorizontalAlignment.Left, 32: shape.getBounds().width, 33: shape.getBounds().height); 34: break; 35: 36: //If the best seller is product B, then display an overlay with product B. 37: case “productB”: 38: shape.addOverlay( 39: “myOverlay” , 40: “<Image Source=\”productB.jpg\” Width=\”50\” Height=\”70\”><\/Image>”, 41: vwaVerticalAlignment.Top, 42: vwaHorizontalAlignment.Left, 43: shape.getBounds().width, 44: shape.getBounds().height); 45: break; 46: 47: //You can add more cases below as needed for different shelves. 48: 49: default: 50: } 51: } 52: 53: // Function Name: shapeMouseLeave 54: // Parameters: 55: // source — a reference to the base HTML element of the vwaControl. 56: // args — the shape ID of the shape the mouse just left. 57: // Description: 58: // On leaving a shelf shape (a shape that contains a “Best Seller” shape 59: // data), this function removes the existing overlay on that shape. 60: shapeMouseLeave = function(source, args) 61: { 62: //Get the name of the shape that was just entered 63: var shape = vwaShapes.getItemById(args) 64: 65: //And remove the overlay 66: shape.removeOverlay(“myOverlay”); 67: }
Getting this to work
To add this to your solution, copy/paste the above code presented in the earlier “Getting Started”. Note that this code makes the following assumptions:
-
- There are shapes in the drawing with the shape data whose label is “Best Seller” and whose value is “product?” (where ? is A, B etc…).
- You have bitmap images of these products, named productA.jpg etc… stored in the same directory as the .JS file. They should be 50 pixels wide and 70 pixels high.
Example Scenario #2: Search and Highlighting…
In this second example, the scenario is a network diagram mashup that allows a network administrator to find computers in a network diagram that have a particular string and to highlight them; this is useful when looking at a computer topology to quickly spot computers that meet a particular spec, or contain a particular set of characters in their name. We are effectively “scraping” the diagram to find shapes that contain a particular piece of text in either shape text or shape data.
Key JavaScript API Capabilities Used in this Solution
Capability | In this example… |
Interacting with web page elements that don’t expose web part connections. | The search text box in this solution is not a SharePoint web part. |
Exposing diagram data, such as shape name and shape data to your solutions. | The solution searches on data within the drawing. |
Combining data form inside and outside a diagram to build dynamic composite mashups | Search text (data from outside the document) changes throughout a session, diagram markup must do so at the same rate. |
Code
To solve this problem, the algorithm iterates through all the shapes on the page and does a string compare against the name/shape data of that shape and highlight the shapes that contain that string. One implementation of this scenario is detailed below:
1: // Creates the user interface for this code sample which consists of a 2: // text box to enter the find-text and a button to trigger the 3: // find operation. 4: document.write(“Find text:”); 5: document.write(“<BR><input type=\”text\” id=\”findText\”><\/input>”); 6: document.write(“<BR><Button id=\”findButton\” type=\”button\””+ 7: “onClick=\”find()\”>Find<\/Button>”); 8: 9: // Function Name: find 10: // Parameters: None 11: // Description: This function iterates through vwaShapes and highlights 12: // the shapes who’s name, shape data or shape hyperlinks 13: // contains the find-text. The search is case insensitive. 14: function find() { 15: // Iterate through the collection of shapes on the page, if either the 16: // shape name, shape data or shape hyperlinks contain the find-text, 17: // highlight it and jump back to the “shapeContainsFindText:” label 18: // to process the next shape. 19: 20: shapeContainsFindText: 21: for (var i=0; i<vwaShapes.getCount(); i++) { 22: // Extract the find-text from the text box and covert it to lower case. 23: var findText = document.getElementById(‘findText’).value.toLowerCase(); 24: 25: // Get a reference to the shape at index “i” of the shape collection 26: // and remove existing highlights. 27: var shape = vwaShapes.getItemAtIndex(i); 28: shape.removeHighlight(); 29: 30: // Get a reference to the shape name, shape data and shape hyperlinks 31: // collections. 32: var shapeName = shape.getName(); 33: var shapeData = shape.getShapeData(); 34: var shapeHyperlinks = shape.getHyperlinks(); 35: 36: // Highlight the shape if the find text is found within the shape name 37: // and return to the top of the loop to process the next shape. 38: if(shapeName.toLowerCase().indexOf(findText) != -1) { 39: shape.addHighlight(2,”#00FF00″); 40: continue shapeContainsFindText; 41: } 42: 43: // If the shape name did not contain the find-text, search through the 44: // the shape’s shape data values for the find-text. If the find-text 45: // is found highlight the shape and return to the top of the loop to 46: // process the next shape. 47: for (var j=0; j<shapeData.length; j++) { 48: if (shapeData[j].value.toLowerCase().indexOf(findText) !=-1) { 49: shape.addHighlight(2,”#00FF00″); 50: continue shapeContainsFindText; 51: } 52: } 53: 54: // If the shape name or shape data did not contain the find-text, 55: // search through the shape’s hyperlinks for the find-text. If the 56: // find-text is found, highlight the shape and return to the top of 57: // the loop to process the next shape. 58: for (var j=0; j<shapeHyperlinks.length; j++) { 59: if (shapeHyperLinks[j].value.toLowerCase().indexOf(findText) !=-1) { 60: shape.addHighlight(2,”#00FF00″); 61: continue shapeContainsFindText; 62: } 63: } 64: 65: // If the text hasn’t been found in the web drawing, then warn the user. 66: alert(“Text not found in this web drawing!”); 67: } 68: }
Getting this to work
To add this to your solution, copy/paste the above code presented in the earlier “Getting Started” section. Note that using the code above, the find text box and button will appear in the place of the Content Editor web part.
Example #3: Workflow Visualization
For our third example, we’d like to highlight an in-house use of the Visio Services JavaScript OM, namely the new SharePoint workflow visualization feature in SharePoint 2010. In a nutshell, using this feature, you can now create a workflow using Visio/SharePoint Designer and visualize the progress of an instance of that workflow in SharePoint.
The progress visualization is in fact an example of using the Visio mash-up to overlay data on an existing diagram. The SharePoint workflow team used the JavaScript API to overlay workflow instance data in the context of the workflow diagram. Taking a look at the picture below, all the visuals and the text highlighted in the green box are not actually part of the published diagram but are actually drawn over the workflow shape at run-time using the mash-up API. As the data changes during the lifetime of a workflow, so will the overlay.
As you can see, the integration between the Visio published diagram and the overlays is pretty seamless!
More information to come…
We’re in the process of developing a full MSDN reference for the JavaScript Mashup API, including samples and an overview document. We’ll make sure to announce this on the blog when it’s made publicly available. Please note that the contents of this blog entry are preliminary and may change by the time Visio Services 2010 is officially released.
Try it out
We hope this blog post has given you enough to get started building your own JavaScript solutions. Feel free to share your thoughts either through send-a-smile or by commenting on this blog post.
Appendix – API Reference
Object | Description |
Vwa.VwaControl
|
The VwaControl object represents an instance of the Visio Web Access (VWA) web part. It has the following functional groups:
· Web part properties: using getVersion() and getDisplayMode() you can access web part properties such as the web part’s version and its rendering technology (Silverlight or raster).
· Drawing-level: using getDiagramUrl()/setDiagramUrl(string diagramUrl) in tandem with openDiagram()* you can assign which diagram to display in the web part and then load it. External data connections in a drawing can be refreshed using refreshDiagram()*.
· Page-level: using getAllPageIds() in tandem with setActivePage(string pageId)* you can iterate through all the pages in a drawing. You can retrieve a reference to a particular page by using getActivePage().
· Events: using addHandler(string eventName, function functionName), removeHandler(string eventName, function functionName), clearHandlers() you can manage user interaction with the drawing by adding and removing event handlers to events such as: o diagramcomplete – fired when a drawing finishes loading. o shapeselectionchange – fired when a new shape is selected. o shapemouseenter – fired when the mouse enters a shape’s outline. o shapemouseleave – fired when the mouse exits a shape’s outline. More details about the API’s event model seciton.
· Errors: using displayCustomMessage(string HTML)and hideCustomMessage() you can show and hide and your applications’ custom HTML error message pages.
* These methods are asynchronous which means that they trigger a non-blocking call back to Visio Services to replace the page currently displayed with another one. These operations have the side effect of making the current VwaControl object invalid which effectively stops you from coding against the OM after these statements. The best way to follow-up such calls is to create a handler for diagramcomplete, which fires when the VwaControl has finished loading the new page, and to put your code in that event handler. More details about the API’s event model section.
|
Page
|
The Page object represents the active Web drawing page currently displayed in the web part. It has the following functional groups:
· Page: using getId() and getSize() you can access properties of the currently displayed page such as its page id and its size in pixels
· View: using getPosition()/setPosition(int x, int y) and getZoom()/setZoom(double zoom%) you can change the zoom center and zoom percentage of the web part’s viewport. You can also use centerViewonShape(string shapeID) to focus the view on a particular shape and isShapeInView(string shapeID) to determine if a shape is in the current view.
· Shapes: using getSelectedShape() and setSelectedShape(string shapeID) you can change the currently selected shape. You can also get a reference to the collection of shapes in the currently displayed page by using getShapes().
|
ShapeCollection
|
The ShapeCollection object represents the collection of shape objects on the currently displayed page. It has the following functional groups:
· Shapes: using getCount() in tandem with getItemAtIndex(int index) you can easily iterate through all the shapes on a page; the array is 0 based. You can also use getItemByName(string shapeName), getItemById(string shapeId) or getItemByGuid(string shapeGuid) to index into the page’s shape collection using different Visio shape identifiers.
|
Shape
|
The Shape object represents a single shape on the active Web drawing page. It has the following functional groups:
· Shape Properties: using getName(), getId() & getGuid() you can access the shape’s various Visio identifiers such as shape name, Id and Guid. You can also access a shape’s meta-data by using getHyperlinks(), getShapeData() and getBounds().
· Markup: using addHighlight(integer width, string color), removeHighlight(), addOverlay(string overlayID, string content, Vwa.VerticalAlignment vPos, Vwa.HorizontalAlignment hPos, integer overlayHeight, integer overlayWidth), addPredefinedOverlay(Vwa.OverlayType overlayType, string overlayID, string content, [Vwa.VerticalAlignment vPos], [Vwa.HorizontalAlignment hPos], [integer overlayHeight], [integer overlayWidth]) and removeOverlay(string overlayID) you can manage shape markup.
Highlighting a shape draws a colored rectangle around the bounding box of the Shape as seen below:
A shape overlay is a way of adorning a particular shape with a developer visual. An example of this concept is a pushpin icon that is placed on top of a map in an online mapping application. With this set of functions, you can tag a Visio shape with your own overlay to bring attention to it: Using the various arguments of addOverlay() and addPredefined() overlay you can configure the contents, size of the overlay as well as its alignment relative to the bounding box of the shape. Each shape may have more than one overlay; these overlays cannot however have the same developer-defined overlayID.
Note that when the underlying drawing is rendered in PNG, overlays be either HTML or an image. If the diagram is rendered in Silverlight, the overlay can be XAML or an image. |