다음을 통해 공유


A content management tool with dashboard based on SharePoint List

 

1.  Scenario

The SharePoint OOTB List has saved us a lot of time on managing mess data. It provides three forms to create/view/edit items, the ability to save the views we want with some specific filtering and sorting condition, versioning for easy restoring, and we can make it advanced with workflow contains the specific business logic.

However, if there is a need for better user experience, interacting with the public APIs and a bit of script to customize the web page would be required.

Suppose there is a requirement like this:

1.       We need a content collection tool which collects ideas from contributors, the newly ideas will be reviewed by reviewers.

2.       We may need to filter the list in a convenient way, get the wanted result with the data from the list and display in a chart or rank list. 

We can add some buttons in Metro style to display the counting result of the data from the list dynamically. When we click them, the list will be filtered and sorted to display a friendly set of items. Also, we need to display a trend of the mess data graphically in some beautiful charts.  If we want to find out some outstanding contributors, top contributor board would be more comfortable than the top N items in the OOTB list view.

The page would look like this:

https://7ciwvg.dm2302.livefilestore.com/y2pQMgyXruaES5-urd_cqsOCOqVxdTwOSL2LoKKQkeKDyhItFSz4YEPo6QWEl9i40tVbga6jpX0ppjO0IN2vV7JCOfN3y2MTGbJLOR2IwFrmvo/Capturetool3.PNG?psid=1

2.  Introduction

Engineers will come up with some ideas in the daily job and write a content to enlighten others. Reviewers will help to review ideas or contents and publish the contents if qualified.

The complete process looks like this:

https://7ciwvg.dm2302.livefilestore.com/y2p-hkIiwjOIaHKm_PvgtBM_8b33cHZhKwXJg4JdOOw-tpMFb6UtWjIZhY7LqLg5lZN6tbU24JNCnb_8IdO2IcsnkTTfaqYht5efU-JoK0A-0U/ad3.png?psid=1

As we can see that, only the approved idea can be written as a content and only the approved content can be published.

2.1 How it works

We build the whole tool in only one page. All ideas and contents will be saved in a custom list. This is how it looks like:

https://7ciwvg.dm2302.livefilestore.com/y2p7W-A2Vp8Q8PLkDuVJ-BStU7uHbL4bKv39BOoqsoVdktqQpX0b97ndhyJ8brlEP4UpWm24QdGhvkhmCQjGonoTEECEKueSGhDJlI51X7p3-g/ad4.png?psid=1

There are three parts in this page:

1         

2         

2.1         

2.1.1        Top menu

The top menu contains three elements:

A Drop Down menu for filtering data by team, it will refresh the other two parts with the filtered data:

https://7ciwvg.dm2302.livefilestore.com/y2pRW5rvb4mmlSSXXpwH68N8Q6HJ8acDxrbp8W-v0HGEsyistTbiJsvUukCNKYxciFFtO8Czuuj0SrpTCDRJW5kGwrtMZBjsYq0gUVX0Z3H-Gs/ad5.png?psid=1

A hyperlink “STATISTIC” links to a PowerBI report whose data source is the custom list.

A hyperlink “FEEDBACK” for collecting feedbacks:

https://7ciwvg.dm2302.livefilestore.com/y2p0Yb4N4Zfyr97bRimAGhlpOhOgWQSphxiYwndCyf_X5H_VCjKw9pmEJbyLgAsDrykC4uT79GerhT7rKNcdxHKo5KmqrOCzECsH3rdaD5pnIk/ad6.png?psid=1

The feedbacks will be saved in another list:

https://7ciwvg.dm2302.livefilestore.com/y2pCA12R8iAJqf2o9zmmgzHU3wO6ZWOHjYwDEWj4cyF8pXbS6NL1kwxHul3eE2YVy-jAKB5feh2oSB8YMYGwAJE37P45SvfpuMh2y38P_nGRY4/ad7.png?psid=1

2.1.2        Information menu

This part will display the calculated data retrieved from the list within tiles, chart and ranking list.

The tiles can be clicked to filter and refresh the list view.

2.1.3        List view

A list stores all ideas and contents with the properties needed. It can be filtered by the Top menu and Information menu.

The customisation on the OOTB custom list template makes it more powerful and more suit for this scenario:

https://7ciwvg.dm2302.livefilestore.com/y2p-3Zsd8NdUSwT3QXs9a8maMIsuu2zMBW-laVWBjgNH6SOCmyLy3p9h_mT8oNuFpOvhUv-OUPyB31LmMIkE_G9Oyrc-cREztU7sy786TReTB8/ad8.png?psid=1

1. An item levelled comment feature (based on OOTB Tags & Notes feature) for other users make comments to an idea or content:

https://7ciwvg.dm2302.livefilestore.com/y2pmfL7PagFg_sSCzFk9RxjmJN5b7PZHbZYny-GkHfvXN91OagUKCqPMAPsbiyI_yI-DlNYt_sG3VIkgiuvwYNy8bBU6hU4ATl62yXbvg3pSno/ad9.png?psid=1

2. Title column: When there is no attachment in the current item, it redirects to the default DisplayForm page. If there is, it will open the attachment (usually a .docx file) in Word Online in a new tab.

3. ECB menu: Add some custom shortcuts for the popular actions:

https://7ciwvg.dm2302.livefilestore.com/y2pSV3E8xjEstV27AIhkf3T1PYH3R3IaZOFhlcHvyx1ue4yvKHxbLMMmotVft0iyg2ZxoKWytOsdQVV1lkTnYwHVSu0VJZVSjGXsLueFdFI7lI/ad12.png?psid=1

4. A hyperlink column stores the hyperlink points to the website where the content is published to.

1.     

2.     

3.    How to achieve it

This solution will be hosted in SharePoint Online environment, so we do all the job using JavaScript, REST API and Client Object Model.

The Drop Down menu, tiles, rank list are generated with some HTML+CSS.

The Trend Chart, we take advantage of the Combo chart in the Google chart library.  

The list view is hosted in a <iframe> which can be easily filtered and refreshed by just passing a generated URL with query string.

For the customisation on the list view and the ECB menu, JSLink with Client Object Model would be OK.

3.1 Specific to every part

3.1.1        Top menu

When user selects a team here, there will be a request sent out for retrieving items of the list. By default, the limit is 100 when using REST API to get list items, so we can append a “$top=1000” to require more items from server.

Code snippet like this:

$.ajax({
    url: _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('"  + listName + "')/items?$top=1000",
    method: "GET",
    headers: { "Accept":  "application/json; odata=verbose"  },
    success: function  (data) {
        console.log("getListItems succ");
        console.log(data);        
    },
    error: function  (data) {
    alert("getListItems error");
        //failure(data);
    }
});

Then we will get the “data” as a JSON format string, it contains all the values we need from the list:

https://7ciwvg.dm2302.livefilestore.com/y2p2ctRs3vOUy4qU-OzVe6wzZgEhRcK2CgdmmLThZbNfCQKWsNz9YYDLL7KnaSxIOwNtlgTlL2tfJeBnDN_KyjVPNZ19gAkT_lKtyLgUZnloBA/ad11.png?psid=1

We can get the values we want like this:

 

//get item Count
var arr = [], len;
for(key in  data.d.results) 
{
    arr.push(key);
}
len = arr.length;
 
for(var ii=0; ii<len; ii++)
{
    var team = data.d.results[ii].Team;
    var month = data.d.results[ii].Month;
}

As we need to know the counts of each type of ideas or contents, we use an array for saving the counters:

//ary to store all counters for tiles: all/pendingIdea/pendingContent/my/approvedIdea/approvedContent
var aryAllCounters = [0,0,0,0,0,0];
for(var ii=0; ii<len; ii++)
{
    //get pendingIdeaCount
    if(data.d.results[ii].Statuss === 'Pending')
    {
        aryAllCounters[1]++;
    }
}

Once all the numbers are ready, we can do the refreshing. 

As the list view page is hosted in a <iframe>, all we need to do is passing a constructed URL with query string:

url_team = URL + "?FilterField1="+FIELD_MYTEAM+"&FilterValue1=" + sel_val;
$iframe.attr('src', url_team);

The feedback dialog hosts another page which contains two buttons and one text area.

The HTML code of the FEEDBACK button:

<a id="feedback" href="#" onclick="javascript:openDialogBox('../SitePages/Feedback.aspx');">FEEDBACK</a>

The openDialogBox() function:

function openDialogBox(url){     
    var options = SP.UI.$create_DialogOptions();
    options.url = url;
    options.height = 130;
    options.width = 425;
    options.title = "Feedback";
    SP.UI.ModalDialog.showModalDialog(options);
}

In the Feedback.aspx page, when user click submit button, we will save the content of the text area into the feedback list:

function addListItem() 
{
    this.clientContext = new  SP.ClientContext.get_current();
    this.oList = clientContext.get_web().get_lists().getByTitle('Feedback');
 
    var itemCreateInfo = new SP.ListItemCreationInformation();
    this.oListItem = this.oList.addItem(itemCreateInfo);
 
    //set person field
    var userValue = new SP.FieldUserValue();
    //userValue.set_lookupId(this.currentUser.get_id());
    userValue.set_lookupId(_spPageContextInfo.userId);
    oListItem.set_item('Provider', userValue);
 
    //Sets the specified field value
    oListItem.set_item('Title', str);
 
    //datetime field
    var currDate = new Date();
    oListItem.set_item('Submit_Time',currDate);
     
    oListItem.update();
    clientContext.executeQueryAsync(Function.createDelegate(this, this.onQuerySucceeded_add), Function.createDelegate(this, this.onQueryFailed));
}

 

3.1.2        Information menu

3.1.2.1  Tile shortcut

In the click event of the tiles, the code will pass a generated URL with query string to the <iframe>:

//filter list only
$tile.click(function(){
 
    //distinguish tiles by id
    var v = $(this).attr('id');      
    switch(v)
    {   
    case S_MY_CONTENT:
    url_team1 = URL + "?FilterField1="+FIELD_COMPOSER+"&FilterValue1=" + currentUsername;
    break;
 
    //...
 
    case S_PENDING_IDEA:
    url_team1 = url_team + "&FilterField2="+FIELD_STATUS+"&FilterValue2=Pending&FilterField3="+FIELD_IDEATYPE+"&FilterValue3=Idea";    
    }
    break;   
    $iframe.attr('src', url_team1);    
});
3.1.2.2  Trend chart

The chart will be initialized with the numbers by month stored in a 3D array:

google.load("visualization", "1", {packages:["corechart"]});
google.setOnLoadCallback(drawVisualization);
function drawVisualization(ary) 
{
    // Some raw data (not necessarily accurate)
    var data = google.visualization.arrayToDataTable(ary);
 
    var view = new google.visualization.DataView(data);
    view.setColumns([0, 1,
                   { calc: "stringify",
                     sourceColumn: 1,
                     type: "string",
                     role: "annotation"
                      
                     },
                   2]);
 
    // Create and draw the visualization.
    var ac = new google.visualization.ComboChart(document.getElementById('chart1'));
    ac.draw(view, {
        //legend: 'top',
        legend: {position: 'top', textStyle: {fontSize:13, fontName:'Segoe UI,Lucida Grande,Verdana,Arial,Helvetica,sans-serif'}},       
        title : '',
        //width: 0,
        //height: 285,
        vAxis: {title: "", format:'#',viewWindowMode:'explicit',
              viewWindow:{
                min:0
              },ticks: ticks
        },
        //hAxis: {title: ""},
        lineWidth: 4,
        bar: {groupWidth: "60%"},
        seriesType: "bars",
        series: {1: {type: "line"}},
        chartArea:{left:20,top:35,width:'95%',height:'82%'},
        colors: ['#A4C400', '#F9A13B']
    });
}
3.1.2.3  Top contributors rank list

When retrieving list items, we can get the “AuthorId” which represents the id of the user in the siteUserInfoList. We run another request to retrieve all items in the siteUserInfoList which stores the username with the URL of profile.

Then we can use a hash table(provided by jshashtable.js) to store the user id, username and profile URL:

$.ajax({
    url: _spPageContextInfo.webAbsoluteUrl + "/_api/web/siteUserInfoList/Items",
    method: "GET",
    headers: { "Accept":  "application/json; odata=verbose"  },
    success: function  (data) {
        console.log(data);
 
        //get item Count
        var arr = [], len;
        for(key in  data.d.results) 
        {
            arr.push(key);
        }
        len = arr.length;
 
        var ht_authors = new Hashtable();
        for(var ii=0; ii<len; ii++)
        {       
            if(authorSet.contains(data.d.results[ii].Id))
            {
                if(data.d.results[ii].Picture != null)
                {
                    ht_authors.put(data.d.results[ii].Id, data.d.results[ii].Title+'|'+data.d.results[ii].Picture.Url);
                }
                else
                {
                    ht_authors.put(data.d.results[ii].Id, data.d.results[ii].Title+'|');
                }                
            }
        }
        console.log("ht_authors.keys(): "+ht_authors.keys());
        console.log("ht_authors.values(): "+ht_authors.values());
    },
    error: function  (data) {
    alert("error");
        //failure(data);
    }
});

 

3.1.3        List view

For the Comment button, custom title link and the custom published link of each item, we can use JSLink to achieve.

Comment button: It is supposed to be the OOTB “Type” column, I change the icon and modify the click event of it to pop up a comment dialog which take advantage of the OOTB “Tags&Notes” feature;

Custom Title link: As there will be two situations of an item: has attachment or not. We will need to run a request to get the URL of attachment and change the hyperlink of the Title field accordingly:

 

(function () {
 
    // Create object that have the context information about the field that we want to change it output render  
    var linkFiledContext = {};
    linkFiledContext.Templates = {};
    linkFiledContext.Templates.Fields = {        
        //"Attachments": { "View": AttachmentsFiledTemplate }
        "LinkTitle": { "View": TitleFieldTemplate }, 
        "Published_x0020_Link": { "View": PublishedLinkFieldTemplate },
        "DocIcon": { "View": DocIconFieldTemplate },
        "MyTeam": { "View": MyTeamFieldTemplate }
    };
    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(linkFiledContext);
})();
 
function DocIconFieldTemplate(ctx)
{
    var htmlStr = "";
    var listId = ctx.listName;
    var itemId = ctx.CurrentItem.ID;
    var s = listId + "," + itemId;
    htmlStr += "<img width='16' height='16' class=' ms-draggable' alt='Comment' src='"+_spPageContextInfo.webAbsoluteUrl+"/Shared%20Documents/img/comment-icon.png' border='0' ms-draggableragId='0' onclick='CommentIcon(\""+ s +"\")'></img>";
    return htmlStr;
}
 
function CommentIcon(s)
{
    var listId = s.split(',')[0];
    var itemId = s.split(',')[1];
    var url=_spPageContextInfo.webAbsoluteUrl+"/_layouts/15/socialdataframe.aspx?listid="+listId+"&id="+itemId+"&mode=1";
    console.log(url);
    openCustomDialog(url,"Comment",650,520);
}
 
function openCustomDialog(pageUrl,title,width,height) 
{  
   SP.UI.ModalDialog.showModalDialog({  
       url: pageUrl,
       width: width,
       height: height,
       title: title,
       dialogReturnValueCallback: function  (result){
            if(result== SP.UI.DialogResult.OK)
            { 
                parent.window.location.href=parent.window.location.href;         
            }   
       }       
    });  
}
 
function PublishedLinkFieldTemplate(ctx)
{
    //console.log(ctx);
    var htmlStr = "";
    var itemPublishedLink = "";
    var itemPublishedLinkDesc = "";
    if((ctx.CurrentItem.Published_x0020_Link != ''))
    {
        itemPublishedLink = ctx.CurrentItem.Published_x0020_Link;
        itemPublishedLinkDesc = ctx.CurrentItem["Published_x0020_Link.desc"];
        htmlStr = "<a href='" + itemPublishedLink + "' target='_blank'>"  + itemPublishedLinkDesc + "</a>";
    }
    return htmlStr;
}
 
 
function MyTeamFieldTemplate(ctx)
{
    var htmlStr = "";
    var itemMyTeam = "";
    if((ctx.CurrentItem.MyTeam[0] != undefined) && (ctx.CurrentItem.MyTeam[0] != null))
    {
        itemMyTeam = ctx.CurrentItem.MyTeam[0].lookupValue;
        htmlStr = itemMyTeam;
    }
    return htmlStr;
}
 
function TitleFieldTemplate(ctx) {
    console.log(ctx.CurrentItem);
    var itemId = ctx.CurrentItem.ID;
    var itemTitle = ctx.CurrentItem.Title;
    var listName = ctx.ListTitle;
    var siteUrl = _spPageContextInfo.webAbsoluteUrl;
    var listUrl = _spPageContextInfo.webAbsoluteUrl +  "/Lists/" +listName;
     
    var fileNames = getAttachmentsNames(listName, itemId);  
    console.log(fileNames);   
    var fileNameAry = fileNames.split("|");
    var htmlStr = "";
 
    //check the attachment existence
    if(fileNameAry[0] != '')
    {   
        for(var j = 0; j < fileNameAry.length; j++)
        {
            var fileName = fileNameAry[j];
             
            var s1 = "<a class=\"ms-listlink ms-draggable\" onmousedown=\"return VerifyHref(this, event, '1', 'SharePoint.OpenDocuments.3', '1";
            //1``https://microsoft.sharepoint.com/teams/spfrmcs
            var s2 = "/_layouts/15/WopiFrame.aspx?sourcedoc=";
            //2``/teams/spfrmcs/Lists/Content%20Pool
            var s3 = "/Attachments/";
            //3``137
            var s4 = "/";
            //4``[Forum FAQ] Highlight the list tab in Quick Launch when the list view changes.docx
            var s5 = "&action=default'); return false;\" href=\"";
            //5``https://microsoft.sharepoint.com/teams/spfrmcs/Lists/Content Pool
            var s6 = "/Attachments/";
            //6``137
            var s7 = "/";
            //7``[Forum FAQ] Highlight the list tab in Quick Launch when the list view changes.docx
            var s8 = "\" target=\"_blank\" DragId=\"1\">";
            //8``Highlight the list tab in Quick Launch when the list view changes
            var s9 = "</a>";
            var s = s1+siteUrl+s2+listUrl+s3+itemId+s4+fileName+s5+listUrl+s6+itemId+s7+fileName+s8+itemTitle+s9;
            htmlStr += s; 
 
            //console.log(htmlStr);
            if (j != fileNameAry.length - 1) 
            {
               htmlStr += "<br/>";
            }
        }
    }
    //if no attachments, set the <a> point to displayForm
    else
    {
        htmlStr += "<a class='ms-listlink ms-draggable' onclick='EditLink2(this,28);return false;' onfocus='OnLink(this)' href='" + siteUrl + "/_layouts/15/listform.aspx?PageType=4&ListId=%7BE54A4FBB%2DDDC2%2D4F7E%2D8343%2D8A1C78757CF4%7D&ID=" + itemId + "&ContentTypeID=0x010079A1D928FF77984C80BFEF1D65C3809F' target='_blank' DragId='0'>" + itemTitle + "</a>";
    }
 
    return htmlStr;
}
 
function getAttachmentsNames(listName,itemId) {
   
    var url = _spPageContextInfo.webAbsoluteUrl;
    var requestUri = url + "/_api/web/lists/getbytitle('" + listName + "')/items(" + itemId + ")/AttachmentFiles";
    var str = "";
    // execute AJAX request
    $.ajax({
        url: requestUri,
        type: "GET",
        headers: { "ACCEPT":  "application/json;odata=verbose"  },
        async: false,
        success: function  (data) {
            for (var i = 0; i < data.d.results.length; i++) 
            { 
                if(i != 0)
                {
                    str += "|";
                }
                str += data.d.results[i].FileName;              
            }          
        },
        error: function  (err) {
            //alert(err);
        }
    });
    return str;
}

3.2 How to make them work together

When selecting an option in the Drop Down menu, the Information menu and the List view will be refreshed separately.

When clicking the tiles, only the list view will be filtered and refreshed, the other parts will not be influenced.

When items created/modified, the whole page will be refreshed to keep all the numbers in each part updated.  A workflow will also be triggered to inform engineers or reviewers the progress of an item or content.

 

3.3 Other customisations

3.3.1        ECB menu and permission control

As we need to refresh the page when new item or modify item, we put all the form pages in a custom modal dialog and execute the refresh in the success callback function.

There are three roles: Site owner, reviewer and engineer. They have limited privileges according to the roles they are:

Site owner: Full control on the list, can see all the buttons in the ECB menu;

https://7ciwvg.dm2302.livefilestore.com/y2pHNRAcJXHurfz6RzbolMGSPkbjRQquqWgJ41vvnegwSIjsTnhp12gw4sHI32t19x5rKbjKuzhY81DVt-AKo-QYXPxsKLAIybkhPp5_KoonOU/ad10.png?psid=1

Reviewer: There is another list which stores the names of each team and reviewers’ names of each team. The reviewer has limited full control only on the team they belong to. To other teams, the role can be seen as a visitor;

Composer (create owner): The one who contribute an idea. For the ideas of others, this role can be seen as visitor.

The ECB menu they can see is:

https://7ciwvg.dm2302.livefilestore.com/y2pk1mTobRrgtdvCV2nvQYnoxnJiskSrRgdV226QuBOE30d2U5bxooJ4fUIj4EN6s4ypIe7tUTSI7JqQS9VQxV8H71qo3qTBrUS2LWFhqZXndE/ad13.png?psid=1

For the visitor, the ECB menu will only display a few buttons:

https://7ciwvg.dm2302.livefilestore.com/y2pucf06RwTA0XhieMdR0ty3q1nWUz7yUZ9WnFswuBcghgQjEA9e8n56eoiIu20KXJrtO_HAszuiOFKHF_mqcwx0d_vE9mEm-RxdCvbWhQVzxg/ad14.png?psid=1

 

 

The code:

(function () {
    var viewContext = {};
    viewContext.Templates = {};
    viewContext.OnPostRender = OnViewPostRender;
    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(viewContext);
})();
 
function OnViewPostRender(ctx) {        
    $("a[title='More options']").removeAttr("onclick");
    $(".ms-list-itemLink").removeAttr("onclick");
    $("a[title='More options']").attr("onclick", "showMenuList(this);return false;");
}
 
function showMenuList(obj) {
 
    var itemId = $(obj).parents("tr").attr("id").split(",")[1];
    //show ECB menu                             
    CoreInvoke('ShowECBMenuForTr', obj, event);
 
    var teamId = getCurrentTeamId("Content Pool", itemId);
    var styles = "";
    if (isSiteOwner("Technet SharePoint Team Owners")) {
 
        styles = "li[text='Delete Item ']{display:block;} li.ms-core-menu-separator:last-child{display:block;} ul.ms-core-menu-list > li:nth-last-child(5){display:block;} li[text='Edit Item ']{display:block;} li[text='Upload Document']{display:block;} li[text='Approve']{display:block;} li[text='Reject']{display:block;} li[text='Add Publish Link']{display:block;}";
 
    } else  if (isReviewer("List1_FAQ_team", teamId, "Reviewers")) {
 
        styles = "li[text='Delete Item ']{display:block;} li.ms-core-menu-separator:last-child{display:block;} ul.ms-core-menu-list > li:nth-last-child(5){display:block;} li[text='Edit Item ']{display:block;} li[text='Upload Document']{display:block;} li[text='Approve']{display:block;} li[text='Reject']{display:block;} li[text='Add Publish Link']{display:block;}";
 
    } else  if (isComposer(obj)) {
 
        styles = "li[text='Delete Item ']{display:block;} li.ms-core-menu-separator:last-child{display:block;} ul.ms-core-menu-list > li:nth-last-child(5){display:block;} li[text='Edit Item ']{display:block;} li[text='Upload Document']{display:block;} li[text='Approve']{display:none;} li[text='Reject']{display:none;} li[text='Add Publish Link']{display:none;}";
 
    } else  {
 
        styles = "li[text='Delete Item ']{display:none;} li.ms-core-menu-separator:last-child{display:none;} ul.ms-core-menu-list > li:nth-last-child(5){display:none;} li[text='Edit Item ']{display:none;} li[text='Upload Document']{display:none;} li[text='Approve']{display:none;} li[text='Reject']{display:none;} li[text='Add Publish Link']{display:none;}";
 
    }
 
    includeStyleElement(styles);
}
 
//get current team id
function getCurrentTeamId(listName,itemId){
    var teamId="";
     var requestUri = _spPageContextInfo.webAbsoluteUrl +
                  "/_api/Web/Lists/getByTitle('"+listName+"')/items("+itemId+")?$select=MyTeamId";
     
    // execute AJAX request
    $.ajax({
        url: requestUri,
        type: "GET",
        headers: { "ACCEPT":  "application/json;odata=verbose"  },
        async: false,
        success: function  (data) {
           if(data.d.MyTeamId!=null){
                teamId=data.d.MyTeamId;
           }else{
                teamId="0";
           }              
        },
        error: function  () {
            //alert("Failed to get details");
        }
    });
 
    return teamId;  
}
 
//check whether is owner
//Technet SharePoint Team Owners
function isSiteOwner(groupName) {
    var flag = false;
    var requestUri = _spPageContextInfo.webAbsoluteUrl +  "/_api/Web/effectiveBasePermissions";
 
    // execute AJAX request
    $.ajax({
        url: requestUri,
        type: "GET",
        headers: { "ACCEPT":  "application/json;odata=verbose"  },
        async: false,
        success: function  (data) {
            var permissions = new SP.BasePermissions();
            permissions.fromJson(data.d.EffectiveBasePermissions);
            flag = permissions.has(SP.PermissionKind.managePermissions);           
        },
        error: function  () {
            //alert("Failed to get details");
        }
    });
 
    return flag;
}
 
function isComposer(obj) {
    var flag = false;
    var userId = _spPageContextInfo.userId;
    var composerId = $(obj).parents("tr").find("a[href*='userdisp.aspx']").attr("href").split("ID=")[1];
    if (composerId == userId) {
        flag = true;
    }
    return flag;
}
 
//check whether is reviewer
function isReviewer(listName,teamId,peopleColumn){
    var flag=false;         
    var userId=_spPageContextInfo.userId;
                     
    // begin work to call across network
    var requestUri = _spPageContextInfo.webAbsoluteUrl +
                  "/_api/Web/Lists/getByTitle('"+listName+"')/items?$select=ID&$filter=(ID eq '"+teamId+"' and "+peopleColumn+"Id eq '"+userId+"')";
     
    // execute AJAX request
    $.ajax({
        url: requestUri,
        type: "GET",
        headers: { "ACCEPT":  "application/json;odata=verbose"  },
        async: false,
        success: function  (data) {
           if(data.d.results.length>0){
                flag=true;
           }           
        },
        error: function  () {
            //alert("Failed to get details");
        }
    });
             
    return flag;
}
 
//insert style into page 
function includeStyleElement(styles) {  
    var style = document.createElement("style");    
    style.type = "text/css";
    (document.getElementsByTagName("head")[0] || document.body).appendChild(style);
    if (style.styleSheet) { 
        //for ie
        style.styleSheet.cssText = styles;
    } else  {
        //for w3c
        style.appendChild(document.createTextNode(styles));
    }
}

 

3.3.2        Workflow email customisation

The email will only be sent to engineer or reviewer in the three scenarios:

When engineer uploads an idea or content, reviewer will receive an email;

When engineer uploads a content to an existing idea, reviewer will receive an email;

When reviewer approve/reject an idea or content, engineer will receive an email;

The design of the workflow process:

https://7ciwvg.dm2302.livefilestore.com/y2pDgoGwBm1zOAK-s1PN36t2NE1YFuXfpDTHDtPrAox3NLeI0wyyPj6GQAVFqtUvOrqkbjbuLxxn4EfiyWj81iCRK9bOCKRvCogiRgQiSLRcgI/ad15.png?psid=1

 

 The email design like this:

Email to engineer

https://7ciwvg.dm2302.livefilestore.com/y2poFlv4E2qjSO4mLvdHwcT5iRSzZIBZWI03fSy6hv3bdYiNCVhByAgmBa1Dlm4NnMH7geXE6Z0PKYD6L1CTwgFBRbJySCIqCUmZ7fD9hmeJvM/ad16.png?psid=1

Email to reviewer

https://7ciwvg.dm2302.livefilestore.com/y2pRSIKYPgztOfRDYX7g1YtrR9jQaLnC8QGBXlhtJXqxuIrH17CljYrBoJ0TTvXJ4EfQmtA5P0OEdqq8kGi56wU8sZnV7Diwo-JK4Fi1hovXMs/ad17.png?psid=1

Happy coding!