tag:blogger.com,1999:blog-47686157805561502024-03-05T13:57:25.256+01:00Sharing to the Point<i>This was a triumph.<br>
I'm making a note here: HUGE SUCCESS.</i>Unknownnoreply@blogger.comBlogger18125tag:blogger.com,1999:blog-4768615780556150.post-11669276811905009592015-11-27T11:44:00.001+01:002015-11-27T11:44:20.055+01:00C#: make dropdown list auto complete or searchable or filtering using JQuery with ASP.Net Dropdownlist ControlVery interesting post!<br /><br />
<br /><br />
<a href="http://jayeshsorathia.blogspot.be/2013/04/make-dropdown-list-auto-complete-using-javascript-in-asp-net.html">Beginning .net | .Net Tips | C# Tips | MVC | c# .net programming | vb .net programming | SQL Server: Make dropdown list auto complete or searchable or filtering using JQuery with ASP.Net Dropdownlist Control</a>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-4768615780556150.post-33876927170717097972015-04-09T12:17:00.001+02:002015-04-14T16:40:02.395+02:00How to prevent users from deleting other users their attachments from items in SharePoint 2013I have been inactive for quite a long time. It was a busy period at work, and I've been learning to program server-side as well. But recently, I wrote another piece of code in JavaScript, and I'd like to share this with you!<br />
<br />
This script will do the following:<br />
<ul>
<li>When the user edits an existing item, check if the item has attachments;</li>
<li>If the item has one or more attachments, get the author of each attachment;</li>
<li>Check for each attachment if the current user is the author. If the current user is not the author, disable the option to delete the attachment and put the text in grey.</li>
</ul>
<br />
<br />
Why is this useful? Because if you have multiple users working on one list item (in my case, a request form that requires multiple users to add an attachment), they won't accidentally delete each others attachments. <br />
<br />
I added the script to an existing script, named "scripts.js" which is located in my "Style Library" folder. The "scripts.js" file is referenced in my master page, so that it will run on every page.<br />
<br />
<pre class="line-numbers"><code class="language-javascript">if ((document.referrer == "" || document.referrer == null)
&& window.location.pathname.toLowerCase().indexOf("editform.aspx") > -1) {
// These two lines are required, without it my code won't run.
SP.SOD.executeFunc('SP.js', 'SP.ClientContext');
SP.SOD.executeFunc('sp.runtime.js');
var currentItemID;
var attachmentAuthor = [];
var itemArray = [];
$(document).ready(function(){
// Get ID of the current item.
currentItemID = window.location.href.toLowerCase();
currentItemID = currentItemID.substring(currentItemID.toLowerCase().indexOf("?id=") + 4);
// Remove the line below in case the URL of your item is
// not shown as a modal dialog.
currentItemID = currentItemID.substring(0, currentItemID.toLowerCase().indexOf("&isdlg"));
// Save the ID of the current item in the session
// (not necessary, but I prefer it this way)
sessionStorage.setItem("SessionItemCurrentItemID", currentItemID);
// Get attachments of current item.
var url = "/_api/Web/Lists/getByTitle('" + "stbz" + "')/Items(" + currentItemID + ")/AttachmentFiles",
qs = "?$select=ID,Author/Title,*&$expand=Author,AttachmentFiles",
siteUrl = "https://path-to-your-site.com";
$.ajax( {
url : siteUrl + url + qs,
type : 'GET',
headers : {
'accept' : 'application/json;odata=verbose',
'content-type' : 'application/json;odata=verbose'
},
success : successHandler,
fail : failHandler
});
function successHandler(data) {
if (data) {
// If the item has attachments, then run this function.
$.each(data.d.results, function() {
getWebProperties(sessionStorage.getItem("SessionItemCurrentItemID"));
});
}
}
function failHandler(data, errCode, errMessage) {
console.log('Error: ' + errMessage);
}
function getWebProperties(itemID) {
var attachmentFiles;
var ctx = new SP.ClientContext.get_current();
var web = ctx.get_web();
var attachmentFolder = web.getFolderByServerRelativeUrl('Lists/stbz/Attachments/' + itemID);
attachmentFiles = attachmentFolder.get_files();
ctx.load(attachmentFiles);
ctx.executeQueryAsync(function(){
// I can't remember what the $2_1 was again, but anyway...
for (var j = 0; j < attachmentFiles["$2_1"].length; j++) {
var author = attachmentFiles.itemAt(j).get_author();
attachmentAuthor.push([attachmentFiles.itemAt(j).get_name(),author]);
// You'll need to load the author along with the title parameter,
// in order to be able to fetch the name of the author later.
ctx.load(author, 'Title');
ctx.executeQueryAsync(function(){}, function(err) {});
}
// Loop is not necesarrily required, but you will need to set a
// timeout. Comes in handy when you have a lot of attachments.
checkAttachmentsLoop();
}, function(err) {});
}
function checkAttachmentsLoop() {
setTimeout(function(){
if (attachmentAuthor.length) {
checkAttachments();
}
else {
checkAttachmentsLoop();
}
},100);
}
function checkAttachments() {
for (var h = 0; h < attachmentAuthor.length; h++) {
// if you log attachmentAuthor[h][0] to the concole, you'll get
// the name of the attachment.
// if you log attachmentAuthor[h][1].get_title()) to the console,
// you'll get the name of the author.
var currentAuthor = attachmentAuthor[h][1].get_title();
// If the current user is not the author of the current attachment,
// then we disable the ability to delete the attachment from the item.
if (currentAuthor != sessionStorage.getItem("sessionItemUserName")) {
var tr = document.getElementById("idAttachmentsTable").getElementsByTagName("tr");
for (var i = 0; i < tr.length; i++) {
var currentTR = $(tr)[i];
var anchors = $(currentTR).find("a")[0];
var deletes = $(currentTR).find("a")[1];
if (anchors.innerHTML == attachmentAuthor[h][0]) {
$(currentTR).attr("disabled", "disabled");
$(anchors).css("color", "#b1b1b1");
$(anchors).removeAttr("href");
$(anchors).removeAttr("onclick");
$(deletes).css("text-decoration", "line-through");
$(deletes).css("color", "#b1b1b1");
$(deletes).removeAttr("href");
$(deletes).removeAttr("onclick");
}
}
}
}
}
}
}
else {
if (sessionStorage.getItem("SessionItemCurrentItemID") != null) {
sessionStorage.removeItem("SessionItemCurrentItemID");
}
}
</code></pre>
<br />
And there you have it! I know, I didn't format the code that nicely... And I mix up jQuery and JavaScript quite often... But anyway, it is readable.<br />
<br />
In case you would like to see an example of the code in action, here it is:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDcKIYcrGhCHg7kSmqToSlMRFDpXvyPUMRxyeq2qNPqmHjX7wPRU6Quc8ct2pLjirlqdCxamJqs2PpGQ-zdrnLrxWvOiu37CCxgCHevlkvlHrF6zKTa_2aAbtARPvgs2aq8OuDkI8ZtNU/s1600/attachment.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDcKIYcrGhCHg7kSmqToSlMRFDpXvyPUMRxyeq2qNPqmHjX7wPRU6Quc8ct2pLjirlqdCxamJqs2PpGQ-zdrnLrxWvOiu37CCxgCHevlkvlHrF6zKTa_2aAbtARPvgs2aq8OuDkI8ZtNU/s1600/attachment.png" height="33" width="320" /></a></div>
<br />
As you can see, the first attachment is one I added. I am the author of that attachment. And thus, I can also decide whether or not I'm going to delete it. The second attachment however, was not uploaded to the item by me. I am not its author. Therefore, I am not allowed to delete it.<br />
<br />
If you have any questions, feel free to ask!Unknownnoreply@blogger.com0België50.503887 4.469935999999961547.917913 -0.69363800000003817 53.089861 9.633509999999962tag:blogger.com,1999:blog-4768615780556150.post-41008451803211305222014-11-03T10:09:00.001+01:002014-11-03T10:09:48.496+01:00IT'S OVER NINE THOUSAAAAAAAAAAND!!!!<h2>
Actually, it's already over ten thousand by now, but I just couldn't let a title like that slip away! :D</h2>
It has been quite a while since I made a new post. Fear not, I will post something SharePoint-related soon. My work has the priority here, but if I can find some spare time then I will continue writing on the new post. Sneak peek: calendar overlays combined with checkboxes!<br />
<br />
Let's give you guys a short update about my project, to inform you about what it is exactly that I'm so busy with.<br />
Currently, I'm mainly creating and improving workflows using Nintex. Also in general I'm improving some of my scripts and the CSS of my project, along with some extra's to make it more user friendly. Most of my work goes to those workflows, I need to test them over and over again to make sure there is absolutely no error at all, since it is my intention to make them work properly from the start and then never have to change them again (unless there are major changes required). And other than that, I'm now also learing VBA.<br />
<br />
Hopefully I'll be able to finish these workflows soon and when I have some spare time left I'll come back to entertain you all with a new post. Stay tuned!Unknownnoreply@blogger.com0België50.503887 4.469935999999961547.917913 -0.69363800000003817 53.089861 9.633509999999962tag:blogger.com,1999:blog-4768615780556150.post-63532680136584006202014-06-16T12:32:00.000+02:002014-06-16T12:32:10.645+02:00Sorry for my absence!I have been neglecting my blog lately and I just wanted to pop in to tell you all that I'll try to get back more often!
<br><br>
For the past month I have been learning C# and so far I managed to create a WinForm tool that allows me to synchronize the members of groups in the API with groups in the SharePoint environment. So, basically, when I connect to the API and I see that there are new members in a group called "HR employees", but those new members are not yet in the corresponding SharePoint group, then I can just select the new members and synchronize them. They then get added to the corresponding SharePoint group for the HR employees. Same goes for when I need to remove members: if a user no longer is present in the API group (I keep saying group, but I believe it's called a resource) but still is present in the corresponding SharePoint group, then it means I need to remove that user from the SharePoint group. <br><br>
This all works really neat now, I'm even using a background worker and a progress bar! <br><br>
So anyway. I'll be back sometime soon, currently still learning C# and trying to make tools for practice. <br>Until next time!Unknownnoreply@blogger.com0België50.503887 4.469935999999961547.917913 -0.69363800000003817 53.089861 9.633509999999962tag:blogger.com,1999:blog-4768615780556150.post-68003241630559650192014-03-05T14:45:00.002+01:002014-03-05T16:11:53.396+01:00Live notifications from new items in lists/libraries to which current user is subscribed, using JavaScript in SharePoint 2013<div style="text-align: center;">
<span style="color: white;"><span style="font-size: large;"><b>Warning! Long post! </b></span></span></div>
<div style="text-align: center;">
<span style="color: white;"><span style="font-size: large;"><b>Screenshots can be found at the end of the post. Take your time to read everything.</b></span></span><br />
<span style="color: white;"><span style="font-size: large;"><b>---------------- </b></span></span></div>
<br />
So the past six days I've been working on a notification script for our SharePoint intranet.<br />
At first I didn't even know where to start, so I asked a question on <a href="http://sharepoint.stackexchange.com/questions/91585/facebook-like-notifications-when-new-item-is-added-to-certain-list-library" target="_blank">Stack Exchange</a>. After getting some inspiration from the answers I received (thanks you guys!) I decided I should just give it a try.<br />
<br />
I wanted a script that would show a notification in the top right corner of the page whenever a new item was added to a certain list or library.<br />
I also wanted to give users the ability to "subscribe" to a list or library, and hence let them see only notifications whenever a new item was added to a list or library to which they were subscribed, so I also made a script to handle the subscribing. <br />
<br />
These are the two scripts I've written: <b>notification.js</b> and <b>subscribe-to-list-or-library.js</b>.<br />
Both scripts are working in the latest versions of Google Chrome, Mozilla Firefox and Internet Explorer. <br />
<br />
Since I don't want to put too much comments in the code, I just decided to explain both scripts first and then provide you with the code. So please take some time to read how these scripts work and what steps they contain, and then take a look at the code. It will be a lot easier to understand the code when you read the explanation about it first.<br />
<br />
<h3>
Script: subscribe-to-list-or-library.js</h3>
This script has the following features:<br />
<ul>
<li>subscribe to a list or library by clicking a button named "subscribe"</li>
<li>unsubscribe from a list or library by clicking a button named "unsubscribe"</li>
<li>be subscribed to multiple lists or libraries</li>
<li>receive live notifications whenever a new item was added to one or more lists or libraries to which a user is subscribed </li>
</ul>
Do note that it's possible to subscribe to a list or library from any sub site. So for example, you can subscribe to list X on sub site A, and library Y on sub site B, and receive notifications from list X and library Y while browsing content on sub site C. I tried explaining this as easy as I could, so there you have it.<br />
Also, regarding the buttons: there is no such thing as a button to subscribe and another button to unsubscribe. They are one and the same button. Actually it's not even a button. It's a div element disguised as a button. I just change the text inside the div based on whether or not a user is subscribed. If a user is, then the text in the div will be "unsubscribe". If a user is not, then it will be "subscribe". Simple as that.<br />
<br />
<h4>
Steps on how this script works</h4>
First of all, you need to provide a button (a div, actually, but I'll call it a button) on a page that has a web part of a list or library. The button contains two other elements, one div that will hold either the text "subscribe" or "unsubscribe", and one hidden div that will hold the name of the list or library a user should be able to subscribe to. This name has to be correct and is case sensitive. <br />
And now for the steps. Keep in mind that part of the script runs as soon as the page has loaded, and certain functions only run on the click of a button.<br />
<br />
On page load:<br />
<ul>
<li>Find all elements that have a class named "subscrButton" (these are the subscribe buttons), and for each element, store the name of the list/library in a variable, as well as the text from the first div (which contains either "subscribe" or "unsubsribe").</li>
<li>Still in the "for each" statement: get all list items from a list named "Subscribed users", and for each row in that list, check if the current user is in that row and if he/she is subscribed to a list/library with the same name as the one stored in a variable.</li>
<ul>
<li>If true, set a boolean named "inList" to true. </li>
<li>If false, set a boolean named "inList" to false.</li>
</ul>
<li>If the boolean named "inList" is false, then set the innerHTML of the first div to "subscribe".</li>
<li>If the boolean named "inList" is true, then set the innerHTML of the first div to "unsubscribe".</li>
</ul>
So that part of the code is only to check whether or not a user is already subscribed to a list or library, and set the correct text to the button.Next comes the actual subscribe/unsubscribe functionality. <br />
<br />
On button click:<br />
<ul>
<li>Store the name of the list or library linked to that button, in a variable.</li>
<li>Store the name of the page on which the user clicked the button, in a variable.</li>
<li>Store the path to the sub site in which the page with the button that the user clicked on is located, in a variable. </li>
<li>Store the text of the button ("subscribe" or "unsubscribe") in a variable.</li>
<li>If the text of that variable equals "unsubscribe":</li>
<ul>
<li>Set the innerHTML of the button that was clicked to "subscribe".</li>
<li>Check the "Subscribed users" list and look for the row that contains both the name of the user and the name of the list/library from which the users wants to unsubscribe.</li>
<ul>
<li>If the row has been found, set another variable named "listIDDel" to the ID of the current row that matches the name of the user and the name of the list/library.</li>
</ul>
<li> Update the "Subscribed users" list by deleting the row that has the id matching "listIDDel" (and thus, removing the user from the "Subscribed users" list).</li>
<li>After the user has successfully been deleted from the "Subscribed users" list, run some functions named "runInOtherFile" and "runDeleteInOtherFile" which are both located in notification.js. I'll explain the use of these later.</li>
</ul>
<li>If the text of that variable equals "subscribe":</li>
<ul>
<li>Set the innerHTML of the button that was clicked to "unsubscribe".</li>
<li>Update the "Subscribed users" list by making a new item with the following values: the name of the user, name of the list/library, url of the page, path to the sub site.</li>
<li>After the user has successfully been added to the "Subscribed users" list, run a function named "runInOtherFile" which is located in notification.js. I'll explain the use of this one later. </li>
</ul>
</ul>
<br />
<h3>
Script: notification.js</h3>
This script has the following features:<br />
<ul>
<li>Send a notification whenever a new item was added to a list or library to which a user is subscribed</li>
</ul>
<br />
A minor down side: the script does not keep track of which items were added to certain lists or library since the last time a user logged on. So it's not possible to store notifications somewhere about new items and greet a user with a list of all the new items that were added since the last time he/she logged in. This script is for live notifications, nothing else.<br />
<br />
It also won't show a globe with a number on top of it saying "you have x new notifications" like on Facebook, I'm not keeping track of that. While I might be able to add such functionality to this script, this will be for another time.<br />
<br />
<h4>
Steps on how this script works</h4>
The script will contain two types of code. One that will run only on page load, and one that will run every five seconds.<br />
<br />
On page load:<br />
<ul>
<li>Check the "Subscribed users" list and look for rows that contain
both the name of the user and the name of any list/library to which a user is subscribed.</li>
<ul>
<li>For each row in that list: check if the user is in the row. If he/she is, raise the value of a counter by 1 and push the current row to an array. We then push the content of that array to another array, which will act as a container array (multidimensional array, actually). </li>
</ul>
<li>If the counter equals 0, meaning no rows matching the current user were found, nothing will happen and the notification script will stop.</li>
<li>If the counter is not 0, meaning one or more occurences of the current user were found in the "Subscribed users" list, we will do a loop. For each item in the multidimensional array:</li>
<ul>
<li>Store a unique session in the session.</li>
<li>Set an interval of five seconds for a function named "repeatEvery5Seconds".</li>
</ul>
</ul>
Every five seconds:<br />
<ul>
<li>The function "repeatEvery5Seconds" will run for each item in the multidimensional array, storing the following values in variables:
the name of the user, name of the list/library, url of the page, path to
the sub site, name of the session item. </li>
<li>The function "repeatEvery5Seconds" will trigger another
function named "fetchCurrentListStatus". </li>
<ul>
<li>The function "fetchCurrentListStatus" will check each list or library to which a user is subscribed to, and push each row to an array. </li>
<ul>
<li>If the length of the array is null or 0, the list will be considered empty and an empty session variable will be set. </li>
<li>If the length of the array is not null or not 0:</li>
<ul>
<li>If there is a session item present for the current list and if that session item is not empty, then reset that session item to a new value matching the array and run a function named "itemChange".</li>
<li>Else if there is a session item present for the current list and if that session item is null or 0, then reset the session item to a new value matching the array and run a function named "itemChange". </li>
<li>If the length of the array matches the length of the session item, then that indicates no changes were made.</li>
</ul>
</ul>
<li>The function "itemChange" will run when called.</li>
<ul>
<li>If the length of the array is smaller than the length of the session item, then this means that an item was deleted from the list/library. The session item will then be updated, and will contain the same value as the array.</li>
<li>Else if the length of the array is larger than the length of the session item, then this means that a new item was added to the list/library. </li>
<ul>
<li>We will then compare all elements in the array with all elements in the session item, and if we find the one that is not present in the session storage, we will search for that item in the list/library and fetch its name. </li>
<ul>
<li>We then create a div that will be our notification, and we will give it a text containing the name of the new item and a link to the page on where the user can find it. After five seconds, the notification will be removed.</li>
</ul>
<li>We then check if the session item matches the array. If not, set the session item to match the value of the array. </li>
</ul>
</ul>
</ul>
</ul>
Only when called by "subscribe-to-list-or-library.js": <br />
<ul>
<li>The function "runInOtherFile":</li>
<ul>
<li>Will run with a timeout of one second, and will replace the array that was made on page load and that holds the list of all the lists/libraries to which a user is subscribed. Since the user decided to unsubscribe from a list/library, this array needs to be updated. This function will do so be checking the "Subscribed users" list again. </li>
</ul>
<li>Yhe function "runDeleteInOtherFile":</li>
<ul>
<li>For each item (array) in the multidimensional array, check if there is an item that matches the name of the list from which the user wanted to subscribe. If there is, then find the corresponding session item and set its value to nothing. And then we will remove the item from the multidimensional array (by using the split function).</li>
</ul>
</ul>
<br />
There you have it. That was just the explanation about the scripts. I tried to write it as short as possible. Nest step: setting up the necessary lists and buttons.<br />
<br />
<h3>
Prerequisites </h3>
Before you can actually use the scripts, you'll need to set up some things first. Like a list where you will store your subscribed users, and buttons for each list/library you want to have your users subscribe to.<br />
<br />
<h4>
Create a list for your subscribers</h4>
You need to make a simple list at the top site level named "<b>Subscribed users</b>". If you want my code to work straight away, then I suggest you use that name.<br />
<br />
Your will then need to make three new columns for your list: <b>ListLibID</b>, <b>PageURL </b>and <b>SubsiteURL</b>. I originally used to store the GUID of a list/library in the ListLibID column, but after several problems with that in different browsers I decided to just use the name of a list or library instead of the GUID. That was also the main reason on why I also needed a column to save the subsite in (required to call the list across sub sites).<br />
<br />
Make sure your list named "Subscribed users" is allowed to be edited by everyone, meaning everyone should have edit permissions. You can change the settings of the view of the list so that it is only visible to you or not visible at all (use the filter for this, set some impossible filter so no item will ever be shown but will still be present in the list).That way you can avoid users sneaking around and trying to meddle with the list. I just used CSS to hide that particular list and the list itself from the site content, so nobody ever finds it.<br />
<br />
<h4>
Create buttons for your lists/libraries</h4>
Now that you have your list ready, it is time to make some buttons. You'll only need to make these for list or libraries of which you want users to be able to subscribe to.<br />
<br />
On the page that holds the web part of the list/library of which users should be able to subscribe to, place the following code directly above the code from the web part:<br />
<pre class="line-numbers"><code class="language-javascript"><div class="buttonContainer">
<div class="subscrButton">
<div class="subscrButtonTitle"></div>
<div class="innerSubscr" style="display: none;">
List or library name here
</div>
</div>
</div></code></pre>
<br />
In case you want it, here's the CSS of the button above (note: not identical to the style as seen in screenshots!):
<br />
<pre class="line-numbers"><code class="language-css">.buttonContainer {
position: relative;
}
.subscrButton {
background-color: white;
border: 1px solid rgb(185, 184, 184);
font-family: "Segoe UI Semilight","Segoe UI","Segoe", Tahoma,
Helvetica,Arial,sans-serif;
color: #0066CC;
text-align: center;
z-index: 100;
position: absolute;
right: 0;
padding: 3px 10px 5px 10px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
opacity: 0.8;
}</code></pre>
<h4>
Add references to the scripts</h4>
I suggest you just put a reference to the scripts in your master page. For example:<br />
<pre class="line-numbers"><code class="language-javascript"><!--SPM:<sharepoint:scriptlink id="scriptLink12" language="javascript"
localizable="false" ondemand="false" runat="server"
name="~sitecollection/Style Library/Scripts/subscribe-to-list-or-library.js">
-->
<!--SPM:<sharepoint:scriptlink id="scriptLink13" language="javascript"
localizable="false" ondemand="false" runat="server"
name="~sitecollection/Style Library/Scripts/notification.js">
--></code></pre>
<br />
Make sure your master page and your scripts are checked in.<br />
<br />
Now, for the most interesting part,<b> the actual code</b>.<br />
<br />
<h3>
The code</h3>
<h4>
subscribe-to-list-or-library.js</h4>
<pre class="line-numbers"><code class="language-javascript">SP.SOD.executeOrDelayUntilScriptLoaded( 'SP.UserProfiles.js',
"~sitecollection/Style%20Library/Scripts/jquery.SPServices-2013.01.min.js");
SP.SOD.executeFunc('SP.js', 'SP.ClientContext');
var firstDiv;
var inList = new Boolean();
inList = false;
var listIDDel;
var SURL = $().SPServices.SPGetCurrentSite();
var PURL = window.location.pathname;
var LLID;
var USER = $().SPServices.SPGetCurrentUser({
webURL: "",
fieldName: "Title",
fieldNames: {},
debug: false
});
$("#content").find($("div.subscrButton")).each(function(){
LLID = this.childNodes[1].innerHTML.replace(/[\u200B]/g, '');
firstDiv = this.childNodes[0].innerHTML.replace(/[\u200B]/g, '');
$().SPServices({
operation: "GetListItems",
async: false,
webURL: 'https://your-site-here.com/',
listName: 'Subscribed users',
completefunc: function (xData, Status) {
$(xData.responseXML).find("z\\:row, row").each(function() {
if (($(this).attr("ows_Title") == USER)
&& ($(this).attr("ows_ListLibID") == LLID)) {
inList = true;
}
});
}
});
if (inList == false) {
this.childNodes[0].innerHTML = 'SUBSCRIBE';
}
else if (inList == true) {
this.childNodes[0].innerHTML = 'UNSUBSCRIBE';
inList = false;
}
});
$("div.subscrButton").click(function(){
LLID = this.childNodes[1].innerHTML.replace(/[\u200B]/g, '');
firstDiv = this.childNodes[0].innerHTML.replace(/[\u200B]/g, '');
console.log(firstDiv);
if (firstDiv == "UNSUBSCRIBE") {
this.childNodes[0].innerHTML = 'SUBSCRIBE';
console.log('User "' + USER + '" wants to unsubscribe. ');
$().SPServices({
operation: "GetListItems",
async: false,
webURL: 'https://your-site-here.com/',
listName: 'Subscribed users',
completefunc: function (xData, Status) {
$(xData.responseXML).find("z\\:row, row").each(function() {
if (($(this).attr("ows_Title") == USER)
&& ($(this).attr("ows_ListLibID") == LLID)) {
listIDDel = $(this).attr("ows_ID");
}
});
}
});
$().SPServices({
operation: 'UpdateListItems',
webURL: 'https://your-site-here.com/',
listName: 'Subscribed users',
updates: '<batch onerror="Continue" precalc="True">' +
'<method cmd="Delete" id="1">' +
'<field name="ID">'+ listIDDel +'</field>' +
'<field name="Title">'+ USER +'</field>' +
'<field name="ListLibID">'+ LLID +'</field>' +
'<field name="PageURL">'+ PURL +'</field>' +
'<field name="SubsiteURL">'+ SURL +'</field>' +
'</method>' +
'</Batch>',
completefunc: function(xData, Status) {
console.log('User "'+USER+'" is now removed from the subscribers list.');
}
});
runInOtherFile();
runDeleteInOtherFile(LLID);
}
else if (firstDiv == "SUBSCRIBE") {
this.childNodes[0].innerHTML = 'UNSUBSCRIBE';
console.log('User "'+USER+'" wants to subscribe. ' +
'Adding user to subscribers list now.');
$().SPServices({
operation: 'UpdateListItems',
webURL: 'https://your-site-here.com/',
listName: 'Subscribed users',
updates: '<batch onerror="Continue" precalc="True">' +
'<method cmd="New" id="1">' +
'<field name="Title">'+ USER +'</field>' +
'<field name="ListLibID">'+ LLID +'</field>' +
'<field name="PageURL">'+ PURL +'</field>' +
'<field name="SubsiteURL">'+ SURL +'</field>' +
'</method>' +
'</batch>',
completefunc: function(xData, Status) {
console.log('User "'+USER+'" has been added to the subscribers list.');
}
});
runInOtherFile();
}
});</code></pre></br>
<h4>
notification.js</h4>
<pre class="line-numbers"><code class="language-javascript">var USER = $().SPServices.SPGetCurrentUser({
webURL: "",
fieldName: "Title",
fieldNames: {},
debug: false
});
var currentViewUser;
var currentViewLLIB;
var currentViewPURL;
var currentViewSURL;
var currentViewCOUNT;
var counter = 0;
var tempArr;
var itemArray = new Array();
var itemArrayContainer = new Array();
var itemArrayReplaced = new Array();
var itemArrayContainerReplaced = new Array();
console.log('Notification script has been loaded! Starting script now...'
+ '\n--------------------------------------------------------');
runOnLoad();
function runOnLoad() {
$().SPServices({
operation: "GetListItems",
async: false,
webURL: 'https://your-site-here.com/',
listName: 'Subscribed users',
completefunc: function (xData, Status) {
$(xData.responseXML).SPFilterNode("z:row").each(function() {
if ($(this).attr("ows_Title") == USER) {
counter++;
itemArray[counter] = new Array($(this).attr("ows_Title"),
unescape($(this).attr("ows_ListLibID")),
$(this).attr("ows_PageURL"),
$(this).attr("ows_SubsiteURL"), counter);
itemArrayContainer.push(itemArray[counter]);
}
});
}
});
}
if (counter != 0) {
for (var j = 0; j < itemArrayContainer.length; j++) {
sessionItem = "sessionItem" + (j + 1);
sessionStorage.setItem(sessionItem, "");
}
function repeatEvery5Seconds() {
for (var i = 0; i < itemArrayContainer.length; i++) {
currentViewUser = itemArrayContainer[i][0];
currentViewLLIB = itemArrayContainer[i][1];
currentViewPURL = itemArrayContainer[i][2];
currentViewSURL = itemArrayContainer[i][3];
currentViewCOUNT = "sessionItem" + itemArrayContainer[i][4];
fetchCurrentListStatus(currentViewLLIB, currentViewCOUNT);
}
}
var t = setInterval(repeatEvery5Seconds,5000);
}
else {
console.log('User "'+currentViewUser+'" is not in the subscribers list. '
+ 'Notifications will not be shown.');
}
function fetchCurrentListStatus(currentViewLLIB, currentViewCOUNT) {
var listItemArray = new Array();
var listItemArrayBackup = new Array();
$().SPServices({
operation: "GetListItems",
async: false,
crossDomain: true,
webURL: currentViewSURL,
listName: currentViewLLIB,
completefunc: function (xData, Status) {
$(xData.responseXML).find("z\\:row, row").each(function() {
var rowData = $(this).attr("ows_ID");
listItemArray.push(rowData);
listItemArrayBackup.push(rowData);
});
}
});
if (listItemArray.length == null || listItemArray.length == 0) {
console.log('List is empty.');
sessionStorage.setItem(currentViewCOUNT, listItemArray);
}
else if (listItemArray.length != null || listItemArray.length != 0) {
console.log('Server list: \t\t"' + listItemArray + '"');
if ( sessionStorage.getItem(currentViewCOUNT).length != 0
|| sessionStorage.getItem(currentViewCOUNT) != 0
|| sessionStorage.getItem(currentViewCOUNT) != "" ) {
tempArr = sessionStorage.getItem(currentViewCOUNT).split(",");
console.log('Session list: \t\t"' + tempArr + '"');
itemChange();
}
else if (sessionStorage.getItem(currentViewCOUNT).length == 0
|| sessionStorage.getItem(currentViewCOUNT) == 0
|| sessionStorage.getItem(currentViewCOUNT) == "" ) {
sessionStorage.setItem(currentViewCOUNT, listItemArray);
tempArr = sessionStorage.getItem(currentViewCOUNT).split(",");
console.log('Session list: \t\t"' + tempArr + '"');
itemChange();
}
if (tempArr.length == listItemArray.length) {
console.log('No new item was added in the past 5 seconds.'
+ '\n--------------------------------------------------------');
}
}
function itemChange() {
if (listItemArray.length == tempArr.length) {}
else if (listItemArray.length < tempArr.length) {
console.log('An item was deleted. Now updating session storage.');
sessionStorage.setItem(currentViewCOUNT, listItemArray);
}
else if (listItemArray.length > tempArr.length) {
console.log('An item has been added. Now updating session storage.');
var array1 = listItemArray;
var array2 = tempArr;
var index;
for (var i=0; i<array2 .length="" i="" if="" index=""> -1) {
array1.splice(index, 1);
}
}
var currentItemName;
var newListItemArray = new Array();
for (var i = 0; i < array1.length; i++) {
var currentItem = array1[i];
$().SPServices({
operation: "GetListItems",
ID: currentItem,
async: false,
crossDomain: true,
webURL: currentViewSURL,
listName: currentViewLLIB,
completefunc: function (xData, Status) {
$(xData.responseXML).SPFilterNode("z:row").each(function() {
if ($(this).attr("ows_ID") == currentItem) {
currentItemName = $(this).attr("ows_LinkFilename");
}
newListItemArray.push(currentItemName);
});
}
});
}
var div = document.createElement("div");
div.style.width = "auto";
div.style.height = "21px";
div.style.background = "#F7F7F7";
div.style.color = "#3C82C7";
div.style.border = "1px solid #d7d6d8";
div.style.float = "right";
div.style.padding = "5px";
div.style.borderBottomLeftRadius = "4px";
div.innerHTML = 'A document named "' + currentItemName
+ '" was added to <a href="' + currentViewPURL + '">'
+ currentViewLLIB + '</a>.';
div.className = "notification";
document.getElementById("notificationArea").appendChild(div);
$(document.getElementsByClassName("notification")).hide();
$(document.getElementsByClassName("notification")).fadeIn(1500);
setTimeout(function() {
$(document.getElementsByClassName("notification")).fadeOut(1500);
document.getElementById("notificationArea").removeChild(div);
}, 5000);
if (tempArr != listItemArray) {
sessionStorage.setItem(currentViewCOUNT, listItemArrayBackup);
}
}
}
}
function runInOtherFile() {
console.log('Now running "runInOtherFile()". ');
counter = 0;
setTimeout(function() {
itemArrayContainerReplaced.length = 0;
$().SPServices({
operation: "GetListItems",
async: false,
webURL: 'https://your-site-here.com/',
listName: 'Subscribed users',
completefunc: function (xData, Status) {
$(xData.responseXML).SPFilterNode("z:row").each(function() {
if ($(this).attr("ows_Title") == USER) {
counter++;
itemArrayReplaced[counter] = new Array($(this).attr("ows_Title"),
unescape($(this).attr("ows_ListLibID")),
$(this).attr("ows_PageURL"),
$(this).attr("ows_SubsiteURL"), counter);
itemArrayContainerReplaced.push(itemArrayReplaced[counter]);
}
});
}
});
console.log('List of subscribed users has been updated.'
+ '\n--------------------------------------------------------');
itemArrayContainer = itemArrayContainerReplaced;
}, 1000);
}
function runDeleteInOtherFile() {
for (var k = 0; k < itemArrayContainer.length; k++) {
if (itemArrayContainer[k][1] == LLID) {
var tempCurrentViewCOUNT = "sessionItem" + itemArrayContainer[k][4];
sessionStorage.setItem(tempCurrentViewCOUNT, ""); // 0);
itemArrayContainer.splice(k, 1);
}
}
console.log('Session corresponding to the list/library from which user '
+'has unsubscribed is now empty.');
}</code></pre>
<br />
That's all the code. All of it. Everything you need to get fancy notifications. You can always change the style of the notification, it may be in the script but you can just personalize it in whatever way you want to.<br />
<br />
<h3>
Some screenshots</h3>
Console - when subscribed to two lists/libraries, first five seconds:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAQv-_sgLYN4sHlvPxEIVz2RBCFP4LBmxS-JRlru5GalH4lo7B_yg880SN5r51Z8_LftRIAQVcSEFiFyJz-K7MpiKEvK6xgSyHRzKmi9TPQqITb4XZRnrTLqVBj983a1eX_1TErYW5k-M/s1600/screen1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAQv-_sgLYN4sHlvPxEIVz2RBCFP4LBmxS-JRlru5GalH4lo7B_yg880SN5r51Z8_LftRIAQVcSEFiFyJz-K7MpiKEvK6xgSyHRzKmi9TPQqITb4XZRnrTLqVBj983a1eX_1TErYW5k-M/s1600/screen1.png" height="66" width="320" /></a></div>
<br />
Console - when subscribed to two lists/libraries, after more than fifteen seconds:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvgrQ4xhX9E7KVv0VBvNKR-NqpR9uj33c5bfnAc6epZ-WBpT8qaGDCLhMzc3RPCqpgSJ5aXrrvvoLBfKZItjLFaPA1gVikLPHp-Y_DrPZpAdMOhGFYnEyVtO5QVS8YUUPjVVRvAIho6uI/s1600/screen2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvgrQ4xhX9E7KVv0VBvNKR-NqpR9uj33c5bfnAc6epZ-WBpT8qaGDCLhMzc3RPCqpgSJ5aXrrvvoLBfKZItjLFaPA1gVikLPHp-Y_DrPZpAdMOhGFYnEyVtO5QVS8YUUPjVVRvAIho6uI/s1600/screen2.png" height="142" width="320" /></a></div>
<br />
Console - when a new item has been added to a list/library to which the current user is subscribed to:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsRE7vRTUcRMMyfzkU59DLOgUaSxNBi9wDW5HxGsH8FpAG4H5TciTn72RAEFiIIBurxQIBjMubwIMQgYnwLuITl5WxJsILuED2JT3gYcDqYHmf_eqIN7af00CdJK0nming8YVhCJgRc0g/s1600/screen3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsRE7vRTUcRMMyfzkU59DLOgUaSxNBi9wDW5HxGsH8FpAG4H5TciTn72RAEFiIIBurxQIBjMubwIMQgYnwLuITl5WxJsILuED2JT3gYcDqYHmf_eqIN7af00CdJK0nming8YVhCJgRc0g/s1600/screen3.png" height="105" width="320" /></a></div>
<br />
Browser - notification the current user sees when a new item was added to a list/library to which the current user is subscribed to (<i>translation: "A document named 'mouse-pointer.jpg' was added to TESTER."</i>):<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4x7A0yzwrtwOpK7EyOI_YCffJSe1EPC2QCPvKhz-RnRA6NM10acNaCxtCR-lfVUPYWC-_VucZ0x75ILXGJL8Up_21fYYbIQbmfhBBggh20eV9yRghDYzBHwdEjE6D1eXThf1n5GwN4gA/s1600/screen4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4x7A0yzwrtwOpK7EyOI_YCffJSe1EPC2QCPvKhz-RnRA6NM10acNaCxtCR-lfVUPYWC-_VucZ0x75ILXGJL8Up_21fYYbIQbmfhBBggh20eV9yRghDYzBHwdEjE6D1eXThf1n5GwN4gA/s1600/screen4.png" height="79" width="320" /></a></div>
<br />
Console - when an item gets deleted from a list/library to which the current user is subscribed to:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRjT3xFfmE3JlSPPbaGqEkAHqrhCMSxaLetFxOXLLQQtaF3DXu4vkiHHgtsnx8slXC4wQJUOvTo7iVaLRPaAGMlHfTiRkG7_Dq46qy3J-tN1cojOC2mX2ZDzp4iKhZ4PWci5MyLwGXgnw/s1600/screen5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRjT3xFfmE3JlSPPbaGqEkAHqrhCMSxaLetFxOXLLQQtaF3DXu4vkiHHgtsnx8slXC4wQJUOvTo7iVaLRPaAGMlHfTiRkG7_Dq46qy3J-tN1cojOC2mX2ZDzp4iKhZ4PWci5MyLwGXgnw/s1600/screen5.png" height="83" width="320" /></a></div>
<br />
Console - when the current user unsubscribes from a list/library (it's the one with the long array of ID's):<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCd6wcaH9zZ9gvud4PYkV_ZTY2dRJ9MfAwSN-kY8m1CAZw_v-ju-X-5K5JnhrcI0lEe0WWPOLH4xhhmj1Br6aeQeE4imsVBS9B6kxxXRwENLiEJBnD3kZ5Eks4EFN37-P-kSOxmY_nRko/s1600/screen6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCd6wcaH9zZ9gvud4PYkV_ZTY2dRJ9MfAwSN-kY8m1CAZw_v-ju-X-5K5JnhrcI0lEe0WWPOLH4xhhmj1Br6aeQeE4imsVBS9B6kxxXRwENLiEJBnD3kZ5Eks4EFN37-P-kSOxmY_nRko/s1600/screen6.png" height="113" width="320" /></a></div>
<br />
Console - when the current user subscribes to a list/library (again, the one with the long array of ID's):<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFYtwnKBIAyGbLZwcyIhTAMynpiyFF_aAmPwmXdGxZebz2a0xqdDUYWFqeSssz14xJUzzk7SxddrBUYoG7i17SNNSKn0bFc6sc96R4AZNdUO7lyupnKizVD9V-iQY63eQREQCtyZJXWwI/s1600/screen7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFYtwnKBIAyGbLZwcyIhTAMynpiyFF_aAmPwmXdGxZebz2a0xqdDUYWFqeSssz14xJUzzk7SxddrBUYoG7i17SNNSKn0bFc6sc96R4AZNdUO7lyupnKizVD9V-iQY63eQREQCtyZJXWwI/s1600/screen7.png" height="108" width="320" /></a></div>
<br />
Browser - the buttons; there is only one button per list but I used an image editor to place these next to each other, just to demonstrate:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQs9JxL6LJtm8XVc_VHXWyMOZSMfuGUoHaFwk7CmL2H9tWggVme3BiuL5P545J8mtKTxKcqdYAC83gz0LaTayrUFYwHEl1ohZXc7JPOrqZ5CBG2gIuvDHGTQz5IsJy8pygVwf_fy9B-9c/s1600/screen8.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQs9JxL6LJtm8XVc_VHXWyMOZSMfuGUoHaFwk7CmL2H9tWggVme3BiuL5P545J8mtKTxKcqdYAC83gz0LaTayrUFYwHEl1ohZXc7JPOrqZ5CBG2gIuvDHGTQz5IsJy8pygVwf_fy9B-9c/s1600/screen8.png" /></a></div>
<br />
<h3>
The end</h3>
That was it, the whole blog post. My longest one so far I believe. I hope I explained things clear enough, I did my best to tell you every bit of information I have about these scripts. I hope that one day I'll be able to rewrite this functionality in C#, to make it server-side. But until that day, I'm happy with this and I hope you are happy with it too. :)<br />
<br />
Do let me know if you are having trouble or problems with one of the scripts and I'll do my best to help you out. <br />
<br />
As an extra tip, I suggest you minify the scripts and get rid of all console logs. Just to make it less<br />
heavy.<br />
<br />
Enjoy! Unknownnoreply@blogger.com0België50.503887 4.469935999999961547.917913 -0.69363800000003817 53.089861 9.633509999999962tag:blogger.com,1999:blog-4768615780556150.post-63139094330994415812014-02-12T15:26:00.002+01:002015-04-09T12:17:17.011+02:00How to have a discussion board web part with modal dialogs and auto-refreshing content in SharePoint 2013Imagine you added a discussion board as a web part to a page. Whenever you want to add a new discussion, by default it will always open in a new page rather than in a modal dialog (even if you ticked the "Display forms in a modal dialog" option under "Advanced settings" of the discussion board). As a matter of fact, existing discussions also open in new pages.<br />
<br />
For my end users, this is annoying since they will lose track of the page they were originally on. I made it so that they will always know on what page they are, by highlighting the link of the current page in the term-driven subsite navigation. And when they want to create a new discussion or want to look at an existing one, that term-driven subsite navigation can't tell you the path of the "AllItems.aspx" page your user has ended on.<br />
<br />
So! In order to stick to the page that has the discussion board web part, I had to write a small script.<br />
<br />
<h3>
How do we create a new discussion or open an existing discussion in a dialog rather than on a new page?</h3>
You'll need two scripts for this: one that runs only on the page that has the discussion board web part, and one that is referenced on the master page (so that it will always run, on any page, regardless whether it has a discussion board or not).<br />
<br />
I named the first script "discussion-board.js" and gave it the following code. Comments have been added to the code to further explain it.<br />
<pre class="line-numbers"><code class="language-javascript">// When called, this function opens the dialog.
function openDialog(pUrl) {
var options = {
url : pUrl,
dialogReturnValueCallback: OnDialogClose
};
SP.SOD.execute('sp.ui.dialog.js', 'SP.UI.ModalDialog.showModalDialog',
options);
}
// When the user closes the dialog by either pressing OK when adding a new
// item, by clicking the cancel button or by closing it with the X on the
// top right corner, this function will run.
function OnDialogClose(dialogResult, returnValue) {
// The line below will refresh the content of the web part, by acting as
// if the refresh button was clicked.
$('.ms-comm-refreshIcon').trigger('click');
// The line below will run the clickMe() function, I also added a timeout
// because I noticed that it sometimes doesn't run properly. This timeout
// should make sure that the function will always run.
clickMe();
setTimeout(function() {
clickMe();
}, 500);
}
// When called, this function makes sure that a new discussion or an existing
// discussion is opened in a modal dialog instead of on a new page.
function clickMe() {
// The lines below replaces the value of the default onclick and href
// attributes so that it won't open on a new page, but in a dialog.
$('a[href*="NewForm.aspx"]').each(function() {
$(this).attr('onclick', 'openDialog("' + $(this).attr('href') + '")');
$(this).attr('href','javascript:void(0)');
});
// Same for the following lines, when the user is on the page that
// contains the discussion board web part, we want the existing
// discussions to be displayed in a dialog as well rather than on a new
// page.
$('a[href*="Forum.aspx"]').each(function() {
$(this).attr('onclick', 'openDialog("' + $(this).attr('href') + '")');
$(this).attr('href','javascript:void(0)');
});
}
// When the window first loads, we want to be sure that the discussions are
// already set to open in a dialog. So we will run the clickMe() function on
// load, and set the timeout again just to be sure (in my case, it doesn't
// work without the timeout).
window.onload = function () {
clickMe();
setTimeout(function() {
clickMe();
}, 500);
};
// The lines below are needed as well, because when a user changes the view
// of the web part (to for example "Recent" or "My discussions"), then the
// values of the attributes href and onclick get back their original value.
// We don't want a user to see the discussions on a new page, so we once
// again set it so that the clickMe() function runs as soon as the user
// clicks anywhere inside the content div (which in my case, has "content"
// for its ID.
$("#content").click(function() {
clickMe();
setTimeout(function() {
clickMe();
}, 500);
});</code></pre>
<br />
So that's the first script that you'll need. Include this script to the page that contains the discussion board web part. Do so by adding a script editor web part at the bottom of the page and add the following code tot it:<br />
<pre class="line-numbers"><code class="language-javascript"><script src="/Style%20Library/Scripts/discussion-board.js"
type="text/javascript"></script></code></pre>
<br />
Please do note that your path may be different, so change it if necessary.
<br />
<br />
And now for the script that has to have a reference on the master page. I already have a file called "scripts.js" which runs on every page, so I just added the following code to that script:<br />
<pre class="line-numbers"><code class="language-javascript">var ref = document.referrer;
var url = window.location.pathname;
$(document).ready(function(){
// If the current url ends with "AllItems.aspx" and the previous url
// contained "IsDlg=", stating that it was a dialog, then run the
// following code.
if ( (url.indexOf('AllItems.aspx') > -1 && ref.indexOf('IsDlg=') > -1) ) {
// The line below will close the dialog.
SP.UI.ModalDialog.commonModalDialogClose(SP.UI.DialogResult.Cancel);
}
else {
// Do nothing.
}
});</code></pre>
<br />
The reason why we have to state that the dialog has be closed, is because when you delete a discussion through a modal dialog, it will show you the AllItems.aspx page <b>inside the dialog</b> instead of returning to the original page (in my case, Forum.aspx). So to fix this, I check if the dialog came from a page that had "IsDlg=" in the url, and if the url of the dialog is currently "AllItems.aspx" then that must mean that a discussion was deleted. So then we can close the dialog. <br />
<br />
Check if your second script is added to your master page. I use a HTML master page so my reference looks like this: <br />
<pre class="line-numbers"><code class="language-javascript"><!--SPM:<SharePoint:ScriptLink language="javascript" OnDemand="false"
name="~sitecollection/Style Library/Scripts/scripts.js"
ID="scriptLink4" runat="server" Localizable="false"/>--></code></pre>
<br />
Again, do note that your path may be different, so change it where necessary.
<br />
<br />
If you did everything as I explained it here, you will now have a discussion board web app on a page that will:<br />
<ul>
<li>open all existing discussions in a modal dialog;</li>
<li>open all new discussions in a modal dialog;</li>
<li>automatically refresh the content of the web part whenever a new discussion is adde; </li>
<li>automatically refresh the content of the web part whenever an existing discussion has been edited or deleted;</li>
<li>returns to the page with the web part and closes the dialog after deleting a discussion.</li>
</ul>
<br />
As always, if you have any questions or need any help do let me know by placing a comment!<br />
This post can also be found as an answer on <a href="http://sharepoint.stackexchange.com/questions/90316/how-to-get-modal-dialogs-for-a-discussion-board-wp-auto-refresh-the-content-of/90317#90317" target="_blank">SharePoint StackExchange</a>. Unknownnoreply@blogger.com6België50.503887 4.469935999999961547.917913 -0.69363800000003817 53.089861 9.633509999999962tag:blogger.com,1999:blog-4768615780556150.post-43567503921259096832014-01-28T17:03:00.002+01:002014-02-05T12:35:42.547+01:00IT'S OVER ONE THOUSAAAAAAAAAAND!!!!<h2>
My blog just got over one thousand views!!! </h2>
<h3>
</h3>
<h3>
Hurray!!! </h3>
<h3>
</h3>
<h4>
Confetti for everyone!!!</h4>
<h4>
</h4>
I probably just viewed my own blog so many times that I'm now up to one thousand. Although I did set it so that my own views wouldn't count. But meh, /care, one thousand views!<br />
<br />
Also, someone actually clicked on the advertisement box on the right. Which gave me a very tiny small amount of eurocents, but still money nonetheless!<br />
So if you happen to stumble upon this post, could you like... Just... Do a tiny click on that advertisement box on your right? Yeah. That one, right underneath the "Subscribe to" thingy. It won't cost you a thing, it won't do any harm either, but it will give me a (once again) very tiny small amount of money, which in return makes me a very tiny bit more happier each time someone clicks it. <br />
<br />
And who knows, it might even be interesting! I have no idea what the advertisement box tells you, it is supposed to show you advertisements that are relevant to whatever business you searched for on Google. In my case it shows me something in French about becoming a florist, although I have absolutely no idea why it would show something like that since I never even searched for flowers online. Peculiar.<br />
<br />
<br />
I think I'll keep making posts like these every time my views increase by a thousand. Eventually, I'll get to 9000. And you should know what that means. Oh boy, do I look forward to that moment... That will be one heck of a post.Unknownnoreply@blogger.com0België50.503887 4.469935999999961547.917913 -0.69363800000003817 53.089861 9.633509999999962tag:blogger.com,1999:blog-4768615780556150.post-59392409958132428422014-01-24T11:11:00.002+01:002014-02-05T11:45:05.386+01:00My new favourite syntax highlighter: Prism!The past few days, I noticed some parts of my code were missing in the Syntax Highlighter and that sometimes, the code wasn't shown in the highlighter at all (it was just dull text). When I edited the affected blog posts however, the missing pieces of code were there and so were the elements for the highlighter. So I figured it might have had to do something with the Syntax Highlighter.<br />
<div>
<br /></div>
<div>
I decided to search for a new highlighter, and came across <a href="http://prismjs.com/" target="_blank">Prism</a>. This is a very neat highlighter, it is lightweight and extensible, offers six different styles, has various plugins and is supported by most browsers.</div>
<div>
For my blog, I'm using the Prism highlighter with the Okaidia theme and I also included the Line Numbers plugin. </div>
<div>
<br /></div>
<h3>
How to use Prism on Blogger/Blogspot</h3>
<div>
First of all, I discovered that I couldn't host JavaScript files on Blogger/Blogspot. Secondly, I didn't like the idea of hosting scripts elsewhere. I tried hosting it on Google Drive and that sort of worked, but still I wanted to find an easier way.</div>
<div>
So I checked the source of <a href="http://prismjs.com/" target="_blank">prismjs.com</a> and its pages, searched for the scripts and found them there. Not quite sure if I'm allowed to do that... But it was easier for me and I think those will always be the most up-to-date scripts. </div>
<div>
<a href="http://prismjs.com/prism.js" target="_blank">This </a>is the core JavaScript code of Prism. </div>
<div>
<a href="http://prismjs.com/plugins/line-numbers/prism-line-numbers.js" target="_blank">This </a>is the JavaScript code for the line numbers plugin.</div>
<div>
<a href="http://prismjs.com/plugins/line-numbers/prism-line-numbers.css" target="_blank">This </a>is the CSS code for the line numbers plugin.</div>
<div>
And <a href="http://prismjs.com/themes/prism-okaidia.css" target="_blank">this</a> is the CSS code for the Okaidia theme.</div>
<div>
<br /></div>
<div>
I'm not going to provide the link to all the scripts, plugins and themes for Prism since you can probably find most of them yourself by changing the URLs a bit or by doing a search through the source code of the site. </div>
<div>
<br /></div>
<div>
Now that we have the necessary files, we need to include them to the template. </div>
<div>
To add them, you need to edit the HTML of your template. You can do so by going to the settings of your blog, select "Template" from the left hand side navigation, and then choose "Edit HTML". </div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgU-XYd8zOLaqTyD7s_C_nKvJjfOaxKendjcgiksj-ILqwU8Xvz1009nwzQMluM70XC8uKY87LbGE3VDSyEknkMuNehOHrYYt7DXBE0nNGhdW3aKzEe8N03AKeWFFZSnCQwaWoY484i2ts/s1600/Live-on-Blog.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgU-XYd8zOLaqTyD7s_C_nKvJjfOaxKendjcgiksj-ILqwU8Xvz1009nwzQMluM70XC8uKY87LbGE3VDSyEknkMuNehOHrYYt7DXBE0nNGhdW3aKzEe8N03AKeWFFZSnCQwaWoY484i2ts/s1600/Live-on-Blog.png" /></a></div>
<div>
<br />
Right before the </head> tag, you paste the following code (source of the scripts may be different depending on which plugins or styles you use):</div>
<div>
<pre class="line-numbers"><code class="language-javascript"><script src='http://prismjs.com/prism.js' type='text/javascript'/>
<script src='http://prismjs.com/plugins/line-numbers/prism-line-numbers.js'
type='text/javascript'/>
<link
href='http://prismjs.com/themes/prism-okaidia.css' rel='stylesheet'/>
<link
href='http://prismjs.com/plugins/line-numbers/prism-line-numbers.css'
rel='stylesheet'/></code></pre>
<br /></div>
<div>
Now, when you create a new blog post and you wish to add code to it, you can use the following to highlight your code:</div>
<pre class="line-numbers"><code class="language-javascript"><pre class="line-numbers"><code class="language-javascript">
// Your code here.
</code></pre></code></pre>
<br />
Do note that depending on which plugins you have, you can change the class in the <b>pre </b>tag. You can read more about the different plugins and how to use them <a href="http://prismjs.com/index.html#plugins" target="_blank">here</a>. Also, depending on what kind of code you will be highlighting, you can change the class in the <b>code </b>tag.<br />
To highlight CSS, use <b>language-css</b> class for the code tag.<br />
To highlight HTML, use <b>language-markup</b> class for the code tag.<br />
To highlight JavaScript, use <b>language-javascript</b> class for the code tag.<br />
<br />
<h3>
Got code that's not showing up? No problem. We can fix this. </h3>
I noticed that if you want to use HTML code inside JavaScript (or even script tags in JavaScript), the code will likely not show. I think this might be related to HTML being stripped off. For example, see the following code:<br />
<pre class="line-numbers"><code class="language-javascript">var site = "www.prismjs.com";
var title = "PrismJS";
var text = "<a href="https://www.blogger.com/%22%20+%20site%20+%20%22">" + title + "</a>";</code></pre>
<br />
Noticed anything strange? I surely did. I added an anchor tag to that code, yet it is not showing.<br />
Let me show you the same code, but now I changed the "less than" character ("<") with "&lt;" and this is the result:<br />
<pre class="line-numbers"><code class="language-javascript">var site = "www.prismjs.com";
var title = "PrismJS";
var text = "<a href='" + site + "'>" + title + "</a>";</code></pre>
<br />
Bam! Suddenly the code works. Just to give you an idea, this is how it looks like (and how I write it in order for it to show up properly) when I edit the blog post with the code:<br />
<pre class="line-numbers"><code class="language-javascript">var site = "www.prismjs.com";
var title = "PrismJS";
var text = "&lt;a href='" + site + "'>" + title + "&lt;/a>";</code></pre>
<br />
So when you use HTML inside JavaScript, or even when you use "<script></script>" tags or regular expressions, always make sure to replace the "less than" character with "<". That way, no code will be missing from the highlighter and visitors will be able to correctly copy and use your code.
<br />
<br />
<h3>
All done! Let's start making more blog posts now!</h3>
Now that I finally have a syntax highlighter that does what I want it to do, I can continue writing blog posts about my SharePoint experiences without having to worry about visitors not seeing my code. ^_^<br />
<br />
I would like to thank <a href="http://lea.verou.me/" target="_blank">Lea Verou</a> and <a href="https://github.com/LeaVerou/prism/graphs/contributors" target="_blank">all these people</a> who created Prism, without them I would probably still struggle with highlighting stuff. Please do check out <a href="http://prismjs.com/" target="_blank">Prism</a>, I highly recommend it for Blogger/Blogspot or any other site.Unknownnoreply@blogger.com0België50.503887 4.469935999999961547.917913 -0.69363800000003817 53.089861 9.633509999999962tag:blogger.com,1999:blog-4768615780556150.post-79806312963785868812014-01-23T11:11:00.002+01:002014-02-05T12:28:53.708+01:00How to get a term-driven breadcrumb trail with full hierarchy in SharePoint 2013On the 6th of May, 2013, I had asked a question on <a href="http://sharepoint.stackexchange.com/questions/67584/how-do-i-create-a-term-driven-breadcrumb-trail" target="_blank">Stack Overflow</a> on how one could create a term-driven hierarchical breadcrumb trail in SharePoint 2013. Alas, never really got an answer that suited my needs.<br />
<br />
Yesterday, I suddenly had the idea to just try and find a solution with JavaScript. The idea was to loop through the subsite navigation and see which list item resembled the current page, then find all the parent elements of that list item and fetch their values. And in case I was on a page that wasn't part of a subsite (but rather just a page at the top site collection), I would just fetch the title of the page and add that to the breadcrumb trail.<br />
<br />
Here's a screenshot of how my breadcrumb looked like earlier:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijGxmDvIqo3BE0RWdskQBJep9AozSdL7GjvX-N6LUFMMQSWUz8bVjMe6u7dVrptCaJz_Nx0qvfmOPbw7sOBJ1IdjnAxOc8cItf-aajTXGiBDgfQj9x7_iGYqI2Pxpp2PhRTwduoqJzoq8/s1600/OLD_BREADCRUMB.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijGxmDvIqo3BE0RWdskQBJep9AozSdL7GjvX-N6LUFMMQSWUz8bVjMe6u7dVrptCaJz_Nx0qvfmOPbw7sOBJ1IdjnAxOc8cItf-aajTXGiBDgfQj9x7_iGYqI2Pxpp2PhRTwduoqJzoq8/s1600/OLD_BREADCRUMB.png" /></a></div>
<br />
<br />
<br />
And here's a screenshot of how it looks like now:<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJdPpGtDibrjMw9nL2g5LhOvggN_oYxT7KxBXw5_0XjqcVaL9TzIB-7RRKDOCSZj9z1Wb4-AHlnyjlYGCurCdpshQQQodAB2m1ZnFtXo3HhNdzNTyly8G4yjARlc-E1RlLrW4m8ZBULoc/s1600/NEW_BREADCRUMB.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJdPpGtDibrjMw9nL2g5LhOvggN_oYxT7KxBXw5_0XjqcVaL9TzIB-7RRKDOCSZj9z1Wb4-AHlnyjlYGCurCdpshQQQodAB2m1ZnFtXo3HhNdzNTyly8G4yjARlc-E1RlLrW4m8ZBULoc/s1600/NEW_BREADCRUMB.png" /></a></div>
<br />
<br />
<br />
Pretty nice, huh?<br />
<br />
Let's just get started, I'll give you the code.<br />
<br />
<h3>
The code</h3>
<pre class="line-numbers"><code class="language-javascript">// Just add the following line.
SP.SOD.executeOrDelayUntilScriptLoaded("SP.UserProfiles.js",
"~sitecollection/Style Library/Scripts/jquery.SPServices-2013.01.js");
var siteCollection = "Your site collection name";
var siteCollectionUrl = "/";
var pageName;
var subsite;
// Fetch the name of the subsite you're currently on.
$().SPServices({
operation: "SiteDataGetWeb",
async: false,
completefunc: function (xData, Status) {
subsite = $(xData.responseXML).SPFilterNode("Title").text();
}
});
// Fetch the url of the subsite you're currently on.
var subsiteUrl = $().SPServices.SPGetCurrentSite();
// Fetch the title of the page, remove a piece of text from the title
// (in my case, "Pages - "), then remove any white spaces before and
// after the title.
var pageTitle = document.getElementsByTagName("title")[0].innerHTML
.replace("Pages - ", "").replace(/^\s\s*/, "").replace(/\s\s*$/, "");
// If the current site is the same as the site collection (meaning it is
// not a subsite), then use the url of the current page.
if (subsite == siteCollection) {
// Create the string that will contain the breadcrumb trail.
var text = "<a href='" + siteCollectionUrl + "'>" + siteCollection
+ "</a> > <a href='" + document.URL + "'>" + pageTitle + "</a>";
}
// In any other case, use the url of the subsite.
else {
// Create the string that will contain the breadcrumb trail.
var text = "<a href='" + siteCollectionUrl + "'>" + siteCollection
+ "</a> > <a href='" + subsiteUrl + "'>" + subsite + "</a>";
}
$(function runMe() {
// Set the ID of your subsite navigation.
var $this = $("#NavRootAspMenu");
if($this != null) {
// Remove the class "static" from the last item in the navigation
// (because it(s a link to edit the navigation) and remove the last
// child element (since this doesn't have a href attribute).
$(".ms-listMenu-editLink").removeClass("static");
$("ul[id*='RootAspMenu'] li.ms-navedit-editArea:last-child").remove();
$this.find("li").each(function(i){
var elem = $($this).find("li.static")[i];
// If elem finds an anchor tag that contains the following class, then
// add a new class. We'll use this class for the parent elements.
if ($(elem).find("a").hasClass("ms-core-listMenu-selected")) {
$(elem).addClass("parentSelected");
}
});
// If the subsite navigation contains elements with the class
// "parentSelected" and that element contains an anchor, a span and
// another span with the class "menu-item-text", then for each of those
// elements do the following.
$this.find(".parentSelected > a span span.menu-item-text")
.each(function(j) {
$(this).addClass("bcn"); //bcn = breadcrumbnode
var crumbLink = $($this).find(".parentSelected > a")[j].href;
var crumbName = $("span.bcn")[j].innerHTML;
// If the link equals the url of the site collection, then this list
// item is a category and it does not require an anchor tag.
if (crumbLink == "https://your-site-collection.com/") {
text = text + " > " + crumbName;
}
// In any other case, this list item has a page and we will add an
// anchor tag to the breadcrumb trail.
else {
text = text + " > <a href='" + crumbLink + "'>" + crumbName + "</a>";
}
});
}
// When we ran through the navigation, apply the new breadcrumb trail to
// the element in the master page.
document.getElementById("customBreadcrumb").innerHTML = text;
});</code></pre>
<br />
You'll also need to have the jQuery library for SharePoint Web Services, you can find it <a href="http://spservices.codeplex.com/" target="_blank">here</a>. <br />
In order for our code to work properly, we will need to replace some code from the master page and add a reference to the breadcrumb script on the master page. Be sure to read the JavaScript code first, you will need to fill in a name for your site collection (var siteCollection) and give the url of your site collection followed by a slash (var crumbLink). <br />
<br />
<h3>
Adding a reference to the master page</h3>
If we want to apply this code on the site collection and all subsites, then we should add a reference to our script in the master page. Please do note that I'm using a HTML master page. This is the code you should add:<br />
<pre class="line-numbers"><code class="language-javascript"><!--SPM:<SharePoint:ScriptLink language="javascript" ID="scriptLink1"
runat="server" name="~sitecollection/Style Library/Scripts/breadcrumb.js"
OnDemand="false" Localizable="false"/>--></code></pre>
<br />
Do note that the ID might be different. You must make sure that you do not already have a scriptlink with the same ID, so change the number of the ID and make it unique.<br />
Also, the name (path to your script) might be different. Make sure that it matches the path of where your script is located.<br />
<br />
<h3>
Replacing code in the master page</h3>
Next, we need to replace some code in the master page.
In my HTML master page, I replaced any piece of code that had to do with the breadcrumb. Just to give you an idea, the following pieces of code are the ones that I REMOVED from my master page:<br />
<pre class="line-numbers"><code class="language-javascript"><!--SPM:<asp:sitemappath runat="server"
sitemapproviders="SPSiteMapProvider,SPXmlContentMapProvider"
rendercurrentnodeaslink="true"
nodestyle-cssclass="breadcrumbNode"
currentnodestyle-cssclass="breadcrumbCurrentNode"
rootnodestyle-cssclass="breadcrumbRootNode"
hideinteriorrootnodes="false"
SkipLinkText=""/>-->
<!--SPM:<SharePoint:AjaxDelta id="DeltaPlaceHolderPageTitleInTitleArea"
runat="server">-->
<!--SPM:</SharePoint:AjaxDelta>-->
<!--SPM:<SharePoint:AjaxDelta BlockElement="true"
id="DeltaPlaceHolderPageDescription" CssClass="ms-displayInlineBlock
ms-normalWrap" runat="server">-->
<a href="javascript:;" id="ms-pageDescriptionDiv"
style="display: none;">
<span id="ms-pageDescriptionImage"></span>
</a>
<span class="ms-accessible" id="ms-pageDescription">
<!--SPM:<asp:ContentPlaceHolder id="PlaceHolderPageDescription"
runat="server"/>-->
</span>
<!--SPM:<SharePoint:ScriptBlock runat="server">-->
<!--SPM:_spBodyOnLoadFunctionNames.push("setupPageDescriptionCallout");-->
<!--SPM:</SharePoint:ScriptBlock>-->
<!--SPM:</SharePoint:AjaxDelta>--></code></pre>
<br />
It is very important that you keep the following code in your master page, but wrap something around it with a class. Like so:<br />
<pre class="line-numbers"><code class="language-javascript"><span class="hideDefaultBreadcrumb">
<!--SPM:<asp:ContentPlaceHolder id="PlaceHolderPageTitleInTitleArea"
runat="server">-->
<!--SPM:<SharePoint:SPTitleBreadcrumb runat="server"
RenderCurrentNodeAsLink="true"
SiteMapProvider="SPContentMapProvider"
CentralAdminSiteMapProvider="SPXmlAdminContentMapProvider">-->
<!--SPM:<pathseparatortemplate>-->
<!--SPM:<SharePoint:ClusteredDirectionalSeparatorArrow
runat="server"/>-->
<!--SPM:</PATHSEPARATORTEMPLATE>-->
<!--SPM:</SharePoint:SPTitleBreadcrumb>-->
<!--SPM:</asp:ContentPlaceHolder>-->
</span></code></pre>
<br />
Then add the class to your style sheet:<br />
<pre class="line-numbers"><code class="language-css">.hideDefaultBreadcrumb {
display: none;
}</code></pre>
<br />
The reason that we must keep this content placeholder, is that otherwise you'll suddenly miss a great portion of the "Apps you can add". I forgot about this at first and then suddenly had only three apps left, but after reading about it <a href="http://blog.sharepointexperience.com/2013/08/missing-apps-you-can-add-with-custom-master-page-in-sharepoint-2013/" target="_blank">here</a> I learned that I should have left in the content placeholder with ID PlaceHolderPageTitleInTitleArea. So that's fixed now.<br />
<br />
I also removed a span with the ID "ctl00_DeltaPlaceHolderPageTitleInTitleArea" in the master page. <br />
<br />
Bear in mind that your code might be different from mine, so always check your site with source view in your browser, to see the ID's and classes of the elements in the breadcrumb. <br />
Now, ADD the following code to the location of where you just removed the piece of code that was previously your breadcrumb:<br />
<pre class="line-numbers"><code class="language-javascript"><span id="customBreadcrumb"></span></code>
</pre>
<br />
In that tiny span, your breadcrumb trail will appear. This will be dynamically filled with text and links once your script is running. <br />
<br />
Make sure your master page and your script are checked in. Now you can finally see the full breadcrumb trail, with the correct hierarchy! <br />
No need to deploy solutions, no need to find a workaround or use webparts to do the trick, just some simple JavaScript is all you need. ;)
<br />
<br />
Enjoy!Unknownnoreply@blogger.com8België50.503887 4.469935999999961547.917913 -0.69363800000003817 53.089861 9.633509999999962tag:blogger.com,1999:blog-4768615780556150.post-7375497901511151272013-12-20T15:18:00.000+01:002014-02-05T12:29:04.838+01:00How to redirect all but a few users to a custom page in SharePoint 2013Imagine that you are working in a production environment, you're close to your deadline on which all users will be granted access to your site. But what if you're not ready yet? What if your supervisor wants you to "deny" all users access to the site by redirecting them to one page, all users except for a select few?<br />
<br />
In such a case, you'll need a script that will check the name of the user, see if he/she is allowed full access to the site, and if not redirect the user to a custom page. I used Jane Doe and John Doe as the users that are allowed full access to the site (I advise you to add your own name as well). <br />
<br />
<h3>
The code</h3>
We need to make a JavaScript file (I named mine "custom-redirect.js") and we will add the following code to the file:<br />
<pre class="line-numbers"><code class="language-javascript">SP.SOD.executeFunc('sp.runtime.js');
SP.SOD.executeOrDelayUntilScriptLoaded('SP.UserProfiles.js',
"~sitecollection/Style Library/Scripts/jquery.SPServices-2013.01.js");
var url = window.location.pathname;
if (url.indexOf('Redirect.aspx') > -1) {
// Do not run the script if we're already on the page.
}
else {
function redirectMe() {
var userListArray = new Array();
userListArray.push("Jane Doe");
userListArray.push("John Doe");
var user = $().SPServices.SPGetCurrentUser({
webURL: "",
fieldName: "Title",
fieldNames: {},
debug: false
});
function include(arr, obj) {
for (var i = 0; i < arr.length; i++) {
if (arr[i] == obj) return true;
}
}
if(include(userListArray,user)) {}
else {
window.location =
"http://your-site-here.com/Pages/Redirect.aspx";
}
}
}</code></pre>
<br />
The code will check if we're not on the redirect page already, since there's no need to keep running the script when the user has already been redirected. If we're not, then we'll continue with the function called redirectMe(). <br />
<br />
We need to make an array that will hold the names of all users that will be granted full access to the site. Push the names of those users to the array.<br />
<br />
Then, we use SPServices to check the "Title" (also known as the full name of the user) of the current user. We will need a small function to check if an array contains an object as well. Using that function, we then check if the name of the current user is included in the array. If it is, then we don't need to do anything. If the name of the user is not in the array, then we redirect the user to a different page, in my case a custom redirect page. <br />
And that's all there is to it! Quite simple, now that I look back at it.<br />
<br />
You'll need to have the jQuery library for SharePoint Web Services in order to get the current user, you can find it <a href="http://spservices.codeplex.com/">here</a>. <br />
Also make sure you added a reference to your script in your master page, like so:<br />
<pre class="line-numbers"><code class="language-javascript"><!--SPM:<SharePoint:ScriptLink language="javascript" ID="scriptLink9"
runat="server" OnDemand="false" Localizable="false"
name="~sitecollection/Style Library/Scripts/custom-redirect.js" />--></code></pre>
<br />
You'll also need to add the following line of code <b>directly</b> in your master page:<br />
<pre class="line-numbers"><code class="language-javascript"><script type="text/javascript">
window.onpaint = redirectMe();
</script></code>
</pre>
<br />
Make sure your master page and your script are checked in. Now you can freely continue working on your site without anyone seeing that it is not yet finished! Better yet, those who you have granted full access to your site can contribute as well! <br />
<br />
If you have any questions, feel free to ask. ;)Unknownnoreply@blogger.com0België50.503887 4.469935999999961550.503887 4.4699359999999615 50.503887 4.4699359999999615tag:blogger.com,1999:blog-4768615780556150.post-25777151041317459982013-12-20T14:32:00.001+01:002014-02-05T11:44:37.800+01:00How to prevent the page from going to "AllItems.aspx" after deleting a list item or a document in SharePoint 2013Abouth a month ago on the 19th of November, I noticed something while I was cleaning up my site. I discovered that when I deleted a list item or a document from a list or library, the browser would send me to the "AllItems.aspx" of that list or library.<br />
<br />
This was something I didn't really pay attention to at first, but then it came to my mind that end users might find this confusing since they would suddenly end up on a different page whenever they deleted something. Off course I understand that <i>you</i> probably wouldn't find this confusing at all, but imagine that a user with no knowledge of anything related to IT or computers would be in that situation. If such a user would end up on a page that no longer has the trusted design with a title and a paragraph of text, and a list or library that is suddenly showing way more columns than what he/she is used to see, that user would probably end up sending me an e-mail asking me for help because he/she is stuck. Yeah.<br />
<br />
To avoid any confusion, no matter how small the problem, I wanted to make sure users would never end up on the "AllItems.aspx" page whenever they deleted something. So, not knowing where to start and not finding any relevant results on Google, I asked the question on <a href="http://sharepoint.stackexchange.com/questions/83149/how-to-run-javascript-code-only-when-list-item-gets-deleted" target="_blank">Stack Exchange</a>. I had thought that something like this wouldn't be so hard to figure out, but was dissappointed when my first and only "answer" was just a comment saying that I could take pointers. After a few weeks with still no solution, I gave up hope and started a bounty on the question. <br />
Suddenly many more answers appeared, yet none were offering me an example in JavaScript. Most answers required me to use C# and seemed way to complicated for a problem this small, and after a while I thought that maybe my question wasn't clear enough.<br />
<br />
Until someone answered with three options, one of which was to use cookies. When I started my search, I discovered that there was a successor to cookies. Named HTML5 Web Storage.<br />
<br />
<h3>
The logic behind the idea</h3>
First of all, we notice that when you want to delete something (be it a list item or a document, I'll just call it "item" from now on), you first need to click on the edit button for that item before you can choose to delete it. When I edit an item, it opens in a modal dialog. As you may or may not know, the URL of a modal dialog contains "EditForm.aspx".<br />
If I then choose to delete the item, it directs me to the "AllItems.aspx" page. Based on this, we now know that we can write a piece of code that will only run when the user ends up on the "AllItems.aspx" page, having the "EditForm.aspx" page as its referrer.<br />
<br />
Next, we just write a piece of code that will take us back to the previous page. But pay attention: technically, the previous page is the modal dialog. We need to go back to the page <i>before </i>we launched the modal dialog, so the page on which you clicked on a list item from a list or a document from a library.<br />
<br />
To save the URL of that page somewhere, I used HTML5 Web Storage.<br />
At this point, I just want to share the code with you. After all, there's really not much left to explain.<br />
<br />
<h3>
The actual code</h3>
<pre class="line-numbers"><code class="language-javascript">var ref = document.referrer; // Stores URL of previous page.
var url = window.location.pathname; // Stores URL of current page.
// The following code will run if the user edits a list item or properties of
// a document.
if (url.indexOf("EditForm.aspx") > -1) {
sessionStorage.setItem("page", ref);
}
if (url.indexOf("AllItems.aspx") > -1
&& ref.indexOf("EditForm.aspx") > -1) {
window.location = sessionStorage.getItem("page");
}</code></pre>
<br />
There you have it! It can't be easier, really!<br />
If you follow the code, you'll see that if the URL in your browser window contains "EditForm.aspx", it will store the URL of the "previous" page as a session object.
As soon as the URL in your browser window contains "AllItems.aspx" AND if the referrer (aka URL of the previous page) contains "EditForm.aspx", it will set the window location to the URL we stored as a session object earlier. The user won't even see that he/she has been redirected back to that page.
<br />
<br />
To make things work, you'll need to make sure that you've put a reference to your script in your master page. I had written the code in a new JavaScript file named "no-all-items.js" and added a reference to it in the master page:
<br />
<pre class="line-numbers"><code class="language-javascript"><!--SPM:<SharePoint:ScriptLink language="javascript" ID="scriptLink10"
runat="server" OnDemand="false" Localizable="false"
name="~sitecollection/Style Library/Scripts/no-all-items.js" />--></code></pre>
<br />
And there you have it. Make sure your script is checked in, as well as your master page, and you're ready to go!
<br />
Enjoy!Unknownnoreply@blogger.com3België50.503887 4.469935999999961547.917913 -0.69363800000003817 53.089861 9.633509999999962tag:blogger.com,1999:blog-4768615780556150.post-71043074022849506612013-12-02T11:16:00.000+01:002014-02-05T12:29:54.909+01:00How to hide links in the navigation from users that don't have edit permissions for certain pages in SharePoint 2013Imagine you have a site that has managed navigation. Most of the pages in the site can be seen by all the users, but there is one page that has unique permissions. This page, for example, is named "<b>Secret headquarters</b>" and should only be visible to the members of the group "<b>Secret service</b>". All other users shouldn't even see the link to that page in the navigation, so basically nobody else but the members of the group should know there is such a page.
<br />
<br />
So, how do we hide a link to a page with custom permissions? And how exactly can we find out in which groups the user is in, and if the user is a member of the group "Secret service"?<br />
Let me explain that to you.<br />
<br />
<h2>
Preparing a page for unique permissions</h2>
First of all, you need to create a group that will hold all the users that will have access to the "secret" page. I named my group "Secret service" and added some users. The group has read and edit permissions.<br />
Then, you will need to create the page (if not already) that will be made hidden to all users except those who are a member of the group "Secret service". When you made the page, do the following:
<br />
<ol>
<li>In your subsite, click on the "<b>Settings</b>" button on the top right corner</li>
<li>Click on "<b>Site content</b>"</li>
<li>Click on the name of the "<b>Pages</b>" library (or the "<b>Subsites</b>" library, depending on where you store your pages)</li>
<li>Find the page you want to make secret, and click on the "<b>...</b>" on the right side of its name</li>
<li>In the small modal dialog, click on "<b>...</b>" again and select "<b>Shared with</b>", then click on "<b>Advanced</b>"</li>
<li>In the ribbon, top left icon, click on "<b>Remove Unique Permissions</b>", click "<b>OK</b>"</li>
<li>Select the remaining groups and then click on "<b>Remove User Permissions</b>", click "<b>OK</b>"</li>
<li>Click on "<b>Grant Permissions</b>", type in the name of the group that will have access to the page (in my case, that will be "<b>Secret service</b>")</li>
<li>Click on "<b>Show options</b>" at the bottom of the dialog and <u>untick </u>"<b>Send an email invitation</b>"</li>
<li>Select "<b>Edit</b>" permissions, press "<b>OK</b>"</li>
<li>repeat steps 8 and 9, now select "<b>Read</b>" permissions, press "<b>OK</b>"</li>
</ol>
At this point, users who are not a member of the group "Secret service" will still see a link to the page in the navigation, but when they click on it, they will get a "Access denied" message.<br />
<br />
<h2>
Testing the permissions of the page
</h2>
This will be quick and easy to test if you have a dummy account. If not, then I hope you have a colleague willing to spend 10 minutes of his/her time testing your environment. But let's just continue with the idea of having a dummy account.<br />
<br />
<i>First, let's add the dummy to the group and see if the dummy can access the page:</i><br />
<ol>
<li><b>With your administrator account</b>, add the dummy account to the group "<b>Secret service</b>"</li>
<li><b>Log in with the dummy account</b>, navigate to the page "<b>Secret headquarters</b>"
<ul>
<li>If you can see the page with the dummy, then you did well!</li>
<li>If you can't see the page with the dummy, you probably didn't add the dummy user to the group "Secret service".</li>
</ul>
</li>
<li><b>Still on the dummy account</b>, check if the dummy can see other pages in the same subsite
<ul>
<li>If you can still see all other pages with the dummy, then you did well!</li>
<li>If you can't see other pages with the dummy, then you probably set the unique permissions for the whole subsite instead of just the one page</li>
</ul>
</li>
</ol>
<br />
<i>Now we just need to check if the dummy will get an "Access denied" when the dummy tries to access the page without being a member of the group:</i><br />
<ol>
<li><b>With your administrator account</b>, remove the dummy account from the group "<b>Secret service</b>"</li>
<li><b>Log in with the dummy account</b>, navigate to the page "<b>Secret headquarters</b>"
<ul>
<li>If you can't see the page with the dummy, you did well!</li>
<li>If you can see the page with the dummy, then you probably didn't remove the dummy from the group "Secret service".</li>
</ul>
</li>
</ol>
If you passed these small tests, then we are ready to go to the next step!
<br />
<br />
<h2>
Writing the code to hide the page from the navigation</h2>
Let's first write down what we want to achieve:<br />
<ol>
<li>Loop through all links in the navigation on the left side of the subsite</li>
<li>When we encounter a list item in which the href attribute ends with "<b>Secret-headquarters.aspx</b>", we want to check the permissions of that page</li>
<li>If we encounter such an element, we will run a function that will fetch all the groups in which the current user is in
<ul>
<li>If the current user is a member of the group "Secret service", we will take no action and leave the navigation as is.</li>
<li>If the current user is not a member of the group "Secret service", then we will select that list item holding the link to "Secret-headquarters.aspx" and set it <b>hidden</b>.</li>
</ul>
</li>
</ol>
I included some comments, be sure to read those too!<br />
<pre class="line-numbers"><code class="language-javascript">// The following three lines are required, don't forget to find a copy
//of "jquery.SPServices-2013.01.min.js" and add a reference to it here.
SP.SOD.executeFunc("sp.runtime.js");
SP.SOD.executeFunc("SP.js", "SP.ClientContext");
SP.SOD.executeOrDelayUntilScriptLoaded("SP.UserProfiles.js",
"~sitecollection/Style Library/Scripts/jquery.SPServices-2013.01.min.js");
var siteUrl = "";
var element = "";
$(document).ready(function() {
// We only want to loop through the navigation on the left side of the
// subsite;
if($("#NavRootAspMenu") != null) {
// If present, remove the last list item. This sometimes appears and
// causes problems since it doesn't have a href attribute.
$("ul[id*='RootAspMenu'] li.ms-navedit-editArea:last-child").remove();
}
});
runMe();
function runMe() {
var $this = $("#NavRootAspMenu");
if($this != null) {
$this.find("li").each(function(i){
// For each list item that has a "a" element, fetch the "href"
// attribute and write it to siteUrl.
siteUrl = $this.find("a.static")[i].href;
// When the siteUrl ends with "Secret-headquarters.aspx", save the
// current element to "element" and run a function.
if (siteUrl.indexOf("Secret-headquarters.aspx") > -1) {
element = $this.find("a.static")[i];
sharePointReady(siteUrl, element);
}
});
}
}
function sharePointReady(siteUrl, element) {
// Create an array that will hold a list of all the groups where the
// current user is a member of.
var userGroupArray = new Array();
var group;
// The line below is handy in case you have multiple pages you want to
// hide, but need to be accessed by different groups.
if(siteUrl.indexOf("Secret-headquarters.aspx") >- 1) {
group = "Secret service";
}
// Get all groups where the current user is a member of.
var userGroup = $().SPServices({
operation: "GetGroupCollectionFromUser",
userLoginName: $().SPServices.SPGetCurrentUser(),
async: false,
completefunc: function(xData, Status) {
$(xData.responseXML).find("Group").each(function() {
// Push the name of the group to the array.
userGroupArray.push($(this).attr("Name"));
});
}
});
// This useful little function is to check if an element is contained in
// your array.
function include(arr, obj) {
for (var i = 0; i < arr.length; i++) {
if (arr[i] == obj) return true;
}
}
// If the array contains the group "Secret service", then do nothing.
if(include(userGroupArray,group)) {
//console.log("You can edit this!");
}
// If the array does not contain the group "Secret service", then hide
// the element from the current user so that he/she cannot navigate to
// the page.
else {
//console.log("You can't edit this!");
element.style.display="none";
}
}</code></pre>
<br />
That's it! We're ready with the script. Now it's time to test it out and see if it works.<br />
<h2>
Adding a reference to the master page</h2>
If we want to apply this code on multiple subsites, then it is best that we add a reference to our script in the master page. I just added the code to an existing script that was already loaded on the master page (I use a HTML master page), but if you want to add it as a separate script, this is how it might look like:
<br />
<pre class="line-numbers"><code class="language-javascript"><!--SPM:<SharePoint:ScriptLink language="javascript" ID="scriptLink1"
runat="server" name="~sitecollection/Style Library/Scripts/scripts.js"
OnDemand="false" Localizable="false"/>--></code></pre>
<br />
Do note that the ID might be different. You must make sure that you do not already have a scriptlink with the same ID, so change the number of the ID and make it unique. <br />
Check in your master page and your script, and go take a look at the page.<br />
You can re-do the steps mentioned in "<b>Testing the permissions of the page</b>", and this time you will immediately see if the list item for the page "Secret headquarters" is present in the list or not.<br />
It should now be hidden from users who are not a member of the group "Secret service", and it will remain visible to those who are a member of that group. <br />
<br />
<h2>
Enjoy!</h2>
If you have any questions, please do not hesitate to ask!
<br />
Special thanks go to <a href="http://sharepoint.stackexchange.com/users/10008/ali-sharepoint" target="_blank">Ali Sharepoint</a> from Stack Exchange, who helped me with the code.<br />
The code for "<b>JavaScript Array Contains</b>" was found on <a href="http://css-tricks.com/snippets/javascript/javascript-array-contains/" target="_blank">www.css-tricks.com</a>.Unknownnoreply@blogger.com11België50.503887 4.469935999999961547.917913 -0.69363800000003817 53.089861 9.633509999999962tag:blogger.com,1999:blog-4768615780556150.post-60646111138725620612013-11-27T11:01:00.001+01:002014-02-05T12:30:05.080+01:00UPDATE: How to show calendar events in modal dialogs in SharePoint 2013I recently just noticed that my code for showing events in a calendar in a modal dialog does not always do its job. It appears that when you change to another month and then try to open an event in that month (or in any other month, since it fails the moment you change the month), the event won't open in a modal dialog! This also seemed to be the case when you changed the year. <br />
<br />
Since I didn't just want to run the script whenever a user clicked a certain element (like those for the months and for the year), I came up with a different and rather easy fix.
<br />
I decided to just run the code everytime the user clicks anywhere inside the #content div. Don't worry, it won't alter any link inside your navigation, only those that have "DispForm.aspx" at the end.
<br />
<pre class="line-numbers"><code class="language-javascript">// When called, this function opens the dialog.
function openDialog(pUrl) {
var options = {
width : 600,
height : 400,
url : pUrl };
SP.SOD.execute("sp.ui.dialog.js", "SP.UI.ModalDialog.showModalDialog",
options);
}
// When called, this function makes sure each event in the calendar is
// opened in a modal dialog instead of on a new page.
function clickMe() {
$('a[href*="DispForm.aspx"]').each(function() {
$(this).attr("onclick", "openDialog('" + $(this).attr("href") + "')");
$(this).attr("href","javascript:void(0)");
});
}
window.onload = function () {
clickMe();
// A timeout is required for when the page first loads (if you would not
// use a timeout, then the script won't always load).
setTimeout(function() {
clickMe();
}, 500);
// When you click anywhere inside #content, it will run the function
// "clickMe()" twice, once with and once without a timeout.
$("#content").click(function() {
clickMe();
setTimeout(function() {
clickMe();
}, 500);
});
};</code></pre>
<br />
<b>Please do note that it might be possible that your div has a different ID</b>. I modified my HTML master page and I believe I added the div with #Content myself (it just wraps around the #sideNavBox and #contentBox divs), but if you're using the Seattle HTML master page, then you'll need to replace the code for the #content div (which I commented in the code below) with the code for #sideNavBox and #contentBox. <br />
<pre class="line-numbers"><code class="language-javascript"> //$("#content").click(function() {
// clickMe();
// setTimeout(function() {
// clickMe();
// }, 500);
//});
$("#sideNavBox").click(function() {
clickMe();
setTimeout(function() {
clickMe();
}, 500);
});
$("#contentBox").click(function() {
clickMe();
setTimeout(function() {
clickMe();
}, 500);
});</code></pre>
<br />
Or you could just modify your HTML master page as well and place a wrapping div around #sideNavBox and #contentBox.
<br />
<br />
Do make sure that you put a reference to this script on the page with the calendar. I mentioned this in my previous post but I'll say it again in case this is the first time you read about this.<br />
I saved my code in a file named "calendar.js", under a folder named "Scripts" in the "Style library" directory. <br />
Edit the page with the calendar, and at the bottom of a page, add a new script editor web part. Add the following code to that web part:
<br />
<pre class="line-numbers"><code class="language-javascript"><script src="/Style%20Library/Scripts/calendar.js"
type="text/javascript"></script></code></pre>
<br />
Now save the page, make sure it is checked in and published, and try it out. If all went well, you'll now be able to open events in a calendar in a modal dialog! ;)
<br />
<br />
If you have any questions, do not hesitate to ask.Unknownnoreply@blogger.com17België50.503887 4.469935999999961547.917913 -0.69363800000003817 53.089861 9.633509999999962tag:blogger.com,1999:blog-4768615780556150.post-81102667365347346252013-11-19T11:40:00.001+01:002014-02-05T12:30:15.171+01:00How to show calendar events in modal dialogs in SharePoint 2013<div style="text-align: center;">
<b><span style="color: #b45f06;">!!! UPDATE !!!</span></b></div>
<div style="text-align: center;">
I noticed that my code won't always work, especially when you change to another month in the calendar. So I've rewritten it, you can find the new and correct code in <a href="http://imagali.blogspot.be/2013/11/update-how-to-show-calendar-events-in.html">this </a>post. </div>
<div style="text-align: center;">
_________________________</div>
<br />
I have a calendar on a page. When you make a new event in the calendar, you get to make it in a default SharePoint modal dialog (I enabled this in the advanced settings of the calendar). However, when you open an existing event in the calendar, it goes to a new page and shows that event as if I never even enabled modal dialogs. <br />
This is some unwanted behavior, what I really want is that any event in the calendar is shown in a modal dialog, just like when you make or edit an event.
<br />
<br />
So, I decided to write some code. I added comments to the code to explain to you what it does and what it's for.
<br />
<pre class="line-numbers"><code class="language-javascript">// When called, this function opens the dialog.
function openDialog(pUrl) {
var options = {
width : 600,
height : 400,
url : pUrl };
SP.SOD.execute("sp.ui.dialog.js", "SP.UI.ModalDialog.showModalDialog",
options);
}
// When the class "ms-acal-month" is loaded, add an onclick attribute to
// all the links ending with "DispForm.aspx" so that the calendar items
// will open in a dialog instead of on a new page.
$(".ms-acal-month").ready(function () {
setTimeout(function modal() {
$("a[href*='DispForm.aspx']").each(function() {
$(this).attr("onclick", "openDialog('" + $(this).attr("href") + "')");
$(this).attr("href","javascript:void(0)");
});
}, 500);
});
// This function adds an onclick attribute to the class "ms-cal-nav" (the
// a tag that shows/hides extra items), code is needed when there are
// more than three calendar items on a day.
$(".ms-acal-month").ready( function() {
setTimeout(function() {
$("a.ms-cal-nav").attr("onclick", "clickMe()");
}, 500);
});
// This function is called when the onclick attribute has been triggered.
// It needs to add the onclick attribute again, since SP automatically
// removes this attribute as soon as the function was triggered.
function clickMe() {
setTimeout(function() {
$("a.ms-cal-nav").attr('onclick', "clickMe()");
$("a[href*='DispForm.aspx']").each(function() {
$(this).attr("onclick", "openDialog('" + $(this).attr("href") + "')");
$(this).attr("href","javascript:void(0)");
});
}, 500);
}</code></pre>
<br />
I saved my code in a file named "calendar.js", under a folder named "Scripts" in the "Style library" directory. <br />
In order for the code to do its work, you'll have to put a reference to it on the page with the calendar. Edit the page with the calendar, and at the bottom of a page, add a new script editor web part. Add the following code to that web part:
<br />
<pre class="line-numbers"><code class="language-javascript"><script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/jquery-ui.min.js"
type="text/javascript"></script>
<script type="text/javascript"
src="~sitecollection/Style%20Library/Scripts/calendar.js"></script></code></pre>
<br />
Now save the page, make sure it is checked in and published, and try it out. If all went well, you'll now be able to open events in a calendar in a modal dialog! ;)
<br />
<br />
If you have any questions or if you are having problems with the code (not working, errors, unwanted behavior,...) feel free to ask! Unknownnoreply@blogger.com3België50.503887 4.469935999999961547.917913 -0.69363800000003817 53.089861 9.633509999999962tag:blogger.com,1999:blog-4768615780556150.post-5412980653604833352013-10-25T14:25:00.001+02:002014-02-05T12:30:34.053+01:00How to make an accordion menu for a subsite in SharePoint 2013If 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. <br />
<br />
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.<br />
<br />
Here's a screenshot of a term driven navigation with all the terms and underlaying terms visible, no accordion used yet:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj33599FbFsZleV_BI_ydTgq5ppxaWri9MoDvl_001Yuf9Hf5DH6YAiYyafLikXQZ_cJpiK9vh4Ivl_GOsp_EjIK_fTLi2fTeDdUNHdUrLdN8SZ8lH_dZZS1LcDhvN9Q7080xjHd_cQ0Bk/s1600/Non-dynamic+menu.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj33599FbFsZleV_BI_ydTgq5ppxaWri9MoDvl_001Yuf9Hf5DH6YAiYyafLikXQZ_cJpiK9vh4Ivl_GOsp_EjIK_fTLi2fTeDdUNHdUrLdN8SZ8lH_dZZS1LcDhvN9Q7080xjHd_cQ0Bk/s1600/Non-dynamic+menu.png" /></a></div>
<br />
What we want is a navigation that looks like this:<br />
<table style="margin-left: auto; margin-right: auto;">
<tbody>
<tr>
<td style="vertical-align: top;"><div class="separator" style="clear: both; vertical-align: top;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPF3Fxi31BLomVGRw0K-2eGN34GCKBq1Hg44yvSXw6yPysNx3VWu0UgP5FRM4gn0xQnERtdzsZAoioOd1_abAHxatl7RNnX1GAINtq_RHD2huq17m_boWXTNQnkvBHvmDJnu8_WsPY1QQ/s1600/Stage+1+menu.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPF3Fxi31BLomVGRw0K-2eGN34GCKBq1Hg44yvSXw6yPysNx3VWu0UgP5FRM4gn0xQnERtdzsZAoioOd1_abAHxatl7RNnX1GAINtq_RHD2huq17m_boWXTNQnkvBHvmDJnu8_WsPY1QQ/s1600/Stage+1+menu.png" /></a> =></div>
</td>
<td style="vertical-align: top;"><div class="separator" style="clear: both; vertical-align: top;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7OUprQQq3HhinPlka49yksblk1Ikta2JREKVPsQSASRnvXbEvs8OaoskHtHkVuqVGQVq_BP9fC2WHkMQhKnWZbxAEZPFyXYtmzvocfpTPr0iNQ9MAe4bGb-GhCRciPOuBm48vlyyeeSc/s1600/Stage+2+menu.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7OUprQQq3HhinPlka49yksblk1Ikta2JREKVPsQSASRnvXbEvs8OaoskHtHkVuqVGQVq_BP9fC2WHkMQhKnWZbxAEZPFyXYtmzvocfpTPr0iNQ9MAe4bGb-GhCRciPOuBm48vlyyeeSc/s1600/Stage+2+menu.png" /></a> =></div>
</td>
<td style="vertical-align: top;"><div class="separator" style="clear: both;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyA3qI-WK8iU4yJGCIjygQphV_g4c8gW83yULWZdwydmOnYUXHQcvBwcYTyZkjAE5AAJoSNqKtBSeNah_F3unb-OMk9zLdgdJqtDzvYTqgdJpdSf0Qp0ayJy5IXhk6LEqaVtmQDDD9xT8/s1600/Stage+3+menu.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyA3qI-WK8iU4yJGCIjygQphV_g4c8gW83yULWZdwydmOnYUXHQcvBwcYTyZkjAE5AAJoSNqKtBSeNah_F3unb-OMk9zLdgdJqtDzvYTqgdJpdSf0Qp0ayJy5IXhk6LEqaVtmQDDD9xT8/s1600/Stage+3+menu.png" /></a></div>
</td>
</tr>
</tbody></table>
<br />
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.<br />
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.<br />
<br />
<h3>
And now the real work.</h3>
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 "<b>~sitecollection/Style Library/Scripts/script.js</b>". You will also need jQuery, so make sure you have that as well. My jQuery file its path is "<b>~sitecollection/Style Library/Scripts/jquery-1.10.2.min.js</b>".<br />
<br />
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 "<b>ScriptLink</b>" lines. The code below has five ScriptLink references (references to default SharePoint scripts), the code you must add has to be after those scripts.<br />
<pre class="line-numbers"><code class="language-javascript">// 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"/>--></code></pre>
<br />
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).<br />
<br />
<h3>
Let's start with the script.
</h3>
This is what the code in my script.js file looks like. I will try to add comments in the code.<br />
<pre class="line-numbers"><code class="language-javascript">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);
}
}
});
}</code></pre>
<br />
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 <b>find </b>the following piece of code:
<br />
<pre class="line-numbers"><code class="language-javascript"><script type="text/javascript">
//<![CDATA[
var g_pageLoadAnimationParams = { elementSlideIn : "sideNavBox",
elementSlideInPhase2 : "contentBox" };
//]]>
</script></code></pre>
<br />
We <b>will not change that part</b>, but we will have to paste some code underneath it. Here's the code: <br />
<pre class="line-numbers"><code class="language-javascript"><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></code></pre>
<br />
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.
<br />
<br />
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!<br />
<br />
<br />
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!Unknownnoreply@blogger.com14België50.503887 4.469935999999961550.503887 4.4699359999999615 50.503887 4.4699359999999615tag:blogger.com,1999:blog-4768615780556150.post-34998405604145348832013-10-25T11:48:00.003+02:002014-02-05T12:31:12.602+01:00How to show more than three levels of sub-menu items in a subsite navigation in SharePoint 2013<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFfeqyqbbvgzV-eQI0NfdmAAAQo0ZI1mYhX5-rWZY7C37y7-U8LHCH-y3psfbdanqexUomk1dT-8lt6cVnIfYzr4gZeYdqYIYizyX0y6yKzReJ8__m6Xt7ipjelhJSfVDHKFXXLTdNzBA/s1600/Levels+in+navigation.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFfeqyqbbvgzV-eQI0NfdmAAAQo0ZI1mYhX5-rWZY7C37y7-U8LHCH-y3psfbdanqexUomk1dT-8lt6cVnIfYzr4gZeYdqYIYizyX0y6yKzReJ8__m6Xt7ipjelhJSfVDHKFXXLTdNzBA/s1600/Levels+in+navigation.png" /></a>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).<br />
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.<br />
<br />
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).<br />
<br />
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.<br />
Anyway, I'll demonstrate what you need to do. I will use the code of the <b>oslo.html</b> master page.<br />
<br />
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:<br />
<pre class="line-numbers"><code class="language-javascript"><!--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"/>--></code></pre>
<br />
Reformat it so that it looks like this:<br />
<pre class="line-numbers"><code class="language-javascript"><!--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"/>--></code></pre>
<br />
Now in order to get them to display from top to bottom, you have to change the value of Orientation from <b>Orientation:"Horizontal"</b> to <b>Orientation:"Vertical"</b>. <br />
To increase the amount of levels to display, change the value of StaticDisplayLevels from <b>StaticDisplayLevels="1"</b> to <b>StaticDisplayLevels="6"</b> and change the value of MaximumDisplayLevels from <b>MaximumDynamicDisplayLevels="2"</b> to <b>MaximumDisplayLevels="6"</b>. <br />
Your code should now look like this:<br />
<pre class="line-numbers"><code class="language-javascript"><!--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"/>--></code></pre>
<br />
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. ;)
<br />
<br />
Any questions, let me know.Unknownnoreply@blogger.com1België50.503887 4.469935999999961547.917913 -0.69363800000003817 53.089861 9.633509999999962tag:blogger.com,1999:blog-4768615780556150.post-89352680809784408222013-10-23T11:30:00.000+02:002014-02-05T12:31:24.928+01:00How to make a contact form in SharePoint and submit the messages to a SharePoint listI 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.<br />
<div>
<br /></div>
<div>
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. </div>
<div>
<br />
Here are some screenshots of what it will look like.<br />
<br />
The contact page with the contact form:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibcdTVFnMX4NEd0ThfFE-KO7q5XtPJ8Xwu56fnLMRaokW_-Mo9wTt_eiHADVFdSTDdBxS6JaykEcWi268-eBmylgdIPb3yjggE4EbRzdlSUQGrqhJbRmyE0dek48kzUt9f7LId3DOrQCw/s1600/contact.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibcdTVFnMX4NEd0ThfFE-KO7q5XtPJ8Xwu56fnLMRaokW_-Mo9wTt_eiHADVFdSTDdBxS6JaykEcWi268-eBmylgdIPb3yjggE4EbRzdlSUQGrqhJbRmyE0dek48kzUt9f7LId3DOrQCw/s400/contact.png" height="161" width="400" /></a></div>
<br />
The contact form when you try to click send without filling in anything:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhs5WXRYf5Lb0at4etbbu6Yk1ZQjmy-glxb_f7rwrK51EmZlnGjFVXXWXzHI9CG9DCA3zuKbrrJ6vySzqabyYwYgEPRPbzK3H0XhEIZ5C7LCKpE2Peq5F6TLH_l_d2bZKIvOpf4coTxw8A/s1600/contact-fail.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhs5WXRYf5Lb0at4etbbu6Yk1ZQjmy-glxb_f7rwrK51EmZlnGjFVXXWXzHI9CG9DCA3zuKbrrJ6vySzqabyYwYgEPRPbzK3H0XhEIZ5C7LCKpE2Peq5F6TLH_l_d2bZKIvOpf4coTxw8A/s400/contact-fail.png" height="103" width="400" /></a></div>
<br />
The contact form when you filled in the fields and pressed send:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ7DsHn3PM2hNN4sckM6DZr4N4eQqLMZMzS5oLkH5_IRwEQHzs5LBNypw2I1OXq8lXTSzyKQwBGTieSNPgyWnFOnDWL9Y9rOlZB-pew8hdujoJi6Wm7i9ChudV3eysy_9aqZRQsu5ty_Q/s1600/contact-success.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ7DsHn3PM2hNN4sckM6DZr4N4eQqLMZMzS5oLkH5_IRwEQHzs5LBNypw2I1OXq8lXTSzyKQwBGTieSNPgyWnFOnDWL9Y9rOlZB-pew8hdujoJi6Wm7i9ChudV3eysy_9aqZRQsu5ty_Q/s400/contact-success.png" height="103" width="400" /></a></div>
<br />
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).<br />
<br />
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:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIRx5lGBK0xCklV4L7FQrv1gLiU7VpUllL5Z8260dLGSYVpXokH-CcRUON2rjLN4yXhlFh6xygckeaFSJrl95CaX3AyeJc6B3jEgvXOrBJZTAW5R6NwCuphetzldvakwdTeN-F6Sq-GmQ/s1600/contact-overview.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIRx5lGBK0xCklV4L7FQrv1gLiU7VpUllL5Z8260dLGSYVpXokH-CcRUON2rjLN4yXhlFh6xygckeaFSJrl95CaX3AyeJc6B3jEgvXOrBJZTAW5R6NwCuphetzldvakwdTeN-F6Sq-GmQ/s320/contact-overview.png" height="124" width="320" /></a></div>
<br />
Depending on the status of the message, it can be found under either "<b>Sent</b>", "<b>Received</b>" or "<b>Answered</b>". These three tabs are three web parts with different views. <br />
<br />
<h2>
Let's begin. </h2>
<b>First of all</b>, you will need to make a SharePoint list in which you want to store all the messages.<br />
<div>
My list is named "<b>Contact</b>". <br />
I created 7 new columns: "<b>Date</b>", "<b>Name</b>", "<b>Email</b>", "<b>Title</b>", "<b>Message</b>", "<b>Status</b>" and "<b>Answer</b>". 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. </div>
<div>
<br />
Here's an overview of all the columns your list will need:<br />
<table>
<tbody>
<tr>
<th>Column</th><th>Type</th><th>Used for</th>
</tr>
<tr>
<td>Date</td><td>Date and Time</td><td>Storing the date and time of the message</td>
</tr>
<tr>
<td>Name</td><td>Single line of text</td><td>Storing the name of the user who sent the message</td>
</tr>
<tr>
<td>Email</td><td>Single line of text</td><td>Storing the e-mail address of the user who sent the message</td>
</tr>
<tr>
<td>Title</td><td>Single line of text</td><td>Storing the subject of the message</td>
</tr>
<tr>
<td>Message</td><td>Multiple lines of text</td><td>Storing the message</td>
</tr>
<tr>
<td>Status</td><td>Choice</td><td>Setting the status of the message (Sent/Received/Answered)</td>
</tr>
<tr>
<td>Answer</td><td>Multiple lines of text</td><td>Storing the answer of the administrator</td>
</tr>
</tbody></table>
<br />
<br />
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". <br />
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.
</div>
<br />
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):<br />
<pre class="line-numbers"><code class="language-markup"><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></code></pre>
<br />
Put a script editor web part at the bottom of the <i>Contact</i> 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):<br />
<pre class="line-numbers"><code class="language-javascript"><script type="text/javascript"
src="~sitecollection/_catalogs/masterpage/MasterPageGallery/Contact.js">
</script></code></pre>
<br />
For the <i>Contact overview</i> page, edit the source and add the following code: <br />
<pre class="line-numbers"><code class="language-markup"><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></code></pre>
<br />
Then put a script editor web part at the bottom of your <i>Contact overview</i> page and include the following code:<br />
<pre class="line-numbers"><code class="language-javascript"><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></code></pre>
<br />
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): <br />
<pre class="line-numbers"><code class="language-css">#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;
}</code></pre>
<br />
All right! We have now set up the <i>Contact</i> page and the <i>Contact overview</i> page and styled them. Don't forget to add the content editor web parts to the <i>Contact overview</i> page.
<br />
<br />
<h2>
Now all we need is functionality!</h2>
Create a JavaScript file, I created mine in SharePoint Designer 2013 and it is located in <i>_catalogs/masterpage/MasterPageGallery/Contact.js</i>. You can put it somewhere else if you want to, but this is just where I put it.<br />
<br />
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!
<br />
<pre class="line-numbers"><code class="language-javascript">// 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();
})(); </code></pre>
<br />
<br />
That's it! With this code, you should now have a functional contact form. :)<br />
<br />
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.<br />
<br />
<br />
Regards,
<br />
<br />
Magali
</div>
Unknownnoreply@blogger.com3België50.503887 4.469935999999961547.917913 -0.69363800000003817 53.089861 9.633509999999962tag:blogger.com,1999:blog-4768615780556150.post-78349676761850183862013-10-22T16:30:00.000+02:002014-02-05T11:42:57.744+01:00Trying 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. :)<br />
<br />
<h3>
Perhaps a small introduction is in place!</h3>
My name is Magali. Yes, I am female.<br />
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. <br />
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.<br />
<br />
<h3>
Other than that, I also have a wide arrange of hobbies. </h3>
I'm often told that I have too many, but I enjoy them all. Variation, yay! Here's what I like to do:<br />
<ul>
<li>play the piano</li>
<li>watch series and movies</li>
<li>go to the movies</li>
<li>play video games (both on computer and consoles)</li>
<li>make things with perler beads in 8-bit style</li>
<li>sew hand-made felt plushies</li>
<li>craft things out of cardboard (mostly miniature furniture)</li>
<li>collecting Pokémon games</li>
<li>going for a swim</li>
<li>dozens of other things </li>
</ul>
<br />
<h3>
Also, I'm <b>geeky</b>. </h3>
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!). <br />
<br />
Anyway, please do enjoy my blog. Expect lots of coding, crafting, geeky creativity and cat pictures!<br />
<br />
<br />
- Magali<br />
<br />
<br />
PS: The cake IS a lie. No, seriously, did you see that recipe?Unknownnoreply@blogger.com0België50.503887 4.469935999999961547.917913 -0.69363800000003817 53.089861 9.633509999999962