SharePoint list item popovers on hover with AngularJS

Here’s how you can create Bootstrap style popovers of SharePoint list items using AngularJS.

<!DOCTYPE html>
<head>
<link rel=”stylesheet” href=”bootstrap.css” />
<link rel=”stylesheet” href=”font-awesome.css” />

<script type=”text/javascript” src=”modernizr.custom.js”></script>
<script type=”text/javascript” src=”jquery.js”></script>
<script type=”text/javascript” src=”angular.min.js”></script>
<script type=”text/javascript” src=”bootstrap.js”></script>
<script type=”text/javascript” src=”ui-bootstrap-tpls.min.js”></script>

</head>
<body ng-app=”toolTips”>

<div ng-controller=”mainController”>
<table>
<tr ng-repeat=”faq in faqs” bs-popover>
<td>
<a href=”#” data-popover=”true” rel=”popover” data-content=”{{faq.Body}}” data-original-title=”{{faq.Title}}”>
{{faq.Title}}
</a>
</td>
</tr>
</table>
</div>

<script type=”text/javascript”>
var app = angular.module(‘toolTips’, [‘ui.bootstrap’]);
app.controller(‘mainController’, function($scope, $http, $sce) {

$http({
method: ‘GET’,
url: “/_api/web/lists/getByTitle(‘someList)/items?$orderby=Title asc”,
cache: true,
headers: { “Accept”: “application/json;odata=verbose” }
}).success(function (data, status, headers, config) {
$scope.faqs = data.d.results;
//$scope.trustAsHtml = $sce.trustAsHtml;
$(“#contentLoading”).hide();

}).error(function (data, status, headers, config) {

});

});

app.directive(‘bsPopover’, function() {
return function(scope, element, attrs) {
element.find(“a[rel=popover]”).popover({ placement: ‘bottom’, html: ‘true’});
};
});

$(‘body’).popover({ selector: ‘[data-popover]’, trigger: ‘click hover’, placement: ‘auto’, delay: {show: 50, hide: 2000}});
</script>

</body>
</html>

 

Testing AngularJS on SharePoint: 1 Installing Protractor on Windows

protractor-logo

1: Install Protractor and Webdriver-Manager

Open a Node command prompt
protractorinstall1

Two tools will be installed when you run “npm install -g protractor“.

Test your install by running “protractor –version”

2: Update Webdriver-Manager

Next, download some needed binaries with “webdriver-manager update

3: Start the server

Start the server with “webdriver-manager start“.

What you should have now is a running instance of Selenium. Protractor tests send their requests to this server which controls the local browser. Status information should be visible here: http://localhost:4444/wd/hub

4: Create a simple test

This will require two files. One with some basic configuration information “conf.js” and another with a simple test “todo-spec.js”. Put the following in each respective file.

conf.js

        exports.config = {
            seleniumAddress: ‘http://localhost:4444/wd/hub’,
            specs: [‘todo-spec.js’]
        };

todo-spec.js

describe(‘angularjs homepage todo list’, function() {
    it(‘should add a todo’, function() {
        browser.get(‘https://angularjs.org’);

        element(by.model(‘todoList.todoText’)).sendKeys(‘write first protractor test’);
        element(by.css(‘[value=”add”]’)).click();

        var todoList = element.all(by.repeater(‘todo in todoList.todos’));
        expect(todoList.count()).toEqual(3);
        expect(todoList.get(2).getText()).toEqual(‘write first protractor test’);

        // You wrote your first test, cross it off the list
        todoList.get(2).element(by.css(‘input’)).click();
        var completedAmount = element.all(by.css(‘.done-true’));
        expect(completedAmount.count()).toEqual(2);
    });
});

To run the test, from the Node command prompt, cd into the folder where you have saved the two files and execute the following “protractor conf.js“.

simpletest

Using stacktrace.js to log AngularJS errors to a SharePoint list

stacktrace.js: is a Framework-agnostic, micro-library for getting stack traces in all web browsers allowing you to debug your JavaScript by giving you a nicely detailed stack trace of function calls leading to an error (or any condition you specify).

http://www.stacktracejs.com/


1: Create a new list, in this example we will call it “apperrors” and it will be at the root. Create the following columns:

  • errorMessage as multiple lines without formatting.
  • stackTrace as multiple lines without formatting.

2: First include stacktrace.js after your AngularJS script.

<script type=”text/javascript” src=”angular-1.3.15/angular.min.js”></script>
<script type=”text/javascript” src=”stacktrace.min.js”></script>

3: By default AngularJS catches errors and log them verbosely to the console. This is highly desirable so we will keep that behavior and just add an interception in so that we can use stacktrace.js to centrally log any errors.

app.provider(“$exceptionHandler”, {
    $get: function( errorLogService ) {
        return( errorLogService );
    }
});

4: Even though since step 1 stacktrace.js is now in the global scope it is not ‘correct’ to reference a global object inside an AngularJS component. Therefore the stacktrace feature needs to be wrapped in an AngularJS service that will expose the print method.

app.factory(“stacktraceService”, function() {
    return({
        print: printStackTrace
    });

});

5: The error logging service is a wrapper around the core error handling of AngularJS.

app.factory(“errorLogService”, function( $log, $window, stacktraceService ) {
    function log( exception, cause ) {
        $log.error.apply( $log, arguments );
        try {
            var URL = window.location.href;
            var errorMessage = JSON.stringify(exception.toString());
            var stackTrace = JSON.stringify(stacktraceService.print({ e: exception }));
            var item = {
                “__metadata”: { “type”: “SP.Data.apperrorsListItem”},
                “Title”: URL,
                “errorMessage”: errorMessage,
                “stackTrace”: stackTrace
            };
            $.ajax({
                url: _spPageContextInfo.webAbsoluteUrl + “/_api/web/lists/getbytitle(‘apperrors’)/items”,
                type: “POST”,
                contentType: “application/json;odata=verbose”,
                data: JSON.stringify(item),
                headers: {
                        “Accept”: “application/json;odata=verbose”,
                       “X-RequestDigest”: $(“#__REQUESTDIGEST”).val()
                },
                success: function (data) {
                    console.log(JSON.stringify(data, null, 4));
                },
                error: function (data) {
                    console.log(JSON.stringify(data, null, 4));
                }
            });
            } catch ( loggingError ) {
                $log.warn( “Error logging failed” );
                $log.log( loggingError );
            }
    }
    // Return the logging function.
    return( log );
});

5: That’s it. In theory if all has gone correctly any AngularJS errors will now get written to the SharePoint list.

http://www.bennadel.com/blog/2542-logging-client-side-errors-with-angularjs-and-stacktrace-js.htm

https://angularjs.org/

http://www.stacktracejs.com/

Using PowerShell to export SharePoint list items into CSV and then delete the same items from the list

The below code can be used to export list items, if found, info column by column. It then deletes the items that it exported from the list.

if ((Get-PSSnapin “Microsoft.SharePoint.PowerShell” -ErrorAction SilentlyContinue) -eq $null) {
    Add-PSSnapin “Microsoft.SharePoint.PowerShell”
}

# BEGIN CONFIG

$fileNameStamp = Get-Date -UFormat “%Y_%m_%d_%H_%M”
$fileAndPath =”D:\DATA\myData-$fileNameStamp.csv”
$web = Get-SPWeb -identity “http://10.1.1.10/”
$list = $web.Lists[“MyList”]

# END CONFIG
# BEGIN: STEP 1: Export current items in the list to CSV
$listitems = $list.Items.Count;
# Break out if the list has no content. Stops the creation of empty files.
if ($listitems -lt 1) {
    break;
} else {
    #Array to Hold Result – PSObjects
    $ListItemCollection = @()
    #Get All List items”
    $list.Items | foreach {
       $ExportItem = New-Object PSObject
       $ExportItem | Add-Member -MemberType NoteProperty -name “ID” -value $_[“ID”]
       $ExportItem | Add-Member -MemberType NoteProperty -name “Title” -value $_[“Title”]
       $ExportItem | Add-Member -MemberType NoteProperty -name “Created” -value $_[“Created”]
       $ExportItem | Add-Member -MemberType NoteProperty -name “CreatedBy” -value $_[“Created By”]
       #Add the object with property to an Array
       $ListItemCollection += $ExportItem
    }
    #Export the result Array to CSV file
    $ListItemCollection | Export-CSV $fileAndPath -NoTypeInformation
}
# END: STEP 1: Export current items in the list to CSV

# BEGIN: STEP 2: Delete the exported items from the list
Import-CSV $fileAndPath -Header ID,Title | Foreach-Object{
    Write-Host ” Looking for item #” $_.ID “…”;

    $items = $list.Items | Where {$_[“ID”] -eq $_.ID}
    foreach($item in $items)
    {
        $item.Delete();
        break;
    }
}
# END: STEP 2: Delete the exported items from the list

#BEGIN: CLEANUP
$web.Dispose();
#END: CLEANUP

Storing SharePoint list data locally in IndexedDB with PouchDB and AngularJS

pouchdbPouchDB is an open-source JavaScript database inspired by Apache CouchDB that is designed to run well within the browser.

PouchDB was created to help web developers build applications that work as well offline as they do online. It enables applications to store data locally while offline, then synchronize it with CouchDB and compatible servers when the application is back online, keeping the user’s data in sync no matter where they next login.

The below code should give you a full life cycle of:

  1. Delete a DB if it exists
  2. Create a new DB
  3. Get SharePoint list data
  4. Add returned data to the IndexedDB
  5. Console out errors if issues are encountered.

 

var req = indexedDB.deleteDatabase(“demodata”);
req.onsuccess = function() {
    console.log(“Deleted database successfully”);
    // Create the client side DB
    var db = new PouchDB(‘demodata’);
    console.log(“DB created”);

    // Get info
    db.info().then(function (info) {
        console.log(info);
    });

    // BEGIN: Get list data
    $http({
        method: ‘GET’,
        url: _spPageContextInfo.webAbsoluteUrl + “/_api/web/lists/getByTitle(‘demolist’)/items”,
        cache: true,
        headers: {
            “Accept”: “application/json;odata=verbose”
        }
    }).success(function(data, status, headers, config) {
        var demodataValues = data.d.results;
        var demodata = {
            “_id”: “demo”,
            “demodataValues”: demodataValues
        };
        db.put(demodata);
        console.log(“Demo injected.”);
    }).error(function(data, status, headers, config) {

        console.log(“SP error”);

    });
    // END: Get list data
};
req.onerror = function() {
    console.log(“Couldn’t delete database”);
};
req.onblocked = function() {
    console.log(“Couldn’t delete database due to the operation being blocked”);
};

PowerShell to shrink SharePoint auditdata table

I recently had a content database that was out of control size wise because of the Auditdata table and needed it shrunk promptly. This piece of PowerShell did the job.

NOTE: This is a bit of a hammer so all things look like a nail approach but it may be good for your specific situation.

if ((Get-PSSnapin “Microsoft.SharePoint.PowerShell” -ErrorAction SilentlyContinue) -eq $null) {
   Add-PSSnapin “Microsoft.SharePoint.PowerShell”
}

foreach ($site in get-spsite -Limit ALL)
{
   Write-host ‘Deleting audit data for site: ‘ $sc.URL
   $i = -350
   do {
      Write-Host $site.URL ‘ – Delete day ‘ $i ‘ : ‘ ([System.DateTime]::Now.ToLocalTime().AddDays($i))
      $site.audit.deleteentries([System.DateTime]::Now.ToLocalTime().AddDays($i))
      $site.audit.update()
      $i++
   }
   while ($i -le 1)
   $site.Dispose()
}

SharePoint PowerShell script to check if a site collection is alive and email if not.

This might help somebody. In brief the script checks the array of URLs and if it gets a non 200 (OK) response back it emails out an alert. It can also be used as a keep alive script. Best run on a non farm connected server.

$urls= @("http://webapp/sitecollection1/Default.aspx",

"http://webapp/sitecollection2/default.aspx",

"http://webapp/sitecollection3/default.aspx",

"http://webapp/sitecollection4/default.aspx");

# Request all URLs in the array

foreach ($objItemin$urls) {

Write-Host“Checking $objItem”;

$req= [system.Net.WebRequest]::Create($objItem)

$req.UseDefaultCredentials=$true

try {

$res=$req.GetResponse()

} catch [System.Net.WebException] {

$res=$_.Exception.Response

}

$int= [int]$res.StatusCode

$status=$res.StatusCode

write-host“$int $status”

if ($int-ne 200) {

Write-Host”  Sending Email…”;

$enc  =New-ObjectSystem.Text.utf8encoding;

$smtp=”emailserver.domain.com”;

$to=”Recipient 1<username@domain.com>”;

$toCC=Recipient 2 <username@domain.com>”;

$from=”SharePoint farm <username@domain.com>”;

$ScriptName=$MyInvocation.MyCommand.Name;

$scriptPath=split-path-parent$MyInvocation.MyCommand.Definition;

$body=”This was generated by the script $ScriptName in $scriptPath”;

$subject=”URL check failure on $objItem – ‘$int : $status'”;

send-MailMessage-SmtpServer$smtp-To$to-Cc$toCC-From$from-Subject$subject-Body$body-BodyAsHtml-Encoding$enc;

Write-Host”  Sent.”;

}

}

Using AppFabric Distributed Cache on SharePoint 2013

After encountering some issues I thought I’d post a few bullets that may help someone.  They mostly cover memory and things to not do.

How to import your IIS logs into Piwik

Piwik is pretty awesome if you’re in the market for the functionality it provides. Unfortunately a lot of people who use IIS, SharePoint, .NET are not familiar with Python and may struggle with getting their IIS logs into Piwik. This should help if you are in that situation.

  1. Move your logs to a central location. PowerShell as a scheduled job is perfect for this. In this example let us assume it is D:LOGS
  2. Let us also assume that you are running Piwik on the local machine aka localhost on port 1000 and the ID of the site in Piwik is 1. These values are in the code below.
  3. Create a script called importFiles.py and paste the below into it. The TAB indents are pretty important.

import os, fnmatch, subprocess

def find_files(directory, pattern):
    for root, dirs, files in os.walk(directory):
        for basename in files:
            if fnmatch.fnmatch(basename, pattern):
                filename = os.path.join(root, basename)
                yield filename

for filename in find_files(‘C:LOGS’, ‘*.log’):
    #print (filename)
    os.system(“C:/inetpub/wwwroot/Piwik/misc/log-analytics/import_logs.py –url=http://localhost:1000/ ” +str(filename) +” –idsite=1″)

Notes about the above.

  • You must NOT install Python 3.x. Install 2.x. This is a compatibility issue with Piwik.
  • I use MariaDB. If you are not familiar with it think of it as simply as it being a fully compatible fork of MySQL.
  • You can definitely optimize this script further by reading the Piwik docs. This is about as basic as it can be and still get the job done.
  • This is recursive so if you are moving the logs into server named subfolders, a good idea if you have a farm, it should work just fine.
  • I would strongly recommend that you move the files to a “processed” folder outside of the path above once you’re done. 
  • Make sure to set index.php as your default document in the IIS site you are running Piwik from.
If you need to do over then you need to get the log file data out of the DB. to do so remove the appropriate tables and delete the appropriate values similar to below. More details are in the Piwik FAQ here.
DROP TABLE piwik_archive_numeric_2011_01, piwik_archive_numeric_XX;
DELETE FROM piwik_log_visit WHERE idsite = X;
DELETE FROM piwik_log_link_visit_action WHERE idsite
= X;
DELETE FROM piwik_log_conversion WHERE idsite
= X;
DELETE FROM piwik_log_conversion_item WHERE idsite
= X;
More info and relevant downloads can be gotten here: 

Searching the source of ALL pages in a SharePoint farm using PowerShell

Recently I had to find all instances of a jQuery specific reference in a small to mid sized farm. I had not had to do this before and after a few minutes I decided that PowerShell was the tool to use. Here’s what I cobbled together. It’s neither great nor finished but it is probably useful for somebody right now.

###########################################################################################################################
# BEGIN: Get all pages ASPX & HTM*
function TrawlInventory($Url) {
    $farm = [Microsoft.SharePoint.Administration.SPFarm]::Local;
    $site = new-object Microsoft.SharePoint.SPSite $Url;

    foreach ($web in $site.AllWebs) {
        $webUrl = $web.Url;
        write-host “- Checking: $webUrl”;

        foreach ($list in $web.Lists) {
            $listitemsCount = $list.Items.Count;
            write-host “- – Checking: $list which has $listitemsCount items”;

            if ($listitemsCount -lt 1000) {
                foreach ($item in $list.Items) {
                    $itemName = $item.name.ToString().ToLower();
                    write-host “- – – – Checking file: $itemName”;

                    if ($itemName -like ‘*aspx’ -Or $itemName -like ‘*htm’) {
                        $thisURL = $item.Url;
                        $thisWEB = $web.Url;
                        if ($thisURL){
                            $thisItemsFullURL = “$thisWEB/$thisURL”;
                            $itemsFullURL = $thisItemsFullURL.ToString();
                            $uri = “$itemsFullURL”;
                            write-host “- – – – – Checking source for: $uri”;
                            $wc = New-Object System.Net.WebClient;
                            $wc.UseDefaultCredentials = $true;
                            $json = $wc.DownloadString($uri);
                            $jsonLC = $json.ToLower();
                            if ($jsonLC.Contains(“string1“) -Or $jsonLC.Contains(“string2”)) {
                                write-host “Found reference: $itemsFullURL”;
                                Add-Content -Path “curls.txt” -Value “$itemsFullURL”;
                            }
                            $json = “”;
                            $jsonLC = “”;
                        }
                    }
    $itemName = “”;

                }

            } else {
                write-host “Too many itemns to check: $listitemsCount”;            
            }

        }
        $web.Dispose();                
    }
}
###########################################################################################################################
$thisDomain = “http://WebAppName/site-collection/”;
TrawlInventory($thisDomain);
# END: Get all pages ASPX & HTM*
###########################################################################################################################