What COMPANY to use?

As you know, when creating an application consuming NAV Web Services you need to specify the Company name as part of the URL to the Web Service, but what company should you be using?

Some applications are web front-ends placing data from the web application into NAV. For applications like this you typically would have a config file in which you specify what company things needs to go to. For these applications, this post adds no further value.

Other applications are integration applications, like a lot of the applications you can see on my blog:

  • Search
  • “My” gadgets
  • MAP
  • Edit In Excel

for all of these applications, it really doesn’t make sense to run with a different company than the users default company.

Example – if you search through your NAV data – you really want to search through the data in the active company – not just any company.

Wouldn’t it be nice if you could type in the URL

/Codeunit/Search”>http://localhost:7047/DynamicsNAV/WS/<default>/Codeunit/Search

and then the <default> would be replaced by the authenticated users default company – unfortunately this doesn’t work (I have suggested this feature for v7 though:-)). Instead, we have to do the work in the Web Service consuming application. Easiest solution is of course to create a Codeunit with a function, returning the default company of a user, call that and then build your URL for calling the Page / Codeunit web service.

A function like that could be:

GetDefaultCOMPANY() : Text[30]
Session.SETRANGE(“My Session”,TRUE);
Session.FINDFIRST;
WindowsLogin.SETRANGE(ID,Session.”User ID”);
WindowsLogin.FINDFIRST;
UserPers.SETRANGE(“User SID”,WindowsLogin.SID);
UserPers.FINDFIRST;
EXIT(UserPers.Company);

The problem with this approach is (as you probably already figured out) that every call to a Web Service will require 2 roundtrips instead of one and for Page based Web Service access there really isn’t much you can do better.

For Codeunit based Web Service access you can however avoid a lot of these roundtrips by using a very simple pattern in the way you write your functions. I have rewritten my search method to return a Text[30] and start off with the following lines of code:

company := GetDefaultCOMPANY();
IF company <> COMPANYNAME THEN
EXIT(company);

and the consumer will have to build up the URL for the Web Service in code with whatever company (the first in the list of companies would be just fine), call the web service and if it returns a different company than the one used to invoke the web service, build a new URL and try again.

In the Search gadget this would look like (the lines in Red are the important changes)

// the “real” search function
function doSearch(searchstring) {
    specifiedCompany = GetCompany();
usedCompany = specifiedCompany;
if (specifiedCompany == “default”) {
if (myCompany == “”) {
Companies = GetCompanies();
if (Companies != null)
myCompany = Companies[0].text;
}
usedCompany = myCompany;
}

    // Get the URL for the NAV 2009 Search Codeunit
var URL = GetBaseURL() + encodeURIComponent(usedCompany) + “/Codeunit/Search”;

    // Create XMLHTTP and send SOAP document
xmlhttp = new ActiveXObject(“Msxml2.XMLHTTP.6.0”);
xmlhttp.open(“POST”, URL, false, null, null);
xmlhttp.setRequestHeader(“Content-Type”, “text/xml; charset=utf-8”);
xmlhttp.setRequestHeader(“SOAPAction”, “DoSearch”);
xmlhttp.Send(‘<?xml version=”1.0″ encoding=”utf-8″?><soap:Envelope xmlns:soap=”‘ + SoapEnvelopeNS + ‘”><soap:Body><DoSearch xmlns=”‘ + CodeunitSearchNS + ‘”><searchstring>’ + searchstring + ‘</searchstring><result></result></DoSearch></soap:Body></soap:Envelope>’);

    // Find the result in the soap result and return the rsult
xmldoc = xmlhttp.ResponseXML;
xmldoc.setProperty(‘SelectionLanguage’, ‘XPath’);
xmldoc.setProperty(‘SelectionNamespaces’, ‘xmlns:soap=”‘ + SoapEnvelopeNS + ‘” xmlns:tns=”‘ + CodeunitSearchNS + ‘”‘);

    userCompany = xmldoc.selectSingleNode(‘//tns:return_value’);
myCompany = userCompany.text;

    if ((specifiedCompany == “default”) && (myCompany != usedCompany)) {
// Default company has changed – research
return doSearch(searchstring);
}

   … do the actual searching

}

In this sample I use three variables:

specifiedCompany is the company specified in the config file (default means use users default company)

usedCompany is the company used to invoke the last WS method

myCompany is my current belief of the users current company, which gets replaced if a method returns a new default company.

Using a pattern like this will help lowering the number of round trips and still allow your consuming application to use the users default company.

This “trick” is only possible in NAV 2009 SP1. NAV 2009 RTM will change the users default company to the company you use to invoke the Web Service with – which again will cause the above function to always return the same company name as the one you invoke the Web Service with.

Enjoy

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

Microsoft Windows Vista Gadget – My “Stuff”

This is my second gadget. My first gadget was the Search gadget, which you can find here.

I won’t repeat any explanation as to what a gadget is, nor will I talk about Javascript – instead I will focus upon the things, which are new in this post:

  • Returning an XMLPort from Web Services to Javascript
  • Running a Web Service method asynchronous
  • Use the same gadget for invoking three different methods
  • Poor mans error handling in Javascript

What I want to do is to create a gadget that can show My Customers, My Vendors or My Items.

image

image

image

The flyout from the gadget should be additional information about the record in the gadget, like

image

Clicking on the customer number should open the Customer Task Page and it should be easy to add additional information to this window (the things that are interesting to quickly get access to for our users).

In the Settings dialog you should be able to control things like

image

I will not discuss My Activities in this post, although it would be cool to have a Vista Gadget with My Activities (must do that some other day).

Ready? – well here we go…

The GetMyStuff Codeunit

In NAV we need to expose something, through which we can get access to My Customers, My Vendors and My Items – and the easiest thing would of course be to just expose the 3 pages: 9150 (My Customers), 9151 (My Vendors) and 9152 (My Items).

The problem with this approach is, that we need to get a bookmark back (for opening Task Pages) from the Web Service – meaning that we either create another field in these pages – or we create another Web Service Method for returning the bookmark. I really don’t want to modify base objects if I can avoid it and adding another function just multiplies the number of times we invoke Web Service method by a factor, which I am not interested in doing either.

So I am going to go with a Codeunit, 3 XML ports – and the Codeunit basically just have these three functions.

GetMyCustomers(VAR result : XMLport “My Customers”)
mycustomer.SETRANGE(“User ID”,USERID);
result.SETTABLEVIEW(mycustomer);

GetMyItems(VAR result : XMLport “My Items”)
myitem.SETRANGE(“User ID”,USERID);
result.SETTABLEVIEW(myitem);

GetMyVendors(VAR result : XMLport “My Vendors”)
myvendor.SETRANGE(“User ID”,USERID);
result.SETTABLEVIEW(myvendor);

Now returning a XMLPort (which is a NAV construct) from a Web Service sounds weird – and of course we do NOT return the XML Port object to the Web Service Consumer. What we get is the output of the XMLPort – and if you send data into the XMLPort, it is going to run the XMLPort for input.

The XMLPort for My Customers looks like:

image

and if we take a look at the WSDL for a Codeunit which returns this XMLPort, the schema for the return type very much looks like the above definition

image

and the schema for the method references this as both input and output

image

In the XMLPort properties, I have set the following properties

image

and in the Table element properties MinOccurs is set to Zero. If MinOccurs is 1 (the default), the XMLPort will return an empty My Customer if the list is empty – we do not want that.

Codebehind on the XMLPort sets the three variables _Name, _Bookmark and _Phone.

MyCustomer – Export::OnAfterGetRecord()
rec.GET(“My Customer”.”Customer No.”);
ref.GETTABLE(rec);
_Name := rec.Name;
_Phone := rec.”Phone No.”;
_Bookmark := FORMAT(ref.RECORDID,0,10);

Where rec is type Record of Customer and ref is a RecordRef.

The My Vendors and My Items XMLPorts are created following the same pattern and my gadget code will be made in a way, so that additional fields in the XMLPort will be displayed in the flyout – so you can add whatever you want to this XMLPort.

The Gadget

Again – using Javascript and instead of listing the entire Gadget Sourcecode I will describe the flow.

The main HTML body contains two areas, in which I insert the content via DHTML. These areas are called CAPTION and CONTENT.

CAPTION is for holding the top part of the gadget, where is says My Customer, My Vendor etc. and CONTENT is for holding the list of records. The reason for the caption to be dynamic is that it changes based on the selected type of course.

The first code executed in the script (except for the variable declarations) is the onreadystatechanged event handler – and note that the construct

// Microsoft suggests using onreadystatechange instead of onLoad
document.onreadystatechange = function()
{
if(document.readyState==”complete”)
{
// Initialize Settings
System.Gadget.settingsUI = “settings.html”;
        System.Gadget.onSettingsClosed = settingsClosed;

        // Initialize flyout
System.Gadget.Flyout.file = “flyout.html”;
System.Gadget.Flyout.onShow = flyoutShowing;  

        // Set the Caption of the Gadget
setCaption();

        // Initialize timer – to refresh every x seconds
setTimeout( “refresh()”, 1000 );
}
}

creates the delegate and assigns it to the event handler in one go.

In the event handler we setup the Settings dialog to use settings.html and the flyout to use flyout.html. setCaption creates a HTML string for the Caption and sets that into the CAPTION DHTML area and lastly, we setup the refresh method to be called in 1 second. This is done in order for the Gadget to be added smoothly and without any delays from connecting to Web Services etc.

The refresh function starts by adding another call to refresh delayed (depending on the RefreshInterval) and call setContent. setConent is the function, which does the actual Web Service method invoke.

// Refresh Gadget
function refresh()
{
// Initialize timer – to refresh every
setTimeout( “refresh()”, GetRefreshInterval()*1000 );

    // Set the Content of the Gadget
setContent();
}

The three functions: GetRefreshInterval(), GetType() and GetBaseURL() are only for getting variables from the settings dialog. All functions will default the settings to the default value set in the top of the Javascript section, if they are not already defined. The reason for writing the values to the settings file here is that the settings.html becomes much simpler this way.

The settingsClosed event handler is setup in the onreadystatechanged above and is called when the Settings dialog is opened (clicking on the wrench next to the gadget). This event handler will update the Caption and call refresh (in order to force a refresh now – and setup a new timeout).

// Refresh Gadget on Settings Closed
function settingsClosed(event)
{
// User hits OK on the settings page.
if (event.closeAction == event.Action.commit)
{
// Caption might have changed based on settings
setCaption();

        // Refresh content, update refresh Interval
setTimeout(“refresh()”, 1000);
}
}

The setContent function starts out by setting up some global variables based on the type shown in the gadget

// Setup variables and invoke Web Service method for getting My records
function setContent()
{
RTCpage = ”;
Type = GetType();
if (Type == ‘My Vendors’)
{
// Settings for My Vendors

        RTCpage = ’26’;
WSfunction = ‘GetMyVendors’;
XMLPortResultNS = ‘urn:microsoft-dynamics-nav/xmlports/myvendors’;
XMLPortResultNode = ‘MyVendor’;
}
else if (Type == ‘My Items’)
{
// Settings for My Items

        RTCpage = ’30’;
WSfunction = ‘GetMyItems’;
XMLPortResultNS = ‘urn:microsoft-dynamics-nav/xmlports/myitems’;
XMLPortResultNode = ‘MyItem’;
}
else if (Type == ‘My Customers’)
{
// Settings for My Customers
RTCpage = ’21’;
WSfunction = ‘GetMyCustomers’;
XMLPortResultNS = ‘urn:microsoft-dynamics-nav/xmlports/mycustomers’;
XMLPortResultNode = ‘MyCustomer’;
}
else
{
RTCpage = ”;
}
// Invoke GetMyStuff Web Service
try
{
xmlhttp = new ActiveXObject(“Msxml2.XMLHTTP.4.0”);
xmlhttp.open(“POST”, GetBaseURL()+”Codeunit/GetMyStuff”, false, null, null);
xmlhttp.setRequestHeader(“Content-Type”, “text/xml; charset=utf-8”);
xmlhttp.setRequestHeader(“SOAPAction”, “GetMyStuff”);

        // Setup event handler when readystate changes
xmlhttp.onreadystatechange = function()
{
if ((xmlhttp.readyState == 4) && (xmlhttp.Status == 200))
{
xmldoc = xmlhttp.ResponseXML;
xmldoc.setProperty(‘SelectionLanguage’, ‘XPath’);
xmldoc.setProperty(‘SelectionNamespaces’, ‘xmlns:tns=”‘+XMLPortResultNS+'”‘);
myXML = xmldoc.selectNodes(‘//tns:’+XMLPortResultNode);
updateGadget(true);
}
else
{
updateGadget(false);
}
}
xmlhttp.Send(‘<?xml version=”1.0″ encoding=”utf-8″?><soap:Envelope xmlns:soap=”
http://schemas.xmlsoap.org/soap/envelope/&#8221;><soap:Body><‘+WSfunction+’ xmlns=”urn:microsoft-dynamics-schemas/codeunit/GetMyStuff”><result></result></’+WSfunction+’></soap:Body></soap:Envelope>’);
}
catch(e)
{
// Something went wrong – display: “Service not available”, indicating that there of course are no bugs in the above code:-)
updateGadget(false);
}
}

After setting up the variables, we initialize the xmlhttp and setup a delegate function for onreadystatechange on the xmlhttp (this gets invoked when the Web Service method is done). After this we invoke Send with a SOAP document conforming to the WSDL for the Web Service.

When the onreadystatechange event handler is executed we read the XML and update the content of the Gadget. If anything goes wrong we call the updateGadget function with false – indicating that it should change the content to an error message.

The UpdateGadget builds a HTML table and inserts this table into the CONTENT area of the gadget. For every row we add a call to showflyout if the user clicks the row in order to get additional information.

// Add a row to newHTML
newHTML += ‘<tr><td height=”18″ valign=”middle” background=”Images/gadgetmiddle.png”>’;
newHTML += ‘

‘;
newHTML += ‘ ‘+myXML[o].childNodes[1].text+’
‘;
newHTML += ‘

</td></tr>’;
o++;

The showFlyout function is pretty simple

// Show flyout with additional information
function showFlyout(no)
{
System.Gadget.Flyout.show = false;
flyoutNo = no;
System.Gadget.Flyout.show = true;
}

and after this method has been called, the next thing happening is that the flyoutShowing event handler is invoked – and in this event handler we can calculate the content of the flyout and set it in the flyout.

// Flyout Showing event handler
// Calculate content of flyout
function flyoutShowing()
{
flyoutHTML = ‘<table width=”100%” border=”0″ hspace=”0″ vspace=”0″ cellpadding=”0″ cellspacing=”0″>’;
flyoutHTML += ‘<tr><td height=”31″ align=”left” valign=”middle” background=”Images/topband.png” nowrap><p><strong><font color=”#FFFFFF” size=”3″ face=”Segoe UI”>&nbsp;’+myXML[flyoutNo].childNodes[1].text+'</font></strong></p></td></tr>’;
flyoutHTML += ‘<tr><td valign=”top”><table cellspacing=”5″>’;
for(i=3; i<myXML[flyoutNo].childNodes.length; i++)
{
flyoutHTML += ‘<tr><td>’+myXML[flyoutNo].childNodes[i].nodeName+'</td><td>’;
if (i==3)
{
flyoutHTML += ‘<a href=”dynamicsnav:////runpage?page=’+RTCpage+’&bookmark=’+myXML[flyoutNo].childNodes[2].text+’&mode=view”>’+myXML[flyoutNo].childNodes[i].text+'</a>’;
}
else
{
flyoutHTML += myXML[flyoutNo].childNodes[i].text;
}
flyoutHTML += ‘</td></tr>’;
}
flyoutHTML += ‘</table></td></tr></table>’;

    obj = System.Gadget.Flyout.document.getElementById(“CONTENT”);
obj.innerHTML = flyoutHTML;
}

Really mostly string manipulation in order to create a string that looks correct. I could probably have done the same with XSLT – but this seems pretty easy. As you can see the function enumerates the content of the childNodes to myXML – and adds everything to the flyout. This means that if you add some fields to the XMLPort, then these will be included in the flyout as well.

The flyout.html only contains an empty HTML document with a CONTENT area, which is set by the code above.

Settings.html

The settings.html is really simple, with a method for reading the settings and setting them onto the form

// Initialize settings Form
document.onreadystatechange = function()
{
if(document.readyState==”complete”)
{
// Read settings and set in form
URL.value = System.Gadget.Settings.read(“URL”);
RefreshInterval.value = System.Gadget.Settings.read(“RefreshInterval”);
Type.value = System.Gadget.Settings.read(“Type”);
}
}

A method which for setting the settings back into the settings file.

// Event handler for onSettingsClosing
System.Gadget.onSettingsClosing = function(event)
{
if (event.closeAction == event.Action.commit)
{
// Write new URL into settings
System.Gadget.Settings.writeString(“URL”, URL.value);
System.Gadget.Settings.writeString(“RefreshInterval”, RefreshInterval.value);
System.Gadget.Settings.writeString(“Type”, Type.value);

        // State that it is OK to close the settings form
event.cancel = false;
}
}

and the form itself in HTML

<table width=”100%” height=”100%”>
<tr><td>
Web Service Base URL:<br>
<input type=”textbox” id=”URL” maxlength=”250″>
</td></tr>
<tr><td>
My type:<br>
<select name=”Type” size=”1″>
<option value=”My Customers”>My Customers</option>
<option value=”My Vendors”>My Vendors</option>
<option value=”My Items”>My Items</option>
</select>
</td></tr>
<tr><td>
Refresh Interval:<br>
<select name=”RefreshInterval” size=”1″>
<option value=”10″>5 seconds</option>
<option value=”30″>30 seconds</option>
<option value=”60″>1 minute</option>
<option value=”300″>5 minutes</option>
</select>
</td></tr>
</table>

I will let the code speak for itself.

If you want to see the entire thing, feel free to download it from http://www.freddy.dk/GetMyStuff.zip. the Zip file both contains the Gadget (open that and install – or rename to .zip) and a .fob file with the NAV 2009 objects. You will need to expose the GetMyStuff Codeunit as a webservice with the same name.

Enjoy

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

Search in NAV 2009 – Part 3 (out of 3)

If you haven’t read part 2 and part 1 of the Search in NAV 2009 posts, you should do so before continuing.

This is the 3rd and final part of the Search in NAV 2009 post. In this section I will show how to create a Windows Vista Gadget and have this gadget connect to NAV through Web Services and search in NAV (like the System Tray version in part 2).

We will create an installable Gadget like:

image

and when installed the user should be able to perform searches like:

image

Giving the user the opportunity to click on items and link into NAV 2009.

As you will notice the results window is different from the results window in part 2 and the main reason for this is, that I couldn’t get intra document links (that be <A HREF=”#Customers”> and <A NAME=”Customers”>) to work in a flyout. Every time you would click a link which should reposition yourself in the document – it would reload the page and leave me with a blank page.

After having struggled with this for some hours I decided that that piece was primarily done in order to help keyboard users – and since Gadgets kind of require the mouse I decided to remove the intra document links.

If anybody finds a way to do this, feel free to add a comment and make me smarter! 🙂

I actually think the sample shows that the strategy of having the Web Service just return a result set and have the consumer format this in the way that fits the consumer is the right decision.

What is a .Gadget file?

If you ever downloaded a file called Something.Gadget and opened it, you might get a warning like this

image

and if you say Install, then the Gadget gets installed – very easy indeed, but what is this?

As a hint – try to rename the file to Something.Gadget.zip – and you will see.

The file extension Gadget is known by Windows Sidebar, which will look for a Gadget.xml in the .zip file and if a correct Gadget.xml is present, it will display this installation dialog and if you select to Install, the .zip file is unpacked into a directory under

C:\Users\<username>\AppData\Local\Microsoft\Windows Sidebar\Gadgets

and add the gadget to the sidebar.

Note that the .zip file should NOT contain the outer directory – only the content.

And what is the Gadget then?

The Gadget is actually just a small html document, which can contain Javascript, VB Script code or other client side code, supported in html. The first file read by the Sidebar is the Gadget.xml file, which in my Search example looks like:

<?xml version=”1.0″ encoding=”utf-8″ ?>
<gadget>
<name>NAV Search Gadget</name>
<namespace>NAVsearch</namespace>
<version>1.0</version>
<author name=”Freddy Kristiansen”>
<info url=”
http://blogs.msdn.com/freddyk” />
</author>
<copyright>None, feel free to use!</copyright>
<description>Freddys NAV Search Gadget</description>
<icons>
<icon height=”48″ width=”48″ src=”Images/Navision.ico” />
</icons>
<hosts>
<host name=”sidebar”>
<base type=”HTML” apiVersion=”1.0.0″ src=”gadget.html” mce_src=”gadget.html” />
<permissions>full</permissions>
<platform minPlatformVersion=”0.3″ />
</host>
</hosts>
</gadget>

So, this is where you define Name, Namespace, Version, Author, etc.  But also Icon to display in the Add Gadget and the html document to display in the sidebar (in this case gadget.html).

In my sample I will be using Javascript – not because I by any means is an expert in Javascript – but I did some Javascript coding back around year 2000 – so I guess it is time to refresh my memory. I use notepad as my editor – and the biggest problem I have run into is, that whenever I make a mistake (like misspell something btw. Javascript is case sensitive) execution of Javascript will just stop without giving any form of error. I guess there are better way to write Javascript than this – I just haven’t found it.

Gadget.html

Note, that this is not an HTML tutorial, I expect you to know the basic constructs of HTML and Javascript – you should be able to find a LOT of content around these things on the Internet.

The body section of my gadget looks like this:

<body bgcolor=”0″ leftmargin=”0″ topmargin=”0″ >
<g:background opacity=”100″></g:background>
<table width=”100%” height=”100%” border=”0″ hspace=”0″ vspace=”0″ cellpadding=”0″ cellspacing=”0″>
<tr>
<td height=”36″ align=”left” valign=”top” background=”Images/gadgettop.png” nowrap><p style=”margin-top: 10px”><strong><font color=”#FFFFFF” size=”3″ face=”Segoe UI”>&nbsp;NAV Search</font></strong></p></td>
</tr>
<tr>
<td height=”22″ valign=”middle” background=”Images/gadgetmiddle.png”>
<input type=”textbox” id=”SearchText” onFocus=”hideFlyout();”><input type=”image” src=”Images/search.png” id=”doSearch” onClick=”search();”>
</td>
</tr>
<tr>
<td height=”28″ border=”0″ background=”Images/gadgetbottom.png”>

Microsoft Dynamics NAV

</td>
</tr>
</table>
</body>

As you can see, most of this is HTML in order to make the Gadget look right. The only two Javascript methods that are called is

  • hideFlyout() – when the textbox receives focus.
  • search() – when you click the search icon (or press enter in the text box)

and of course our Gadget has references to some images from an Images folder.

The main search function looks like this

// Main search function
// search after the content in the textbox
function search()
{
// If flyout is shown, hide it
if (System.Gadget.Flyout.show)
{
hideFlyout();
}

    // Get search string
str = document.getElementById(“SearchText”).value;
if (str != “”)
{
// Perform search
result = doSearch(str);
if (result != “”)
{
// Store HTML to use when flyout pops out
newHTML = result;
// Display result in flyout
System.Gadget.Flyout.show = true;
}
}
}

System.Gadget.Flyout is part of the Gadget Framework and gives you access to set a document used for flyouts, show the flyout and hide it again.

The flyout is (as you can imagine) also just a HTML document – even though it doesn’t behave totally like a normal browser showing a HTML document – more about that later.

As you can see, the function, which will be doing the Web Service connection and the “real” search is doSearch:

// the “real” search function
function doSearch(searchstring)
{
// Get the URL for the NAV 2009 Search Codeunit
var URL = GetBaseURL() + “Codeunit/Search”;

// Create XMLHTTP and send SOAP document
xmlhttp = new ActiveXObject(“Msxml2.XMLHTTP.4.0”);
xmlhttp.open(“POST”, URL, false, null, null);
xmlhttp.setRequestHeader(“Content-Type”, “text/xml; charset=utf-8”);
xmlhttp.setRequestHeader(“SOAPAction”, “DoSearch”);
xmlhttp.Send(‘<?xml version=”1.0″ encoding=”utf-8″?><soap:Envelope xmlns:soap=”
http://schemas.xmlsoap.org/soap/envelope/”><soap:Body><DoSearch xmlns=”urn:microsoft-dynamics-schemas/codeunit/Search”><searchstring>’+searchstring+'</searchstring><result></result></DoSearch></soap:Body></soap:Envelope>’);

// Find the result in the soap result and return the rsult
xmldoc = xmlhttp.ResponseXML;
xmldoc.setProperty(‘SelectionLanguage’, ‘XPath’);
xmldoc.setProperty(‘SelectionNamespaces’, ‘xmlns:soap=”
http://schemas.xmlsoap.org/soap/envelope/” xmlns:tns=”urn:microsoft-dynamics-schemas/codeunit/Search”‘);
result = xmldoc.selectSingleNode(“/soap:Envelope/soap:Body/tns:DoSearch_Result/tns:result”).text;

// Load result into XML Document
xmldoc = new ActiveXObject(“Msxml2.DOMDocument.4.0”);
xmldoc.loadXML(result);

    // Load XSL document
xsldoc = new ActiveXObject(“Msxml2.DOMDocument.4.0”);
xsldoc.load(“SearchResultToHTML.xslt”); 

    // Transform
return xmldoc.transformNode(xsldoc);
}

Wow – a lot of code.

This is actually the only code in the Gadget connecting to Web Services – all the other code is housekeeping and has as such nothing to do with NAV 2009. Basically we just get the URL for the Web Service and use XMLHTTP to connect to the Web Service and get a SOAP response back. We use XPath to find the XML result from our codeunit. Load this into a XML Document. Load the XSLT into another XML Document and transform the XML using the XSLT – somehow similar to the way we did it in C# in part 2.

I will post other examples of Gadgets communicating with NAV Web Services, stay tuned.

The basic initialization of the gadget is done in

// Microsoft suggests using onreadystatechange instead of onLoad
document.onreadystatechange = function()
{
if(document.readyState==”complete”)
{
// Initialize Settings and Flyout
System.Gadget.settingsUI = “settings.html”;
System.Gadget.Flyout.file = “flyout.html”; 

        // Add eventhandler for Flyout onShow
System.Gadget.Flyout.onShow = flyoutShowing;  

// Write default Base URL in settings if not already done
GetBaseURL();
}
}

When setting the value of settingsUI on System.Gadget the gadget will get a small image icon when you hover over the Gadget and an Options menu item in the Context menu. Both these options will open the HTML defined in settingsUI.

There is no code in the Flyout – the only special thing is with the flyout is that it contains an IFRAME element, which loads the content.html document in order to get a scrollbar if the content of the flyout becomes too big.

The GetBaseURL function is used under startup – and when we need to connect.

// Get the Base Web Services URL
function GetBaseURL()
{
// Read the URL from settings
var URL = System.Gadget.Settings.readString(“URL”);
if (URL == “”)
{
// No settings in the settings.ini – write the default URL
URL = defaultURL;
System.Gadget.Settings.writeString(“URL”, URL);
}
// Always terminate with /
if (URL.substr(URL.length-1,1) != “/”)
{
URL = URL + “/”;
}
return URL;
}

The reason for calling the function at startup is, that we set the settings to the default URL if it isn’t already defined. It is better that the settings dialog comes up with “some” default than just a blank URL – IMO.

The settings.html contains two functions for doing the housekeeping of the settings:

// Initialize settings Form
document.onreadystatechange = function()
{
if(document.readyState==”complete”)
{
// Read settings and set in form
URL.value = System.Gadget.Settings.read(“URL”);
}
}

// Event handler for onSettingsClosing
System.Gadget.onSettingsClosing = function(event)
{
if (event.closeAction == event.Action.commit)
{
// Write new URL into settings
System.Gadget.Settings.writeString(“URL”, URL.value);

        // State that it is OK to close the settings form
event.cancel = false;
}
}

I will let the code speak for itself.

The System.Gadget.Settings read and write functions stores the settings in

C:\Users\<username>\AppData\Local\Microsoft\Windows Sidebar\settings.ini

and the settings will be stored in clear text, you will actually be able to modify this file as well.

That’s it for the NAV Search demo – I hope you like it, you can download the Gadget from http://www.freddy.dk/Search – Part 3.zip. Note that this download cannot stand alone – you need the NAV piece of this, which you can find in Part 1.

Enjoy

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV