SharePoint Lists-Smarter with JavaScript

SharePoint lists are great for tracking data-easy to create, export to Excel, develop against using JSOM.  Problem is when you have a list sometimes you have documents that are associated with the list item.  There are attachments to the list but better to use a document library right?  Maybe create a folder in the document library for each new list item created and tag the folder for the specific folder associating the item to the folder.

Here is where some simple JavaScript and the SharePoint JSOM come in to save the day…

Step 1: Create your SharePoint list, add your fields, etc.

Step 2: Create a new item, when the newform.aspx opens simply select Edit Page from settings.  Settings-Edit page.  Now you can place a content editor web part referencing your script file.

Screenshot (Standard NewForm.aspx un-customized)

SPLISTFORM

Also, add a document library web part to display the folders we are going to create from the script and filter for your specific list item using the web part connection from the list item web part to the document library web part.

Add any other web parts to the page, calendar, tasks lists etc.  This provides a simple all up view for related content tagged to the list item.

Add a content editor web-part or script editor web part referencing your script file (I typically organize conveniently in the site collections style library)

Example script:
//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.2.min.js

//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js

var matterName;
var matterNum;
var folderName;
var collListItem;
var folderUrl ;
var folderpath;

//Execute when the user saves the item create a folder in our document library and tag the folder with relevant data from our list item in order to associate the two
function PreSaveItem()
{

//grab some fields off the newform.aspx to use for tagging the folder we create
matterNum = $(‘input[title=”MatterNum”]’).val();
matterName = $(‘input[title=”Name”]’).val();

//Create the folder

folderName = matterName;
folderUrl = “[SiteUrl]” + “/” + folderName;

createFolder(folderUrl, folderName, matterNum);

return true;
}
function createFolder(folderUrl, fname, matterNumber) {

var clientContext;
var oWebsite;
var oList;
var itemCreateInfo;

clientContext = new SP.ClientContext.get_current();
oWebsite = clientContext.get_web();
oList = oWebsite.get_lists().getByTitle(“Documents”);

itemCreateInfo = new SP.ListItemCreationInformation();
itemCreateInfo.set_underlyingObjectType(SP.FileSystemObjectType.folder);
itemCreateInfo.set_leafName(folderName);
//I am using a specific content type inherited from the folder type
this.oListItem = oList.addItem(itemCreateInfo);
this.oListItem.set_item(“ContentTypeId”, “0x0120003C4F41AA23709B458E3DA2EEFAA5DCAA”);
this.oListItem.set_item(“Title”, fname);
this.oListItem.set_item(“MatterNum”, matterNumber);
this.oListItem.update();
clientContext.load(this.oListItem);
clientContext.executeQueryAsync(
Function.createDelegate(this, successHandler),
Function.createDelegate(this, errorHandler)
);
}
function successHandler() {

//Yeah have a new folder

}

function errorHandler(sender, args) {
alert(‘Record Create failed. ‘ + args.get_message() + ‘\n’ + args.get_stackTrace());
}

//TO DO-This part for next time-build out a complete folder structure based on a pre-built document libary structure
function retrieveFolderStructure() {
var _clientContext1;
_clientContext1 = SP.ClientContext.get_current();
var oList = _clientContext1.get_web().get_lists().getByTitle(‘Matter Documents’);

var camlQuery = new SP.CamlQuery();
camlQuery.set_viewXml(“500”);

this.collListItem = oList.getItems(camlQuery);

_clientContext1.load(collListItem);

_clientContext1.executeQueryAsync(Function.createDelegate(this, this.onQuerySucceeded2), Function.createDelegate(this, this.onQueryFailed2));

}

 

Summary:

Make your SharePoint lists a little smarter.  Why not?  Of course all this works on SharePoint 2013 and Office 365.  Also can use on editform.aspx, the script to access the form fields slightly different but doable np.

Bonus material:

Add a calculated column to your SharePoint list that links users to the specific folder:

=”<a class=’docs’ title=’Documents’ href=”&”‘”&”/[Site]/Shared Documents/Forms/AllItems.aspx?RootFolder=/[Site]/Shared Documents/”&Name&”‘”&”>Documents</a>”

*Set your column format to “number” so displays HTML

 

Advertisements

SharePoint 2013-Create User Profiles from BCS External List using PowerShell

Overview:
We have an internal identity management system that stores and manages identities and custom properties for all employees.  As part of a recent project designed a solution to populate our SharePoint user profile store using information stored about each user in our custom indentity management system.

Alternatives:
We evaluated the Active Directory Import and User Profile Synchronization as well.  The Active Directory Import supports LDAP sources for profiles and although can be configured to import from BCS external list does not actually create user profiles based on that data, rather can populate additional properties for AD user profiles that already exist.  In our case those user profiles do not exist since we are not able to use AD import or profile sync in this environment.

Solution steps::

Step 1:
Configure SharePoint 2013 Secure Store Service and Business Connectivity Services

Step 2:
Configure an external content type and external list in SharePoint Designer 2013 with full CRUD operations enabled bound to a custom database table in SQL where identities are stored.

Note: The external list provides full CRUD operations on the external list to the SQL database table.  This is a key additional feature over a standard SharePoint list which was useful in our case having the option of querying and perform updates to the external list directly from our custom admin form via JavaScript and the SharePoint REST API.

This could be interesting for other use cases as well where updating a custom SQL database table from a SharePoint list is usefull i.e. AngularJS or REACT UI user-interface integrated with SharePoint list data via REST.  Exploring more on this front soon.

Step 3:
Develop PowerShell script to query the SharePoint external list, for each item check for existing profiles in SharePoint, and if none exist create a new profle populated with the data in the external list item for each user.

Example PowerShell script:

#Rod Stagg

#Create SharePoint User Profiles based on data in External List (BCS)

[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)]
[System.String]$siteUrl,
[Parameter(Mandatory=$True)]
[System.String]$listSiteUrl,
[Parameter(Mandatory=$True)]
[System.String]$domain,
[Parameter(Mandatory=$True)]
[System.String]$user
)

Remove-PSSnapin Microsoft.SharePoint.Powershell -ErrorAction SilentlyContinue
Add-PSSnapin Microsoft.SharePoint.Powershell

$claimsformat = “i:0#.w|”;
$accountName = “”;
$fullname = “”;
$lastfirst = “”;
$firstname = “”;
$lastname = “”;

$Web = Get-SPWeb $listSiteUrl
$SourceList = $Web.Lists[“External List”]
$SourceItems = $SourceList.Items | where {$_[‘networkid’] -ne “”}
$site = Get-SPSite $siteUrl
$context = Get-SPServiceContext($site)
$profilemanager = new-object Microsoft.Office.Server.UserProfiles.UserProfileManager($context)
$SourceItems | ForEach-Object {
# Assumes account in domain\account format and networkid field is the unique identifier on external list
$accountName = $claimsformat + $domain + “\” + $_[‘networkid’]
# Assumes username stored as lastname, firstname on external list so split
$lastfirst = $_[‘lastfirst’];
$firstname = $lastfirst -split “,”
Write-Host $firstname[1];
Write-Host $firstname[0];
Write-Host $accountName;
# If user profile doesn’t exist create new one
if ($profilemanager.UserExists($accountName)) {
$userProfile = $profilemanager.GetUserProfile($accountName)
}
else {
$userProfile = $profilemanager.CreateUserProfile($accountName)
}

# Update any necessary user profile properties
$userProfile[“FirstName”].Value = $firstname[1]
$userProfile[“LastName”].Value = $firstname[0]
# Commit changes
$userProfile.Commit()

}

Additional elements of entire solution:

  • SSIS population of SQL database table that is bound to the SharePoint 2013 external list.
  • Windows schedule task to execute PowerShell script on
  • JavaScript to update the external list from our custom form.

 

SharePoint 2013 and AngularJS perform CRUD operations in multiple site collections with REST

SharePoint 2013 and AngularJS-performing CRUD operations in multiple site collections with REST

Overview:

This post covers a specific scenario with SharePoint 2013 and AngularJS (or other javascript framework) to host HTML and JavaScript in one site collection that create/update SharePoint list items that reside in a completely different site collection, and doing so without using SharePoint Hosted Apps.

If you are using AngularJS/SharePoint 2013 in a project that requires both querying AND updating data stored in lists that reside in a separate site collection than where the AngularJS app files are hosted than you may want to read on.

Issue:

The app files are stored at the root of the web application while the data is distributed to separate site collections and team sites for each organization and individual teams. This structure provides a mechanism to distribute the data surfaced in the AngularJS UI using SharePoint to provide the necessary security trimming of the data at each level of the organization.  Using SharePoint for the backend data and security and structuring the information architecture in this manner was designed to eliminate the need to manage user permissions as part of the custom application.

In SharePoint 2013 hosted apps the ability to query and update list items dispersed among multiple site collections is provided by scoping the SharePoint app to tenant and using the SP.RequestExecutor to make the REST calls (I covered in a recent post)

In this particular application however the decision was made not to go with a SharePoint hosted app until a solution could be developed to safely remove the app id from the URL.

Recently,  in fact just last week we ran into a blocking issue performing updates to lists that reside in a different site collection than were the AngularJS app files reside.  Once we rolled out the new site collection/team site structure the development team reported that the REST calls to perform updates to the list items were now all of a sudden returning an error. Curiously these identical REST calls functioned as expected when the data resided in the same site collection as the REST code and although errors now returned when performing updates the code was able to query the data in the separate site collection as expected.

Error:

The REST call to the _api/ContextInfo was failing with the message “the Method was not allowed and that it was using a GET but required a POST”  This was odd since the code was in fact using a POST, not a GET.

Solution:

When I first dug into the issue myself it occurred to me that maybe the form digest issue was the culprit. I reviewed the code being used to make the REST calls to update the data and it all appeared fine-very similar to documented examples from Jeremy Thake, Andrew Connell, and others.   Wictor Wilen has a well documented fix for the form digest issue on his blog but after reviewing against our existing code did not see any substantial difference that would explain why our code was failing.  The code in our specific application is slightly different since it uses promises but had similar characteristics to examples from other sources.

However I did notice our code was hard-coded to the root URL so I tried changing the call to use the URL to the site where the data resided rather than the root site collection.

Example:

FROM:

$.ajax({
    url: "/_api/contextinfo",
    method: "POST",
    headers: { "Accept": "application/json; odata=verbose"},
    success: function (data) {
        $('#__REQUESTDIGEST').val(data.d.GetContextWebInformation.FormDigestValue)
    },
    error: function (data, errorCode, errorMessage) {
        alert(errorMessage)
    }
});
TO:
$.ajax({
    url: /org/team/subteam/_api/contextinfo",
    method: "POST",
    headers: { "Accept": "application/json; odata=verbose"},
    success: function (data) {
        $('#__REQUESTDIGEST').val(data.d.GetContextWebInformation.FormDigestValue)
    },
    error: function (data, errorCode, errorMessage) {
        alert(errorMessage)
    }
});
Unfortunately this did not resolve the issue entirely-I did additional research along with trial and error to determine the root cause of the error.  Finally thought to read the comments on Wictor Wilens post on the form digest issue and there the solution presented itself in one of the comments-one line from a comment stood out that if you use Type: Post  rather than Method: Post the call will give you the $(‘#__REQUESTDIGEST’).val() rather than the object.  Sure enough, turned my chair around and asked the developer to give it a try.  IT WORKED!  So simple but seemed like a needle in the haystack on a day where this one issue was holding up deploying the app for our customer to test.  Seems obvious looking back, but like my grandma said hindsight is 20/20.
Root cause:
SharePoint requires the form digest for creating and updating items in separate site collections but not for querying items and our code was not accounting for providing the correct URL for the source data and was using method: POST rather than TYPE: POST when attempting to access the #_REQUESTDIGEST.val required for the post to execute successfully.

Resolution:
When creating or updating list items from javascript executed in a separate site collection than the SharePoint list resides AND you are not using a SharePoint Hosted App and SP.RequestExecutor ensure you set your REST call to use type: “POST” rather than method: POST AND  pass in the URL to the site where the data actually resides for the _api/contextinfo.

Updated example:

$.ajax({
    url: /org/team/subteam/_api/contextinfo",
    type: "POST",
    headers: { "Accept": "application/json; odata=verbose"},
    success: function (data) {
        $('#__REQUESTDIGEST').val(data.d.GetContextWebInformation.FormDigestValue)
    },
    error: function (data, errorCode, errorMessage) {
        alert(errorMessage)
    }
});
Guess its a good idea to read blog comments:)

Also, big props to Wictor Wilen for sharing great info and also to whoever posted this insightful comment on his blog that saved the day:

“it’s a great post, but one issue is coming with “method: ‘POST’ – which is it is throwing “The HTTP method ‘GET’ cannot be used to access the resource ‘GetContextWebInformation’. The operation type of the resource is specified as ‘Default’. Please use correct HTTP method to invoke the resource”.
 If we’ll use “type – ‘Post” rather than “method – ‘Post’, it will give you the $(‘#__REQUESTDIGEST’).val(). :)”

http://www.wictorwilen.se/sharepoint-2013-how-to-refresh-the-request-digest-value-in-javascript

Rod Stagg

 

TIP using SP.RequestExecutor to access data in SharePoint 2013 hosted apps with AngularJS cross domain

Background: In my current project developing a SharePoint 2013 hosted app where all the UI is developed in AngularJS presentation framework while using SharePoint host site collection and subsites for storing data in lists, security, version control, etc.  Convinced this is the best way to develop great UI in SharePoint after seeing the results of using AngularJS presentation framework for UI and SharePoint hosted app for the data.  There are a couple custom WCF services and also Node.js in the mix as well-very cool and the client very happy with the speed and responsiveness of the single page app.

In the process though, ran into a couple interesting aspects of accessing data from lists stored in both the SharePoint host site collection and subsites.  The few examples I found are from Jeremy Thake here and Andrew Connell here and recommend both since they are excellent.  But overall the web is a little sparse working with data in the host site collection/subsites using the SharePoint cross domain access via SP.RequestExecutor. The one I started from is a decent post on http://msdn.microsoft.com/en-us/library/office/fp179927%28v=office.15%29.aspx#SP15Accessdatafromremoteapp_Hostweb but there are few details when working with AngularJS routes had to work out in addition so sharing for those working on similar projects.

Tips for avoiding issues accessing SharePoint lists in the host site collection/subsites web in a SharePoint 2013 hosted app and angularJS routing.

TIP 1: Avoiding SP not defined issue: ensure the appurl passed to SP.RequestExector cross domain does not contain unacceptable characters in the url obtained from the querystring parameters.

One example being the angularJS “#/” that can get appended to the SPAppWebURL in the querystring

One slightly frustrating issue easy to miss was with the appweburl used by SP.RequestExecutor. Watch out for the SPAppWebURL obtained from the querystring when using SharePoint cross-domain access and AngularJS routes. If SP.Requestor sees the “#/” you may get an error displayed in FireFox as “SP not defined“.

Example URL:
http://app-33fd23755c4644.apps.smartek21:20982/AmazonRodUpdates/Pages/app.html?SPHostUrl=http%3A%2F%2Fwin-6viq5heh7nd%3A20982&SPLanguage=en-US&SPClientTag=0&SPProductNumber=15.0.4569.1000&amp;SPAppWebUrl=http%3A%2F%2Fapp-33fd23755c4644.apps.smartek21%3A20982%2FAmazonRodUpdates#/goals

Removing the “#/” from the appweburl is one way to resolve this issue.

appweburl = decodeURIComponent(getQueryStringParameter(“SPAppWebUrl”)).split(“#/”)[0];

//Get the URI decoded URLs.
hostweburl = decodeURIComponent(getQueryStringParameter(“SPHostUrl”));
//appweburl = decodeURIComponent(getQueryStringParameter(“SPAppWebUrl”));
appweburl = decodeURIComponent(getQueryStringParameter(“SPAppWebUrl”)).split(“#/”)[0];
var scriptbase = hostweburl + “/_layouts/15/”;

// Load the js files and continue to the successHandler

$.getScript(scriptbase + “SP.RequestExecutor.js”, loadUser);

function getQueryStringParameter(paramToRetrieve) {
var params =
document.URL.split(“?”)[1].split(“&”);
var strParams = “”;
for (var i = 0; i < params.length; i = i + 1) {
var singleParam = params[i].split(“=”);
if (singleParam[0] == paramToRetrieve)
return singleParam[1];
}
}

TIP 2: Avoiding scoping issues: ensure you set the proper scope for accessing SharePoint list data in subsites and/or cross site collections

When accessing lists stored in subsites in the host web set the scope to tenant in AppManifest/xml

<AppPermissionRequests>
<AppPermissionRequest Scope=”http://sharepoint/content/tenant&#8221; Right=”Write” />
<AppPermissionRequest Scope=”http://sharepoint/content/sitecollection/web/list&#8221; Right=”Write” />
<AppPermissionRequest Scope=”http://sharepoint/content/sitecollection/web&#8221; Right=”Write” />
</AppPermissionRequests>
Example AngularJS routing used:

var goalsApp = angular.module(‘goalsApp’, [‘ui.bootstrap’, ‘ui.bootstrap.tpls’, ‘ui.router’, ‘ngGrid’]);

goalsApp.config(function ($stateProvider, $urlRouterProvider, $httpProvider) {

$urlRouterProvider.otherwise(‘/goals’);

$stateProvider

// HOME STATES AND NESTED VIEWS

.state(‘dashboard’, {
url: ‘/dashboard’,
templateUrl: ‘../apps/partials/dashboard/dashboard.html’,
controller: ‘dashboard’,
controllerAs: ‘vm’
})
//nest projects on dashboard page
.state(‘dashboard.projects’, {
url: ‘/projects’,
templateUrl: ‘../apps/partials/projects/projects.html’,
controller: ‘projects’,
controllerAs: ‘vm’
})

// customers
.state(‘customers’, {
url: ‘/customers’,
templateUrl: ‘../apps/partials/customers/customers.html’,
controller: ‘customers’,
controllerAs: ‘vm’
})

// goals
.state(‘goals’, {
url: ‘/goals’,
templateUrl: ‘../apps/partials/goals/goals.html’,
controller: ‘businessreview’,
controllerAs: ‘vm’
})

});

AngularJS and SharePoint 2013 together-loving it!

Rod Stagg

http://rstagg.com

Install DLL into the GAC on Windows Server 2012 using Powershell

If like me you may have been accustomed to drag/drop a DLL into the GAC and finding doesn’t work in Windows Server 2012?  There are a number of solutions but sharing a Powershell script I have found useful for this purpose.

From SharePoint Powershell console:

Set-location C:\

[System.Reflection.Assembly]::Load(“System.EnterpriseServices, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a”)

$publish = New-Object System.EnterpriseServices.Internal.Publish
$publish.GacInstall(“C:\my.dll”)

 

 

SharePoint 2013-Printing List Item Forms using jQuery and CSS

UPDATE 4/26/2014:

Have heard from a lot of people who are having some challenges putting all the pieces of this solution together, particularly the calculated field for the print icon.  Providing detailed instructions and a screenshot to get started.

Start from a new team site:

  • Create a new subsite from the Collaboration template using the Team Site template.
  • Use listprint for the site name and URL.
  • Create a new picture library named “Images1”
  • Upload the print icon to the picture library.  Download image here
  • Create a custom list named “List”
  • Add the following columns to the list:
  • Column name: Printed | Column Type: Text | Default value: No
  • Column name: Print Item | Column Type: Calculated Column  Download calculated field formula example here

Capture

 

 

 

 

 

 

 

 

  • Add a new item to the list.
  • Click on the new item link.
  • From the standard DispForm.aspx page select Settings > Edit page
  • Add a new Script Editor web-part to the page (located in the Media and Content Category in web part gallery)
  • Set the Script Editor web-part properties zone index to 2 from the web part settings, layout section.
  • Select Edit Snippet in the Script Editor webpart and paste in the following jQuery/CSS-Download script here
  • Update the siteUrl variable in the jquery to use your site URL as required.
  • Select Page, Stop Editing from the SharePoint ribbon.
  • From site contents select “List”
  • Select a link to one of the two items in the list, select the Print button.
  • From site contents select “List” and notice the first item in the list has been updated to reflect Printed=Yes.

That’s it to getting started.

I have paired down the solution to get you started and make additional customizations specific to your requirements for example the listitemid that gets updated is hard-coded to the first item id since my solution depends on a SharePoint Designer workflow which I did not package up in the wsp (Coming soon) This can be easily updated to use your own jQuery inside the script editor web-part to obtain from the querystring on dispform.aspx or from a custom field that is populated from a SPD workflow when the item gets created.  You can easily create a list on any existing SharePoint 2013 or Office 365 SharePoint site providing you update the script and calculated columns to reflect the correct URLs and image source in the jQuery embedded in the Script Editor web-part and also the Print Item calculated field formula.

Thanks, Rod.

Added Functionality verses standard list item form:

* Add a print button to a SharePoint list item form.

* Displays a print icon linked to the custom print preview form.

* Tracks which list items were printed by the user. Works on both Office 365 and on-premises versions of SharePoint 2013.

printfeature

Capture

 

 

 

 

 

 

Solution Details

Solution designed to meet a requirement from a customer to allow users to easily print a list item from the primary SharePoint list view page in a layout that maximized the allowable printing real-estate while also removing most of the SharePoint toolbars etc. from the printed page.  Also, the customer requested that we track which items had been printed since this solution was part of a larger scheduling solution for SharePoint.   Since the customer is hosting their SharePoint on Office 365 in the cloud we opted to use jQuery and CSS directly on the list item form.  For layout we opted to use a custom list item display form and modify the XSL although this is not necessary to support the jQuery and CSS for printing the list item.

Steps: 

  • jQuery added to list item form with embedded jQuery and CSS overrides in a script editor web-part.
  • Calculated field with a print icon and link to list item form added to the list view.
  • Optional: Customized list item form layout using XSL.

Next Steps:

Package as a SharePoint app that can be added to any site.
Create a custom print template.
Incorporate PDF.

Summary

Even in 2013 it is sometimes necessary to print to paper.  With SharePoint being used more and more for scheduling and tracking systems, workflows, and calendaring the ability to print a SharePoint list item when necessary is an added benefit.  With the advent of CSOM and JSOM developers are increasingly taking advantage of jQuery, HTML, and CSS to tackle specific gaps in the OOB SharePoint features and particularly in the O365 cloud version.

Have fun!

Rod.

Office 365 Branding-Creating a custom masterpage for your Office 365 SharePoint Public-Facing website

In SharePoint 2013, both on-premises and Office 365 brought new options for customizing your SharePoint’s site design.

Highlights:

  • Ability to edit your masterpage in the HTML editor of your choice i.e. Dreamweaver, Visual Studio, or SharePoint Designer 2013.
  • Ability to configure the basic colors of your site from a single theme file.  (Replacing the PowerPoint themes in SP2010)

These two options alone provide a more flexible and straight-forward mechanism for branding your SharePoint 2013 site.

Example screenshot:

ScreenShot

Step 1: Login to your Office 365 tenant:

Login into your Office 365 SharePoint
TIP: Ensure to select the “Keep me signed in” option on your login screen.

Capture

Step 2: Obtain the location of your masterpage files:

  • Navigate to your SharePoint site’s masterpage gallery:
  • Site Settings > Look & Feel > Design Manager
  • From left navigation > Create Themes
  • Locate and copy the path provided by Microsoft for managing your design files for your site: “Map the following location as a network drive: [your path]”

Step 3: Create a shortcut to (map) a network drive to your masterpage gallery:

Instructions for Windows 8: http://windows.microsoft.com/en-us/windows-8/create-shortcut-to-map-network-drive

When you create a shortcut  to a shared folder or PC on a network (also called mapping a network drive), you can get to it from Computer or File Explorer without having to look for it or enter its network address each time.

To create a shortcut to a network drive

  1. Open Computer by swiping in from the right edge of the screen, tapping Search (or if you’re using a mouse, pointing to the upper-right corner of the screen, moving the mouse pointer down, and then clicking Search), entering Computer in the search box, tapping or clicking Apps, and then tapping or clicking Computer.
  2. In the Menu bar, tap or click Computer, and then tap or click Map network drive.
  3. In the Drive list, tap or click a drive letter.You can choose any  available letter.
  4. In the Folder box, enter the path of the folder or computer, or tap or click Browse to find the folder or computer.To connect every time you sign in to your computer, select the Reconnect at sign-in check box.
  5. Tap or click Finish.

Step 4: Edit your masterpage in the web editor of your choice:

From my experience in SharePoint 2013 I prefer to use Visual Studio for editing and saving changes to the masterpage(s).  I have found Dreamweaver to work as well and provides a decent design-time preview and standard features you expect in a web editing tool.

Browse to the mapped drive you created.

TIP: If you receive an error while attempting to open your mapped drive I have found the following to be an acceptable work-around: Navigate to site settings, under Web Designer Galleries choose Masterpage and page layouts link.  Next, from the masterpage and layouts folder, locate the File tab in the ribbon and select “Upload Document” On the upload document screen select the “upload files using Windows Explorer instead”  You should see your masterpage gallery open in windows explorer.

Capture4

Once you have navigated successfully to your network drive containing your design files I recommend creating a new folder on the root masterpage gallery to organize all your design files (masterpage, images, CSS) into a single folder.  Consolidating your custom masterpage files in to a single folder aid in setting virtual paths to your masterpage’s resources and once published provide a visual cue for others to properly assign custom masterpages to thier sites.

  • Make a copy of the Seattle.html or Oslo.html file and rename appropriately i.e. MyCompanyMaster.html.
  • Copy this file into your new custom masterpage folder.
  • Create new subfolders for your masterpage resources i.e. “Images” and “Styles”

Your folder structure may look similiar to this:

Capture5

Right-click the MyCompanyMaster.html file in windows explorer and edit the file in the web editor of your choice.

TIP: Ensure you are always editing the .html version of the masterpage and not the .master version.

Your standard Seattle.html may appear similiar to this:

Capture6

Locate the sections in the masterpage mark-up you would like to alter i.e. add a div for the footer, add or remove snippets, insert HTML markup, link to your custom CSS and image files, etc until the masterpage meets your design requirements.

FYI: In design view SharePoint inserts a commented section highlighting the placeholder for the content that gets rendered on the page at runtime.

TIP: Do not edit any of the built-in SharePoint controls or sections directly, rather navigate to the design manager tool and use the Snippets tool to update properties and then copy the provided mark-up back down to the appropriate section in your masterpage.  This will minimize errors in the Design Manager.

Lessons learned:

The page content area is largely in a div with the id=s4-workspace.

<div id=”s4-workspace”>
<div id=”s4-bodyContainer”>

In the example screenshot above I placed a footer at the bottom of the page that required more flexibility than what appeared the built-in control supported.  To accomplish this I located the correct location in the Seattle masterpage to place a container div proceeding the page content and applied custom CSS styles to ensure the footer always displays below the content of the page.

Example of mark-up changes:
<!–SPM:<SharePoint:ScriptBlock runat=”server”>–>
<!–SPM:var g_Workspace = “s4-workspace”;–>
<!–SPM:</SharePoint:ScriptBlock>–>
<div>
<div class=”grass”>
</div>
<div class=”hills”>
</div>
<div class=”skyline-left”>
</div>
<img style=”position:absolute; bottom:60px; right:0px;” src=”Images/skyline-right.png” alt=”Sound Community Bank” border=”0″ />
</div>
</body>
</html>

Step 5: Saving and previewing your changes on SharePoint:

Saving to SharePoint:

When completed with your edits simply choose save file from your web editor and the changes will be applied to the version in your SharePoint masterpage gallery.  You can preview your masterpage using the Design Manager from Site Settings, Design Manager, Edit masterpages.  If the conversion was successfull your custom masterpage Approval Status will be set to “Draft” and may appear similiar to this:

Previewing on SharePoint:

  • Click on the MyCompanyMaster link to preview your new masterpage.
  • If there are any errors reported with your markup ensure to update the masterpage to correct errors and save.  From my experience the typical errors are isolated to HTML formatting issues i.e. a div tag is missing a closing tag etc.
  • When satisified with the results navigate to the MyCompanyMaster file, select … and publish a major version.  Only published versions will be displayed in the list of available masterpages that can be applied to a site.

Additional options: You do have the option of creating your own HTML layout from scratch and providing you meet some requirements i.e. the mark-up is XML compliant then SharePoint will convert your html to two files, a masterpage.master file and a masterpage.html file.  This option provides significant flexibility to brand your SharePoint similiar to your internet facing website and/or design mockups from your design team.

Helpful links:

How to: Convert an HTML file into a master page in SharePoint Server 2013
http://msdn.microsoft.com/en-us/library/jj822370.aspx

Add snippets to a master page or a page layout in SharePoint Server 2013
http://msdn.microsoft.com/en-us/library/jj822367.aspx

Have fun!

Rod.