{"id":1898,"date":"2016-06-07T10:08:04","date_gmt":"2016-06-07T09:08:04","guid":{"rendered":"http:\/\/blog.repsaj.nl\/?p=1898"},"modified":"2016-06-07T10:23:30","modified_gmt":"2016-06-07T09:23:30","slug":"o365-deploying-a-sharepoint-theme-branding-using-javascript-only","status":"publish","type":"post","link":"http:\/\/blog.repsaj.nl\/index.php\/2016\/06\/o365-deploying-a-sharepoint-theme-branding-using-javascript-only\/","title":{"rendered":"[O365] Deploying a SharePoint theme \/ branding using JavaScript only"},"content":{"rendered":"<p>With provider hosted add-ins being introduced in SharePoint 2013, the world of SharePoint devs shifted to using provisioning schemes to get their stuff in SharePoint sites. And this worked, quite well i might add. You might have read my post on <a href=\"http:\/\/blog.repsaj.nl\/index.php\/2015\/05\/o365-provisioning-spmeta2-vs-o365-pnp-provisioning\/\" target=\"_blank\">SPMeta2 vs PnP <\/a>(which is a bit outdated I must add). These provisioning engines allow your to provision &#8220;stuff&#8221; (files, folders, lists, contenttypes, whatever) to SharePoint. Amongst other things, they have one thing in common: they&#8217;re built on top of the <strong>CSOM<\/strong> (Client Side Object Model) C# SDK. This means that you are forced to run them as a stand-alone task, or deploy a provider hosted app which includes having a server up and running somewhere. So what if you do not want that?\u00a0<!--more--><\/p>\n<p>In my case, I wanted to deploy some branding to a site, plain and simple. Not something you&#8217;d really would want to have to spin up a server for. But I didn&#8217;t want to create a console app for private use either. And so I ventured into the world of JavaScript once again, trying to leverage the <strong>JSOM<\/strong> (JavaScript Object Model) to deploy things.<\/p>\n<p>At this moment, there are no provisioning frameworks like SPMeta2 or PnP available for JSOM yet. The SPMeta guys let me know that they&#8217;re considering a JSOM port, but it&#8217;s not there at this point in time.<\/p>\n<blockquote class=\"twitter-tweet\" data-width=\"550\" data-dnt=\"true\">\n<p lang=\"en\" dir=\"ltr\">considering JSOM port of <a href=\"https:\/\/twitter.com\/hashtag\/SPMeta2?src=hash&amp;ref_src=twsrc%5Etfw\">#SPMeta2<\/a> for both browser and <a href=\"https:\/\/twitter.com\/hashtag\/NodeJS?src=hash&amp;ref_src=twsrc%5Etfw\">#NodeJS<\/a> environments. ~Q3 2016, mostly<\/p>\n<p>&mdash; SPMeta2 (@spmeta2) <a href=\"https:\/\/twitter.com\/spmeta2\/status\/739750490234195968?ref_src=twsrc%5Etfw\">June 6, 2016<\/a><\/p><\/blockquote>\n<p><script async src=\"https:\/\/platform.twitter.com\/widgets.js\" charset=\"utf-8\"><\/script><\/p>\n<p>Ok so we&#8217;re left without a framework at this point in time, but we can still do what&#8217;s needed! In short, there&#8217;s three things I wanted to do:<\/p>\n<ol>\n<li>Copy the required content (css, javascript, images, theme file) to my site.<\/li>\n<li>Set the CSS to be the alternate CSS for the site<\/li>\n<li>Apply the theme to the site<\/li>\n<\/ol>\n<p>Here&#8217;s the good news: there are API&#8217;s for all of this available!<\/p>\n<h2>Copying content<\/h2>\n<p>For copying content, I inspired myself with the bulk upload sample of the PnP team you can find <a href=\"https:\/\/github.com\/OfficeDev\/PnP-Guidance\/blob\/master\/articles\/Bulk-upload-documents-sample-app-for-SharePoint.md\">here<\/a>. This provided the basis for an uploader class. The most important code (all samples are in TypeScript by the way):<\/p>\n<pre class=\"lang:default decode:true \">Upload(sourcePath: string, targetPath: string): JQueryPromise&lt;void&gt; {\r\n\r\n    var deferred: JQueryDeferred&lt;void&gt; = jQuery.Deferred&lt;void&gt;();\r\n\r\n    this.hostWebContext = new SP.ClientContext(Utils.getRelativeUrlFromAbsolute(this.hostWebUrl));\r\n    var web = this.hostWebContext.get_web();\r\n\r\n    this.hostWebContext.load(web);\r\n    this.hostWebContext.executeQueryAsync(\r\n        \/\/ in case of success\r\n        () =&gt; {\r\n            console.log(\"Host Web successfully loaded\");\r\n\r\n            var sourceFile = this.appWebUrl + sourcePath;\r\n            \/\/logMessage(\"Reading file from App Web &lt;a href='\" + sourceFile + \"' target='_blank'&gt;\" + sourcePath + \"&lt;\/a&gt;&lt;br \/&gt;&lt;br \/&gt;\", state.SUCCESS);\r\n            \/\/logMessage(\"&lt;img src='\" + sourceFile + \"'&gt;&lt;br \/&gt;\");\r\n            \/\/ Read file from app web\r\n            $.ajax(&lt;JQueryAjaxSettings&gt;{\r\n                url: sourceFile,\r\n                type: \"GET\",\r\n                dataType: \"binary\",\r\n                processData: false,\r\n                responseType: 'arraybuffer',\r\n                cache: false\r\n            }).done((contents: number) =&gt; {\r\n\r\n                var fileName: string = Utils.getFilenameFromUrl(targetPath);\r\n                var folder: string = Utils.getPathFromUrl(targetPath);\r\n\r\n                \/\/ new FileCreationInformation object for uploading the file\r\n                var createInfo = new SP.FileCreationInformation();\r\n                createInfo.set_content(Utils.arrayBufferToBase64(contents));\r\n                createInfo.set_overwrite(true);\r\n                createInfo.set_url(fileName);\r\n\r\n                var targetFolder = Utils.getRelativeUrlFromAbsolute(this.hostWebUrl) + folder;\r\n\r\n                \/\/ ensure the target folder has been created \r\n                this.ensureTargetFolder(Utils.getRelativeUrlFromAbsolute(this.hostWebUrl), folder).then((folder) =&gt; {\r\n\r\n                    \/\/ add file to the folder\r\n                    var files = folder.get_files();\r\n                    this.hostWebContext.load(files);\r\n                    files.add(createInfo);\r\n\r\n                    \/\/ upload file\r\n                    this.hostWebContext.executeQueryAsync(() =&gt; {\r\n                        deferred.resolve();\r\n                        var loadImage = this.hostWebUrl + \"\/\" + folder + fileName;\r\n                    }, (sender, args) =&gt; {\r\n                        deferred.reject();\r\n                    });\r\n                });;\r\n            }).fail((jqXHR, textStatus) =&gt; {\r\n                deferred.reject();\r\n            });\r\n\r\n        },\r\n        \/\/ in case of error\r\n        (sender, args) =&gt; {\r\n            deferred.reject();\r\n        });\r\n\r\n    return deferred.promise();\r\n}<\/pre>\n<p>But I also needed a way to make sure the target path exists. This code takes care of that:<\/p>\n<pre class=\"lang:default decode:true \">ensureTargetFolder(relativeUrl: string, folderPath: string): JQueryPromise&lt;SP.Folder&gt; {\r\n    \/\/ to find the root folder, we need to traverse down the path until we find a \r\n    \/\/ folder that actually exists\r\n\r\n    var parts = folderPath.split('\/').filter((value) =&gt; { return value.trim() != '' });\r\n    parts = parts.reverse();\r\n\r\n    var deferred: JQueryDeferred&lt;SP.Folder&gt; = jQuery.Deferred&lt;SP.Folder&gt;();\r\n\r\n    var folder = this.hostWebContext.get_web().getFolderByServerRelativeUrl(relativeUrl);\r\n    this.hostWebContext.load(folder);\r\n    this.hostWebContext.executeQueryAsync(() =&gt; {\r\n        this.ensureChildFolders(folder, parts).then((folder) =&gt; {\r\n            deferred.resolve(folder);\r\n        });\r\n    }, (sender, args) =&gt; {\r\n        deferred.reject();\r\n    });\r\n\r\n    return deferred.promise();\r\n}\r\n\r\nensureChildFolders(parentFolder: SP.Folder, folderStructure: string[]): JQueryPromise&lt;SP.Folder&gt; {\r\n    \/\/ try to get the current path... when that succeedes; execute the function appending \r\n    \/\/ the next folder, if it doesn't; first create that folder\r\n\r\n    var deferred: JQueryDeferred&lt;SP.Folder&gt; = jQuery.Deferred&lt;SP.Folder&gt;();\r\n\r\n    if (folderStructure.length == 0) {\r\n        deferred.resolve(parentFolder);\r\n    }\r\n    else {\r\n        var folderUrl = folderStructure.pop();\r\n\r\n        var folderRelativeUrl = Utils.appendPath(parentFolder.get_serverRelativeUrl(), folderUrl);\r\n\r\n        var childFolder = this.hostWebContext.get_web().getFolderByServerRelativeUrl(folderRelativeUrl);\r\n        this.hostWebContext.load(childFolder);\r\n\r\n        this.hostWebContext.executeQueryAsync(() =&gt; {\r\n            \/\/ folder exists; continue with the next part\r\n            this.ensureChildFolders(childFolder, folderStructure).then((folder) =&gt; {\r\n                deferred.resolve(folder);\r\n            });\r\n        }, (sender, args) =&gt; {\r\n            \/\/ folder doesn't exist; create it and then continue\r\n            childFolder = parentFolder.get_folders().add(folderUrl);\r\n\r\n            this.hostWebContext.load(childFolder);\r\n            this.hostWebContext.executeQueryAsync(() =&gt; {\r\n                this.ensureChildFolders(childFolder, folderStructure).then((folder) =&gt; {\r\n                    deferred.resolve(folder);\r\n                });\r\n            }, (sender, args) =&gt; {\r\n                deferred.reject();\r\n            });\r\n        });\r\n    }\r\n\r\n    return deferred.promise();\r\n}<\/pre>\n<h2><\/h2>\n<h2>Set the alternate CSS URL \/ theme<\/h2>\n<p>This was easier than I expected it to be. There simply is a method on the web object that allows you to set the URL. Same goes for the theme. Than there&#8217;s some plumbing, mainly the jQuery promises you&#8217;ll need to make sure all of the async stuff in done in the correct sequence. I found that I couldn&#8217;t combine setting the alternate css url + theme in one execution, it would then complain that the web was being altered by another process. So this is the safe way to get there:<\/p>\n<pre class=\"lang:default decode:true\">ApplyTheme(): JQueryPromise&lt;void&gt; {\r\n    var hostWebContext = new SP.ClientContext(Utils.getRelativeUrlFromAbsolute(this.hostWebUrl));\r\n    var web = hostWebContext.get_web();\r\n\r\n    var deferred: JQueryDeferred&lt;void&gt; = jQuery.Deferred&lt;void&gt;();\r\n\r\n    hostWebContext.load(web);\r\n    hostWebContext.executeQueryAsync(() =&gt; {\r\n        var webRelativeUrl = web.get_serverRelativeUrl();\r\n\r\n        var themeUrl = webRelativeUrl + \"\/_catalogs\/theme\/15\/Atos.spcolor\";\r\n        var bgUrl = webRelativeUrl + \"\/_catalogs\/masterpage\/Atos\/images\/aeirial-view-of_traffic-and_overpasses.jpg\";\r\n        var cssUrl = webRelativeUrl + \"\/_catalogs\/masterpage\/Atos\/atos.css\";\r\n\r\n        web.applyTheme(themeUrl, null, bgUrl, true);\r\n        web.update();\r\n\r\n        hostWebContext.executeQueryAsync(() =&gt; {\r\n\r\n            web.set_alternateCssUrl(cssUrl);\r\n            web.update();\r\n\r\n            hostWebContext.executeQueryAsync(() =&gt; {\r\n                deferred.resolve();\r\n            }, (sender, args) =&gt; {\r\n                deferred.reject(\"Setting alternate CSS failed: \" + args.get_message());\r\n            });\r\n\r\n        }, (sender, args) =&gt; {\r\n            deferred.reject(\"Setting theme failed: \" + args.get_message());\r\n        });\r\n    }, (sender, args) =&gt; {\r\n        deferred.reject(\"Loading the host web context failed: \" + args.get_message());\r\n    });\r\n\r\n    return deferred.promise();\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<h2>That&#8217;s all folks!<\/h2>\n<p>I&#8217;ve pushed the entire project to a GitHub repo you can find here:\u00a0<a href=\"https:\/\/github.com\/atosorigin\/sharepoint-theming-app\" target=\"_blank\">https:\/\/github.com\/atosorigin\/sharepoint-theming-app<\/a>. Feel free to reuse it, feel just as free to brand all of your sites with the Atos branding although unless you&#8217;re a colleague I wouldn&#8217;t know why you would want to do that \ud83d\ude09<\/p>\n","protected":false},"excerpt":{"rendered":"<p>With provider hosted add-ins being introduced in SharePoint 2013, the world of SharePoint devs shifted to using provisioning schemes to get their stuff in SharePoint sites. And this worked, quite well i might add. You might have read my post on SPMeta2 vs PnP (which is a bit outdated I must add). These provisioning engines<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false}}},"categories":[34],"tags":[58,7,39],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p3KFR1-uC","_links":{"self":[{"href":"http:\/\/blog.repsaj.nl\/index.php\/wp-json\/wp\/v2\/posts\/1898"}],"collection":[{"href":"http:\/\/blog.repsaj.nl\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/blog.repsaj.nl\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/blog.repsaj.nl\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/blog.repsaj.nl\/index.php\/wp-json\/wp\/v2\/comments?post=1898"}],"version-history":[{"count":0,"href":"http:\/\/blog.repsaj.nl\/index.php\/wp-json\/wp\/v2\/posts\/1898\/revisions"}],"wp:attachment":[{"href":"http:\/\/blog.repsaj.nl\/index.php\/wp-json\/wp\/v2\/media?parent=1898"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/blog.repsaj.nl\/index.php\/wp-json\/wp\/v2\/categories?post=1898"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/blog.repsaj.nl\/index.php\/wp-json\/wp\/v2\/tags?post=1898"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}