This was a triumph.
I'm making a note here: HUGE SUCCESS.

Search This Blog

Friday, October 25, 2013

How to make an accordion menu for a subsite in SharePoint 2013

If you use term driven navigation to manage all the pages on your subsites, and you have a lot of pages for a certain subsite, then your navigation might turn out to be quite long. In situations like this, it might as well come in handy to have an accordion menu that just collapses or expands its contents (terms) upon click. I'm going to use jQuery and JavaScript for this.

Of course, it would also be important that terms having underlaying terms can't act as a link (the link behind them, if any, shouldn't activate and open a new page). Terms that have underlaying terms should expand and show their underlaying terms.

Here's a screenshot of a term driven navigation with all the terms and underlaying terms visible, no accordion used yet:

What we want is a navigation that looks like this:
 =>
 =>

So basically you have all the categories (the top terms) collapsed. Upon clicking on a term that has underlaying terms, it should expand and show the underlaying terms of that term you just clicked on. And so on.
In the example above, when you open the second category, the underlaying terms expand. In here, there is another term that has underlaying terms, which I named the subcategory. You can once again click on this one and it will expand, showing its respective underlaying terms. If a term no longer has underlaying terms, it cannot expand and it just opens the page.

And now the real work.

You will need to create a JavaScript file (or just add the code in an existing script file if you already use scripts on your site) and make sure to add it to your master page. Mine is named "script.js" and its path is "~sitecollection/Style Library/Scripts/script.js". You will also need jQuery, so make sure you have that as well. My jQuery file its path is "~sitecollection/Style Library/Scripts/jquery-1.10.2.min.js".

To correctly make a reference to your scripts, you must add them to your master page. I'm using a HTML master page. Edit your master page in advanced edit mode and add the reference code after the "ScriptLink" lines. The code below has five ScriptLink references (references to default SharePoint scripts), the code you must add has to be after those scripts.
// The following five lines are already in your master page
<!--SPM:<SharePoint:ScriptLink language="javascript" name="core.js" 
OnDemand="true" runat="server" Localizable="false"/>-->
<!--SPM:<SharePoint:ScriptLink language="javascript" name="menu.js" 
OnDemand="true" runat="server" Localizable="false"/>-->
<!--SPM:<SharePoint:ScriptLink language="javascript" name="callout.js" 
OnDemand="true" runat="server" Localizable="false"/>-->
<!--SPM:<SharePoint:ScriptLink language="javascript" name="sharing.js" 
OnDemand="true" runat="server" Localizable="false"/>-->
<!--SPM:<SharePoint:ScriptLink language="javascript" name="suitelinks.js"
OnDemand="true" runat="server" Localizable="false"/>--> 
     
// Now use these following two lines to reference to your JavaScript
// files, with first referencing to the jQuery script and then your 
// own script.
<!--SPM:<SharePoint:ScriptLink language="javascript" ID="scriptLink1" 
runat="server" OnDemand="false" Localizable="false" 
name="~sitecollection/Style Library/Scripts/jquery-1.10.2.min.js"/>-->
<!--SPM:<SharePoint:ScriptLink language="javascript" ID="scriptLink2" 
runat="server" OnDemand="false" Localizable="false" 
name="~sitecollection/Style Library/Scripts/scripts.js"/>-->

Now that you have correctly made a reference to the jQuery script and your own script, you can save the master page (for now, since we'll add some more later).

Let's start with the script.

This is what the code in my script.js file looks like. I will try to add comments in the code.
function accordionMe(selector, initalOpeningClass) {
   var speedo = 300;
   var $this = selector;
   var accordionStyle = true;

   // First of all, hide all ul's:
   $this.find("li>ul").hide(); 

   // Then for each li you find in that ul,
   $this.find("li").each(function(){ 
      // that isn't empty,
      if ($(this).find("ul").size() != 0) { 
         // find all the ones at the top,
         if ($(this).find("a:first")) { 
            // and make sure it won't do anything on click.
            $(this).find("a:first").click(function(){ return false; });
            // Optional: if you want to style the non-clickable links, 
            // then uncomment the code below. 
            //$(this).find("a:first").addClass("no-click");
         }
      }
   });

   // Open all items.
   $this.find("li."+initalOpeningClass).each(function(){ 
      $(this).parents("ul").slideDown(speedo); 
   });

   // Execute this function on click of li with an a tag.
   $this.find("li a").click(function(){ 
      if ($(this).parent().find("ul").size() != 0) {
         if (accordionStyle) { 
            if(!$(this).parent().find("ul").is(':visible')){
               // Fetch all parents.
               parents = $(this).parent().parents("ul"); 
               // Fetch all visible ul's.
               visible = $this.find("ul:visible"); 
               // Loop through.
               visible.each(function(visibleIndex){ 
                  var close = true;
                  // Check if the parent is closed.
                  parents.each(function(parentIndex){ 
                     if(parents[parentIndex] == visible[visibleIndex]){
                        close = false;
                        return false;
                     }
                  });
                  // If closed, slide the content of the ul up 
                  // (so collapse).
                  if(close){ 
                     if($(this).parent().find("ul") != 
                       visible[visibleIndex]){
                        $(visible[visibleIndex]).slideUp(speedo);
                     }
                  }
               });
            }
         }
         if($(this).parent().find("ul:first").is(":visible")) {
            $(this).parent().find("ul:first").slideUp(speedo);
         }
         else {
            $(this).parent().find("ul:first").slideDown(speedo);
         }
      }
   });
}

There you have it, your code is done! Now all we need to do is make sure it will execute the function as soon as a page is loaded. We need to get back to our master page for this. So open your master page again (in advanced edit mode) and find the following piece of code:
<script type="text/javascript">
//<![CDATA[
 var g_pageLoadAnimationParams = { elementSlideIn : "sideNavBox", 
 elementSlideInPhase2 : "contentBox" };
//]]>
</script>

We will not change that part, but we will have to paste some code underneath it. Here's the code:
<script type="text/javascript">
//<![CDATA[
 var g_pageLoadAnimationParams = { elementSlideIn : "sideNavBox", 
 elementSlideInPhase2 : "contentBox" };

 $(function runOnInitLoad() {
  accordionMe(jQuery("#zz9_RootAspMenu"), "selected");  
  accordionMe(jQuery("#zz10_RootAspMenu"), "selected");  
  accordionMe(jQuery("#zz11_RootAspMenu"), "selected");  
  accordionMe(jQuery("#zz12_RootAspMenu"), "selected");  
  accordionMe(jQuery("#zz13_RootAspMenu"), "selected");  
  accordionMe(jQuery("#zz14_RootAspMenu"), "selected");  
  accordionMe(jQuery("#zz15_RootAspMenu"), "selected");  
  accordionMe(jQuery("#zz16_RootAspMenu"), "selected");  
  runThisCode(); 
  moveScroller();
  setCustomFontName('#fseaFont-1-1-Menu');
 });

 ExecuteOrDelayUntilScriptLoaded(function() { runOnInitLoad(); }, 
 "init.js");
//]]>
</script>

The reason that I use the function on so many different ID's is because these menu ID's get used often, spread across the site. These are just all the ones I needed, so I added them all. Feel free to add/remove some.

After you have saved your master page, checked it in and published it as a major version (and don't forget to check in and publish your scripts as well), we're all done! You now should have a nice accordion menu, sliding up and down as you click on it. :) Enjoy!


PS.: I would like to express my thanks to Mathias Bosman, who seriously helped me with this (basically, he made the whole script work). Thanks Mathias!

How to show more than three levels of sub-menu items in a subsite navigation in SharePoint 2013

In the beginning when I was still learning SharePoint, I had made my first quick launch navigation. The first thing I noticed whas the inability to have more than two or three levels of submenu-items. This was really bothering me since I had to make quite some categories (as I like to call them).
So then I found out I should use a term set navigation. Upon creating a nice navigation with many terms that had underlying terms and so on, I was quite disappointed when I found out it was showing only the first two or three levels. So I started to dig around in the master page, and found out how to fix this.

In the screenshot on the top right of the post, you can see up to six levels can be shown. Each level can be a category to define what kind of pages can be found underneath that category (like terms with underlaying terms, but I just like to refer to them as categories).

To activate this for your SharePoint site, you have to use term driven navigation and edit your master page. I have a HTML based master page, which means that if I make my changes in a HTML master page named "custom.html", it will then automatically convert it to a SharePoint fit master page named "custom.master". You will always need to make your changes in the HTML master page.
Anyway, I'll demonstrate what you need to do. I will use the code of the oslo.html master page.

In SharePoint Designer 2013, open your HTML-based master page in Advanced Editor mode. Search for "V4QuickLaunchMenu". You should find a long piece of code that looks like this:
<!--SPM:<SharePoint:AspMenu 
 id="V4QuickLaunchMenu" 
 runat="server" 
 EnableViewState="false" 
 DataSourceId="QuickLaunchSiteMap" 
 UseSimpleRendering="true" 
 Orientation="Horizontal" 
 StaticDisplayLevels="1" 
 DynamicHorizontalOffset="0" 
 AdjustForShowStartingNode="true" 
 MaximumDynamicDisplayLevels="2" 
 StaticPopoutImageUrl="/_layouts/15/images/menudark.gif?rev=23" 
 StaticPopoutImageTextFormatString="" 
 SkipLinkText="" 
 StaticSubMenuIndent="0"/>-->

Reformat it so that it looks like this:
<!--SPM:<SharePoint:AspMenu 
  id="V4QuickLaunchMenu" 
  runat="server" 
  EnableViewState="false" 
  DataSourceId="QuickLaunchSiteMap" 
  UseSimpleRendering="true"
  Orientation="Horizontal" 
  StaticDisplayLevels="1" 
  DynamicHorizontalOffset="0" 
  AdjustForShowStartingNode="true" 
  MaximumDynamicDisplayLevels="2" 
  StaticPopoutImageUrl="/_layouts/15/images/menudark.gif?rev=23" 
  StaticPopoutImageTextFormatString="" 
  SkipLinkText="" 
  StaticSubMenuIndent="0"/>-->

Now in order to get them to display from top to bottom, you have to change the value of Orientation from Orientation:"Horizontal" to Orientation:"Vertical".
To increase the amount of levels to display, change the value of StaticDisplayLevels from StaticDisplayLevels="1" to StaticDisplayLevels="6" and change the value of MaximumDisplayLevels from MaximumDynamicDisplayLevels="2" to MaximumDisplayLevels="6".
Your code should now look like this:
<!--SPM:<SharePoint:AspMenu 
  id="V4QuickLaunchMenu" 
  runat="server" 
  EnableViewState="false" 
  DataSourceId="QuickLaunchSiteMap" 
  UseSimpleRendering="true"
  Orientation="Vertical" 
  StaticDisplayLevels="6" 
  DynamicHorizontalOffset="0" 
  AdjustForShowStartingNode="true" 
  MaximumDynamicDisplayLevels="6" 
  StaticPopoutImageUrl="/_layouts/15/images/menudark.gif?rev=23" 
  StaticPopoutImageTextFormatString="" 
  SkipLinkText="" 
  StaticSubMenuIndent="0"/>-->

Save the master page. Make sure it is checked in and published as a major version (also, it has to be your defailt master page). Now go check if your term driven navigation is displayed correctly. If it does: hurray! You did it right! If it doesn't, then you might have missed a spot somewhere. ;)

Any questions, let me know.

Wednesday, October 23, 2013

How to make a contact form in SharePoint and submit the messages to a SharePoint list

I had to make a contact page for an intranet SharePoint site, and didn't like the idea of receiving dozens of e-mails every single day. First of all that would really flood my mailbox and I would have to make a folder in my mailbox just for contact messages received by the intranet site, and second of all I just don't like having to read a lot of new e-mails.

So! I decided that I wanted all the messages to be stored in a SharePoint list, and I wrote a script to handle this. The script fetches the subject, message, full name and e-mail address of the user who sends a message through the contact form. Only the subject and the message have to be filled in by the user, the full name and e-mail address are fetched automatically based on the current user. If everything is correct and validates, it saves the data as a new list item. 

Here are some screenshots of what it will look like.

The contact page with the contact form:

The contact form when you try to click send without filling in anything:

The contact form when you filled in the fields and pressed send:

So you see, it has proper validation. If one of the fields or both isn't filled in, it won't get sent as an empty message. If it is filled in properly and the user has pressed send, it will be sent to the Contact list and the input fields and send button will be disabled (to prevent the user from sending the same message again).

If the user has successfully submitted a message, he/she can follow the status of his/her message on another page (or you can put it on the same page, depends on what you like). I've put it on a second page, and this is what it looks like:

Depending on the status of the message, it can be found under either "Sent", "Received" or "Answered". These three tabs are three web parts with different views.

Let's begin. 

First of all, you will need to make a SharePoint list in which you want to store all the messages.
My list is named "Contact".
I created 7 new columns: "Date", "Name", "Email", "Title", "Message", "Status" and "Answer". The "Title" column was a default column that can't be removed, but I'll just use it instead of making a new column for the subject.

Here's an overview of all the columns your list will need:
ColumnTypeUsed for
DateDate and TimeStoring the date and time of the message
NameSingle line of textStoring the name of the user who sent the message
EmailSingle line of textStoring the e-mail address of the user who sent the message
TitleSingle line of textStoring the subject of the message
MessageMultiple lines of textStoring the message
StatusChoiceSetting the status of the message (Sent/Received/Answered)
AnswerMultiple lines of textStoring the answer of the administrator


You will also need to create views for the Contact list. Each view has a filter applied. The first view is filtered to only show items of which the Status column equals the value "Sent", the second view is filtered to only show items of which the Status column equals the value "Received", and the third view is filtered to only show items of which the Status column equals the value "Answered".
What is also very important, is that each view should have 2 filters. The first is to filter the status of the message, the second filter is to make sure only the person who sent the message can see his own message. The items in these views have to satisfy both requirements that have been set in the filters. Other people shouldn't be able to see the messages of others. To do so, set the second filter of all the views to show only items when the column "Made by" equals "[Me]". By doing so, only the person who sent the message will see his message. You as an administrator will off course always be able to see the messages since you can just make another view for yourself and skip the second filter.

When your list is set up, we can begin on preparing the contact page with a contact form. I made a content editor web part and added the following code to it (edit source of the web part first, then add the code):
<table id="ContactTable"> 
   <tbody>
      <tr> 
         <td class="contactLabel">Subject:</td>
         <td colspan="2">
            <input class="contactInput" id="Subject" name="Subject" 
                   type="text" /> 
         </td>
      </tr>
      <tr> 
         <td class="contactLabel">Message:</td>
         <td colspan="2">
            <textarea class="contactInput contactInputMessage" 
                      id="Message" name="Message"> </textarea> 
         </td>
      </tr>
      <tr>
         <td></td> 
         <td>
            <span id="FeedbackField"> </span> 
         </td>
         <td>
            <div id="ClickMeButton">Send​​​​​​​​​​​​​​</div>
         </td>
      </tr>
   </tbody> 
</table>

Put a script editor web part at the bottom of the Contact page and add the following code to it (I stored my script in the MasterPageGallery folder, but wherever you plan on making your script, just make sure that the reference is correct):
<script type="text/javascript" 
src="~sitecollection/_catalogs/masterpage/MasterPageGallery/Contact.js">
</script>

For the Contact overview page, edit the source and add the following code:
<div id="ContactTabs">
   <div id="tabs1">
      <ul>
         <li><a href="#tabs1-1">Sent</a></li>
         <li><a href="#tabs1-2">Received</a></li>
         <li><a href="#tabs1-3">Answered</a></li>
      </ul>
      <div id="tabs1-1">
        <!-- Space reserved for a content editor web part with a view for
             "Sent" -->
      </div>
      <div id="tabs1-2">
        <!-- Space reserved for a content editor web part with a view for
             "Received" -->
      </div>
      <div id="tabs1-3">
        <!-- Space reserved for a content editor web part with a view for
             "Answered" -->
      </div>
   </div>
</div>

Then put a script editor web part at the bottom of your Contact overview page and include the following code:
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
<script>
  $(function() {
    $( "#tabs1" ).tabs();
  });
</script>

This is the CSS for the above contact form and for the contact overview (best to put this in a CSS file and add a link to that file in your master page):
#ContactTable {
 margin-left: auto;
 margin-right: auto;
}

#ClickMeButton {
 padding: 5px;
 background-color: #d7d6d8;
 border: 1px solid gray;
 width: 78px !important;
 margin-top: 10px;
 font-family: 'Francois One', sans-serif;
 text-transform: uppercase;
 letter-spacing: 0.1em;
 text-align: center;
 float: right;
}

.contactLabel {
 width: 100px;
 display: table-cell;
 vertical-align: top;
 padding-top: 4px;
 text-align: right;
 padding-right: 10px;
}

.contactInput { width: 400px; }

.contactInputMessage {
 max-width: 650px !important;
 min-width: 650px !important;
 min-height: 100px;
 max-height: 200px;
}

#ContactTabs .ms-webpartzone-cell { margin-top: 10px; }

#ContactTabs {
 margin-left: auto;
 margin-right: auto;
 width: 700px;
}

All right! We have now set up the Contact page and the Contact overview page and styled them. Don't forget to add the content editor web parts to the Contact overview page.

Now all we need is functionality!

Create a JavaScript file, I created mine in SharePoint Designer 2013 and it is located in _catalogs/masterpage/MasterPageGallery/Contact.js. You can put it somewhere else if you want to, but this is just where I put it.

Here's the code of my Contact.js file, I added comments to explain to you what everything does. Please keep in mind that I might not have put comments everywhere, but I do my best!
// Load the scripts, make sure you have a script for jQuery as well.
SP.SOD.executeOrDelayUntilScriptLoaded(sharePointReady, 
"SP.UserProfiles.js", 
"~sitecollection/Style Library/Scripts/jquery.SPServices-2013.01.min.js"
);
SP.SOD.executeFunc("SP.js", "SP.ClientContext", sharePointReady);

// run the following function.
sharePointReady();

function sharePointReady() {
  // Initialize a new instance of the ClientContext object.
   var clientContext = new  SP.ClientContext.get_current();   
   this.website = clientContext.get_web();
   this.currentUser = website.get_currentUser();
  
   clientContext.load(currentUser);
   clientContext.load(website);

   // Execute the query, add functions to launch when it succeeds/fails.
   clientContext.executeQueryAsync(
      Function.createDelegate(this, this.onRequestSucceeded), 
      Function.createDelegate(this, this.onRequestFailed)
   );
}

function onRequestSucceeded() {   
   // Fetch the full name of the user that is sending the message.
   var fullUserName = $().SPServices.SPGetCurrentUser({
      fieldName: "Title",
      debug: false
   });

   // Fetch the e-mail address of the user that is sending the message.
   var emailAddress = $().SPServices.SPGetCurrentUser({
      fieldName: "Email",
      debug: false
   });
  
   // Execute the following function.
   SendEnquiry();
 
   function SendEnquiry() {
      document.getElementById('ClickMeButton').onclick = function(){   
         var NameField = fullUserName;
         var emailField = emailAddress;
         var subjectField = document.getElementById("Subject").value;
         var messageField = document.getElementById("Message").value;

         // If one of the input fields or both input fields are empty,
         // then give feedback.
         if( (subjectField == "" && messageField == "")
          || (subjectField == "") || (messageField == "") ){
            document.getElementById("FeedbackField").innerHTML =
               "Please fill in all the fields. ";
            document.getElementById('FeedbackField').style.color = "red";
         } 
         // If both fields are filled in, start the function to save
         // the values as a new list item in the Contact list.
         else {   
           SaveInContact(NameField,emailField,subjectField,messageField);
         }
      };
   }

   function SaveInContact(Name,Email,Subject,Message) {
      // Initialize a new instance of the ClientContext object for the 
      // specified SharePoint site.
      var context = new SP.ClientContext.get_current(); 
      var web = context.get_web();
      // Get the list in which you want to store the values, we use the 
      // Contact list.
      var list = web.get_lists().getByTitle("Contact");  
      var listItemCreationInfo = new SP.ListItemCreationInformation();   
      var newItem = list.addItem(listItemCreationInfo);
      // This below is to set which column will get which value, the 
      // first part is the name of the column and the second part is 
      // the value it will get.
      newItem.set_item("Title", Subject);
      newItem.set_item("Name",Name);
      newItem.set_item("Email",Email);
      newItem.set_item("Message",Message);
      newItem.set_item("Status",'Sent');
      newItem.update();

      context.executeQueryAsync(
         Function.createDelegate(this, this.onAddSucceeded), 
         Function.createDelegate(this, this.onAddFailed)
      );
  
      document.getElementById("FeedbackField").innerHTML = 
         "Thank you! Your message has been sent. ";   
      document.getElementById('FeedbackField').style.color = "green";
      // Disable the input fields and the button to prevent the user 
      // from submitting the same message again.
      document.getElementById("Subject").disabled = true;
      document.getElementById("Message").disabled = true;
      document.getElementById("ClickMeButton").disabled = true;
      document.getElementById("ClickMeButton").style.backgroundColor = 
         "#ECEBEC";
   }
}

function onRequestFailed(sender, args) {   
 console.log("Error: " + args.get_message());
 alert("Request failed: " + args.get_message() + '\n' 
   + args.get_stackTrace());
}

function onAddSucceeded() {
 document.getElementById("ClickMeButton").style.color = "green";
}

function onAddFailed(sender, args) {
 alert("Error: " + args.get_message() + "\n"+ args.get_stackTrace());
}

(function () { 
 document.getElementById("ClickMeButton").onclick = SendEnquiry();
})(); 


That's it! With this code, you should now have a functional contact form. :)

Got questions? Need extra information? Are you stuck at some part? Then do not hesitate to comment, I'll do my best to help you as soon as I can. Also, adding console logs everywhere in the code might help. That way you can see what get executed and what doesn't.


Regards,

Magali

Tuesday, October 22, 2013

Trying to take over the world... One post at a time.

My boyfriend told me I should have a blog. To post about all the little things I do that make me happy. Creative things, crafts. I'm also just going to put some handy SharePoint posts here, since I often have the feeling that some of the things I code might actually come in handy for other people. So yeah. :)

Perhaps a small introduction is in place!

My name is Magali. Yes, I am female.
I'm a junior analyst programmer and I work at the college university of Ghent, where I also graduated as bachelor in Applied Computer Science in 2012.
My job is to make a new kind of intranet site in SharePoint 2013. At first I didn't even know how to work with SharePoint since I only had a few workshops during my education, but I was given the opportunity to learn more about SharePoint and to study it in order to properly do my job. And here I am now, actually enjoying SharePoint. I still like to code in JavaScript though, so I often use JavaScript to make things work.

Other than that, I also have a wide arrange of hobbies. 

I'm often told that I have too many, but I enjoy them all. Variation, yay! Here's what I like to do:
  • play the piano
  • watch series and movies
  • go to the movies
  • play video games (both on computer and consoles)
  • make things with perler beads in 8-bit style
  • sew hand-made felt plushies
  • craft things out of cardboard (mostly miniature furniture)
  • collecting Pokémon games
  • going for a swim
  • dozens of other things 

Also, I'm geeky

Indeed I am! A geeky girl. To me, being geeky means that I'm passionate about something. Not only about computer business and gaming, but also about hobbies like crafting. I do tend to sometimes mix the two (like making 8-bit Space Invaders, you just wait until that project is finished and you will be amazed!).

Anyway, please do enjoy my blog. Expect lots of coding, crafting, geeky creativity and cat pictures!


- Magali


PS: The cake IS a lie. No, seriously, did you see that recipe?