├── projects └── readme.md ├── resources ├── checklist.png ├── doughnut.mustache ├── styles.css ├── form-params.js ├── converter.js └── drawDoughnut.js ├── generate ├── readme.md.tpl └── readme.js ├── index.html ├── README.md └── questions.json /projects/readme.md: -------------------------------------------------------------------------------- 1 | This is a placeholder for project data. -------------------------------------------------------------------------------- /resources/checklist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitovi/checklist/HEAD/resources/checklist.png -------------------------------------------------------------------------------- /resources/doughnut.mustache: -------------------------------------------------------------------------------- 1 |

What are the chances your project will succeed?

2 |
-------------------------------------------------------------------------------- /generate/readme.md.tpl: -------------------------------------------------------------------------------- 1 | # <%= heading %> 2 | 3 | ## Questions: 4 | 5 | <%= questions %> 6 | 7 | ## Contributing 8 | 9 | `questions.json` contains a list of questions. To change a question, add or modify the relevant section and submit a pull request. The following types are available: 10 | 11 | - `text` - A text field 12 | - `textarea` - A textarea field 13 | - `number` - A number input 14 | - `section` - Specifies a new section that can contain more questions 15 | - `single` - A list of potential answers in `values` only allowing to select one 16 | - `single` - A list of potential answers in `values` selecting multiple 17 | -------------------------------------------------------------------------------- /generate/readme.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | 5 | var questions = require(path.join(__dirname, '..', 'questions.json')); 6 | var converters = { 7 | default: function(current) { 8 | var markdown = '__' + current.label + '__\n'; 9 | if(current.help) { 10 | markdown += '> ' + current.help + '\n'; 11 | } 12 | return markdown + '\n'; 13 | }, 14 | section: function(current, depth) { 15 | depth = depth || 0; 16 | 17 | var headers = _.times(depth + 1, function() { 18 | return '#'; 19 | }).join(''); 20 | var markdown = headers + ' ' + current.label + '\n\n'; 21 | if(current.help) { 22 | markdown += '> ' + current.help + '\n\n'; 23 | } 24 | current.questions.forEach(function(question) { 25 | markdown += convert(question, depth + 1); 26 | }); 27 | return markdown + '\n'; 28 | }, 29 | single: function(current) { 30 | var markdown = converters.default(current) + '\n'; 31 | current.values.forEach(function(value) { 32 | markdown += '- ' + value + '\n'; 33 | }); 34 | return markdown + '\n'; 35 | }, 36 | multi: function(current) { 37 | var markdown = converters.default(current) + '\n'; 38 | current.values.forEach(function(value) { 39 | markdown += '- [ ] ' + value + '\n'; 40 | }); 41 | return markdown + '\n'; 42 | } 43 | }; 44 | var convert = function(data, depth) { 45 | var converter = converters[data.type]; 46 | converter = converter || converters.default; 47 | return converter(data, depth); 48 | }; 49 | 50 | fs.readFile(path.join(__dirname, 'readme.md.tpl'), function(error, markdown) { 51 | var render = _.template(markdown.toString()); 52 | var data = { 53 | heading: questions.heading, 54 | questions: questions.questions.map(function(q) { 55 | return convert(q, 2); 56 | }).join('\n') 57 | }; 58 | 59 | console.log(render(data)); 60 | }); 61 | 62 | -------------------------------------------------------------------------------- /resources/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Lato', sans-serif; 3 | padding-bottom: 45px; 4 | } 5 | .jumbotron { 6 | padding: 30px; 7 | text-align: center; 8 | margin-top: 30px; 9 | } 10 | .jumbotron h1 { 11 | font-size: 40px; 12 | margin: 0; 13 | } 14 | label { 15 | margin-bottom: 0; 16 | } 17 | small { 18 | color: #999999; 19 | font-weight: 400; 20 | } 21 | .checkbox, .radio { 22 | margin-left: 20px; 23 | margin-top: 0; 24 | margin-bottom: 10px; 25 | } 26 | .checkbox:last-child, .radio:last-child { 27 | margin-bottom: 20px; 28 | } 29 | 30 | 31 | /* Doughnut CSS */ 32 | 33 | .chart { 34 | margin: 0 auto; 35 | width: 450px; 36 | height: 450px; 37 | position: relative; 38 | } 39 | .doughnutTip { 40 | position: absolute; 41 | float: left; 42 | min-width: 30px; 43 | max-width: 300px; 44 | padding: 5px 15px; 45 | border-radius: 1px; 46 | background: rgba(0,0,0,.8); 47 | color: #ddd; 48 | font-size: 17px; 49 | text-shadow: 0 1px 0 #000; 50 | text-transform: uppercase; 51 | text-align: center; 52 | line-height: 1.3; 53 | letter-spacing: .06em; 54 | box-shadow: 0 1px 3px rgba(0,0,0,0.5); 55 | transform: all .3s; 56 | pointer-events: none; 57 | } 58 | .doughnutTip:after { 59 | position: absolute; 60 | left: 50%; 61 | bottom: -6px; 62 | content: ""; 63 | height: 0; 64 | margin: 0 0 0 -6px; 65 | border-right: 5px solid transparent; 66 | border-left: 5px solid transparent; 67 | border-top: 6px solid rgba(0,0,0,.7); 68 | line-height: 0; 69 | } 70 | .doughnutSummary { 71 | position: absolute; 72 | top: 50%; 73 | left: 50%; 74 | color: black; 75 | text-align: center; 76 | text-shadow: 0 -1px 0 #111; 77 | cursor: default; 78 | } 79 | .doughnutSummaryTitle { 80 | position: absolute; 81 | top: 50%; 82 | width: 100%; 83 | margin-top: -27%; 84 | font-size: 22px; 85 | letter-spacing: .06em; 86 | } 87 | .doughnutSummaryNumber { 88 | position: absolute; 89 | top: 50%; 90 | width: 100%; 91 | margin-top: -15%; 92 | font-size: 55px; 93 | } 94 | .chart path:hover { 95 | opacity: .65; 96 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JavaScript Project checklist 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 | 20 |
21 | 22 |
23 |
24 |

Paste in your JSON data...

25 |
26 |
27 |
28 | 29 | 30 |
31 | 32 |
33 |
34 |
35 |
36 |
37 |

JavaScript Project Checklist

38 |

39 | The Github Repository | The Original Article

40 |
41 | 42 | 54 | 55 |
56 |
57 |
58 |
59 | 60 | 61 | -------------------------------------------------------------------------------- /resources/form-params.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery++ - 2.0.0 3 | * http://jquerypp.com 4 | * Copyright (c) 2015 Bitovi 5 | * Mon, 11 May 2015 10:03:45 GMT 6 | * Licensed MIT 7 | 8 | * Includes: jquerypp/dom/form_params/form_params 9 | * Download from: http://bitbuilder.herokuapp.com/jquerypp.custom.js?plugins=jquerypp%2Fdom%2Fform_params%2Fform_params 10 | */ 11 | /*[global-shim-start]*/ 12 | (function (exports, global){ 13 | var origDefine = global.define; 14 | 15 | var get = function(name){ 16 | var parts = name.split("."), 17 | cur = global, 18 | i; 19 | for(i = 0 ; i < parts.length; i++){ 20 | if(!cur) { 21 | break; 22 | } 23 | cur = cur[parts[i]]; 24 | } 25 | return cur; 26 | }; 27 | var modules = (global.define && global.define.modules) || 28 | (global._define && global._define.modules) || {}; 29 | var ourDefine = global.define = function(moduleName, deps, callback){ 30 | var module; 31 | if(typeof deps === "function") { 32 | callback = deps; 33 | deps = []; 34 | } 35 | var args = [], 36 | i; 37 | for(i =0; i < deps.length; i++) { 38 | args.push( exports[deps[i]] ? get(exports[deps[i]]) : ( modules[deps[i]] || get(deps[i]) ) ); 39 | } 40 | // CJS has no dependencies but 3 callback arguments 41 | if(!deps.length && callback.length) { 42 | module = { exports: {} }; 43 | var require = function(name) { 44 | return exports[name] ? get(exports[name]) : modules[name]; 45 | }; 46 | args.push(require, module.exports, module); 47 | } 48 | // Babel uses only the exports objet 49 | else if(!args[0] && deps[0] === "exports") { 50 | module = { exports: {} }; 51 | args[0] = module.exports; 52 | } 53 | 54 | global.define = origDefine; 55 | var result = callback ? callback.apply(null, args) : undefined; 56 | global.define = ourDefine; 57 | 58 | // Favor CJS module.exports over the return value 59 | modules[moduleName] = module && module.exports ? module.exports : result; 60 | }; 61 | global.define.orig = origDefine; 62 | global.define.modules = modules; 63 | global.define.amd = true; 64 | global.System = { 65 | define: function(__name, __code){ 66 | global.define = origDefine; 67 | eval("(function() { " + __code + " \n }).call(global);"); 68 | global.define = ourDefine; 69 | }, 70 | orig: global.System 71 | }; 72 | })({"jquery":"jQuery","zepto":"Zepto"},window) 73 | /*jquerypp@2.0.0#dom/form_params/form_params*/ 74 | define('jquerypp/dom/form_params/form_params', ['jquery'], function ($) { 75 | var keyBreaker = /[^\[\]]+/g, convertValue = function (value) { 76 | if ($.isNumeric(value)) { 77 | return parseFloat(value); 78 | } else if (value === 'true') { 79 | return true; 80 | } else if (value === 'false') { 81 | return false; 82 | } else if (value === '' || value === null) { 83 | return undefined; 84 | } 85 | return value; 86 | }, nestData = function (elem, type, data, parts, value, seen, fullName) { 87 | var name = parts.shift(); 88 | fullName = fullName ? fullName + '.' + name : name; 89 | if (parts.length) { 90 | if (!data[name]) { 91 | data[name] = {}; 92 | } 93 | nestData(elem, type, data[name], parts, value, seen, fullName); 94 | } else { 95 | if (fullName in seen && type != 'radio' && !$.isArray(data[name])) { 96 | if (name in data) { 97 | data[name] = [data[name]]; 98 | } else { 99 | data[name] = []; 100 | } 101 | } else { 102 | seen[fullName] = true; 103 | } 104 | if ((type == 'radio' || type == 'checkbox') && !elem.is(':checked')) { 105 | return; 106 | } 107 | if (!data[name]) { 108 | data[name] = value; 109 | } else { 110 | data[name].push(value); 111 | } 112 | } 113 | }; 114 | $.fn.extend({ 115 | formParams: function (params) { 116 | var convert; 117 | if (!!params === params) { 118 | convert = params; 119 | params = null; 120 | } 121 | if (params) { 122 | return this.setParams(params); 123 | } else { 124 | return this.getParams(convert); 125 | } 126 | }, 127 | setParams: function (params) { 128 | this.find('[name]').each(function () { 129 | var $this = $(this), value = params[$this.attr('name')]; 130 | if (value !== undefined) { 131 | if ($this.is(':radio')) { 132 | if ($this.val() == value) { 133 | $this.attr('checked', true); 134 | } 135 | } else if ($this.is(':checkbox')) { 136 | value = $.isArray(value) ? value : [value]; 137 | if ($.inArray($this.val(), value) > -1) { 138 | $this.attr('checked', true); 139 | } 140 | } else { 141 | $this.val(value); 142 | } 143 | } 144 | }); 145 | }, 146 | getParams: function (convert) { 147 | var data = {}, seen = {}, current; 148 | this.find('[name]:not(:disabled)').each(function () { 149 | var $this = $(this), type = $this.attr('type'), name = $this.attr('name'), value = $this.val(), parts; 150 | if (type == 'submit' || !name) { 151 | return; 152 | } 153 | parts = name.match(keyBreaker); 154 | if (!parts.length) { 155 | parts = [name]; 156 | } 157 | if (convert) { 158 | value = convertValue(value); 159 | } 160 | nestData($this, type, data, parts, value, seen); 161 | }); 162 | for(var key in data){ 163 | if(!data[key].length){ 164 | delete data[key]; 165 | } 166 | } 167 | return data; 168 | } 169 | }); 170 | return $; 171 | }); 172 | /*[global-shim-end]*/ 173 | (function (){ 174 | window._define = window.define; 175 | window.define = window.define.orig; 176 | window.System = window.System.orig; 177 | })(); -------------------------------------------------------------------------------- /resources/converter.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | var getLabel = function(current) { 3 | return current.help ? current.label + '
' + current.help + '' : 4 | current.label; 5 | }; 6 | var optionList = function(name) { 7 | return function(current, depth) { 8 | var dform = { 9 | caption: getLabel(current), 10 | type: name, 11 | options: {}, 12 | name: current.name ? current.name : current.label, 13 | }; 14 | $.each(current.values, function(index, value) { 15 | dform.options[value] = value; 16 | }); 17 | return dform; 18 | } 19 | }; 20 | var converters = { 21 | default: function(current, depth) { 22 | return { 23 | type: 'div', 24 | class: 'form-group', 25 | html: { 26 | caption: getLabel(current), 27 | type: current.type, 28 | name: current.name ? current.name : current.label, 29 | class: 'form-control' 30 | } 31 | }; 32 | }, 33 | section: function(current, depth) { 34 | var dform = { 35 | caption: getLabel(current), 36 | type: 'fieldset', 37 | name: current.name ? current.name : current.label, 38 | id: current.id, 39 | html: [] 40 | }; 41 | $.each(current.questions || [], function(index, question) { 42 | dform.html.push(convert(question, depth + 1)); 43 | }); 44 | 45 | return dform; 46 | }, 47 | single: optionList('radiobuttons'), 48 | multi: optionList('checkboxes') 49 | }; 50 | 51 | var convert = function(data, depth) { 52 | var converter = converters[data.type]; 53 | converter = converter || converters.default; 54 | return converter(data, depth ? depth + 1 : 0); 55 | }; 56 | 57 | var PasteBucket = function () { 58 | var pasteBucket = { 59 | visible: false, 60 | toggle: function (force) { 61 | if (typeof force !== 'undefined') { 62 | this.visible = force; 63 | } else { 64 | this.visible = !this.visible; 65 | } 66 | 67 | if (this.visible) { 68 | $('.paste-tool').show(); 69 | } else { 70 | $('.paste-tool').hide(); 71 | } 72 | }, 73 | processData: function () { 74 | var pasteData = $('.paste-tool .paste-bucket').val(), 75 | self = this; 76 | 77 | if (pasteData.length === 0) { 78 | $('.paste-bucket').parent('.form-group').addClass('has-error'); 79 | return false; 80 | } 81 | self.loadDataFromString(pasteData); 82 | self.toggle(); 83 | }, 84 | checkParams: function () { 85 | 86 | var searchTerms = new can.List(window.location.search.replace('?','').split('&')), 87 | pastedLink; 88 | searchTerms.each(function (item) { 89 | var params = item.split('='); 90 | if (params[0] === 'checklist' && params[1]) { 91 | pastedLink = decodeURI(params[1]); 92 | } 93 | }); 94 | 95 | if (pastedLink) { 96 | this.loadDataFromString(pastedLink); 97 | } 98 | }, 99 | loadDataFromString: function (dataString) { 100 | var pasteDataMap = new can.Map(JSON.parse(dataString)) 101 | pasteDataMap.each(function (val, key, item) { 102 | var $target = can.$('[name='+key+']'); 103 | if ($target.eq(0).is('[type=checkbox]')) { 104 | val.each(function (chkboxVal) { 105 | $target.filter('[value="'+chkboxVal+'"]').prop('checked', true); 106 | }); 107 | 108 | return true; 109 | } 110 | if ($target.eq(0).is('[type=radio]')) { 111 | $target.filter('[value="'+val+'"]').prop('checked', true); 112 | return true; 113 | } 114 | $target.val(val); 115 | 116 | }); 117 | } 118 | } 119 | return pasteBucket; 120 | } 121 | var pasteBucket = new PasteBucket(); 122 | 123 | $('.paste-tool .btn-default').on('click', function () { 124 | pasteBucket.processData(); 125 | }); 126 | pasteBucket.toggle(false);//close the bucket 127 | $('.open-paste-tool-js').on('click', function () { 128 | pasteBucket.toggle(); 129 | }); 130 | 131 | $.getJSON('questions.json').then(function(data) { 132 | var questions = data.questions; 133 | 134 | $('#heading').append(data.heading); 135 | 136 | $.each(questions, function(index, question) { 137 | $('#checklist').dform(convert(question)) 138 | }); 139 | 140 | $('[type="checkbox"]').each(function() { 141 | $(this).next().prepend($(this)).wrap('
'); 142 | }); 143 | 144 | $('[type="radio"]').each(function() { 145 | $(this).next().prepend($(this)).wrap('
'); 146 | }); 147 | 148 | $('#checklist').append("
"); 149 | 150 | //check if linking into results 151 | pasteBucket.checkParams(); 152 | }); 153 | 154 | $('body').on('submit', 'form', function(ev){ 155 | var data = $('form').formParams(); 156 | ev.preventDefault(); 157 | renderScore(data); 158 | }) 159 | 160 | var answers = { 161 | "do-all-employees-go-through-a-technical-": 0.46,//Do all employees go through a technical training? 162 | "what-is-the-projects-vision": 0.45,//What is the project\'s vision? 163 | "how-long-until-something-can-be-released": 0.48,//How long until something can be released? 164 | "does-the-company-have-outings": 0.45,//Does the company have outings? 165 | "is-user-testing-done": 0.45,//Is user testing done? 166 | "the-following-documents-are-created": 0.44,//The following documents are created: 167 | "there-are-code-reviews": 0.28,//There are code reviews 168 | "are-there-unit-tests": 0.2,//Are there unit tests? 169 | "is-there-documentation-for-the-code": 0.28,//Is there documentation for the code? 170 | "continuous-integration": 0.2,//Continuous integration 171 | "the-following-environments-exist": 0.2//The following environments exist 172 | } 173 | 174 | var getScore = function(data){ 175 | var total = 0, count = 0; 176 | for (var key in answers){ 177 | total += answers[key]; 178 | count++; 179 | } 180 | var points = 0; 181 | for(var key in data) { 182 | if(answers[key] && data[key] !== "none" && data[key] !== "No"){ 183 | points+=answers[key]; 184 | } 185 | } 186 | return parseInt(points/total*100, 10); 187 | } 188 | 189 | var renderScore = function(data){ 190 | var score = getScore(data); 191 | $(".links").remove(); 192 | $('#checklist').html(can.view("resources/doughnut.mustache")({ 193 | score: score 194 | })); 195 | $("#doughnutChart").drawDoughnutChart([ 196 | { 197 | title: "Yes, beyond your wildest dreams :)", 198 | value : score, 199 | color: "#4BA658" 200 | }, 201 | { 202 | title: "No, burning flames are in your future :(", 203 | value: 100-score, 204 | color: "#D93B48" 205 | } 206 | ], { 207 | 208 | }); 209 | $('#checklist').append("
"+JSON.stringify(data)+"
"); 210 | } 211 | }); 212 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript Project Checklist 2 | 3 | [Watch this video to learn what this checklist is about.](https://www.youtube.com/watch?v=20ebllexvuc 4 | ) 5 | 6 | 7 | ## Questions: 8 | 9 | __Project name__ 10 | 11 | 12 | __Company name__ 13 | 14 | 15 | __Was the project a success?__ 16 | 17 | 18 | - Yes 19 | - No 20 | 21 | 22 | ### Management 23 | 24 | #### People know what they are trying to accomplish. 25 | 26 | __What is the project's vision?__ 27 | > This is typically a single sentence that describes what the project aspires to be. Example: "A JS framework that allows developers to build better apps, faster". If this doesn't exist, write "none". 28 | 29 | __How will the project measure success?__ 30 | > Example: Increase mobile conversion rates to 0.75-1.0%, currently ~0.3%. If this doesn't exist, write "none". 31 | 32 | __What is the strategy for accomplishing the project's goals?__ 33 | > Example: Combine the desktop and mobile sites for an improved user experience, site parity, and centralized ownership. If this doesn't exist, write "none". 34 | 35 | __What is the project's roadmap? What are the goals, plans and release schedule after the current release?__ 36 | > Example: Phase 1: Complete A, B, C. Phase 2: Complete D, E, F. If there are no plans, write "none". 37 | 38 | ##### People are capable of accomplishing the goals. 39 | 40 | > Do people have the skills needed to accomplish the goals and roadmap? Is the roadmap possible? Is there the access across the organizational bureaucracy? 41 | 42 | __Do all employees go through a technical training?__ 43 | > For example a week long JS training. 44 | 45 | 46 | - Yes 47 | - No 48 | 49 | __Is there at least a yearly additional training opportunities for all employees?__ 50 | 51 | 52 | - Yes 53 | - No 54 | 55 | __How long until something can be released?__ 56 | 57 | 58 | - 3 months 59 | - 6 months 60 | - 1 year 61 | - 1.5 years 62 | 63 | __What is the org chart?__ 64 | > Each person's name and title. Indent subordinates under a manager. If this doesn't exist, write "none". 65 | 66 | __Who has the final say in content and copy decisions?__ 67 | > A person's name. If multiple people, separate names with ";". 68 | 69 | __Who has the final say in design decisions?__ 70 | > A person's name. If multiple people, separate names with ";". 71 | 72 | __Who has final say in technology and infrastructure decisions?__ 73 | > The person's name. If multiple people, separate names with ";". 74 | 75 | __Do product owners frequently (at least once a month) meet with:__ 76 | 77 | 78 | - [ ] UX teams 79 | - [ ] Dev teams 80 | 81 | __Have your companies values, experiences, and goals been expressed to management and the client team?__ 82 | 83 | 84 | - Yes 85 | - No 86 | 87 | __Has this checklist been reviewed with the management, design and development teams?__ 88 | 89 | 90 | - Yes 91 | - No 92 | 93 | 94 | 95 | #### People like each other. 96 | 97 | __Does the company have outings?__ 98 | > Examples: dinners / activities outside work. 99 | 100 | 101 | - Yes 102 | - No 103 | 104 | __How often, in months, do employee reviews happen?__ 105 | 106 | 107 | 108 | 109 | ### UX / UI 110 | 111 | __How many designers on the project?__ 112 | 113 | #### Informed 114 | 115 | __Is user testing done?__ 116 | 117 | 118 | - Yes 119 | - No 120 | 121 | __What user testing techniques are being used?__ 122 | 123 | 124 | - [ ] Usability testing 125 | - [ ] User interviews 126 | - [ ] Surveys 127 | 128 | __Is analytic software being used?__ 129 | 130 | 131 | - Yes 132 | - No 133 | 134 | __Is AB testing being performed?__ 135 | 136 | 137 | - Yes 138 | - No 139 | 140 | __Are the results of user testing, analytics, and other data being discussed at least monthly?__ 141 | 142 | 143 | - Yes 144 | - No 145 | 146 | 147 | #### Quick Iterations 148 | 149 | __How long, on average in weeks, between design changes and a user testing them?__ 150 | 151 | __Are design revisions factored into the estimate?__ 152 | 153 | 154 | - Yes 155 | - No 156 | 157 | __Are beta releases user tested?__ 158 | 159 | 160 | - Yes 161 | - No 162 | 163 | __Are prototypes and mockups user tested?__ 164 | 165 | 166 | - Yes 167 | - No 168 | 169 | 170 | #### Communication 171 | 172 | __The following documents are created with the client:__ 173 | 174 | 175 | - [ ] Design guidelines / goals / statements 176 | - [ ] Personas 177 | - [ ] User stories or use cases. 178 | - [ ] Competitive analysis 179 | 180 | __The following documents are created:__ 181 | 182 | 183 | - [ ] Wireframes and mockups 184 | - [ ] Storyboards 185 | - [ ] Prototypes 186 | - [ ] Prototypes 187 | - [ ] High fidelity comps 188 | - [ ] HTML prototypes 189 | - [ ] HTML style guide 190 | 191 | __Are videos or animations used to express interactions?__ 192 | 193 | 194 | - Yes 195 | - No 196 | 197 | __Are design issues and discussions "publicly" tracked?__ 198 | 199 | 200 | - Yes 201 | - No 202 | 203 | __Where are design issues and discussions tracked?:__ 204 | 205 | 206 | - [ ] Email 207 | - [ ] Project management software (Trello, Basecamp) 208 | - [ ] Issue tracker (Jira / github) 209 | - [ ] Excel 210 | 211 | __Does a design changelog exist?__ 212 | > A design changelog is a document that contains a list of changes to the mockup/prototypes. 213 | 214 | 215 | - Yes 216 | - No 217 | 218 | 219 | 220 | 221 | ### Development 222 | 223 | > The following questions concern development specific problems. 224 | 225 | #### Tools and Environment 226 | 227 | > The essential tools are in place and being used in the right way. 228 | 229 | __Source control is__ 230 | 231 | 232 | - [ ] Used 233 | - [ ] Git 234 | - [ ] Used with a branch and merge strategy. 235 | 236 | __An issue tracker is__ 237 | 238 | 239 | - [ ] Used 240 | - [ ] Integrated with source control. 241 | - [ ] Used by non developers. 242 | 243 | __The following environments exist__ 244 | 245 | 246 | - [ ] Development 247 | - [ ] Test 248 | - [ ] Staging 249 | - [ ] Production 250 | 251 | __Continuous integration__ 252 | 253 | 254 | - [ ] Exists 255 | - [ ] Runs on all commits / pushes 256 | - [ ] Emails on failure 257 | 258 | __A 1-3 step process for the following exist:__ 259 | 260 | 261 | - [ ] Setting up a development environment 262 | - [ ] Testing the application. 263 | - [ ] Building the application into a production distributable. 264 | - [ ] Deploy to test and staging. 265 | 266 | 267 | #### Code quality 268 | 269 | > Practices and patterns that ensure good code. 270 | 271 | __Is a module loader used?__ 272 | > Examples: StealJS, RequireJS, Webpack, sprokets 273 | 274 | 275 | - Yes 276 | - No 277 | 278 | __Is the high level architecture documented and followed?__ 279 | > For example: MVVM plus a client state observable with specified properties. 280 | 281 | 282 | - Yes 283 | - No 284 | 285 | __All modules include:__ 286 | 287 | 288 | - [ ] High level documentation. 289 | - [ ] Tests 290 | - [ ] Inline documentation 291 | - [ ] A demo 292 | 293 | __Are there performance tests?__ 294 | 295 | 296 | - Yes 297 | - No 298 | 299 | __The service layer is:__ 300 | 301 | 302 | - [ ] RESTful 303 | - [ ] Documented 304 | - [ ] Tested 305 | - [ ] Built / working 306 | 307 | __Is technical debt measured?__ 308 | > Is some value (often in days / weeks) of technical debt calculated? 309 | 310 | 311 | - Yes 312 | - No 313 | 314 | __Is technical debt factored into estimates?__ 315 | > Do estimations of time, or points, or effort include discussions of technical debt? 316 | 317 | 318 | - Yes 319 | - No 320 | 321 | 322 | #### Team 323 | 324 | > Does the development team work well together. 325 | 326 | __Is there a QA team or resource?__ 327 | 328 | 329 | - Yes 330 | - No 331 | 332 | __Are teams grouped by specialty?__ 333 | > Example: client vs server 334 | 335 | 336 | - Yes 337 | - No 338 | 339 | __How many front-end developers?__ 340 | 341 | __Do your work alongside the client's developers?__ 342 | > Your developers work on the same code as the client developers. 343 | 344 | 345 | - Yes 346 | - No 347 | 348 | __Is every piece of code known to at least two people?__ 349 | > No piece of code should be "workable" by only one person. 350 | 351 | 352 | - Yes 353 | - No 354 | 355 | __There are code reviews__ 356 | 357 | 358 | - [ ] Every commit 359 | - [ ] Every week 360 | - [ ] Every month 361 | - [ ] Of new people's code 362 | - [ ] Never 363 | 364 | __List examples of the client demonstrating the ability to add or change to new technology as needed.__ 365 | > Examples: Adding memcache, moving to a cloud, setting up a CDN. 366 | 367 | __List examples of needed changes in technology or process.__ 368 | > Examples: Adding memcache, moving to a cloud, setting up a CDN. 369 | 370 | 371 | 372 | 373 | 374 | ## Contributing 375 | 376 | `questions.json` contains a list of questions. To change a question, add or modify the relevant section and submit a pull request. The following types are available: 377 | 378 | - `text` - A text field 379 | - `textarea` - A textarea field 380 | - `number` - A number input 381 | - `section` - Specifies a new section that can contain more questions 382 | - `single` - A list of potential answers in `values` only allowing to select one 383 | - `single` - A list of potential answers in `values` selecting multiple 384 | 385 | -------------------------------------------------------------------------------- /resources/drawDoughnut.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jquery.drawDoughnutChart.js 3 | * Version: 0.4(Beta) 4 | * Inspired by Chart.js(http://www.chartjs.org/) 5 | * 6 | * Copyright 2014 hiro 7 | * https://github.com/githiro/drawDoughnutChart 8 | * Released under the MIT license. 9 | * 10 | */ 11 | ;(function($, undefined) { 12 | $.fn.drawDoughnutChart = function(data, options) { 13 | var $this = this, 14 | W = $this.width(), 15 | H = $this.height(), 16 | centerX = W/2, 17 | centerY = H/2, 18 | cos = Math.cos, 19 | sin = Math.sin, 20 | PI = Math.PI, 21 | settings = $.extend({ 22 | segmentShowStroke : true, 23 | segmentStrokeColor : "#0C1013", 24 | segmentStrokeWidth : 1, 25 | baseColor: "rgba(0,0,0,0.5)", 26 | baseOffset: 4, 27 | edgeOffset : 10,//offset from edge of $this 28 | percentageInnerCutout : 75, 29 | animation : true, 30 | animationSteps : 90, 31 | animationEasing : "easeInOutExpo", 32 | animateRotate : true, 33 | tipOffsetX: -8, 34 | tipOffsetY: -45, 35 | showTip: true, 36 | showLabel: false, 37 | ratioFont: 1.5, 38 | shortInt: false, 39 | tipClass: "doughnutTip", 40 | summaryClass: "doughnutSummary", 41 | summaryTitle: "TOTAL:", 42 | summaryTitleClass: "doughnutSummaryTitle", 43 | summaryNumberClass: "doughnutSummaryNumber", 44 | beforeDraw: function() { }, 45 | afterDrawed : function() { }, 46 | onPathEnter : function(e,data) { }, 47 | onPathLeave : function(e,data) { } 48 | }, options), 49 | animationOptions = { 50 | linear : function (t) { 51 | return t; 52 | }, 53 | easeInOutExpo: function (t) { 54 | var v = t<.5 ? 8*t*t*t*t : 1-8*(--t)*t*t*t; 55 | return (v>1) ? 1 : v; 56 | } 57 | }, 58 | requestAnimFrame = function() { 59 | return window.requestAnimationFrame || 60 | window.webkitRequestAnimationFrame || 61 | window.mozRequestAnimationFrame || 62 | window.oRequestAnimationFrame || 63 | window.msRequestAnimationFrame || 64 | function(callback) { 65 | window.setTimeout(callback, 1000 / 60); 66 | }; 67 | }(); 68 | 69 | settings.beforeDraw.call($this); 70 | 71 | var $svg = $('').appendTo($this), 72 | $paths = [], 73 | easingFunction = animationOptions[settings.animationEasing], 74 | doughnutRadius = Min([H / 2,W / 2]) - settings.edgeOffset, 75 | cutoutRadius = doughnutRadius * (settings.percentageInnerCutout / 100), 76 | segmentTotal = 0; 77 | 78 | //Draw base doughnut 79 | var baseDoughnutRadius = doughnutRadius + settings.baseOffset, 80 | baseCutoutRadius = cutoutRadius - settings.baseOffset; 81 | $(document.createElementNS('http://www.w3.org/2000/svg', 'path')) 82 | .attr({ 83 | "d": getHollowCirclePath(baseDoughnutRadius, baseCutoutRadius), 84 | "fill": settings.baseColor 85 | }) 86 | .appendTo($svg); 87 | 88 | //Set up pie segments wrapper 89 | var $pathGroup = $(document.createElementNS('http://www.w3.org/2000/svg', 'g')); 90 | $pathGroup.attr({opacity: 0}).appendTo($svg); 91 | 92 | //Set up tooltip 93 | if (settings.showTip) { 94 | var $tip = $('
').appendTo('body').hide(), 95 | tipW = $tip.width(), 96 | tipH = $tip.height(); 97 | } 98 | 99 | //Set up center text area 100 | var summarySize = (cutoutRadius - (doughnutRadius - cutoutRadius)) * 2, 101 | $summary = $('
') 102 | .appendTo($this) 103 | .css({ 104 | width: summarySize + "px", 105 | height: summarySize + "px", 106 | "margin-left": -(summarySize / 2) + "px", 107 | "margin-top": -(summarySize / 2) + "px" 108 | }); 109 | var $summaryTitle = $('

').appendTo($summary); 110 | $summaryTitle.css('font-size', getScaleFontSize( $summaryTitle, settings.summaryTitle )); // In most of case useless 111 | var $summaryNumber = $('

').appendTo($summary).css({opacity: 0}); 112 | 113 | for (var i = 0, len = data.length; i < len; i++) { 114 | segmentTotal += data[i].value; 115 | $paths[i] = $(document.createElementNS('http://www.w3.org/2000/svg', 'path')) 116 | .attr({ 117 | "stroke-width": settings.segmentStrokeWidth, 118 | "stroke": settings.segmentStrokeColor, 119 | "fill": data[i].color, 120 | "data-order": i 121 | }) 122 | .appendTo($pathGroup) 123 | .on("mouseenter", pathMouseEnter) 124 | .on("mouseleave", pathMouseLeave) 125 | .on("mousemove", pathMouseMove) 126 | .on("click", pathClick); 127 | } 128 | 129 | //Animation start 130 | animationLoop(drawPieSegments); 131 | 132 | //Functions 133 | function getHollowCirclePath(doughnutRadius, cutoutRadius) { 134 | //Calculate values for the path. 135 | //We needn't calculate startRadius, segmentAngle and endRadius, because base doughnut doesn't animate. 136 | var startRadius = -1.570,// -Math.PI/2 137 | segmentAngle = 6.2831,// 1 * ((99.9999/100) * (PI*2)), 138 | endRadius = 4.7131,// startRadius + segmentAngle 139 | startX = centerX + cos(startRadius) * doughnutRadius, 140 | startY = centerY + sin(startRadius) * doughnutRadius, 141 | endX2 = centerX + cos(startRadius) * cutoutRadius, 142 | endY2 = centerY + sin(startRadius) * cutoutRadius, 143 | endX = centerX + cos(endRadius) * doughnutRadius, 144 | endY = centerY + sin(endRadius) * doughnutRadius, 145 | startX2 = centerX + cos(endRadius) * cutoutRadius, 146 | startY2 = centerY + sin(endRadius) * cutoutRadius; 147 | var cmd = [ 148 | 'M', startX, startY, 149 | 'A', doughnutRadius, doughnutRadius, 0, 1, 1, endX, endY,//Draw outer circle 150 | 'Z',//Close path 151 | 'M', startX2, startY2,//Move pointer 152 | 'A', cutoutRadius, cutoutRadius, 0, 1, 0, endX2, endY2,//Draw inner circle 153 | 'Z' 154 | ]; 155 | cmd = cmd.join(' '); 156 | return cmd; 157 | }; 158 | function pathMouseEnter(e) { 159 | var order = $(this).data().order; 160 | if (settings.showTip) { 161 | $tip.text(data[order].title + " " + data[order].value+"%") 162 | .fadeIn(200); 163 | } 164 | if(settings.showLabel) { 165 | $summaryTitle.text(data[order].title).css('font-size', getScaleFontSize( $summaryTitle, data[order].title)); 166 | var tmpNumber = settings.shortInt ? shortKInt(data[order].value) : data[order].value; 167 | $summaryNumber.html(tmpNumber).css('font-size', getScaleFontSize( $summaryNumber, tmpNumber)); 168 | } 169 | settings.onPathEnter.apply($(this),[e,data]); 170 | } 171 | function pathMouseLeave(e) { 172 | if (settings.showTip) $tip.hide(); 173 | if(settings.showLabel) { 174 | $summaryTitle.text(settings.summaryTitle).css('font-size', getScaleFontSize( $summaryTitle, settings.summaryTitle)); 175 | var tmpNumber = settings.shortInt ? shortKInt(segmentTotal) : segmentTotal; 176 | $summaryNumber.html(tmpNumber).css('font-size', getScaleFontSize( $summaryNumber, tmpNumber)); 177 | } 178 | settings.onPathLeave.apply($(this),[e,data]); 179 | } 180 | function pathMouseMove(e) { 181 | if (settings.showTip) { 182 | $tip.css({ 183 | top: e.pageY + settings.tipOffsetY, 184 | left: e.pageX - $tip.width() / 2 + settings.tipOffsetX 185 | }); 186 | } 187 | } 188 | function pathClick(e){ 189 | var order = $(this).data().order; 190 | if (typeof data[order].action != "undefined") 191 | data[order].action(); 192 | } 193 | function drawPieSegments (animationDecimal) { 194 | var startRadius = -PI / 2,//-90 degree 195 | rotateAnimation = 1; 196 | if (settings.animation && settings.animateRotate) rotateAnimation = animationDecimal;//count up between0~1 197 | 198 | drawDoughnutText(animationDecimal, segmentTotal, data); 199 | 200 | $pathGroup.attr("opacity", animationDecimal); 201 | 202 | //If data have only one value, we draw hollow circle(#1). 203 | if (data.length === 1 && (4.7122 < (rotateAnimation * ((data[0].value / segmentTotal) * (PI * 2)) + startRadius))) { 204 | $paths[0].attr("d", getHollowCirclePath(doughnutRadius, cutoutRadius)); 205 | return; 206 | } 207 | for (var i = 0, len = data.length; i < len; i++) { 208 | var segmentAngle = rotateAnimation * ((data[i].value / segmentTotal) * (PI * 2)), 209 | endRadius = startRadius + segmentAngle, 210 | largeArc = ((endRadius - startRadius) % (PI * 2)) > PI ? 1 : 0, 211 | startX = centerX + cos(startRadius) * doughnutRadius, 212 | startY = centerY + sin(startRadius) * doughnutRadius, 213 | endX2 = centerX + cos(startRadius) * cutoutRadius, 214 | endY2 = centerY + sin(startRadius) * cutoutRadius, 215 | endX = centerX + cos(endRadius) * doughnutRadius, 216 | endY = centerY + sin(endRadius) * doughnutRadius, 217 | startX2 = centerX + cos(endRadius) * cutoutRadius, 218 | startY2 = centerY + sin(endRadius) * cutoutRadius; 219 | var cmd = [ 220 | 'M', startX, startY,//Move pointer 221 | 'A', doughnutRadius, doughnutRadius, 0, largeArc, 1, endX, endY,//Draw outer arc path 222 | 'L', startX2, startY2,//Draw line path(this line connects outer and innner arc paths) 223 | 'A', cutoutRadius, cutoutRadius, 0, largeArc, 0, endX2, endY2,//Draw inner arc path 224 | 'Z'//Cloth path 225 | ]; 226 | $paths[i].attr("d", cmd.join(' ')); 227 | startRadius += segmentAngle; 228 | } 229 | } 230 | function drawDoughnutText(animationDecimal, segmentTotal, data) { 231 | $summaryNumber 232 | .css({opacity: animationDecimal}) 233 | .text((segmentTotal * animationDecimal).toFixed(1)); 234 | var tmpNumber = settings.shortInt ? shortKInt(segmentTotal) : segmentTotal; 235 | $summaryNumber.html(data[0].value+'%').css('font-size', getScaleFontSize( $summaryNumber, tmpNumber)); 236 | } 237 | function animateFrame(cnt, drawData) { 238 | var easeAdjustedAnimationPercent =(settings.animation)? CapValue(easingFunction(cnt), null, 0) : 1; 239 | drawData(easeAdjustedAnimationPercent); 240 | } 241 | function animationLoop(drawData) { 242 | var animFrameAmount = (settings.animation)? 1 / CapValue(settings.animationSteps, Number.MAX_VALUE, 1) : 1, 243 | cnt =(settings.animation)? 0 : 1; 244 | requestAnimFrame(function() { 245 | cnt += animFrameAmount; 246 | animateFrame(cnt, drawData); 247 | if (cnt <= 1) { 248 | requestAnimFrame(arguments.callee); 249 | } else { 250 | settings.afterDrawed.call($this); 251 | } 252 | }); 253 | } 254 | function Max(arr) { 255 | return Math.max.apply(null, arr); 256 | } 257 | function Min(arr) { 258 | return Math.min.apply(null, arr); 259 | } 260 | function isNumber(n) { 261 | return !isNaN(parseFloat(n)) && isFinite(n); 262 | } 263 | function CapValue(valueToCap, maxValue, minValue) { 264 | if (isNumber(maxValue) && valueToCap > maxValue) return maxValue; 265 | if (isNumber(minValue) && valueToCap < minValue) return minValue; 266 | return valueToCap; 267 | } 268 | function shortKInt (int) { 269 | int = int.toString(); 270 | var strlen = int.length; 271 | if(strlen<5) 272 | return int; 273 | if(strlen<8) 274 | return '' + int.substring(0, strlen-3) + 'K'; 275 | return '' + int.substring( 0, strlen-6) + 'M'; 276 | } 277 | function getScaleFontSize(block, newText) { 278 | block.css('font-size', ''); 279 | newText = newText.toString().replace(/(<([^>]+)>)/ig,""); 280 | var newFontSize = block.width() / newText.length * settings.ratioFont; 281 | // Not very good : http://stephensite.net/WordPressSS/2008/02/19/how-to-calculate-the-character-width-accross-fonts-and-points/ 282 | // But best quick way the 1.5 number is to affinate in function of the police 283 | var maxCharForDefaultFont = block.width() - newText.length * block.css('font-size').replace(/px/, '') / settings.ratioFont; 284 | if(maxCharForDefaultFont<0) 285 | return newFontSize+'px'; 286 | else 287 | return ''; 288 | } 289 | /** 290 | function getScaleFontSize(block, newText) { 291 | block.css('font-size', ''); 292 | newText = newText.toString().replace(/(<([^>]+)>)/ig,""); 293 | var newFontSize = block.width() / newText.length; 294 | if(newFontSize