zondag 24 maart 2013

Aanpassen van SharePoint lijst weergaven met JavaScript


Een tijdje geleden heb ik blogposts gemaakt over het aanpassen van de SharePoint user interface middels JavaScript (zie links onderaan deze blog post onder "Verder lezen"). Het ging toen over edit pages van lijst items waarin ik velden manipuleerde door er JavaScripts op los te laten waarmee ik, na het laden van de pagina, veldwaarden, gedragingen en opmaak kon wijzigen.

Onlangs vroeg een klant mij om een soort dashboard te maken waarin bij taken werd vermeld of ze op tijd danwel te laat waren afgerond. Iets dat je gemakkelijk met de juiste tools kunt maken. Helaas had ik ook in dit geval weer niet de beschikking over deze tools (en ook niet de juiste rechten), zodat ik weer moest terugvallen op de "dirty JavaScript tricks" die ik eerder had gebruikt.

Reden om na de opdracht maar weer eens een blog post te schrijven... hieronder het recept van mijn oplossing...

Ingredienten
- Een lege web part pagina.
- Een takenlijst met "due date" (datum/tijd) waarop een taak afgerond moet zijn.
- Een stukje JavaScript waarvan ik de werking hieronder zal toelichten.
- Een Image library met daarin de plaatjes voor de LED's.

Doel
Een lijst met daarin voor iedere taak, waarvan de "due date" datum/tijd kleiner is dan de huidige datum/tijd (today), een indicatie dat de taak niet op tijd is afgerond. In dit geval heb ik gekozen voor een rood LEDje (lampje).

Probleem
In SharePoint is het niet mogelijk om de huidige datum/tijd (Today) te gebruiken in een berekende kolom. Het is dus helaas niet mogelijk om in een berekend veld aan te geven of een taak te laat is afgerond.

Oplossing

Stap 1: Uitbreiden van de takenlijst met een extra kolom met een gemakkelijk te herkennen berekende waarde
Oplossing die ik hiervoor heb gekozen is om een aparte kolom toe te voegen waarin ik de "due date" in een eigen formaat weergeef. Dit formaat heb ik gekozen om er zeker van te zijn dat de datum - en tijd delen (jaar, maand, dag etc.) in een vaste volgorde worden weergegeven. Afhankelijk van instellingen kan het datum formaat namelijk anders zijn dan gewenst. Verder wil ik dat de kolomwaarden van de "due date" gemakkelijk door JavaScript zijn te herkennen zodat ik deze gemakkelijk kan inlezen en gebruiken. Hiervoor heb ik een berekende kolom gemaakt met de volgende formule:

=TEXT(DueDate,"~yyyy~m~d~h~m~s~")

Hierin wordt de DueDate omgezet in een string waarde met daarin het jaar in 4 karakters, de maand in minimaal 1 karakter, et cetera.

Dit resulteert in kolomwaarden zoals: <TD Class="ms-vb2">~2013~3~22~8~30~0~</TD>

In een SharePoint lijst zijn deze velden dus gemakkelijk te herkennen:
<TD Class="ms-vb2">~2013~3~22~8~30~0~</TD>

Stap 2: Aanmaken van een web part pagina met daarin de lijst en een Content Editor Web Part
De lijst moet zichtbaar zijn in een web pagina. Gezien de lijst moet worden aangepast met JavaScript is een Content Editor Web Part nodig waarin we het JavaScript plaatsen waarmee de lijst wordt aangepast. Van belang is dat het Content Editor Web Part onder de lijst wordt geplaatst. Reden hiervoor is dat de DOM nodes in de lijst beschikbaar moeten zijn voor het JavaScript.

Het JavaScript dat ik het Content Editor Web Part wordt geplaatst heeft de volgende functionaliteit:
- GetAllListFields: functie die de speciale lijstvelden opneemt in een array (vIconListFields). Zo kunnen we ze gemakkelijk gebruiken. Leuke extra uitdaging hierbij is dat je hiervoor de getElementsByClassName functie nodig hebt die in IE niet voorhanden is. Daarvoor heb ik een aparte functie gemaakt.
- ConvertFieldsToIcons: functie die de objecten in de vIconListFields array uitleest, bepaalt of de data/tijden daarin eerder of later vallen dan de huidige datum/tijd, en de HTML code in de objecten vervangt door een rood of groen plaatje (LED).

Omdat ik het niet kon laten moest ik de LED's ook laten animeren. Een rode LED wordt geanimeerd zodat het gaat flikkeren. Hiertoe laat ik het plaatje middels een setInterval JavaScript call varieren.

De volledige JavaScript code hieronder, happy coding!

<script language="JavaScript">

// ----------------------------------------
    // Global constants and variables
var vIconListFields = new Array();

// ----------------------------------------
// Functions
    function GetAllListFields(ioIconListFields) {

        // Look for nodes like &lt;TD Class="ms-vb2"&gt;~2013~3~22~8~30~0~&lt;/TD&gt;
 
   vAllMsVb2Nodes = document.getElementsByClassName("ms-vb2");

for (var vIndex = 0; vIndex &lt; vAllMsVb2Nodes.length; ++vIndex ) {

if ( 
   vAllMsVb2Nodes[vIndex].innerText.substring(0,1) == "~" &&
vAllMsVb2Nodes[vIndex].innerText.substring(vAllMsVb2Nodes[vIndex].innerText.length-1, vAllMsVb2Nodes[vIndex].innerText.length) == "~"
) {
   // Add node to Fields array
ioIconListFields[ioIconListFields.length] = vAllMsVb2Nodes[vIndex];
}

}

    }

function ConvertFieldsToIcons(ioIconListFields) {

   var vCurrentDateTime = new Date();
var vTimeDifference;

for (var vIndex = 0; vIndex &lt; ioIconListFields.length; ++vIndex ) {

// Get date in DOM field into Date object to compare with current date/time
var vDateTimeArray = ioIconListFields[vIndex].innerText.split("~");
   var vDateTime = new Date(vDateTimeArray[1], vDateTimeArray[2], vDateTimeArray[3], vDateTimeArray[4], vDateTimeArray[5], vDateTimeArray[6], 0);

vTimeDifference = vDateTime.getTime() - vCurrentDateTime.getTime();

// Change contents off DateTime cell according to TimeDifference
if (vTimeDifference&gt;=0) {
   ioIconListFields[vIndex].innerHTML = "&lt;img src='http://portal.demo.intra/DigitaalLoket/StatusIcons/icon_green.png'/&gt;";
}
else {
  ioIconListFields[vIndex].innerHTML = "&lt;img class='AnimatedLED' src='http://portal.demo.intra/DigitaalLoket/StatusIcons/icon_red.png'/&gt;";
}

// Dispose
delete vDateTime;
}

}

function changeImage()
{

if ( vCurrentImageFileIndex &lt; vImageFiles.length - 1 )
++vCurrentImageFileIndex;
else
vCurrentImageFileIndex = 0;

for ( var i = 0, len = vImageObjects.length; i &lt; len; i++ ) {
vImageObjects[i].src = vImageFiles[vCurrentImageFileIndex];
}

}
   
// ----------------------------------------
   // Helper functions

   // Implement getElementsByClassName because IE does not support this natively

if (!document.getElementsByClassName) {

document.getElementsByClassName = function(classname) {
var elArray = [];
var tmp = document.getElementsByTagName("*");
var regex = new RegExp("(^|\\s)" + classname + "(\\s|$)");
for (var i = 0; i &lt; tmp.length; i++) {

if (regex.test(tmp[i].className)) {
elArray.push(tmp[i]);
}
}

return elArray;
};
}

// ----------------------------------------
// Main procedure

    GetAllListFields(vIconListFields);
ConvertFieldsToIcons(vIconListFields);

// Initialize animation function

var vImageFiles = new Array();
vImageFiles[0] = "http://portal.demo.intra/DigitaalLoket/StatusIcons/icon_clear.png";
vImageFiles[1] = "http://portal.demo.intra/DigitaalLoket/StatusIcons/icon_red.png";

var vCurrentImageFileIndex = 0;
var vImageObjects = document.getElementsByClassName("AnimatedLED");

setInterval("changeImage()", 500);

</script>

Verder lezen
SharePoint 2010 Content Editor Web part maakt rommel van mijn JavaScriptjes
Aanpassen van lijst- en bibliotheekformulieren in SharePoint 2010, zonder SharePoint Designer of InfoPath
Aanpassen van standaard webpagina's voor nieuwe items en bewerken van items: een generieke aanpak