Managed Metadata in Sharepoint 2013 DisplayTemplate

Applicability: Sharepoint 2013, Sharepoint Online

Display Templates in Sharepoint allow individual content types to be displayed differently in Sharepoint Content Search results (template files are here _catalogs/masterpage/Display Templates/Content Web Parts. In addition, since each content type can have different columns, the display templates allow properties to be remapped to rendered fields as well.

Display templates are by default deactivated, and requires a manual activation step in the Site Settings, under Publishing Infrastructure.

Each display template has an ugly looking metadata segment that makes it looks like MS Word-generated HTML.

<mso:ManagedPropertyMapping msdt:dt="string">
  'RenderPropertyName'{Web Part Label}:'PublishingImage;RefinableText01;...',
  'Link URL'{Link URL}:'Path',
  'Line 1'{Line 1}:'Title',
  'Line 2'{Line 2}:'Description',
  'Line 3'{Line 3}:'',
  'SecondaryFileExtension',
  'ContentTypeId'
</mso:ManagedPropertyMapping>

The key take away here are:

  • when rendering in the display templates, use RenderPropertyName;
  • when presenting the property to be customised in the Web Part – use Web Part Label;
  • to set the default properties, set them in the final segment.

The actual rendering appears to be done on the client-side using a custom Javascript templating language that is reminiscent of PHP.

<!--#_
var propertyValue = $getItemValue(ctx, "RenderPropertyName");
 _#-->
_#= propertyValue =#_

Hat tip to @cann0nf0dder for working this out!

Clean Code with AngularJS on Windows – Part 2

This is Part 2 of a series on AngularJS testing.

Our basic test case is a user registration service.

using a TDD philosophy, we start with our failing test.

// file: tests/userReg.js
describe('Test Suite', function() {

  var scope;

  // before each test is run, load the userReg module
  beforeEach(angular.mock.module('userRegApp'));

  // before each test is run, sets the scope
  beforeEach(angular.mock.inject(function($rootScope, $controller) {
     scope = $rootScope.$new();
  }))

  // single asynchronous unit test
  // by including 'done' argument, the test will not complete
  // until done() is called.
  it('service should asynchronously return true', function(done) {

    // inject() is kind of like a DI constructor
    // note that userRegistrationService is registered as a 
    // factory in the userRegApp module
    angular.mock.inject(function($q, userRegistrationService) {

        // mock the userRegistrationService.usernameExists()
        var deferred = $q.defer();
        spyOn(userRegistrationService, 'usernameExists').and.returnValue(deferred.promise);
        deferred.resolve(true);

        userRegistrationService.usernameExists('admin')
            .then(function(result) {
                expect(result).toBe(true);
            })
            .finally(done);

        scope.$apply();

    })
  });

})

We also need to configure karma.conf.js to load angular.js, angular-mocks.js, our source files and test files.

// Karma configuration

  // list of files / patterns to load in the browser
  files: [
    'bower_components/angular/angular.js',
    'bower_components/angular-mocks/angular-mocks.js',
    'app/*.js',
    'tests/**/*.js'
  ],

Launch karma to test

$ node_modules/karma/bin/karma start karma.conf.js

Karma should spit out long error messages such as this:

 Error: [$injector:modulerr] Failed to instantiate module userRegApp due to:
 Error: [$injector:nomod] Module 'userRegApp' is not available! You either misspelled the module name or forgot to load it.

Create a new file called app/userRegistration.js

angular.module('userRegApp', [])
.factory('userRegistrationService', ['$http', '$q', function($http, $q) {
  return {
    usernameExists: function(userName) {

      // .NET developers may recognize deferred as
      // being the equivalent of TaskCompletionSource
      var deferred = $q.defer();
      $http.get('/webapi/v1/usernameExists/' + userName)
        .success(function(payload) { deferred.resolve(payload.data); })
      return deferred.promise;
    }
  }
}]);

Karma should report that the unit test passed.

INFO [watcher]: Added file "...../ngtest/app/userRegistration.js".
Chrome 39.0.2171 (Windows 8.1): Executed 1 of 1 SUCCESS (0.036 secs / 0.032 secs)

Clean Code with AngularJS on Windows – Part 1

Writing clean, testable code with AngularJS requires a specific toolchain to be present, these are:

  • npm (package manager for back end)
  • karma
  • bower (package manager for front end – to install angularjs components)

This exercise takes approximately 5 minutes (for packages to download). It installs the basic components necessary.

If there’s an existing project, use npm to automatically install all dependencies specified in package.json. Use a special switch so that binaries are built using Visual Studio 2013 (otherwise defaults to VS2010)
$ npm install --msvs-version=2013

Create an empty package.json file

file: package.json
{
}

Install Karma

# install karma
$ npm install karma --save-dev --msvs-version=2013
 
#install plugins
$ npm install karma-jasmine karma-chrome-launcher --save-dev --msvs-version=2013
 
#configure karma
$ node_modules/karma/bin/karma init karma.conf.js
 
Which testing framework do you want to use ?
Press tab to list possible options. Enter to move to the next question.
> jasmine
 
Do you want to use Require.js ?
This will add Require.js plugin.
Press tab to list possible options. Enter to move to the next question.
> no
 
Do you want to capture any browsers automatically ?
Press tab to list possible options. Enter empty string to move to the next question.
> Chrome
>
 
What is the location of your source and test files ?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Enter empty string to move to the next question.
>
 
Should any of the files included by the previous patterns be excluded ?
You can use glob patterns, eg. "**/*.swp".
Enter empty string to move to the next question.
>
 
Do you want Karma to watch all the files and run the tests on change ?
Press tab to list possible options.
> yes
 
Config file generated at "....\karma.conf.js".

Install Bower


# install bower (front-end package manager)
$ npm install -g bower --save
 
# creates bower.json
$ bower init
 
# install angular and angular mocks
$ bower install --save angular angular-mocks

git-tf notes

Working with Xamarin iOS with an existing project means there’s a new tool to pick up. The source code repository is on TFS, while my development machine is a Macbook Pro.

Luckily, Microsoft has provided a java based command line client called git-tf.

One of the sub-requirements is that I need to fork the entire TFS repository to git, as my development build machine will be building off a git repository. I’ll need to regularly merge back changes into the TFS.

The first step is to take a full history of the TFS repository.


$ git-tf clone --deep https://tfs2012.mydomain.com/tfs/mycollection $/myproject/Trunk MyLocalDir
username: chui.tey
password:

Next, push it into my remote git repository on Bitbucket.


git remote add bitbucket git@bitbucket.org:/teyc/myproject.git

Calibre E-book – Authoring Plugins with a Custom Context Menu

I’ve been hacking with the Calibre ebook reader for a few hours, and I’d like to share with you some notes of how to add a custom context menu to a book, so that we can open a secondary folder where the book has been exported to.

A good place to start is to download the interface_demo.zip from the Plugins documentation. Unzip the directory, and run Calibre in debug mode from that directory.

mkdir demo
cd demo
unzip interface_demo.zip

REM loads calibre plugin from the current directory
../calibre-customize.exe  -b .

REM launch calibre gui in debug mode
../calibre-debug.exe -g

Next, add the custom action to the context menu manually through the preference toolbar. I’ve taken some screenshots below:

That done, we can go in and hack on the plugin until it works.

In my particular case, I’ve got an alternate filepath stored in a custom metadata field, and I’d like to open the alternate containing folder.

#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai

__license__   = 'GPL v3'
__copyright__ = '2014 Chui Tey teyc@cognoware.com'
__docformat__ = 'restructuredtext en'


from calibre.gui2.actions import InterfaceAction
import os

class InterfacePlugin(InterfaceAction):

    name = 'Open Oracle Folder'
    action_spec = ('Open Oracle folder', 'images/blank.png',
                   'Open the file in the Oracle folder', 'CTRL+O')
    dont_add_to = frozenset(['context-menu-device'])
    action_type = 'current'

    def genesis(self):
        #icon = get_icons("images/blank.png")
        #self.qaction.setIcon(icon)
        #self.qaction.setEnabled(True)
        self.qaction.triggered.connect(self.open_folder)

    def open_folder(self):
        rows = self.gui.current_view().selectionModel().selectedRows()
        if not rows or len(rows) == 0:
            return
        not_found = []
        for i, row in enumerate(rows):
            if i > 5: continue
            mi = self.gui.library_view.model().db.get_metadata(row.row())
            filepath = mi.get('#filepath', None)
            if filepath:
                import os
                folderpath = os.path.split(filepath)[0]
                if os.path.isdir(folderpath):
                    os.startfile(folderpath)
                else:
                    not_found.append(folderpath)
        if not_found:
            if len(not_found) > 1:
                raise Exception("Folders %s not there!" % ", ".join(not_found))
            else:
                raise Exception("Folder %s not there!" % ", ".join(not_found))

            # actual path
            # path = self.gui.library_view.model().db.abspath(row.row())

    def location_selected(self, loc):

        enabled = loc == 'library'
        self.qaction.setEnabled(enabled)

Tip: Create an App icon for your website

If you already have a web site, and you are thinking about creating a mobile app version of this, there is a less expensive approach.

Mobile users who bookmark your website can get essentially an App experience because your website will have a proper App icon, rather than a screenshot of your website.

Here’s a flow chart of what you will need to support iOS, Android and Windows

Cocktail for Windows Store – Part 3

One style of Entity Framework development is known as Code-First. With Code-First style, you’d define the schema using .NET classes and properties. This style of development makes it easy to keep the data model under source control.

Entity Framework comes with a powershell script that detects the schema changes as you add/modify the classes, and generates the necessary upgrade scripts for you.

Our project involves an assignment tracker for schools. Students can be assigned one or more assignments in subjects they are enrolled to. Assignments are created by the teachers who teach the relevant subject.


// Schema.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace AssignmentTracker
{
    using IdeaBlade.Aop;

    [ProvideEntityAspect]
    public class Assignment
    {
        [Key]
        public int Id { get; set; }

        [MaxLength]
        public string Description { get; set; }

        [ForeignKey("Student")]
        public int StudentId { get; set; }

        [ForeignKey("Subject")]
        public int SubjectId { get; set; }

        public DateTime? DateDue { get; set; }

        [Association("Assignment_StudentSystemUser", "StudentId", "Id")]
        public Student Student { get; set; }

        [Association("Assignment_Subject", "SubjectId", "Id")]
        public Subject Subject { get; set; }
    }

    [ProvideEntityAspect]
    public class Subject
    {
        public Subject()
        {
        }

        [Key]
        public int Id { get; set; }

        [Required]
        [MaxLength(255)]
        public string Description { get; set; }

        [ForeignKey("Teacher")]
        public int TeacherId { get; set; }

        [Association("Subject_Teacher", "TeacherId", "Id")]
        public Teacher Teacher { get; set; }

        public IList<Student> Students { get; set; }
    
    }

    [ProvideEntityAspect]
    public class SystemUser
    {
        [Key]
        public int Id { get; set; }

        [MaxLength(100)]
        [Required]
        public string UserName { get; set; }

        [MaxLength(100)]
        public string FullName { get; set; }

    }

    public class Student : SystemUser
    {
        public Student()
        {
            //Subjects = new RelatedEntityList<Subject>();
        }

        [Required]
        [MaxLength(15)]
        public string StudentNumber { get; set; }

        public ICollection<Subject> Subjects { get; set; }
    }

    public class Teacher : SystemUser
    {
        public virtual IList<Subject> Subjects { get; set; }
    }
}

Schema.cs is added to AssignmentTracker.Server.dll and added as a link to the AssignmentTracker.RT project. This is the main value from DevForce, the same code that is used on the server is deployed on the client.

Ideablade Cocktail for Windows Store – Part 2

A Cocktail project is usually grouped into folders called Views and ViewModels. The Views folder contains all the XAML files, and the ViewModels contain the datacontext that each view binds against.

We’ll create the missing GroupedItemsPageViewModel first, and then drag them into the Views and ViewModels folder. All Cocktail ViewModels that represent a screen should inherit from Screen, and INavigationTarget. The INavigationTarget has two purposes:

  1. It provides notification when a screen has been navigated to, or navigating away from – Similar to Silverlights Navigation events
  2. It also provides notification that the screen is about to go into suspended state, so that we can save the work and resume later.
using Caliburn.Micro;
using Cocktail;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AssignmentTracker
{
    public class GroupedItemsPageViewModel : Screen, INavigationTarget
    {
        private INavigator _navigator;

        public GroupedItemsPageViewModel(INavigator navigator)
        {
            _navigator = navigator;
        }

        #region INavigationTarget

        public void LoadState(object navigationParameter, Dictionary<string, object> pageState, Dictionary<string, object> sharedState) { }

        public void OnNavigatedFrom(NavigationArgs args) { }

        public void OnNavigatedTo(NavigationArgs args) { }

        public void OnNavigatingFrom(NavigationCancelArgs args) { }

        public string PageKey { get; set; }

        public void SaveState(Dictionary<string, object> pageState, Dictionary<string, object> sharedState) { }

        #endregion
    }
}

So far, we have not loaded any entity from the database yet. That’ll be the next topic.

Next: Querying entities