Save List Attachments to SharePoint Document Library

Is it possible to save list item attachments to the document library? The short answer is yes.

This question is really common, and as you might know, there isn’t an OOTB way to do it. Still, I do understand this need because having the attachment documents in the SharePoint document library brings many benefits; search and filtering, metadata, collaboration, etc. I remember, when I was looking for a solution for this, I did find many blogs about how to upload images to the SharePoint document library with Power App.

At the end of the day, the solution for this comes to the point I had in my Tech Days 2020 session ‘The Right Tool for the Job – Combine Power Platform and SharePoint.’ Know your tools and use the right tool for the job. There is no need to read the attachment blobs in Power App and then upload the data somehow. Everything can be done with Power Automate, SharePoint, and some smart additions to extend user experience with UI only customizations.

Prepare the Library

There isn’t anything special you will need to the list that has the attachments. For the document library, I like to add a field to link the documents and the item in the referring list. So, add a column called ParentID as metadata to the document library where you want to save the document. Later, we will use this column to give the users more accessible access to the documents.

Copy the Attachments

In this example, I have a simple list with few columns and a Canvas Power App with a simple. The Save button is using the basic SubmitForm to save the given details. The attachment handling is done so that the end-user uses the default attachment field functionality to save the attachment document to the list. Then we will use a Power Automate flow to move them to the document library. There are few ways to build the necessary Power Automation.

  1. You can build a flow that is triggered on SharePoint When an item is created.
    1. In this method, the user, who is using the form, will need edit permission to the list
    2. If needed, the end-user doesn’t need permission to the document library because that connection is handled with the account that is used to create the flow
    3. But in many cases, we want the end-users to have access both on the list and document library
  2. You can initialize the flow from the Power App
    1. In this case, the user will need permission both in the list and the document library

Let’s use the option two here and initialize the flow straight from the Power App. From the ribbon in the Power Apps studio, select Action -> Power Automate -> Create a new flow. This will open a new Power Automate for you that is triggered by a Power App.

  1. Add an Initialize variable action in the beginning
    1. Name can be, for example, ListItemID
    2. Type is Integer
    3. For the value, open the dynamic value panel and select Ask in PowerApps
    4. I also like to rename the action for easier recognition in later steps
  2. Then we can select a SharePoint action related to attachments
    1. There are multiply available, and we need to use the Get attachments one
    2. Connect the action to the correct SharePoint list
    3. The id parameter is coming from the variable initialized in the beginning
  1. Next, add Apply to each action under the Control category
    1. For the output, select the Body from the Get attachment step
  2. Then add a Create file action under the SharePoint category
    1. Connect the step to the correct document library
    2. Set File name from the dynamic value panel and select the DisplayName coming from the Get attachments step
    3. File Content can also be set from the dynamic values, and you can use the AbsoluteUri parameter from the Get attachments step

Finally, remember to give a name to the flow and save it. I used the name ‘Save Attachments Demo.’ After this, we can go back to the Power App and connect the flow to the application. First, select the Save button and open the OnSelect property. Then from the ribbon, select Action -> Power Automate -> select the flow we just created. If you had something in the OnSelect property, you might lose the written function, but with undo, you can get them back.

We need to modify the OnSelect property of the button with the following functions.

SubmitForm(frmMyReport);

SaveAttachmentsDemo.Run(frmMyReport.LastSubmit.ID);

  • submit form is used to send the form details to the SharePoint list
  • Then we call the flow and give the necessary details to copy the attachments

The forms in Power Apps have some data state-related properties (LastSubmited, Unsaved, Updates) we can use in the process. LastSubmited property holds all the fields and their values of the last submitted form. We will send the list item ID parameter to the Power Automate. This id is then saved in the ListItemID variable at the beginning of the flow. Test the form with few attachments, and after the submission, the flow should copy the list attachments to the document library.

Finetuning

At this point, we can copy the attachments to the document library, but this leaves us two copies in different places, which is not ideal. Let us make some additions to the flow to overcome this.

The next step is to add the list item id as metadata to the document. This can be done with Update file properties action. The correct file is found with dynamic id fetch from the Create file action. Set the ParentID with the value in the ListItemID variable.

In the final step, we will delete the attachment from the list item. This can be done with Delete attachment action. Point the action to the list and get the item based on the ListItemID variable. The File Identifier id is fetched from the Get Attachment action.

Save the flow and make a test run with the app. Now we have a solution where the user can give list details and documents with Power Apps form. The form details are saved in the SharePoint list, and the documents are saved in a document library.

Linking the Details

So, what is the benefit of saving the id of the list item as metadata for the documents? With the id, we can build an easy functionality where users can access the desired documents straight from the list.

  1. Add a new text column to the list
    1. I like to name the column as Link to Documents
  2. Go to the document library, filter the list based on the ParentID field.
  3. Next, open the column formatting setting of the Link to Documents field
  4. Paste the following JSON to the column formatting field.

{
"$schema": "https://developer.microsoft.com/json-schemas/sp/column-formatting.schema.json",
"elmType": "a",
"txtContent": "Show Documents",
"attributes": {
"target": "_blank",
"href": "='LINK TO YOUR LIBRARY/Forms/AllItems.aspx?FilterField1=ParentID&FilterValue1=' + [$ID]"
},
"style": {
"border": "none",
"background-color": "transparent",
"cursor": "pointer",
"color": "Blue"
}
}

  1. Replace the ‘LINK TO YOUR LIBRARY’ with an URL pointing to your document library.
    1. This link in the JSON is pointing to a filtered view in the document library
    2. The FilterValue1 is set dynamically based on the ID of each list row ([$ID])
  2. Save the formatting and close the formatting panel

Now we have an easy way to navigate to the document straight from the list. When you click the Show Document link in the list, you will be directed to the documents library, and you will see only the documents related to the list item. More information on column formatting, like using the icons, can be found here: https://github.com/SharePoint/sp-dev-list-formatting

Advertisement

Cascading Fields in Power Apps Forms – Basics and Few Tips

As far as I remember, customizing SharePoint forms and cascading fields have been one of the main areas that organizations have asked. I have done these customizations for classic and modern SharePoint with InfoPath, custom solutions, and other form solutions. Now we can use Power Apps in many cases, and they’re still is no exception here. Many app creators are wondering how to make these customizations and how can I create cascading fields, for example?

A full walkthrough for custom forms would need a separate series of posts, so I am concentrating on the cascading fields and small form examples here. Of course, with Power Apps, it’s not about SharePoint only, and the following techniques can be used against other data connectors also. Here is my typical way of implementing cascading fields in Power Apps. I’ve used this way against small SharePoint lists and in multifield large business scenarios.

Main Requirements

  1. We have a SharePoint list that has a Type field
  2. We want to able to select the type from a range of values that can be categorized into multiple different categories
  3. We want to give the administrators an easy way to maintain these values, so they should be placed to a SharePoint list
    • There are two SharePoint lists Main Category and Sub Category
    • Sub Category has a lookup column pointing to the Main Category

In the custom form, the users are first selecting the category, and then they can select the value belonging to this category from the second drop-down box

Basic Form With Cascading Fields

Let’s create a simple canvas app for our form. Form only has a text box for the title, two drop-down boxes for selecting the categories, and a submit button. You also need to connect the app to all the three lists in SharePoint.

Even though this is a simple list, remember the naming convention. Taking a habit to rename the controls and screen will help you in the future. You can also use grouping, as I did for the labels, to make it easier to find important elements in the tree view.

  1. Add the necessary label, one text input, and two drop-down box
  2. Select the Items property of the first drop-down (ddpMainCategory in the example)
  3. Connect the items to the Main Category list
  4. Make sure the value property is set as Title
  5. Do the same thing to another drop-down (ddbSubCategory) but connect it to the Sub Category list

The basic setup is now done, but we are missing the cascading part. The category selection doesn’t filter the value list. We need to change the items selected for the second drop-down to make this happen.

In the items selection of the ddpSubCategory, we need to filter the items based on the selection in the other drop-down box

Filter('Sub Category','Main Category'.Value = ddpMainCategory.Selected.Title) 

After this, we have the basic cascading fields ready

Saving the Details

In the SharePoint list, we only have the Title, and Type Value, and the values are now read from a different SharePoint list. Therefore we didn’t use a Power Apps form element where we have the SubmitForm action available. To save the details from our custom form, we need to use the Patch command. Add the following command to the OnSelect -event of the Save button. Again, remember to comment your code. It helps. I’ll promise

These functions will save the details to SharePoint based on what the user selected from the drop-down and resets the form for the next one. The idea in the Patch command is:

//Save details to SharePoint
Patch('Important List',
Defaults('Important List'),
    {
        Title: txtTitle.Text,
        'Type Value': ddbSubCategory.Selected.Title
    }
);

//Reset form element
Reset(txtTitle);
Reset(ddpMainCategory);
Reset(ddbSubCategory);
  • First ‘Import List’ is the name of the data source where we are running the patch command
  • Defaults command will tell to create a new item in the list
  • Next, we are adding the values from the form into the SharePoint columns of the new item
  • Value from the second drop-down box can be read from the Selected property
  • More info from the Patch – function: https://docs.microsoft.com/en-us/powerapps/maker/canvas-apps/functions/function-patch

Cascading Fields – Next Version

You might notice a couple of things in this way of building cascading drop-downs. Updating the ddbSubCategory field might take some time after we change the value in ddpMainCategory, and the first item in the drop-down box is selected automatically. In many situations, we want users to see an empty value in the drop-down and take action to choose the one they need.

  1. To tackle these issues, we will read the drop-down selection values to collections in the app start
  2. Open the App OnStart settings and add the following functions to save the selected items from SharePoint to two collection
//Main Categories to collection
ClearCollect(MainCatSelection,"");
Collect(MainCatSelection,'Main Category');

//Sub Categories to collection
ClearCollect(SubCatSelection,"");
Collect(SubCatSelection,'Sub Category');
  1. First, we are clearing the possible old collections and adding one empty row into them
  2. Then we collect the SharePoint rows from the lists and save them to the collections
  3. Next, we need to update the items settings of the drop-downs
    1. ddpMainCategory = MainCatSelection
    2. ddbSubCategory = Filter(SubCatSelection, IsBlank(Title) Or ‘Main Category’.Value = ddpMainCategory.Selected.Title)
  4. In the ddbSubCategory, we are filtering the collection so that the first black item is selected and also all the other rows that have the selected category value in the ‘Main Category’ field

Click the Run OnStart action for the App to populate the collection for the test. Now we have cascading drop-downs that are fast to use and show an empty value before the user is making the necessary actions.

Reading Word File Content from Office 365

I had a case where I needed to read a document content from Office 365 through REST API and use that on my project. More specifically I wanted to use the content in my Word Add-in. Back then I was struggling to be able to read the content in a correct way. I didn’t find a good solution on how to manipulate the data I’m getting back from REST API.

Use it in an Application

Finally, I did solve the issue and I will show you how. This is just a quick example of the functionality without a complete application. I’m having a session on upcoming Saturday 10/28/2017 in SharePoint Saturday New England at 9:00 am.

My session title is “Tools for Information Worker – Introduction to Office Add-ins Development.” On the session, I will demonstrate a complete example on how to use these techniques, and I will also share the source code of the application after the session.

http://spsnewengland.org/agenda/

Stay tuned and follow me in Twitter @mikkokoskinen to know when the application is available.

Reading in Node.js Application

But back on the solution. You are able to use the REST API call called getfilebyserverrelativeurl with the $value attribute to get document with content.

<span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>
executor.executeAsync({
  url: "<app web url>/_api/SP.AppContextSite(@target)/web
    /getfilebyserverrelativeurl('/Shared Documents/filename.docx')/$value
    ?@target='<host web url>'",
  method: "GET",
  binaryStringResponseBody: true,
  success: successHandler,
  error: errorHandler
});

More info:  https://msdn.microsoft.com/en-us/library/office/dn450841.aspx

If you change the placeholders from the above REST call and navigate into that on your browser, the document will be downloaded automatically. In a case you have ever used Microsoft Graph to get documents from a document library, you may have seen a parameter called @microsoft.graph.downloadUrl. For example, this call will list documents from the library with a given id: https://graph.microsoft.com/v1.0/drives/{library-id}/root/children.

GraphImage

In case you didn’t know @microsoft.graph.downloadUrl gives you a short-term access to the file without a need to send authentication inside a call header etc. The URL has a temporary authentication token that is valid only for a couple of minutes. But the same thing with that URL. If you navigate to that URL, the document will be downloaded.

So how to get the content into a variable and use it? Here’s a short explanation on how you can use the call return value in an application that is using Node.js and TypeScript.  Maybe your application is a Word add-in, and you want to read the document in Office 365 as a starting point for your own document.

In the example, we have a situation that you have the @microsoft.graph.downloadUrl of the file, and you want to download the content into a variable.

  1. For easy call based on URL, we will use a module called node-fetch.
    1. It’s light-weight module that brings window.fetch to Node.js
    2. More information from here: https://www.npmjs.com/package/node-fetch
  2. Run npm install –save node-fetch in the terminal window to install the module for the project.
  3. Open the TypeScript file where you want to add a function for the call.
    NodeFetch
  4. Add a new reference for node-fetch: import fetch = require(‘node-fetch’);
  5. Then add a function that uses the @microsoft.graph.downloadUrl to get the content of the document.
      1. The URL is sent as a parameter in the function call.
      2. This function is resolving a promise so that we can use await functionality when we are calling the function.
    static getTemplateDocument(templateURL: string) {
            return new Promise<string>(async (resolve, reject) => {
                let templateArray: any;
                fetch (templateURL, {body: 'buffer'}).then(res => {
                    res.buffer().then( data => {
                        templateArray = data;
                        resolve(templateArray);
                    });
                });
            });
        }
    
  6. The important part is to set the body setting of the fetch call as a buffer. The default value for the body is empty, but we specifically want to get the content of the document.
    1. When this setting is set, we can use the buffer() function of the result we are getting back from the fetch to read the data.

And the Data is?

We are almost there. The question is that what does the getTemplateDocument call actually send back to us?

The answer is that we are getting back a Uint8Array that holds the content of the template Word document. We can now use this array in a way our application needs it. In Office.js there is a function called insertFileFromBase64. With this function, we can add a content of a docx file into our current document as long as the file is base64 encoded. And because we already have the file in Uin8Array format, it’s easy to make the transformation and insert the file.

Here’s a short example code for that when we have the file back in a result attribute from the function call above.

var templateBuffer = result.data;
    var u8 = result.data;
    var b64encoded = btoa(String.fromCharCode.apply(null, u8));

    Word.run(function (context) {

        // Create a proxy object for the document.
        var thisDocument = context.document;

        // Queue a command to get the current body.
        // Create a proxy range object for the selection.
        var body = context.document.body;

        // Queue a command to replace the body.
        body.insertFileFromBase64(b64encoded, Word.InsertLocation.replace);

        // Synchronize the document state by executing the queued commands,
        // and return a promise to indicate task completion.
        return context.sync().then(function () {
            console.log('Added the content of the file .');
        });
    })
    .catch(function (error) {
        console.log('Error: ' + JSON.stringify(error));
        if (error instanceof OfficeExtension.Error) {
            console.log('Debug info: ' + JSON.stringify(error.debugInfo));
        }
    });<span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>

Use case: List Posts – Using Widget Wrangler and AngularJS in the web part development

In this post, I will show you how you can create a widget or web part that show you a list of posts from SharePoint blog site. The element is done purely as client side coding. One thing that is true with SharePoint is the fact that you can do the same things in multiple ways. Same goes here with this example, but my aim here is to demonstrate how you can create plugins, web parts, apps however you want to call them, easily and purely as client side development. I think that a word widget is most descriptive, and that is what we will be using here.

This example is based on real life solution that I made on my last project on top of SharePoint 2013 on-prem. The widget is also tested to be working in SharePoint Online. Technically you could use it any other web platform, and that is the key why I want to present you a framework called Widget Wrangler.

What is Widget Wrangler?

Why try to summarise something when it’s done perfectly in the actual source? “The Widget Wrangler is a lightweight framework for managing the loading of javascript “widgets” on a web page.”. With the framework, you can create isolated widgets and control the loading of each file and dependency you need for the element.

With Widget Wrangler (later ww) you can encapsulate the functionality of the widget so that different, or even multiple of the same kind, elements won’t be interference with the hosting page and other widgets. This way the isolation and creation of truly separated UI and functionality are easier to do. The framework also manages the efficient loading when multiple web parts on a page use the same javascript libraries or CSS files.

<div class="latestPostWP">
 <div posts-element></div>

 <script type="text/javascript" src="/ourfirm/offices/SiteAssets/js/pnp-ww.min.js" 
 ww-appname="LatestPostWPApp" 
 ww-apptype="Angular"
 ww-appcss='[{"src": "/ourfirm/offices/SiteAssets/webparts/LatestPost/latestPost.css", "priority":0}]'
 ww-appScripts='[{"src": "/ourfirm/offices/SiteAssets/js/angular.min.js", "priority":0}}
 ]'>
 </script>
</div> 

You can download Widget Wrangler and find more information from here:
https://github.com/Widget-Wrangler/ww

There’s also more deeply demonstration available in Channel9 PnP Web Cast:
PnP Web Cast – Introducing Widget Wrangler for SharePoint development

Post-Listing Widget

To demonstrate the use of ww, let’s create a simple widget that lists blog post from SharePoint blog site. Actually, I have built this type of functions multiple times before so this is a real world example. I will use my favored framework AngularJS and SharePoint REST API to accomplish the actual functionality. This way we can create a solid client side solution and have a flexible separation of functionality and presentation. And that should be the starting point for every customization IMO.

Requirements

  • Get blog post from SharePoint blog site.
  • Show three latest post and other through pagination.
  • From each post title (link to post) 365 first characters from the blog post.
  • Show to order an email alert for new posts.
  • Show link to RSS feed.

The whole solution can be found from my GitHub repository https://github.com/MikkoKoskinen/WW-Demo-ListPost

From there you can find four files:

  • WWPostsWPHTML.html – File containing the ww implementation that was saved on the script web part.
  • listPost.js – The angular application that implements the widget functionality.
  • listPost.html – The presentation layer of the widget.
  • listPost.css – Styling of the widget.

I’m also using following frameworks and extensions:

Widget Wrangler Section

<div class="latestPostWP">
 <div posts-element blogSiteURL='' listTitle='Posts' listID='74DF3BE3-5536-45B6-B171-B97C1BCD61D1'></div>

 <script type="text/javascript" src="/ourfirm/offices/SiteAssets/js/pnp-ww.min.js" 
 ww-appname="LatestPostWPApp" 
 ww-apptype="Angular"
 ww-appcss='[{"src": "/ourfirm/offices/SiteAssets/webparts/LatestPost/latestPost.css", "priority":0}]'
 ww-appScripts='[{"src": "/ourfirm/offices/SiteAssets/js/angular.min.js", "priority":0},
 {"src": "/ourfirm/offices/SiteAssets/js/truncate.js", "priority":1},
 {"src": "/ourfirm/offices/SiteAssets/js/dirPagination.js", "priority":1},
 {"src": "/ourfirm/offices/SiteAssets/webparts/LatestPost/latestPost.js", "priority":2}
 ]'>
 </script>
</div> 

Above you can see the WW section of the solution that has to be added to the page. This code will handle the loading of the widget. I’m a fan of script web part, so I have placed that code in the snippet section. You could, of course, use other methods of adding the code of course. As we can see, you can control very well on what you want to load and in which order. Key things in the code are.

  1. Everything has to be wrapped inside a div. Div with class ‘latestPostWP’ in our case.
    1. This is an important thing to remember because ww won’t work without it.
  2. Ww implementation is done inside the script tag that is calling the library on the source attribute. After that, you can give the necessary settings.
  3. ww-appname = The name of your widget application. In Angular implementation, this has to match with the module name.
  4. ww-apptype = Type of the framework used on the widget. On the time of writing this, only Angular is supported.
  5. ww-appcss = List of CSS style sheet you want to be loaded for the widget. You can give multiple files and control the loading order with the parameter.
  6. ww-appScripts = List of script files sh you want to be loaded for the widget. You can give multiple files and control the loading order with the parameter.

And that’s it. It’s just that simple.

Now, if look at the browser console after page load, you can see that ww has initialized a widget on the page. As you can see our widget has an index number 0. If there were multiple of these elements on the page, all of them would have them own number. This is the power of ww and encapsulating of the widgets.

Presentation

<div class="resultItem announcementItem" dir-paginate="post in posts | itemsPerPage: 2" pagination-id="posts"> 
 <div class="resultTitle"> 
 <a class="" href="{{viewItemURL}}{{post.ID}}">{{post.Title}}</a> 
 </div> 
 <div class="resultContent"> 
 <span class="resultDate" ng-bind="post.PublishedDate | date:'LLLL dd, yyyy'"></span> 
 <br>
 <div class="resultDetail">{{ post.Body | htmlToPlaintext | characters:350 :true}}</div> 
 <div class="resultMore"><a href="{{viewItemURL}}{{post.ID}}">More≫</a></div> 
 </div> 
</div>
<div class="blogActions">
 <div class="action">
 <a class="ms-calloutLink" target="_blank" href="{{listRSSURL}}">
 <span style="height:16px;width:16px;position:relative;display:inline-block;overflow:hidden;" class="s4-clust ms-blog-linkCommandImage">
 <img src="/ourfirm/offices/_catalogs/theme/Themed/4D8112E7/spcommon-B35BB0A9.themedpng?ctag=3" style="position:absolute;left:-236px !important;top:-66px !important;border-width:0px;"></span>&nbsp;<span class="ms-splinkbutton-text">RSS Feed</span></a>
 </div>
 <div class="action">
 <a class="ms-calloutLink" href="{{postAlertURL}}">
 <span style="height:16px;width:16px;position:relative;display:inline-block;overflow:hidden;" class="s4-clust ms-blog-linkCommandImage"><img src="/ourfirm/offices/_catalogs/theme/Themed/4D8112E7/spcommon-B35BB0A9.themedpng?ctag=3" style="position:absolute;left:-236px !important;top:-30px !important;border-width:0px;"></span>&nbsp;<span class="ms-splinkbutton-text">Alert Me</span>
 </a>
 </div>
 <div class="ms-clear"></div>
</div>
<dir-pagination-controls pagination-id="posts"></dir-pagination-controls>

Here you can see the presentation layer. Technically it’s just plain HTML with some Angular code. But I would like to highlight few relevant things, though.

I’m not using the default repeater to show a list of fetched post. The code is using pagination extension for that. This will provide us an easy way to divide the result to a different section. Pagination is done in the first div element “dir-paginate=”post in posts | itemsPerPage: 2″ pagination-id=”posts””. The first setting is the same than in default repeater telling to loop through all the post in posts variable. Next, we will give the amount of visible items per page. Finally, we give a unique id for the pagination element. This way we can connect the repeater, and the pagination action element added as the last element on the widget (dir-pagination-controls div). The extension is also handling all the necessary functions like showing the page count and back and forward links.

Next thing to notice here is that we are modifying the date format for better visibility with ng-bind element.

 ng-bind="post.PublishedDate | date:'LLLL dd, yyyy'"
{{ post.Body | htmlToPlaintext | characters:350 :true}}

Lastly, we are showing a short teaser from post body with element above. We can truncate the text with Angular Truncate filter and give the amount of character shown to the user. True settings tell that also complete words can be cut. htmlToPlaintext is our custom filter that takes out any HTML elements form the body text. More about this soon.

Functionality

(function() {
  angular
    .module('LatestPostWPApp', ['truncate','angularUtils.directives.dirPagination'])
    .filter('htmlToPlaintext', function () {
        return function(text) {
            return  text ? String(text).replace(/&amp;lt;[^&amp;gt;]+&amp;gt;/gm, '') : '';
        };
    })
    .directive('postsElement', function() {
        return {
            restrict : 'EA',
            transclude : false,
            templateUrl: '/ourfirm/offices/SiteAssets/webparts/LatestPost/latestPost.html',
            controller: function ($scope, $log, $q, $http, $attrs) {

                $scope.getEvents = function getEvents() {
                    return $http({
                        method : &amp;quot;GET&amp;quot;,
                        url: _spPageContextInfo.webAbsoluteUrl + &amp;quot;/&amp;quot; + $attrs.blogsiteurl + &amp;quot;/_api/web/lists/GetByTitle('&amp;quot; + $attrs.listtitle + &amp;quot;')/items?$orderby=PublishedDate desc&amp;quot;,
                        headers: { &amp;quot;Accept&amp;quot;: &amp;quot;application/json;odata=verbose&amp;quot; }
                    })
                    .then(function sendResponseData(response) {
                        // Success
                        return {
                            Items: response.data.d
                        }

                    }).catch(function(response) {
                        $log.error('HTTP request error: ' + response.status)
                        return $q.reject('Error: ' + response.status);
                    });
                };

                $scope.getEvents()
                .then(function(data) {
					//Get list items
					$scope.posts = data.Items.results;

					if ($scope.posts.length &amp;gt; 0) {
                        $scope.viewItemURL = _spPageContextInfo.webAbsoluteUrl + &amp;quot;/&amp;quot; + $attrs.blogsiteurl + &amp;quot;/Lists/&amp;quot; + $attrs.listtitle + &amp;quot;/Post.aspx?ID=&amp;quot;;
                        $scope.listRSSURL = _spPageContextInfo.webAbsoluteUrl + &amp;quot;/&amp;quot; + $attrs.blogsiteurl + &amp;quot;/_layouts/15/listfeed.aspx?List={&amp;quot; + $attrs.listid + &amp;quot;}&amp;quot;;
                        $scope.postAlertURL = _spPageContextInfo.webAbsoluteUrl + &amp;quot;/&amp;quot; + $attrs.blogsiteurl + &amp;quot;/_layouts/15/SubNew.aspx?List={&amp;quot; + $attrs.listid + &amp;quot;}&amp;amp;Source=&amp;quot; + _spPageContextInfo.serverRequestPath + &amp;quot;&amp;quot;;
                    }
					else {

					    $scope.noItemsFound = true;
					}
				});
            }
        };
    }); // End directive()
}()); // End IFFE

The functionality of the widget is done as an Angular application. On the first rows of the code, we are giving a name for the application and loading the necessary extension. First thing on the code is a custom filter called htmlToPlaintext. You may call this function with a parameter holding some text content. The function will strip out all HTML elements with a regular expression and return a pure text content. This is used in the presentation layer.

 .directive('postsElement', function() {
 return {
 restrict : 'EA',
 transclude : false,
 templateUrl: '/ourfirm/offices/SiteAssets/webparts/LatestPost/latestPost.html',
 controller: function ($scope, $log, $q, $http, $attrs) {

After this, we have created a directive element named ‘postsElement’ that is will be called on the script web part.

Important things for the directive are to give the right path to the template file and pass-through the variables with $attrs parameter. As we can see from the directive element, we are passing a couple of parameters to be used during the functionality.

  • blogSiteURL = If the widget is placed on another web than in blog site, you can give the URL with this parameter. URL is used during the REST call to get the post. URL should be given as relative against site collection root.
  • listTitle = Title of the list where posts are read. The title is used during the REST call to get the post.
  • listID = Id of the list where posts are saved. This is used in email alert and RSS functions.

In the first section of the code, we are creating a function called ‘getEvents’ that makes and REST API call against SharePoint and gets all the posts from a given list. The call is using the parameters mentioned above to do the call. If the call is successful, the found data items will be returned.

Next, the code will construct a deferred object from the function above. We are catching the promise and saving the found data items to the ‘posts’ variable. Also if some items were found, we would construct few parameters for view item, RSS and alert links. These parameters are used in the template during the construction of the widget.

<div class="resultMore"><a href="{{viewItemURL}}{{post.ID}}">More≫</a></div>

And that’s it. Here you have a relative simple POC of client-side widget that is reading information from SharePoint and using Widget Wrangler framework for better maintenance.

Your Day with Microsoft NextGen Portals

Microsoft Ignite 2015 last month was huge. Basically everything from Microsoft stack was presented there. For me all things related to modern workplaces, meaning Office 365 and SharePoint, was under the microscope. But I have to say that the information flow was a blast and it was almost impossible to digest all the new things and news that were presented.

We heard about Groups, Delve, Infopedia, SharePoint 2016, Yammer etc. Microsoft is building their cloud and portal solutions based on following strategy – Cloud first, mobile first. You definitely saw in Ignite.

After the conference many may wonder that how is this all wrapped around together and what tools I should use? I don’t have a clear answer to that. There’s always the one and only “It depends” factor. But now after a while of reading and thinking all the new, I decided to give it a try. On one point of view at least.

I made a presentation of one full day for typical information worker and how these new or currently existing tools can be used and how they may help users during the day. At the same time you can see the main published NextGen portal tools from Ignite.

One new cool tool available for both public Office 365 and for enterprise Office 365 is Sway. With Sway you can quickly create nice looking mobile ready presentation. That is why I used that also. Remember to add Sway to your tool box.

>> Your Day with Microsoft NextGen Portals