<

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x

A MegaMenu for SharePoint 2010 using a DVWP and a List

Published on
20,190 Points
8,790 Views
4 Endorsements
Last Modified:
Awarded
Ingeborg Hawighorst (Microsoft MVP / EE MVE)
Love data viz with Excel and Power BI. Power Query rocks. Have you tried it?

The vision:

A MegaMenu for a SharePoint portal home page

The mission:

Make it easy to maintain. Allow rich content and sub headers as well as standard links. Factor in frequent changes without involving developers or a lengthy Dev/Test/Prod release cycle. The personal assistant of the marketing boss should be able to make changes on the fly. Instantaneous. Without any knowledge of HTML or CSS, so editing code is out. If it’s more complicated than filling in a time sheet, it won’t fly. And do all that with just the browser interface and SharePoint Designer. No Visual Studio, no custom code.

What’s a MegaMenu, anyway?
There are quite a few sites out there describing how to create impressive MegaMenus if you Bingle a bit. Rave reviews of the concept from Jacob Nielsen. Flashy sites from developers strutting their stuff. JavaScript, jQuery – the choice is yours. Most of them even work.

None of those take into account a SharePoint background, though. The MegaMenu content is always somehow “there already”, nicely configured in a nested construct of UL and LI tags, with hard-coded <A href> tags and titles. Not something the Marketing Assistant will want to get his head around if he wants to add a few items and a flashy “Hot and new” icon to a new menu entry.

So, to achieve the vision and make the mission possible, we need to come up with some practicable steps.

Here’s the plan:

Create a SharePoint list that stores all the items to feature in the MegaMenu
Create a view on that list with a Data View Web Part (DVWP)
Modify the DVWP to show as nested lists instead of the standard table structure
Apply the CSS and the jQuery


What you need:
A SharePoint 2010 site.  
SharePoint Designer 2010
jQuery Hover Intent plug-in: The only non-standard SharePoint elements required are the jQuery core and the library for Hover Intent. This is a variation of a JavaScript functionality that displays stuff if the mouse hovers over specific screen elements. Download the Hover Intent here: http://cherne.net/brian/resources/jquery.hoverIntent.html
Why use Hover Intent? JavaScript mouse hover events normally fire immediately when the mouse hovers over the item. This can lead to a lot of screen flicker and  a general perception of “nervous” behavior. Hover Intent waits for the mouse to pause before the event is triggered, Check out the link above for a demo of what it does. Don't forget to download the jQuery core file.
The CSS and jQuery script calls posted at http://www.sohtanaka.com/web-design/mega-drop-downs-w-css-jquery/

I also assume that you have a basic knowledge of the SharePoint 2010 browser interface , basic knowledge of SharePoint Designer 2010, Data View Web Parts (DVWP) and how to style screen elements with CSS.

The final look and feel we’re after is described here: http://www.sohtanaka.com/web-design/mega-drop-downs-w-css-jquery/ . In my Sharepoint site, it looks like this:
 MegaMenu
Have a look around and try out the demo to see what the end product should look like. Once you’re comfortable with the concept, come back and let’s take action.

1. Create a SharePoint list for menu items


If we want the MegaMenu to be configurable, then a SharePoint List will be the most logical way to achieve that. For the sake of normalizing data, I suggest an approach with two lists.

List for MegaMenu headers
This list is called MegaMenu Headers and has three columns:
Title: -- Single line of text -  The MegaMenu tab title (if you use graphic files for the MegaMenu tab background, these won’t ever be visible, but they will help with the general orientation).
Order – number – the order in which the headers appear on the final page. This column will be the first sorting and grouping criterion of the mega menu. The column will be inherited by the list that stores the MegaMenu details.
CSSClass – Single line of text – This value needs to be manually created in the CSS file that formats the MegaMenu. Every header tab will have a specific width and position, defined by a CSS class. To make the formatting easier to maintain, assign the CSS class name here and then make sure that the CSS file actually has an entry that defines the properties for that class.


List for MegaMenu Content
The Headers provide the outer envelope, but the meat of the functionality will be with the individual content items of the menus. For this, we need another SharePoint List. The list is called MegaMenu Content and has these columns:

Column			Type
MegaHeader		Lookup  
MenuRow			Number  
MenuColumn		Number  
ItemNumber		Number  
Title			Single line of text    (Don’t make this required!! It will not always have data)
MenuLink		Hyperlink or Picture  
ItemImage		Hyperlink or Picture  
ItemBody		Multiple lines of text  (This is rich text)
Published		Yes/No  
ItemWidth		Choice  (the choices are: “default, 2, 3, 4”. Make the default value "default". The code below will look out for that.)
MegaHeader:Order	Lookup  
MegaHeader:CssClass	Lookup  

Open in new window


The last two columns, MegaHeader:Order and MegaHeader:CssClass are created by ticking their column names when defining the Lookup column for MegaHeader.

Now fill your list with some content. For each item, make sure you select a MegaHeader value and specify MenuRow, MenuColumn and ItemNumber.Pay These numbers will influence the order of the items in a menu panel.
Then specify at least one of the columns Title, MenuLink, ItemImage or ItemBody If a menu item has a Title specified, it will be formatted as a h2 element. A MenuLink for an item with a title is optional.
ItemWidth is optional. Leave it at default unless you want an item to span more than one column. In that case, the first item in that column requires the ItemWidth to be set.



2. Creating a DVWP


Fire up SharePoint Designer 2010, open an existing Web Part Page or a Wiki Page and create a DVWP:
Insert > Data View > Empty Data View
In the new, empty data view, click the link to select a data source and select the list MegaMenu Content. It does not really matter which fields you select for the display, because we will gut the DVWP content and replace it with a custom XSL Template. So select a few fields and click “Insert Selected Fields as > Multiple Item View”.

By default, the DVWP only shows 10 items. Fix that by clicking “Paging > Display All Items”.

Next, click the Sort & Group icon and add these fields to the sort order:

-      MegaHeader:Order
-      MenuRow
-      MenuColumn
-      ItemNumber

3. Customising the DVWP


By default, a DVWP is displayed as a table. We need to change this to a nested list with this structure:

<ul id="topnav">
	<li>Header Tab
		<div class=”sub”>
			<div class=”row”>
				<ul>
					<li>Menu Item
					<li>Menu Item
					<li>Menu Item
				</ul>
			</div>
		</div>
	<li>Next Header tab
</ul>

Open in new window


This image shows the nested classes and divs:
 Classes and divsred = <div class="sub">
orange = <div class="row">
purple = <ul> or <ul class="span2">, depending on the data

So, let's apply the custom template. Find the first template in the XSL and delete all template code down to the last </xsl:template> tag. Make sure to keep the opening <Xsl><xsl:stylesheet> tags intact.

Without further ado, here is the template code that I used, starting with the <Xsl> tag:

<Xsl>
<xsl:stylesheet 
		version="1.0"
		xmlns:x="http://www.w3.org/2001/XMLSchema" 
		xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" 
		xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" 
		xmlns:asp="http://schemas.microsoft.com/ASPNET/20" 
		xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" 
		xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
		xmlns:msxsl="urn:schemas-microsoft-com:xslt" 
		xmlns:SharePoint="Microsoft.SharePoint.WebControls" 
		xmlns:ddwrt2="urn:frontpage:internal"
		exclude-result-prefixes="x d asp xsl msxsl ddwrt ddwrt2">
<xsl:output method="html" indent="no"/>
<xsl:decimal-format NaN=""/>
<xsl:param name="dvt_apos">&apos;</xsl:param>
<xsl:param name="ManualRefresh">
</xsl:param><xsl:variable name="dvt_1_automode">0</xsl:variable>

<xsl:key name="MHeaders" match="Row" use="@MegaHeader_x003a_Order"/>
<xsl:key name="MRows" match="Row" use="concat(@MegaHeader_x003a_Order, '-', @MenuRow)"/>
<xsl:key name="MColumns" match="Row" use="concat(@MegaHeader_x003a_Order,'-', @MenuRow,'-', @MenuColumn)"/>
	
	<xsl:template match="/" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:SharePoint="Microsoft.SharePoint.WebControls">
		<xsl:call-template name="Setup"/>
	</xsl:template>

	<xsl:template name="Setup">
		<xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row"/>
		<!-- start unordered list for the navigation top tabs -->
		<ul id="topnav">
			<xsl:variable name="HeaderLoop" select="//Row[generate-id() = generate-id(key('MHeaders', @MegaHeader_x003a_Order)[1])]"/>
	    	<xsl:for-each select="$HeaderLoop">
				<xsl:variable name="LinkStart" select="string('&lt;a href=&quot;#&quot; class=&quot;')"/>
				<xsl:variable name="LinkClass"><xsl:value-of select="@MegaHeader_x003a_CssClass"/></xsl:variable>
				<xsl:variable name="LinkEnd" select="string('&quot; &gt;')"/>
				<xsl:variable name="LinkVar" select="concat($LinkStart,$LinkClass,$LinkEnd)"/>
				<xsl:variable name="CloseATag" select="string('&lt;/a&gt;')"/>
				<!-- build the list items for the top tabs -->
				<li>
	     	 		<xsl:value-of select="$LinkVar" disable-output-escaping="yes"/>
        			<xsl:value-of select="substring-after(@MegaHeader., ';#')"/>
	      			<xsl:value-of select="$CloseATag" disable-output-escaping="yes"/>
	      			<!-- create a container for each menu panel -->
	      			<div class="sub">
						<xsl:variable name="RowLoop" select="key('MHeaders', @MegaHeader_x003a_Order)[generate-id() = generate-id(key('MRows', concat(@MegaHeader_x003a_Order, '-', @MenuRow))[1])]"/>
			    		<xsl:for-each select="$RowLoop">
			    			<!-- create a container for each row -->
			    			<div class="row">
								<xsl:variable name="ColumnLoop" select="key('MRows', concat(@MegaHeader_x003a_Order, '-', @MenuRow))[generate-id() = generate-id(key('MColumns', concat(@MegaHeader_x003a_Order,'-', @MenuRow,'-', @MenuColumn))[1])]"/>
								<xsl:for-each select="$ColumnLoop">
									<!-- start a new unordered list for each column -->
									<!-- if column width is specified, use that as a class -->
									<xsl:variable name="SpanTagStart" select="string('&lt;ul ')"/>
									<xsl:variable name="SpanTagClass">
										<xsl:choose>
											<xsl:when test="@ItemWidth != string('default')">
												<xsl:value-of select="concat(string('class=&quot;span'),@ItemWidth,string('&quot; '))"/>
											</xsl:when>
											<xsl:otherwise>
												<xsl:value-of select="''"/>
											</xsl:otherwise>
										</xsl:choose>
									</xsl:variable>
									<xsl:variable name="SpanTagEnd" select="string('&gt;')"/>
									<xsl:variable name="SpanTagOpen" select="concat($SpanTagStart,$SpanTagClass,$SpanTagEnd)"/>
									<xsl:variable name="SpanTagClose" select="string('&lt;/ul&gt;')"/>
									<xsl:value-of select="$SpanTagOpen" disable-output-escaping="yes"/>
									<!-- start a new unordered list for each column -->									
									<xsl:variable name="ItemLoop" select="key('MColumns', concat(@MegaHeader_x003a_Order,'-', @MenuRow,'-', @MenuColumn))"/>
									<xsl:for-each select="$ItemLoop">
										<!-- build the a href tag if we have a link -->
										<xsl:variable name="ItemLinkStart" select="string('&lt;a href=&quot;')"/>
										<xsl:variable name="ItemLinkURL"><xsl:value-of select="@MenuLink"/></xsl:variable>
										<xsl:variable name="ItemLinkVar">
											<xsl:choose>
												<xsl:when test="string-length(@MenuLink) &gt; 0">
													<xsl:value-of select="concat($ItemLinkStart,$ItemLinkURL,$LinkEnd)"/>
												</xsl:when>
												<xsl:otherwise><xsl:value-of select="''"/></xsl:otherwise>
											</xsl:choose>
										</xsl:variable>
										<!-- build the list items -->
										<li>
											<!-- if we have a Title, format as h2 -->
											<xsl:choose>
												<xsl:when test="string-length(@MenuLink) &gt; 0 and string-length(@Title) &gt; 0">
												<!-- title and link -->
													<h2>
														<xsl:value-of select="$ItemLinkVar" disable-output-escaping="yes"/>
														<xsl:value-of select="@Title"/>
														<xsl:value-of select="$CloseATag" disable-output-escaping="yes"/>
													</h2>
												</xsl:when>
												<xsl:when test="string-length(@MenuLink) = 0">
												<!-- title only -->
													<h2>
														<xsl:value-of select="@Title"/>
													</h2>
												</xsl:when>
												<xsl:otherwise>
													<xsl:value-of select="$ItemLinkVar" disable-output-escaping="yes"/>
													<xsl:value-of select="@MenuLink.desc"/>
													<xsl:value-of select="$CloseATag" disable-output-escaping="yes"/>
												</xsl:otherwise>
											</xsl:choose>
											<xsl:if test="string-length(@ItemImage) &gt; 0">
												<img border="0" src="{@ItemImage}" alt="{@ItemImage.desc}"/>
											</xsl:if>
											<xsl:if test="string-length(@ItemBody) &gt; 0">
												<xsl:value-of select="@ItemBody" disable-output-escaping="yes"/>
											</xsl:if>
										</li>
									</xsl:for-each>
									<xsl:value-of select="$SpanTagClose" disable-output-escaping="yes"/>
								</xsl:for-each>
							</div>
			    		</xsl:for-each>
			    	</div>		
	    		</li>
        	</xsl:for-each>
        </ul>
   	</xsl:template>   	
</xsl:stylesheet>
</Xsl>

Open in new window



Some explanations


The biggest challenge was to figure out how to do the grouping in XSL. After several approaches, I found that the Muenchian grouping works best for my purposes. For each of the three grouping levels, I created filter keys that get progressively more complex, concatenating the header order, row order and column order.

<xsl:key name="MHeaders" match="Row" use="@MegaHeader_x003a_Order"/>
<xsl:key name="MRows" match="Row" use="concat(@MegaHeader_x003a_Order, '-', @MenuRow)"/>
<xsl:key name="MColumns" match="Row" use="concat(@MegaHeader_x003a_Order,'-', @MenuRow,'-', @MenuColumn)"/>

Open in new window


These keys are created above the first template.

Since each header tab needs its own class assigned, I created variables that store the field CssClass for the current item and then concatenate that with the appropriate opening and closing brackets for the tag.

<xsl:variable name="LinkStart" select="string('&lt;a href=&quot;#&quot; class=&quot;')"/>
<xsl:variable name="LinkClass"><xsl:value-of select="@MegaHeader_x003a_CssClass"/></xsl:variable>
<xsl:variable name="LinkEnd" select="string('&quot; &gt;')"/>
<xsl:variable name="LinkVar" select="concat($LinkStart,$LinkClass,$LinkEnd)"/>
<xsl:variable name="CloseATag" select="string('&lt;/a&gt;')"/>
<!-- build the list items for the top tabs -->
<li>
	<xsl:value-of select="$LinkVar" disable-output-escaping="yes"/>
	<xsl:value-of select="substring-after(@MegaHeader., ';#')"/>
	<xsl:value-of select="$CloseATag" disable-output-escaping="yes"/>

Open in new window


The resulting html looks like this

<a class=”MyClass” href=”#”>TheFirstTab</a>

Open in new window


The same technique is used later on to create the ul tag for the column, so the default column width can be overridden. With a default column width, we need a simple <ul> tag, but when a column width is specified with a 2, for example, then we need <ul class=”span2”>.

<xsl:variable name="SpanTagStart" select="string('&lt;ul ')"/>
<xsl:variable name="SpanTagClass">
	<xsl:choose>
		<xsl:when test="@ItemWidth != string('default')">
			<xsl:value-of select="concat(string('class=&quot;span'),@ItemWidth,string('&quot; '))"/>
		</xsl:when>
		<xsl:otherwise>
			<xsl:value-of select="''"/>
		</xsl:otherwise>
	</xsl:choose>
</xsl:variable>
<xsl:variable name="SpanTagEnd" select="string('&gt;')"/>
<xsl:variable name="SpanTagOpen" select="concat($SpanTagStart,$SpanTagClass,$SpanTagEnd)"/>
<xsl:variable name="SpanTagClose" select="string('&lt;/ul&gt;')"/>
<xsl:value-of select="$SpanTagOpen" disable-output-escaping="yes"/>

Open in new window


Several rows below that, the ul tag is closed with
 <xsl:value-of select="$SpanTagClose" disable-output-escaping="yes"/>

Open in new window




4. Create the CSS


Download the CSS from http://www.sohtanaka.com/web-design/mega-drop-downs-w-css-jquery/. Either plug it into your custom CSS file, if you use one, or load it via script. I’ve added the following CSS classes to allow for the multi-column spanning of items:

ul#topnav li .sub ul{
	list-style: none; /*--This is in the original CSS--*/
	margin: 0; padding: 0;
	width: 180px;
	float: left;
}
ul#topnav li .sub ul.span2{
	width: 360px; /*--This is a new definition--*/
}
ul#topnav li .sub ul.span3{
	width: 540px; /*--This is a new definition--*/
}
ul#topnav li .sub ul.span4{
	width: 720px; /*--This is a new definition--*/
}

Open in new window


Also, make sure that in the section for the #topnav you have definitions for each of the CssClass items you specified in the MegaMenu Headers list.


For testing purposes I used a Web Part Page to contain the DVWP. I created a Content Editor Web Part below the DVWP and linked it to a text file that has the following script:

<script type="text/javascript" src="/managedPath/siteName/Resource Library/jquery.min.js"></script>
<script type="text/javascript" src="/managedPath/siteName/Resource Library/jquery.hoverIntent.minified.js"></script>
<script type="text/javascript">
$(document).ready(function() {
	//On Hover Over
function megaHoverOver(){
	$(this).find(".sub").stop().fadeTo('fast', 1).show(); //Find sub and fade it in
    (function($) {
        //Function to calculate total width of all ul's
        jQuery.fn.calcSubWidth = function() {
            rowWidth = 0;
            //Calculate row
            $(this).find("ul").each(function() { //for each ul...
                rowWidth += $(this).width(); //Add each ul's width together
            });
        };
    })(jQuery); 

    if ( $(this).find(".row").length > 0 ) { //If row exists...

        var biggestRow = 0;	

        $(this).find(".row").each(function() {	//for each row...
            $(this).calcSubWidth(); //Call function to calculate width of all ul's
            //Find biggest row
            if(rowWidth > biggestRow) {
                biggestRow = rowWidth;
            }
        });

        $(this).find(".sub").css({'width' :biggestRow}); //Set width
        $(this).find(".row:last").css({'margin':'0'});  //Kill last row's margin

    } else { //If row does not exist...

        $(this).calcSubWidth();  //Call function to calculate width of all ul's
        $(this).find(".sub").css({'width' : rowWidth}); //Set Width

    }
}
//On Hover Out
function megaHoverOut(){
  $(this).find(".sub").stop().fadeTo('fast', 0, function() { //Fade to 0 opactiy
      $(this).hide();  //after fading, hide it
  });
}

//Set custom configurations
var config = {
     sensitivity: 2, // number = sensitivity threshold (must be 1 or higher)
     interval: 100, // number = milliseconds for onMouseOver polling interval
     over: megaHoverOver, // function = onMouseOver callback (REQUIRED)
     timeout: 500, // number = milliseconds delay before onMouseOut
     out: megaHoverOut // function = onMouseOut callback (REQUIRED)
};

$("ul#topnav li .sub").css({'opacity':'0'}); //Fade sub nav to 0 opacity on default
$("ul#topnav li").hoverIntent(config); //Trigger Hover intent with custom configurations

});
</script>

Open in new window


I keep all my scripts in a Resource Library. The exact path to your script will be different, so please make sure to adjust the path. If you don’t use a Custom CSS file, you can load the CSS in that script as well.


Now it’s time to test. If all goes well in the web part page, you can use the DVWP in your Master Page or a Page Layout. In this case, plug the script straight into the page instead of calling it via a CEWP.

For the Master Page version of the DVWP I created a filter, so the DVWP will only display items where the column “Published” is checked.

Now the marketing assistant can create new menu items in the MegaMenu Content list. He can check if all items behave by opening the Web Part page that has the single, unfiltered DVWP. If he’s happy with that, he can tick the “Publish” check box for the new menu items and they will appear in the filtered DVWP on the Master Page.

Mission accomplished.




~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you liked this article and want to see more from this author,  please click here.

If you found this article helpful, please click the Yes button at the top of
the page, next to the prompt “Was this article helpful?

Thanks!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
Comment
  • 5
  • 2
  • 2
9 Comments
LVL 38

Expert Comment

by:younghv
Excellent Article.
It isn't very often that I get to laugh whilst learning.
Thank you for putting it together.

"YES" vote above.
0
LVL 50

Author Comment

by:Ingeborg Hawighorst (Microsoft MVP / EE MVE)
Thanks, younghv. Glad to raise a smile. Was it the Welsh place name?
0
LVL 38

Expert Comment

by:younghv
Well, that "Column Width..." was the piece of resistance, but the whole Menu had me snorting beer though my nose (sorry for the graphic image) - and it was good Cherman beer.
0
The 7 Worst Nightmares of a Sysadmin

Fear not! To defend your business’ IT systems we’re going to shine a light on the seven most sinister terrors that haunt sysadmins. That way you can be sure there’s nothing in your stack waiting to go bump in the night.

LVL 50

Author Comment

by:Ingeborg Hawighorst (Microsoft MVP / EE MVE)
Let's just hope it was either Bitburger, Warsteiner, Krombacher or some other common Pils variety that did a double take on you. I'd only be worried if you upchucked valuable Kölsch like Mühlen, Päfffgen or Gereon, or any other brand Qlemo or Tolomir would join me in recommending. Since none of these true gems are shipped overseas, I'm not worried. (That's also the downside, though: they don't ship the good stuff overseas, so I can't get ahold of it, either, at my end of the world, and my best attempt at sampling Cherman beer is Kromacher from the local provincial supermarket at an outrageous charge. Those were the days. )
0
LVL 27

Expert Comment

by:Tolomir
I get an DNS error for http://www.sohtanaka.com/web-design/mega-drop-downs-w-css-jquery/

Name Error: The domain name does not exist.

---
So I cannot join in the laughter...

(This is 450 points off btw ;-)

Tolomir (EE points clerk)
0
LVL 50

Author Comment

by:Ingeborg Hawighorst (Microsoft MVP / EE MVE)
I'm sorry, but the link to the www.sohtanaka.com site seems to be rather fragile. Sometimes it works, other times it's down. I don't have the resources to put the content into a site I control myself, so I'm at the mercy of that site being up for the mega menu demo to work.

If you are familiar with the concept of mega menus in general, then you may get the gist of the project's idea from the screenshots posted in the article. Otherwise, any bingle for "mega menu" should get you samples of mega menus in the real web.

When the sohtanaka web site is down, you can't donwload the CSS, though, so I'll upload the CSS and the complete XSLT file here for your convenience.

cheers, teylyn
MegaMenu.css
MegaMenuXSLT.txt
0
LVL 27

Expert Comment

by:Tolomir
great thank you!
0
LVL 50

Author Comment

by:Ingeborg Hawighorst (Microsoft MVP / EE MVE)
Köbes? Eine Runde für Tolomirs Tisch!
0
LVL 50

Author Comment

by:Ingeborg Hawighorst (Microsoft MVP / EE MVE)
As is the way with the internet, sites get created, sites get abandoned. Links die.

The link in my above article to the demo of the MegaMenu has died.

Fear not. The Wayback machine comes to the rescue. Here are the links to the content that does not work anymore from the links above.

MegaMenu Demo

Background on the MegaMenu CSS and HTML structure

cheers, teylyn
0

Featured Post

OWASP: Avoiding Hacker Tricks

Learn to build secure applications from the mindset of the hacker and avoid being exploited.

Join & Write a Comment

A query can call a function, and a function can call Excel, even though we are in Access. This is Part 2, and steps you through the VBA that "wraps" Excel functionality so we can use its worksheet functions in Access. The declaration statement de…
Overview of OneDrive and collaboration.

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month