J’avais envie de découvrir KnockoutJS (KO) et coder la même fonctionnalité m’a paru être un bon exercice pratique.

Première différence : comme KO préfère le JSON, j’ai fait appel aux fonctionnalités de suivi avec des appels REST plutôt que par le ClientContext.

Deuxième différence : l’utilisation de MVVM (Modèle-Vue-VueModèle) propose un confort certains à la lecture et la maintenance du code.

Dans ce cadre d’utilisation et vu mes connaissances, KO m’a bien plu. Simple à appréhender, il permet d’avoir un code plus propre sans trop d’effort. Le nombre de ligne est sensiblement le même mais comme c’est plus lisible et maintenable, on gagne à l’utiliser.

Astuce :

Lorsque l’on utilise la méthode POST dans une page SharePoint, il faut penser à passer le formDigest au risque d’avoir pour réponse : The security validation for this page is invalid »

var formDigest = $("[name='__REQUESTDIGEST']").val();
$.ajax({
headers: { "X-RequestDigest": formDigest },

Les lignes nécessaires sont surlignées dans le code JavaScript.

<ul class="ms-core-menu-list" id="custom-sites-menu">
    <LI>
        <DIV class="subcontainer" data-bind="foreach: sites">
            <DIV data-bind="attr: { id: CleanId }">
                <SPAN class=ms-core-menu-link>
                    <A data-bind="attr: { href: Uri }"><span data-bind="text: Name"></span></A>
                    <A data-bind="click: $root.removeFollowedSite" >
                        <IMG src="/_layouts/15/images/CAA.RSE.Communities/UnfollowWhite.png" width=20 height=20>
                    </A>
                </SPAN>
            </DIV>
        </DIV>
    </LI>
    <LI class=clear>
        <a class="ms-core-menu-link footer" data-bind="attr: { href: personnalSite}">
            Ma page de suivi
        </a>
    </LI>
</ul>

<script type='text/javascript' src='/_layouts/15/CAA.RSE.Communities/js/knockout-3.1.0.js'></script>
<script src="/_layouts/15/CAA.RSE.Communities/js/gla-followedsites-ko.js" type="text/javascript"></script>
<link href="/_layouts/15/CAA.RSE.Communities/css/gla-followedsites.css" rel="stylesheet" type="text/css"/>
// AJAX - Post data to stop following a site
function stopFollowingSite(siteUrl, cleanSiteId) {
    var stopFollowingUrl = "http://sps2013/sites/test/_api/social.following/stopfollowing(ActorType=2,ContentUri=@v,Id=null)?@v='"+ siteUrl + "'"
    var formDigest = $("[name='__REQUESTDIGEST']").val();
    $.ajax({
        url: stopFollowingUrl,
        type: "POST",
        headers: { "ACCEPT": "application/json;odata=verbose", "X-RequestDigest": formDigest },
        success: function(data){},
        error: function (xhr, ajaxOptions, thrownError) {
            alert(xhr.status + " : " + thrownError);
            //alert(xhr.responseText);
        }
    });
}

// wrapper
function FollowedSite(data) {
    this.Name = ko.observable(data.Name);
    this.Uri = ko.observable(data.Uri);
    this.CleanId = ko.computed(function() {return data.Id.split(".").join("");}, this);
}

function TaskListViewModel() {
    // Data
    var self = this;
    self.sites = ko.observableArray([]);
    self.personnalSite = ko.observable();
    self.removeFollowedSite = function(site) {
        stopFollowingSite(site.Uri(), site.CleanId());
        self.sites.remove(site)
    };
    // AJAX - Get all sites the current user is following
    var requestFollowedUri = _spPageContextInfo.webAbsoluteUrl + "/_api/social.following/my/followed(types=4)";
    $.ajax({
        url: requestFollowedUri,
        type: "GET",
        headers: { "ACCEPT": "application/json;odata=verbose" },
        success: function(data){
            var mappedFollowedSites = $.map(data.d.Followed.results, function(item) { return new FollowedSite(item) });
            self.sites(mappedFollowedSites);
        },
        error: function(){alert("Failed to get followed sites.");
        }
    });
    // AJAX - Get current user's personnal site
    var requestFollowedSiteUri = _spPageContextInfo.webAbsoluteUrl + "/_api/social.following/my/followedsitesuri";
    $.ajax({
        url: requestFollowedSiteUri,
        type: "GET",
        headers: { "ACCEPT": "application/json;odata=verbose" },
        success: function(data){self.personnalSite(data.d.FollowedSitesUri);},
        error: function(){alert("Failed to get personnel site.");
        }
    });
}

ko.applyBindings(new TaskListViewModel());

jQuery(document).ready(function () {
    //add link in SuiteLink
    var navU = jQuery("#suiteLinksBox > ul.ms-core-suiteLinkList");
    var addNode = jQuery("<a id='Suite_CustomSites_ShellAddNew' href='#' class='ms-core-suiteLink-a' />")
    .append(jQuery("<span/>")
        .text("Communaut351s")
        .append(jQuery("<span class='ms-suitenav-downarrowBox'/>")
                .append(jQuery("<img class='ms-suitenav-downarrowIcon' src='/_layouts/15/images/spcommon.png?rev=23' />"))
        )
    );
    // retrieve KO generated menu
    var menu = jQuery("#custom-sites-menu");
    
    newLi = jQuery("<li/>").attr("class", "ms-core-suiteLink")
        .append(addNode)
        .append(menu);
    navU.prepend(newLi);
});

Références:

Afficher les sites suivis dans le bandeau

Afficher les sites suivis dans le bandeau – 2

[J’avais envie de découvrir KnockoutJS (KO) et coder la même fonctionnalité m’a paru être un bon exercice pratique.

Première différence : comme KO préfère le JSON, j’ai fait appel aux fonctionnalités de suivi avec des appels REST plutôt que par le ClientContext.

Deuxième différence : l’utilisation de MVVM (Modèle-Vue-VueModèle) propose un confort certains à la lecture et la maintenance du code.

Dans ce cadre d’utilisation et vu mes connaissances, KO m’a bien plu. Simple à appréhender, il permet d’avoir un code plus propre sans trop d’effort. Le nombre de ligne est sensiblement le même mais comme c’est plus lisible et maintenable, on gagne à l’utiliser.

Astuce :

Lorsque l’on utilise la méthode POST dans une page SharePoint, il faut penser à passer le formDigest au risque d’avoir pour réponse : The security validation for this page is invalid »

var formDigest = $("[name='__REQUESTDIGEST']").val();
$.ajax({
headers: { "X-RequestDigest": formDigest },

Les lignes nécessaires sont surlignées dans le code JavaScript.

<ul class="ms-core-menu-list" id="custom-sites-menu">
    <LI>
        <DIV class="subcontainer" data-bind="foreach: sites">
            <DIV data-bind="attr: { id: CleanId }">
                <SPAN class=ms-core-menu-link>
                    <A data-bind="attr: { href: Uri }"><span data-bind="text: Name"></span></A>
                    <A data-bind="click: $root.removeFollowedSite" >
                        <IMG src="/_layouts/15/images/CAA.RSE.Communities/UnfollowWhite.png" width=20 height=20>
                    </A>
                </SPAN>
            </DIV>
        </DIV>
    </LI>
    <LI class=clear>
        <a class="ms-core-menu-link footer" data-bind="attr: { href: personnalSite}">
            Ma page de suivi
        </a>
    </LI>
</ul>

<script type='text/javascript' src='/_layouts/15/CAA.RSE.Communities/js/knockout-3.1.0.js'></script>
<script src="/_layouts/15/CAA.RSE.Communities/js/gla-followedsites-ko.js" type="text/javascript"></script>
<link href="/_layouts/15/CAA.RSE.Communities/css/gla-followedsites.css" rel="stylesheet" type="text/css"/>
// AJAX - Post data to stop following a site
function stopFollowingSite(siteUrl, cleanSiteId) {
    var stopFollowingUrl = "http://sps2013/sites/test/_api/social.following/stopfollowing(ActorType=2,ContentUri=@v,Id=null)?@v='"+ siteUrl + "'"
    var formDigest = $("[name='__REQUESTDIGEST']").val();
    $.ajax({
        url: stopFollowingUrl,
        type: "POST",
        headers: { "ACCEPT": "application/json;odata=verbose", "X-RequestDigest": formDigest },
        success: function(data){},
        error: function (xhr, ajaxOptions, thrownError) {
            alert(xhr.status + " : " + thrownError);
            //alert(xhr.responseText);
        }
    });
}

// wrapper
function FollowedSite(data) {
    this.Name = ko.observable(data.Name);
    this.Uri = ko.observable(data.Uri);
    this.CleanId = ko.computed(function() {return data.Id.split(".").join("");}, this);
}

function TaskListViewModel() {
    // Data
    var self = this;
    self.sites = ko.observableArray([]);
    self.personnalSite = ko.observable();
    self.removeFollowedSite = function(site) {
        stopFollowingSite(site.Uri(), site.CleanId());
        self.sites.remove(site)
    };
    // AJAX - Get all sites the current user is following
    var requestFollowedUri = _spPageContextInfo.webAbsoluteUrl + "/_api/social.following/my/followed(types=4)";
    $.ajax({
        url: requestFollowedUri,
        type: "GET",
        headers: { "ACCEPT": "application/json;odata=verbose" },
        success: function(data){
            var mappedFollowedSites = $.map(data.d.Followed.results, function(item) { return new FollowedSite(item) });
            self.sites(mappedFollowedSites);
        },
        error: function(){alert("Failed to get followed sites.");
        }
    });
    // AJAX - Get current user's personnal site
    var requestFollowedSiteUri = _spPageContextInfo.webAbsoluteUrl + "/_api/social.following/my/followedsitesuri";
    $.ajax({
        url: requestFollowedSiteUri,
        type: "GET",
        headers: { "ACCEPT": "application/json;odata=verbose" },
        success: function(data){self.personnalSite(data.d.FollowedSitesUri);},
        error: function(){alert("Failed to get personnel site.");
        }
    });
}

ko.applyBindings(new TaskListViewModel());

jQuery(document).ready(function () {
    //add link in SuiteLink
    var navU = jQuery("#suiteLinksBox > ul.ms-core-suiteLinkList");
    var addNode = jQuery("<a id='Suite_CustomSites_ShellAddNew' href='#' class='ms-core-suiteLink-a' />")
    .append(jQuery("<span/>")
        .text("Communaut351s")
        .append(jQuery("<span class='ms-suitenav-downarrowBox'/>")
                .append(jQuery("<img class='ms-suitenav-downarrowIcon' src='/_layouts/15/images/spcommon.png?rev=23' />"))
        )
    );
    // retrieve KO generated menu
    var menu = jQuery("#custom-sites-menu");
    
    newLi = jQuery("<li/>").attr("class", "ms-core-suiteLink")
        .append(addNode)
        .append(menu);
    navU.prepend(newLi);
});

Références:

Afficher les sites suivis dans le bandeau

Afficher les sites suivis dans le bandeau – 2

](http://msdn.microsoft.com/fr-fr/library/office/dn194080%28v=office.15%29.aspx)

KnockoutJS

Découvrir KnockoutJS