├── .npmignore ├── .gitignore ├── img └── generator-init.png ├── generators ├── spsync │ ├── templates │ │ ├── _package.json │ │ ├── settings.js │ │ └── _gulpfile.js │ └── index.js ├── spsync-creds │ ├── templates │ │ ├── _package.json │ │ ├── settings.js │ │ └── _gulpfile.js │ └── index.js └── app │ ├── templates │ ├── sample │ │ ├── Control_Starter.html │ │ ├── Item_Starter.html │ │ ├── Control_Minimal.js │ │ └── Item_Minimal.js │ └── config.json │ └── index.js ├── package.json ├── LICENSE └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.tgz -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | *.tgz -------------------------------------------------------------------------------- /img/generator-init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/estruyf/generator-displaytemplates/HEAD/img/generator-init.png -------------------------------------------------------------------------------- /generators/spsync/templates/_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "displaytemplates", 3 | "version": "0.1.0", 4 | "description": "Developing display templates via the gulp-spsync plugin", 5 | "dependencies": { 6 | "gulp": "^3.9.1", 7 | "gulp-watch": "^4.3.9", 8 | "gulp-spsync": "^1.5.3" 9 | }, 10 | "license": "UNLICENSED", 11 | "private": true 12 | } -------------------------------------------------------------------------------- /generators/spsync-creds/templates/_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "displaytemplates", 3 | "version": "0.1.0", 4 | "description": "Developing display templates via the gulp-spsync-creds plugin", 5 | "dependencies": { 6 | "gulp": "^3.9.1", 7 | "gulp-spsync-creds": "^2.2.6", 8 | "gulp-plumber": "^1.1.0" 9 | }, 10 | "license": "UNLICENSED", 11 | "private": true 12 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-displaytemplates", 3 | "version": "1.2.0", 4 | "description": "This is a Yeoman generator to speed up your display templates development process", 5 | "author": "Elio Struyf", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/estruyf/generator-displaytemplates.git" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/estruyf/generator-displaytemplates/issues" 12 | }, 13 | "homepage": "https://github.com/estruyf/generator-displaytemplates#readme", 14 | "files": [ 15 | "generators" 16 | ], 17 | "keywords": [ 18 | "yeoman-generator" 19 | ], 20 | "dependencies": { 21 | "chalk": "1.1.3", 22 | "inquirer": "1.1.3", 23 | "mkdirp": "0.5.1", 24 | "update-notifier": "1.0.2", 25 | "yeoman-generator": "0.24.1", 26 | "yosay": "1.2.0" 27 | }, 28 | "licenses": [ 29 | { 30 | "type": "MIT", 31 | "url": "http://www.opensource.org/licenses/mit-license.php" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Elio Struyf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /generators/spsync/templates/settings.js: -------------------------------------------------------------------------------- 1 | var settings = (function () { 2 | var config = require('./config.json'); 3 | 4 | return{ 5 | checks: function () { 6 | if (typeof config.client_id === 'undefined') { 7 | throw "client_id is required in the configuration file" 8 | } 9 | if (typeof config.client_secret === 'undefined') { 10 | throw "client_secret is required in the configuration file" 11 | } 12 | if (typeof config.site === 'undefined') { 13 | throw "site is required in the configuration file" 14 | } 15 | if (typeof config.fileMetadata === 'undefined') { 16 | config.fileMetadata = []; 17 | } 18 | }, 19 | get: function () { 20 | this.checks(); 21 | return { 22 | client_id: config.client_id, 23 | client_secret: config.client_secret, 24 | site: config.site, 25 | files_metadata: config.fileMetadata 26 | } 27 | }, 28 | getWatch: function () { 29 | this.checks(); 30 | return { 31 | client_id: config.client_id, 32 | client_secret: config.client_secret, 33 | site: config.site, 34 | watch: true, 35 | files_metadata: config.fileMetadata 36 | } 37 | } 38 | } 39 | })(); 40 | 41 | module.exports = settings; -------------------------------------------------------------------------------- /generators/spsync-creds/templates/settings.js: -------------------------------------------------------------------------------- 1 | var settings = (function () { 2 | var config = require('./config.json'); 3 | 4 | return{ 5 | checks: function () { 6 | if (typeof config.username === 'undefined') { 7 | throw "username is required in the configuration file" 8 | } 9 | if (typeof config.password === 'undefined') { 10 | throw "password is required in the configuration file" 11 | } 12 | if (typeof config.site === 'undefined') { 13 | throw "site is required in the configuration file" 14 | } 15 | if (typeof config.fileMetadata === 'undefined') { 16 | config.fileMetadata = []; 17 | } 18 | }, 19 | get: function () { 20 | this.checks(); 21 | return { 22 | username: config.username, 23 | password: config.password, 24 | site: config.site, 25 | files_metadata: config.fileMetadata 26 | } 27 | }, 28 | download: function () { 29 | if (typeof config.location === 'undefined') { 30 | throw "location is required in the configuration file" 31 | } 32 | this.checks(); 33 | return { 34 | username: config.username, 35 | password: config.password, 36 | site: config.site, 37 | startFolder: config.location, 38 | associatedHtml: false 39 | } 40 | } 41 | } 42 | })(); 43 | 44 | module.exports = settings; -------------------------------------------------------------------------------- /generators/spsync/templates/_gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var watch = require('gulp-watch'); 3 | var sp = require('gulp-spsync'); 4 | var settings = require('./settings'); 5 | 6 | var folder = 'src/**/*.*'; 7 | 8 | /* 9 | Default task: uploads all the files to SharePoint 10 | */ 11 | gulp.task('default', function() { 12 | return gulp.src(folder) 13 | .pipe(sp(settings.get())); 14 | }); 15 | 16 | /* 17 | set-metadata task: uploads all the files to SharePoint and overwrites the metadata setting 18 | */ 19 | gulp.task('set-metadata', function() { 20 | var crntSettings = settings.get(); 21 | crntSettings["update_metadata"] = true; 22 | 23 | return gulp.src(folder) 24 | .pipe(sp(crntSettings)); 25 | }); 26 | 27 | /* 28 | publish task: uploads everything, sets metadata and publishes each file 29 | */ 30 | gulp.task('publish', function() { 31 | var crntSettings = settings.get(); 32 | crntSettings["update_metadata"] = true; 33 | crntSettings["publish"] = true; 34 | 35 | return gulp.src(folder) 36 | .pipe(sp(crntSettings)); 37 | }); 38 | 39 | /* 40 | watch task: this task can be used during the development process, it automatically uploads the files when changed 41 | */ 42 | gulp.task("watch", function(){ 43 | return gulp.src(folder) 44 | .pipe(watch(folder)) 45 | .pipe(sp(settings.getWatch())); 46 | }); 47 | 48 | /* 49 | watch task: this task can be used during the development process, it automatically uploads the files when changed and sets metadata 50 | */ 51 | gulp.task("watch-metadata", function(){ 52 | var crntSettings = settings.getWatch(); 53 | crntSettings["update_metadata"] = true; 54 | 55 | return gulp.src(folder) 56 | .pipe(watch(folder)) 57 | .pipe(sp(crntSettings)); 58 | }); 59 | 60 | -------------------------------------------------------------------------------- /generators/app/templates/sample/Control_Starter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Control starter 4 | 5 | 14 | 15 | 16 | 17 | 19 | 20 |
21 | 22 | 44 | 48 | 52 |
_#= $noResults =#_
53 | 56 | 57 |
58 | 59 | 60 | -------------------------------------------------------------------------------- /generators/app/templates/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileMetadata": [ 3 | { 4 | "name": "Item_Minimal.js", 5 | "metadata": { 6 | "__metadata": { "type": "SP.Data.OData__x005f_catalogs_x002f_masterpageItem" }, 7 | "Title": "Item Minimal Template (via GULP)", 8 | "MasterPageDescription": "This is a display template added via gulp.", 9 | "ManagedPropertyMapping": "'Path','Title':'Title'", 10 | "ContentTypeId": "0x0101002039C03B61C64EC4A04F5361F38510660500A0383064C59087438E649B7323C95AF6", 11 | "DisplayTemplateLevel": "Item", 12 | "TemplateHidden": false, 13 | "TargetControlType": { 14 | "__metadata": { 15 | "type": "Collection(Edm.String)" 16 | }, 17 | "results": [ 18 | "SearchResults", 19 | "Content Web Parts" 20 | ] 21 | } 22 | } 23 | }, 24 | { 25 | "name": "Control_Minimal.js", 26 | "metadata": { 27 | "__metadata": { "type": "SP.Data.OData__x005f_catalogs_x002f_masterpageItem" }, 28 | "Title": "Control Minimal Template (via GULP)", 29 | "MasterPageDescription": "This is a display template added via gulp.", 30 | "ContentTypeId": "0x0101002039C03B61C64EC4A04F5361F38510660500A0383064C59087438E649B7323C95AF6", 31 | "DisplayTemplateLevel": "Control", 32 | "TemplateHidden": false, 33 | "TargetControlType": { 34 | "__metadata": { 35 | "type": "Collection(Edm.String)" 36 | }, 37 | "results": [ 38 | "SearchResults", 39 | "Content Web Parts" 40 | ] 41 | } 42 | } 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /generators/spsync-creds/templates/_gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | var plumber = require("gulp-plumber"); 3 | var sp = require('gulp-spsync-creds'); 4 | var settings = require('./settings'); 5 | var onError = function (err) { 6 | this.emit("end"); 7 | }; 8 | 9 | var mainFld = 'src'; 10 | var folder = './src/**/*.*'; 11 | 12 | /* 13 | Default task: uploads all the files to SharePoint 14 | */ 15 | gulp.task('default', function() { 16 | return gulp.src(folder).pipe( 17 | sp.sync(settings.get()) 18 | ); 19 | }); 20 | 21 | /* 22 | set-metadata task: uploads all the files to SharePoint and overwrites the metadata setting 23 | */ 24 | gulp.task('set-metadata', function() { 25 | var crntSettings = settings.get(); 26 | crntSettings["update_metadata"] = true; 27 | 28 | return gulp.src(folder) 29 | .pipe(sp.sync(crntSettings)); 30 | }); 31 | 32 | /* 33 | publish task: uploads everything, sets metadata and publishes each file 34 | */ 35 | gulp.task('publish', function() { 36 | var crntSettings = settings.get(); 37 | crntSettings["update_metadata"] = true; 38 | crntSettings["publish"] = true; 39 | 40 | return gulp.src(folder) 41 | .pipe(sp.sync(crntSettings)); 42 | }); 43 | 44 | /* 45 | watch task: this task can be used during the development process, it automatically uploads the files when changed 46 | */ 47 | gulp.task("watch", function() { 48 | var crntSettings = settings.get(); 49 | crntSettings["cache"] = true; 50 | 51 | gulp.watch(folder, function (event) { 52 | gulp.src(event.path, { base: mainFld }) 53 | .pipe(plumber({ 54 | errorHandler: onError 55 | })) 56 | .pipe(sp.sync(crntSettings)); 57 | }); 58 | }); 59 | 60 | /* 61 | watch task: this task can be used during the development process, it automatically uploads the files when changed and sets metadata 62 | */ 63 | gulp.task("watch-metadata", function(){ 64 | var crntSettings = settings.get(); 65 | crntSettings["update_metadata"] = true; 66 | crntSettings["cache"] = true; 67 | 68 | gulp.watch(folder, function (event) { 69 | gulp.src(event.path, { base: mainFld }) 70 | .pipe(plumber({ 71 | errorHandler: onError 72 | })) 73 | .pipe(sp.sync(crntSettings)); 74 | }); 75 | }); 76 | 77 | 78 | /* 79 | download task: download the files for the specified folder 80 | */ 81 | gulp.task('download', function() { 82 | var crntSettings = settings.download(); 83 | return sp.download(crntSettings).pipe(gulp.dest("src/" + crntSettings.startFolder)); 84 | }); -------------------------------------------------------------------------------- /generators/app/templates/sample/Item_Starter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Item starter 4 | 5 | 15 | 16 | 17 | 18 | 20 | 21 |
22 | 42 |
43 | 44 | _#= $htmlEncode(line1.defaultValueRenderer(line1)) =#_ 45 | 46 |
47 | _#= line1 =#_ 48 | 52 |
_#= line2 =#_
53 | 56 |
57 |
58 |
59 | 60 | 61 | -------------------------------------------------------------------------------- /generators/app/templates/sample/Control_Minimal.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | // Config contains variables that are defined in one place 5 | var config = { 6 | /* IMPORTANT: update these settings before uploading the file to the master page gallery */ 7 | template: 'control_minimal.js' 8 | }; 9 | var templateUrl; 10 | 11 | var register = function () { 12 | if ("undefined" !== typeof (Srch) && "undefined" !== typeof (Srch.U) && typeof (Srch.U.registerRenderTemplateByName) === "function") { 13 | Srch.U.registerRenderTemplateByName(templateUrl, render); 14 | } 15 | }, 16 | render = function (ctx) { 17 | // Display template data 18 | var cachePreviousTemplateData = ctx.DisplayTemplateData; 19 | ctx.DisplayTemplateData = { 20 | 'TemplateUrl': templateUrl, 21 | 'TemplateType': 'Control', 22 | 'TargetControlType': ['SearchResults', 'Content Web Parts'] 23 | }; 24 | 25 | // Checks to see if the client control loaded correctly 26 | if (!$isNull(ctx.ClientControl) && !$isNull(ctx.ClientControl.shouldRenderControl) && !ctx.ClientControl.shouldRenderControl()) { 27 | return ""; 28 | } 29 | 30 | ctx.ListDataJSONGroupsKey = "ResultTables"; 31 | ctx['ItemRenderWrapper'] = itemRendering; 32 | 33 | // HTML markup for the control template 34 | var htmlMarkup = String.format( '', ctx.RenderGroups(ctx)); 37 | 38 | // Caching 39 | ctx.DisplayTemplateData = cachePreviousTemplateData; 40 | 41 | // Return the HTML markup 42 | return htmlMarkup; 43 | }, 44 | itemRendering = function (itemRenderResult, inCtx, tpl) { 45 | var iStr = []; 46 | iStr.push('
  • '); 47 | iStr.push(itemRenderResult); 48 | iStr.push('
  • '); 49 | return iStr.join(''); 50 | }; 51 | 52 | /* DO NOT REMOVE THE FOLLOWING LINES OF CODE */ 53 | // MDS needs to start on the head 54 | // Retrieve all the loaded scripts 55 | var allScripts = document.head.getElementsByTagName("script"); 56 | var scriptUrl = null; 57 | var scriptNr = allScripts.length; 58 | while(scriptNr--) { 59 | var crntScript = allScripts[scriptNr]; 60 | if (crntScript.src !== null) { 61 | // Check if the right script is retrieved based on the filename of the template 62 | if (crntScript.src.indexOf('/_catalogs/') > 0 && crntScript.src.toLowerCase().indexOf(config.template.toLowerCase()) > 0) { 63 | scriptUrl = crntScript.src; 64 | break; 65 | } 66 | } 67 | } 68 | if (scriptUrl !== null) { 69 | // Remove the query string 70 | if (scriptUrl.indexOf('?') > 0) { 71 | scriptUrl = scriptUrl.split("?")[0]; 72 | } 73 | // Insert the site collection token 74 | templateUrl = '~sitecollection' + scriptUrl.substr(scriptUrl.indexOf('/_catalogs/')); 75 | templateUrl = decodeURI(templateUrl); 76 | // Register the template to load 77 | register(); 78 | if (typeof (RegisterModuleInit) === "function" && typeof(Srch.U.replaceUrlTokens) === "function") { 79 | RegisterModuleInit(Srch.U.replaceUrlTokens(templateUrl), register); 80 | } 81 | } 82 | })(); -------------------------------------------------------------------------------- /generators/app/templates/sample/Item_Minimal.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | // Config contains variables that are defined in one place 5 | var config = { 6 | /* IMPORTANT: update these settings before uploading the file to the master page gallery */ 7 | template: 'item_minimal.js', 8 | propertyMappings: { 'Path':null, 'Title':['Title'] } 9 | }; 10 | var templateUrl; 11 | 12 | var register = function () { 13 | if ("undefined" !== typeof (Srch) && "undefined" !== typeof (Srch.U) && typeof (Srch.U.registerRenderTemplateByName) === "function") { 14 | Srch.U.registerRenderTemplateByName(templateUrl, render); 15 | } 16 | }, 17 | render = function (ctx) { 18 | // Display template data 19 | var cachePreviousTemplateData = ctx.DisplayTemplateData; 20 | ctx.DisplayTemplateData = { 21 | 'TemplateUrl': templateUrl, 22 | 'TemplateType': 'Item', 23 | 'TargetControlType': ['SearchResults', 'Content Web Parts'], 24 | 'ManagedPropertyMapping': config.propertyMappings 25 | }; 26 | var cachePreviousItemValuesFunction = ctx.ItemValues; 27 | ctx.ItemValues = function(slotOrPropName) { 28 | return Srch.ValueInfo.getCachedCtxItemValue(ctx, slotOrPropName); 29 | }; 30 | 31 | // Retrieve managed property data 32 | var path = $getItemValue(ctx, 'Path'); 33 | var title = $getItemValue(ctx, 'Title'); 34 | 35 | // HTML markup for an item 36 | var htmlMarkup = String.format( '
    ' + 37 | '{1}' + 38 | '
    ', path, title); 39 | 40 | // Caching 41 | ctx.ItemValues = cachePreviousItemValuesFunction; 42 | ctx.DisplayTemplateData = cachePreviousTemplateData; 43 | 44 | // Return the HTML markup 45 | return htmlMarkup; 46 | }; 47 | 48 | /* DO NOT REMOVE THE FOLLOWING LINES OF CODE */ 49 | // MDS needs to start on the head 50 | // Retrieve all the loaded scripts 51 | var allScripts = document.head.getElementsByTagName("script"); 52 | var scriptUrl = null; 53 | var scriptNr = allScripts.length; 54 | while(scriptNr--) { 55 | var crntScript = allScripts[scriptNr]; 56 | if (crntScript.src !== null) { 57 | // Check if the right script is retrieved based on the filename of the template 58 | if (crntScript.src.indexOf('/_catalogs/') > 0 && crntScript.src.toLowerCase().indexOf(config.template.toLowerCase()) > 0) { 59 | scriptUrl = crntScript.src; 60 | break; 61 | } 62 | } 63 | } 64 | if (scriptUrl !== null) { 65 | // Remove the query string 66 | if (scriptUrl.indexOf('?') > 0) { 67 | scriptUrl = scriptUrl.split("?")[0]; 68 | } 69 | // Insert the site collection token 70 | templateUrl = '~sitecollection' + scriptUrl.substr(scriptUrl.indexOf('/_catalogs/')); 71 | templateUrl = decodeURI(templateUrl); 72 | // Register the template to load 73 | register(); 74 | if (typeof (RegisterModuleInit) === "function" && typeof(Srch.U.replaceUrlTokens) === "function") { 75 | RegisterModuleInit(Srch.U.replaceUrlTokens(templateUrl), register); 76 | } 77 | } 78 | })(); -------------------------------------------------------------------------------- /generators/spsync/index.js: -------------------------------------------------------------------------------- 1 | var generators = require('yeoman-generator'); 2 | var yosay = require('yosay'); 3 | var chalk = require('chalk'); 4 | var mkdirp = require('mkdirp'); 5 | 6 | module.exports = generators.Base.extend({ 7 | // The name constructor is important here 8 | constructor: function () { 9 | // Calling the super constructor is important so our generator is correctly set up 10 | generators.Base.apply(this, arguments); 11 | 12 | // Next, add your custom code 13 | this.option('name', { 14 | type: String, 15 | desc: 'Title of the Office Add-in', 16 | required: false 17 | }); 18 | 19 | this.option('client_id', { 20 | type: String, 21 | desc: 'SharePoint add-in client id', 22 | required: true 23 | }); 24 | 25 | this.option('client_secret', { 26 | type: String, 27 | desc: 'SharePoint add-in secret', 28 | required: true 29 | }); 30 | 31 | this.option('site', { 32 | type: String, 33 | desc: 'SharePoint site URL', 34 | required: true 35 | }); 36 | 37 | this.option('skipInstall', { 38 | type: Boolean, 39 | required: false, 40 | defaults: false, 41 | desc: 'Skip running NPM package manager at the end.' 42 | }); 43 | 44 | this.genConfig = {}; 45 | }, 46 | 47 | prompting: { 48 | askFor: function () { 49 | var done = this.async(); 50 | 51 | var prompts = [ 52 | { 53 | name: 'client_id', 54 | message: 'What is your SharePoint add-in client id?', 55 | default: null, //00000000-0000-0000-0000-000000000000 56 | when: this.options.client_id === undefined 57 | }, 58 | { 59 | name: 'client_secret', 60 | message: 'What is your SharePoint add-in secret?', 61 | default: null, 62 | when: this.options.client_secret === undefined 63 | } 64 | ]; 65 | 66 | this.prompt(prompts, function(responses){ 67 | this.genConfig = Object.assign({}, this.genConfig, this.options, responses); 68 | done(); 69 | }.bind(this)); 70 | } 71 | }, 72 | 73 | writing: { 74 | app: function() { 75 | var done = this.async(); 76 | 77 | // create common assets 78 | this.fs.copyTpl(this.templatePath('_gulpfile.js'), 79 | this.destinationPath('gulpfile.js')); 80 | this.fs.copyTpl(this.templatePath('_package.json'), 81 | this.destinationPath('package.json')); 82 | this.fs.copyTpl(this.templatePath('settings.js'), 83 | this.destinationPath('settings.js')); 84 | 85 | // Create and update the config file with the required properties 86 | var pathToConfigJson = this.templatePath('../../app/templates/config.json'); 87 | if (this.fs.exists(pathToConfigJson)) { 88 | // Load config.json file 89 | var configJson = this.fs.readJSON(pathToConfigJson, 'utf8'); 90 | 91 | // Set the required properties 92 | if (!configJson['client_id']) { 93 | configJson['client_id'] = this.genConfig.client_id; 94 | } 95 | if (!configJson['client_secret']) { 96 | configJson['client_secret'] = this.genConfig.client_secret; 97 | } 98 | if (!configJson['site']) { 99 | configJson['site'] = this.genConfig.site; 100 | } 101 | if (!configJson['skipInstall']) { 102 | configJson['skipInstall'] = this.genConfig.skipInstall; 103 | } 104 | 105 | // Overwrite the existing config.json 106 | this.log(chalk.green('Adding your configuration to the config.json file')); 107 | this.fs.writeJSON(this.destinationPath('config.json'), configJson); 108 | } 109 | 110 | done(); 111 | } 112 | }, 113 | 114 | install: function() { 115 | // Run npm installer? 116 | if (!this.genConfig['skipInstall']) { 117 | this.npmInstall(); 118 | } 119 | } 120 | }); -------------------------------------------------------------------------------- /generators/spsync-creds/index.js: -------------------------------------------------------------------------------- 1 | var generators = require('yeoman-generator'); 2 | var yosay = require('yosay'); 3 | var chalk = require('chalk'); 4 | var mkdirp = require('mkdirp'); 5 | var inquirer = require('inquirer'); 6 | 7 | module.exports = generators.Base.extend({ 8 | // The name constructor is important here 9 | constructor: function () { 10 | // Calling the super constructor is important so our generator is correctly set up 11 | generators.Base.apply(this, arguments); 12 | 13 | // Next, add your custom code 14 | this.option('name', { 15 | type: String, 16 | desc: 'Title of the Office Add-in', 17 | required: false 18 | }); 19 | 20 | this.option('site', { 21 | type: String, 22 | desc: 'SharePoint site URL', 23 | required: true 24 | }); 25 | 26 | this.option('username', { 27 | type: String, 28 | desc: 'Username', 29 | required: true 30 | }); 31 | 32 | this.option('password', { 33 | type: String, 34 | desc: 'Password', 35 | required: true 36 | }); 37 | 38 | this.option('skipInstall', { 39 | type: Boolean, 40 | required: false, 41 | defaults: false, 42 | desc: 'Skip running NPM package manager at the end.' 43 | }); 44 | 45 | this.option('download', { 46 | type: Boolean, 47 | desc: 'Download files after install?', 48 | required: true 49 | }); 50 | 51 | this.genConfig = {}; 52 | }, 53 | 54 | prompting: { 55 | askFor: function () { 56 | var done = this.async(); 57 | 58 | var prompts = [ 59 | { 60 | name: 'username', 61 | message: 'Username:', 62 | default: null, 63 | when: this.options.username === undefined, 64 | type: 'input' 65 | }, 66 | { 67 | name: 'password', 68 | message: 'Password:', 69 | default: null, 70 | when: this.options.password === undefined, 71 | type: 'password' 72 | }, 73 | { 74 | name: 'download', 75 | message: 'Download files after installation?', 76 | default: true, 77 | type: 'confirm' 78 | } 79 | ]; 80 | 81 | inquirer.prompt(prompts).then(function(responses) { 82 | this.genConfig = Object.assign({}, this.genConfig, this.options, responses); 83 | done(); 84 | }.bind(this)); 85 | } 86 | }, 87 | 88 | writing: { 89 | app: function() { 90 | var done = this.async(); 91 | 92 | // create common assets 93 | this.fs.copyTpl(this.templatePath('_gulpfile.js'), 94 | this.destinationPath('gulpfile.js')); 95 | this.fs.copyTpl(this.templatePath('_package.json'), 96 | this.destinationPath('package.json')); 97 | this.fs.copyTpl(this.templatePath('settings.js'), 98 | this.destinationPath('settings.js')); 99 | 100 | // Create and update the config file with the required properties 101 | var pathToConfigJson = this.templatePath('../../app/templates/config.json'); 102 | if (this.fs.exists(pathToConfigJson)) { 103 | // Load config.json file 104 | var configJson = this.fs.readJSON(pathToConfigJson, 'utf8'); 105 | 106 | // Set the required properties 107 | if (!configJson['username']) { 108 | configJson['username'] = this.genConfig.username; 109 | } 110 | if (!configJson['password']) { 111 | configJson['password'] = this.genConfig.password; 112 | } 113 | if (!configJson['site']) { 114 | configJson['site'] = this.genConfig.site; 115 | } 116 | if (!configJson['skipInstall']) { 117 | configJson['skipInstall'] = this.genConfig.skipInstall; 118 | } 119 | if (!configJson['location']) { 120 | configJson['location'] = "_catalogs/masterpage/" + this.genConfig.projectInternalName; 121 | } 122 | 123 | // Overwrite the existing config.json 124 | this.log(chalk.green('Adding your configuration to the config.json file')); 125 | this.fs.writeJSON(this.destinationPath('config.json'), configJson); 126 | } 127 | 128 | done(); 129 | } 130 | }, 131 | 132 | install: function() { 133 | // Run npm installer? 134 | if (!this.genConfig['skipInstall']) { 135 | this.npmInstall("", () => { 136 | 137 | // Run gulp download 138 | if (this.genConfig['download']) { 139 | this.spawnCommand('gulp', ['download']); 140 | } 141 | }) 142 | } else { 143 | // Run gulp download 144 | if (this.genConfig['download']) { 145 | this.spawnCommand('gulp', ['download']); 146 | } 147 | } 148 | } 149 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Yeoman Display Template Generator 2 | Yeoman generator that gives you a kick start for building Search Display Templates. 3 | [![NPM](https://nodei.co/npm/generator-displaytemplates.png?compact=true)](https://nodei.co/npm/generator-displaytemplates/) 4 | 5 | # Installation 6 | Run the following command to install the required packages and Yeoman display template generator: 7 | 8 | ```$ npm install -g gulp yo generator-displaytemplates``` 9 | 10 | # Update 11 | Execute the following command to update the generator to the latest version: 12 | 13 | ```$ npm update -g generator-displaytemplates``` 14 | 15 | # Usage 16 | Once you installed the display templates generator, you can execute this command: 17 | 18 | ```$ yo displaytemplates``` 19 | 20 | ![yo displaytemplates](img/generator-init.png "yo displaytemplates") 21 | 22 | The generator asks you for the following things: 23 | - **Project name** (default: Search Display Templates) 24 | - **Site URL** 25 | - **Sample files** (default: true): creates sample display templates in a sample folder. 26 | - **Skip install** (default: false): if you set this to true, the ``npm install`` command will be skipped. Be aware, if you do this, it will not install the dependencies. 27 | - **Upload files via SharePoint add-in or client credentials** (choice) 28 | - SharePoint add-in: this option uses the [gulp-spsync](https://github.com/wictorwilen/gulp-spsync) plugin 29 | - client credentials: this option uses the [gulp-spsync-creds](https://github.com/estruyf/gulp-spsync-creds) plugin 30 | 31 | Depending on the type of upload mechanism you choose, the next questions will vary. 32 | 33 | **SharePoint add-in questions** 34 | - **Client ID:** specify the add-in client ID 35 | - **Client Secret**: specify the add-in secret 36 | 37 | **Client credentials questions** 38 | - **Username** 39 | - **Password** 40 | - **Download files after installation?** (default: true): automatically downloads the files from SharePoint "_catalogs/masterpage/your-project-name" after the generator ran. 41 | 42 | # Configuration 43 | The generator has two sub-generators: 44 | 1. SharePoint add-in generator 45 | 1. Client credentials generator 46 | 47 | The reason why is that the SharePoint add-in generator makes use of a Gulp plugin that only works for SharePoint Online sites. The client credentials generator will work both on for SharePoint Online and on-premises. 48 | 49 | **SharePoint add-in generator** 50 | 51 | The generator for the SharePoint add-in approach makes use of the **gulp-spsync** plugin created by Wictor Wilen. Go to the [gulp-spsync](https://github.com/wictorwilen/gulp-spsync) repo to check out the configuration process that you have to do on your SharePoint Online site. 52 | 53 | This generator requires the following things in order to run your gulp tasks afterwards: 54 | - **client_id**: this is the ID of your SharePoint add-in; 55 | - **client_secret**: this is the secret of your SharePoint add-in; 56 | - **site**: this is the site URL where your configured the SharePoint add-in. 57 | 58 | **Client credentials generator** 59 | 60 | The client credentials generator makes use of the **gulp-spsync-creds** plugin. Go to the [gulp-spsync-creds](https://github.com/estruyf/gulp-spsync-creds) repo for more information about this plugin. 61 | 62 | This generator requires the following things in order to run your gulp tasks afterwards: 63 | - **Username**: this is the username for accessing your site; 64 | - **Password**: this is the password for the given username; 65 | - **site**: this is the site URL to where you want to upload the files. 66 | - **startFolder**: this is only required for downloading files. 67 | 68 | # Development process 69 | Once you completed all previous steps, you can start your development process. 70 | 71 | ## Display template samples 72 | In the directory you will find a **sample** folder. This contains the following files which can be used to create new templates: 73 | - **control_starter.html**: this is a starter control display template; 74 | - **item_starter.html**: this is a starter item display template; 75 | - **control_minimal.js**: this is a starter control JavaScript display template (this template does not require a HTML file); 76 | - **item_minimal.js**: this is a starter item JavaScript display template (this template does not require a HTML file); 77 | 78 | ## Display template creation 79 | Create your display templates in the following folder: 80 | ``` 81 | . 82 | ├── src/ 83 | │ └── _catalogs/ 84 | │ └── masterpage/ 85 | │ └── (default: search-display-templates)/ 86 | │ └── Create all your files in this folder 87 | ``` 88 | 89 | ## Upload, download, watch, publish 90 | The following Gulp tasks are available: 91 | 92 | **gulp**: this task uploads all the display templates to the masterpage gallery 93 | ``` 94 | $ gulp 95 | ``` 96 | 97 | **gulp set-metadata**: uploads all the files and sets the metadata for the files. Metadata has to be specified in the config.json file and contains sample data. 98 | ``` 99 | $ gulp set-metadata 100 | ``` 101 | 102 | **gulp set-metadata**: uploads all the files and sets the metadata for the files. Metadata has to be specified in the config.json file and contains sample data. 103 | ``` 104 | $ gulp set-metadata 105 | ``` 106 | 107 | **gulp publish**: uploads all the files, sets metadata and publishes each of the files. 108 | ``` 109 | $ gulp publish 110 | ``` 111 | 112 | **gulp watch**: watches for file changes, once a change happens, the file will get uploaded. 113 | ``` 114 | $ gulp watch 115 | ``` 116 | 117 | **gulp watch-metadata**: watches for file changes, once a change happens, the file will get uploaded and metadata will get set. 118 | ``` 119 | $ gulp watch-metadata 120 | ``` 121 | 122 | **gulp download**: download all the files of the folder location "_catalogs/masterpage/your-project-name". 123 | ``` 124 | $ gulp publish 125 | ``` -------------------------------------------------------------------------------- /generators/app/index.js: -------------------------------------------------------------------------------- 1 | var generators = require('yeoman-generator'); 2 | var yosay = require('yosay'); 3 | var chalk = require('chalk'); 4 | var mkdirp = require('mkdirp'); 5 | var inquirer = require('inquirer'); 6 | var updateNotifier = require('update-notifier'); 7 | 8 | var pkg = require('../../package.json'); 9 | 10 | module.exports = generators.Base.extend({ 11 | // The name constructor is important here 12 | constructor: function () { 13 | // Calling the super constructor is important so our generator is correctly set up 14 | generators.Base.apply(this, arguments); 15 | 16 | // Next, add your custom code 17 | this.option('name', { 18 | type: String, 19 | desc: 'Title of the Office Add-in', 20 | required: false 21 | }); 22 | 23 | this.option('client_id', { 24 | type: String, 25 | desc: 'SharePoint add-in client id', 26 | required: true 27 | }); 28 | 29 | this.option('client_secret', { 30 | type: String, 31 | desc: 'SharePoint add-in secret', 32 | required: true 33 | }); 34 | 35 | this.option('site', { 36 | type: String, 37 | desc: 'SharePoint site URL', 38 | required: true 39 | }); 40 | 41 | this.option('skipInstall', { 42 | type: Boolean, 43 | required: false, 44 | defaults: false, 45 | desc: 'Skip running NPM package manager at the end.' 46 | }); 47 | }, 48 | 49 | initializing: function() { 50 | var notifier = updateNotifier({pkg}); 51 | notifier.notify({defer: false}); 52 | 53 | this.log(yosay('Welcome to the\n' + 54 | chalk.yellow('Display Templates') + 55 | ' generator version: ' + pkg.version + '.' + 56 | ' Let\'s create a new project.')); 57 | 58 | // generator configuration 59 | this.genConfig = {}; 60 | }, 61 | 62 | prompting: { 63 | askFor: function () { 64 | var done = this.async(); 65 | 66 | var prompts = [{ 67 | name: 'name', 68 | message: 'Project name', 69 | default: 'Search Display Templates', 70 | when: this.options.name === undefined, 71 | type: 'input' 72 | }, 73 | { 74 | name: 'site', 75 | message: 'What is the SharePoint site URL?', 76 | default: null, 77 | when: this.options.site === undefined, 78 | type: 'input' 79 | }, 80 | { 81 | name: 'sample', 82 | message: 'Do you want the sample files?', 83 | default: true, 84 | type: 'confirm' 85 | }, 86 | { 87 | name: 'skipInstall', 88 | message: 'Skip NPM install at the end?', 89 | default: false, 90 | when: this.options.name === undefined, 91 | type: 'confirm' 92 | }, 93 | { 94 | name: 'plugin', 95 | message: 'Upload files via SharePoint add-in or client credentials?', 96 | type: 'list', 97 | default: 'spsync-creds', 98 | choices: [ 99 | { 100 | name: 'SharePoint Add-in (SP Online only)', 101 | value: 'spsync' 102 | }, 103 | { 104 | name: 'Client credentials', 105 | value: 'spsync-creds' 106 | } 107 | ] 108 | } 109 | ]; 110 | 111 | inquirer.prompt(prompts).then(function(responses) { 112 | this.genConfig = Object.assign({}, this.genConfig, this.options, responses); 113 | 114 | done(); 115 | }.bind(this)); 116 | } 117 | }, 118 | 119 | configuring: function(){ 120 | // take name submitted and strip everything out non-alphanumeric or space 121 | var projectName = this.genConfig.name; 122 | projectName = projectName.replace(/[^\w\s\-]/g, ''); 123 | projectName = projectName.replace(/\s{2,}/g, ' '); 124 | projectName = projectName.trim(); 125 | 126 | // add the result of the question to the generator configuration object 127 | this.genConfig.projectInternalName = projectName.toLowerCase().replace(/ /g, '-'); 128 | this.genConfig.projectDisplayName = projectName; 129 | this.genConfig.rootPath = this.genConfig['root-path']; 130 | }, 131 | 132 | writing: { 133 | app: function() { 134 | // helper function to build path to the file off root path 135 | this._parseTargetPath = function(file){ 136 | return path.join(this.genConfig['root-path'], file); 137 | }; 138 | 139 | var done = this.async(); 140 | 141 | switch (this.genConfig.plugin) { 142 | case 'spsync': 143 | this.composeWith('displaytemplates:spsync', { 144 | options: { 145 | name: this.genConfig.name, 146 | projectInternalName: this.genConfig.projectInternalName, 147 | site: this.genConfig.site, 148 | skipInstall: this.genConfig.skipInstall 149 | } 150 | }, { 151 | local: require.resolve('../spsync') 152 | }); 153 | break; 154 | case 'spsync-creds': 155 | this.composeWith('displaytemplates:spsync-creds', { 156 | options: { 157 | name: this.genConfig.name, 158 | projectInternalName: this.genConfig.projectInternalName, 159 | site: this.genConfig.site, 160 | skipInstall: this.genConfig.skipInstall 161 | } 162 | }, { 163 | local: require.resolve('../spsync-creds') 164 | }); 165 | break; 166 | } 167 | 168 | if (this.genConfig.sample) { 169 | // Create sample files 170 | this.fs.copyTpl(this.templatePath('sample/control_minimal.js'), 171 | this.destinationPath('sample/control_minimal.js')); 172 | this.fs.copyTpl(this.templatePath('sample/item_minimal.js'), 173 | this.destinationPath('sample/item_minimal.js')); 174 | this.fs.copyTpl(this.templatePath('sample/control_starter.html'), 175 | this.destinationPath('sample/control_starter.html')); 176 | this.fs.copyTpl(this.templatePath('sample/item_starter.html'), 177 | this.destinationPath('sample/item_starter.html')); 178 | } 179 | 180 | // Create folder structure 181 | mkdirp(this.destinationPath() + '/src/_catalogs/masterpage/' + this.genConfig.projectInternalName, function (err) { 182 | if (!err) { 183 | // Folder created 184 | } 185 | }); 186 | 187 | done(); 188 | } 189 | }, 190 | 191 | install: function() { 192 | // Nothing to do 193 | } 194 | }); --------------------------------------------------------------------------------