4/05/2015

JavaScript Rating control to use outside list views

There are a lot of tasks when you have to display information from list item in web part or on the page without list view.
For example, you have to create a web part to display any kind of media on the page. And this media contains a Rating field.
For such situations I've created a JavaScript control to render Rating Field outside list views. The control is based on RateIt jQuery plugin.
So no more words, let's have some code.

I'm assuming that jQuery has been already loaded to the page.
(function () {
    if (!window.YourNamespace)
        window.YourNamespace = {};
    var ns = window.YourNamespace;
    
    //
    // I want to be sure that scripts are loaded
    //
    SP.SOD.executeFunc('sp.js', 'SP.ClientContext', function () { });
    SP.SOD.executeOrDelayUntilScriptLoaded(function () { }, 'sp.js');

    SP.SOD.executeFunc('reputation.js', 'Microsoft.Office.Server.ReputationModel.Reputation', function () { });
    SP.SOD.executeOrDelayUntilScriptLoaded(function () { }, 'reputation.js');

    ns.RatingControl = function (settings) {
        if (!settings)
            return;
        var control = {
            parent: settings.parent, // parent DOM node (it could be just any css selector
            listId: settings.listId, // list ID
            itemId: settings.itemId, // item ID
            spContext: null,
            spWeb: null,
            spList: null,
            averageRating: null,
            ratingCount: null,
            _loadCss: function (src) {
                var css = document.createElement("link");
                css.setAttribute("rel", "stylesheet");
                css.setAttribute("type", "text/css");
                css.setAttribute("href", src);

                (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(css);
            },

            init: function () {
                var path = _spPageContextInfo.siteServerRelativeUrl + 'path_to_your_scripts';
                this._loadCss(path + 'rateit.css'); // load standard css for RateIt control
                this._loadCss(path + 'your-rating-control.css'); // load customizations
                jQuery.when(
                    jQuery.getScript(path + 'jquery.rateit.min.js'), // load RateIt
                    this.loadRatingInfo(), // load 
                    jQuery.Deferred(function (deferred) { deferred.resolve(); })
                ).done(jQuery.proxy(this.render, this));
            },

            loadRatingInfo: function () {
                var deferred = new jQuery.Deferred();

                if (!this.spContext) {
                    this.spContext = SP.ClientContext.get_current(); // getting Client Context
                    this.spWeb = this.spContext.get_web(); // getting Web
                    this.spList = this.spWeb.get_lists().getById(this.listId); // getting List
                }

                var item = this.spList.getItemById(this.itemId); // getting Item
                this.spContext.load(this.spWeb);
                this.spContext.load(this.spList);
                this.spContext.load(item); // here we can add the second parameter like 'Include(AverageRating, RatingCount)' 
                                           // to load only needed fields
                this.spContext.executeQueryAsync(jQuery.proxy(function () {
                    this.averageRating = item.get_item('AverageRating') || 0;
                    this.ratingCount = item.get_item('RatingCount') || 0;
                    deferred.resolve();
                }, this), function () {
                    deferred.resolve();
                });

                return deferred;
            },

            render: function () {
                var $parent = jQuery(this.parent),
                    $container = this.$container = jQuery('
'); // initializing container $parent.append($container); // initializing RateIt control $container.rateit({ min: 0, // minimum 0 stars max: 5, // maximum 5 stars step: 1, // step is 1 star resetable: false, // we don't need reset button value: this.averageRating, // current value starwidth: 16, // width of the star image starheight: 16 // height of the star image }).bind('rated', jQuery.proxy(this.onRated, this)); // bind to event }, onRated: function (e, value) { // setting rating of the current user and gettng average one var rating = Microsoft.Office.Server.ReputationModel.Reputation.setRating(this.spContext, this.listId, this.itemId, value); this.spContext.executeQueryAsync(jQuery.proxy(function () { this.$container.rateit('value', rating.m_value); // setting average rating value to control }, this), function () { }); } }; return control; }; })();

To modify the stars images override three css classes:
div.rateit-range
{
    background: url('empty-star.png');
}

div.rateit-hover
{
    background: url('filled-star.png');
}

div.rateit-selected
{
    background: url('filled-star.png');
}

Now you can use it:
var control = new YourNamespace.RatingControl({
    parent: '#some-div',
    listId: 'guid-string',
    itemId: 'intId'
});
control.init();

Have fun!

6 comments:

  1. When I implement this code in a display template in an on-prem 2013 Farm, executeQueryAsync returns a message from SharePoint:

    "We can't assign that badge to any more members. Gifted badges are meant to acknowledge a small set of people in the community and you already have that one assigned to 100 members"

    The display template is for a Content search image slider. What do I do?

    ReplyDelete
  2. Which one of the queries returns this error?
    Did you try the same outside display template (just to check if it works outside the template engine scope)?

    ReplyDelete
    Replies
    1. this.spContext.executeQueryAsync returns the error, and I have not I did not try out side of the Display Template...not quite sure how I would do that.

      Delete
    2. There are 2 times in the code where the query is executed.
      First time is to get list and item information.
      Second time is to set rating of the item.

      The control can be placed on any page. You could use Content Editor web part for example or Custom Actions.
      For display template it may work incorrectly because display template is loaded for each item in search results. Maybe because of that there are some issues, I didn't try such scenario.
      I would suggest to:
      1. check the code separately outside search results and display templates
      2. put control code in a separate js file (not inside display template), reference it once in display template and just create instances for each search result.

      Hope this will lead you to a correct solution.

      Delete
    3. I figured out my issue. I was using:

      var v_ListID = $getItemValue(currentContext, 'ListID');
      var v_ListItemID = $getItemValue(currentContext, 'ListItemID');

      I then, incorrectly, used these objects in the call to Microsoft.Office.Server.ReputationModel.Reputation.setRating(ctx, v_ListID, v_ListItemID, idx).

      The correct call is:
      Microsoft.Office.Server.ReputationModel.Reputation.setRating(ctx, v_ListID.value, parseint(v_ListItemID.value), idx).

      Delete
  3. The main aim of using this program is to create an interaction between the site and the visitor and create an engagement that is not limited to just visiting the site.snipplr

    ReplyDelete