├── .gitignore
├── .nodemonignore
├── .scss-lint.yml
├── LICENSE.txt
├── app.js
├── client
├── admin.js
├── app.js
├── form.js
├── graphs
│ ├── bar.js
│ ├── index.js
│ ├── line.js
│ ├── matrixplot.js
│ ├── numberline.js
│ ├── pie.js
│ ├── radar.js
│ └── stack.js
├── input.js
├── lib
│ ├── cookie.js
│ ├── jargonizer.js
│ ├── modal.js
│ ├── sharemodal.js
│ ├── spinner.js
│ └── stickyheader.js
├── nav.js
├── results
│ ├── graphs
│ │ ├── bar.js
│ │ ├── index.js
│ │ ├── line.js
│ │ ├── matrixplot.js
│ │ ├── number-incomplete.js
│ │ ├── number-percentile.js
│ │ ├── number-responses.js
│ │ ├── number-score.js
│ │ ├── numberline-agreement.js
│ │ ├── numberline-disagreement.js
│ │ ├── pie.js
│ │ ├── responsiveness-table.js
│ │ └── stack.js
│ ├── index.js
│ └── renderers
│ │ ├── even-over.js
│ │ └── original.js
├── shared.js
└── vendor
│ ├── ZeroClipboard.min.js
│ ├── chosen.jquery.js
│ ├── d3.js
│ ├── findAndReplaceDOMText.js
│ ├── jquery.ba-throttle-debounce.js
│ ├── jquery.js
│ ├── jquery.tooltipster.min.js
│ ├── parsley.min.js
│ ├── require.js
│ ├── stupidtable.min.js
│ └── velocity.js
├── config.js
├── config.rb
├── controllers
├── about.js
├── admin.js
├── auth.js
├── dashboard.js
├── export.js
├── home.js
├── overview.js
├── survey.js
├── survey_data.js
└── survey_share.js
├── gulpfile.js
├── lib
├── calculations
│ ├── even-over.js
│ └── original.js
├── email_hash.js
├── random.js
├── route_class.js
└── survey_template.js
├── migrations
├── 001-add-users.js
├── 002-expiring-mongo-sessions.js
├── 003-elevate-aggregate-score.js
└── 004-survey-creators.js
├── models
├── forgot_key.js
├── survey.js
├── survey_response.js
└── user.js
├── package.json
├── processes.json
├── public
├── data
│ ├── jargon.json
│ └── traits.json
├── favicon-16x16.png
├── favicon-32x32.png
├── favicon.ico
├── fonts
│ └── karla
│ │ ├── Karla-Bold-demo.html
│ │ ├── Karla-Bold-webfont.eot
│ │ ├── Karla-Bold-webfont.svg
│ │ ├── Karla-Bold-webfont.ttf
│ │ ├── Karla-Bold-webfont.woff
│ │ ├── Karla-Bold-webfont.woff2
│ │ ├── Karla-BoldItalic-demo.html
│ │ ├── Karla-BoldItalic-webfont.eot
│ │ ├── Karla-BoldItalic-webfont.svg
│ │ ├── Karla-BoldItalic-webfont.ttf
│ │ ├── Karla-BoldItalic-webfont.woff
│ │ ├── Karla-BoldItalic-webfont.woff2
│ │ ├── Karla-Italic-demo.html
│ │ ├── Karla-Italic-webfont.eot
│ │ ├── Karla-Italic-webfont.svg
│ │ ├── Karla-Italic-webfont.ttf
│ │ ├── Karla-Italic-webfont.woff
│ │ ├── Karla-Italic-webfont.woff2
│ │ ├── Karla-Regular-demo.html
│ │ ├── Karla-Regular-webfont.eot
│ │ ├── Karla-Regular-webfont.svg
│ │ ├── Karla-Regular-webfont.ttf
│ │ ├── Karla-Regular-webfont.woff
│ │ └── Karla-Regular-webfont.woff2
├── img
│ ├── chosen
│ │ ├── chosen-sprite.png
│ │ └── chosen-sprite@2x.png
│ ├── hero.jpg
│ ├── landing_hero.png
│ ├── logo.png
│ ├── logo.svg
│ ├── logo_gray.svg
│ └── screenshots
│ │ ├── dashboard.png
│ │ ├── results.png
│ │ └── survey.png
└── vendor
│ └── ZeroClipboard.swf
├── readme.md
├── sass
├── components
│ ├── _buttons.scss
│ ├── _cards.scss
│ ├── _cookie-error.scss
│ ├── _dropdowns.scss
│ ├── _footer.scss
│ ├── _forms.scss
│ ├── _graphs.scss
│ ├── _jargon.scss
│ ├── _modals.scss
│ ├── _nav.scss
│ ├── _progress.scss
│ ├── _sliders.scss
│ └── _tables.scss
├── layouts
│ ├── _dashboard.scss
│ ├── _landing.scss
│ ├── _login.scss
│ ├── _overview.scss
│ ├── _primary.scss
│ └── _share.scss
├── main.scss
├── partials
│ ├── _base.scss
│ ├── _breakpoints.scss
│ ├── _color.scss
│ ├── _grid.scss
│ ├── _type.scss
│ └── _z.scss
└── vendor
│ ├── _chosen.scss
│ ├── _includemedia.scss
│ └── _tooltipster.scss
├── survey_fields.md
├── surveys
├── 20141020.json
├── 20141106.json
├── 20141209.json
├── 20150203.json
└── 20150331.json
├── tasks
├── build.js
├── bundle.js
├── default.js
├── js-lint.js
├── scss-lint.js
├── serve.js
├── style.js
└── watch.js
└── views
├── 404.jade
├── admin
├── account.jade
└── users.jade
├── email
├── activated
│ └── activated.html.jade
├── buttons.jade
├── forgotpassword
│ └── forgotpassword.html.jade
├── layout.jade
├── logo.html
├── registration
│ └── registration.html.jade
├── style.css
├── surveysharedhasaccount
│ └── surveysharedhasaccount.html.jade
└── surveysharednoaccount
│ └── surveysharednoaccount.html.jade
├── form-welcome.jade
├── form.jade
├── forms.jade
├── landing.jade
├── layouts
├── auth_base.jade
└── base.jade
├── partials
├── cookie-error.jade
├── formcomponents.jade
├── formcomponents
│ ├── checkbox.jade
│ ├── email.jade
│ ├── form.jade
│ ├── gridselect.jade
│ ├── password.jade
│ ├── progress.jade
│ ├── radio.jade
│ ├── select.jade
│ ├── single-checkbox.jade
│ ├── slider.jade
│ ├── teamselect.jade
│ ├── text.jade
│ └── textarea.jade
├── modal.jade
├── modals
│ └── share.jade
└── nav.jade
├── survey
├── about.jade
├── done.jade
├── even-over
│ ├── detail.jade
│ └── overview.jade
├── header.jade
├── nav.jade
├── new.jade
└── original
│ ├── nav.jade
│ ├── overview.jade
│ └── scores.jade
├── terms.jade
└── user
├── account.jade
├── dashboard.jade
├── forgot.jade
├── login.jade
├── reset.jade
└── signup.jade
/.gitignore:
--------------------------------------------------------------------------------
1 | # cache files
2 | .sass-cache
3 | migrations/.migrate
4 |
5 | # installed packages
6 | node_modules
7 |
8 | # project/compiled files
9 | design
10 | public/js
11 | public/css/main.css
12 |
13 | # configuration
14 | config/local.js
--------------------------------------------------------------------------------
/.nodemonignore:
--------------------------------------------------------------------------------
1 | client/
2 | public/
3 | views/
4 | sass/
5 | node_modules/
6 | .sass-cache/
7 |
--------------------------------------------------------------------------------
/.scss-lint.yml:
--------------------------------------------------------------------------------
1 | # SCSS linting rules. Taken from the default configuration and modified a bit to suit the Planetary Manifest.
2 | exclude: 'sass/vendor/**'
3 |
4 | linters:
5 | BorderZero:
6 | enabled: true
7 |
8 | ColorKeyword:
9 | enabled: false
10 |
11 | Comment:
12 | enabled: false
13 |
14 | DebugStatement:
15 | enabled: true
16 |
17 | DeclarationOrder:
18 | enabled: true
19 |
20 | DuplicateProperty:
21 | enabled: true
22 |
23 | ElsePlacement:
24 | enabled: true
25 | style: same_line # or 'new_line'
26 |
27 | EmptyLineBetweenBlocks:
28 | enabled: true
29 | ignore_single_line_blocks: true
30 |
31 | EmptyRule:
32 | enabled: true
33 |
34 | FinalNewline:
35 | enabled: true
36 | present: true
37 |
38 | HexLength:
39 | enabled: true
40 | style: short # or 'long'
41 |
42 | HexNotation:
43 | enabled: true
44 | style: lowercase # or 'uppercase'
45 |
46 | HexValidation:
47 | enabled: true
48 |
49 | IdWithExtraneousSelector:
50 | enabled: true
51 |
52 | Indentation:
53 | enabled: true
54 | character: space # or 'tab'
55 | width: 4
56 |
57 | LeadingZero:
58 | enabled: true
59 | style: include_zero # or 'exclude_zero'
60 |
61 | MergeableSelector:
62 | enabled: true
63 | force_nesting: true
64 |
65 | NameFormat:
66 | enabled: true
67 | convention: hyphenated_lowercase # or 'BEM', or a regex pattern
68 |
69 | PlaceholderInExtend:
70 | enabled: true
71 |
72 | PropertySortOrder:
73 | enabled: false
74 | ignore_unspecified: false
75 |
76 | PropertySpelling:
77 | enabled: true
78 | extra_properties: []
79 |
80 | SelectorDepth:
81 | enabled: true
82 | max_depth: 3
83 |
84 | SelectorFormat:
85 | enabled: true
86 | convention: hyphenated_lowercase # or 'snake_case', or 'camel_case', or a regex pattern
87 |
88 | Shorthand:
89 | enabled: true
90 |
91 | SingleLinePerProperty:
92 | enabled: true
93 | allow_single_line_rule_sets: true
94 |
95 | SingleLinePerSelector:
96 | enabled: true
97 |
98 | SpaceAfterComma:
99 | enabled: true
100 |
101 | SpaceAfterPropertyColon:
102 | enabled: true
103 | style: one_space # or 'no_space', or 'at_least_one_space', or 'aligned'
104 |
105 | SpaceAfterPropertyName:
106 | enabled: true
107 |
108 | SpaceBeforeBrace:
109 | enabled: true
110 | allow_single_line_padding: false
111 |
112 | SpaceBetweenParens:
113 | enabled: true
114 | spaces: 0
115 |
116 | StringQuotes:
117 | enabled: true
118 | style: double_quotes # or double_quotes
119 |
120 | TrailingSemicolon:
121 | enabled: true
122 |
123 | TrailingZero:
124 | enabled: false
125 |
126 | UnnecessaryMantissa:
127 | enabled: true
128 |
129 | UnnecessaryParentReference:
130 | enabled: true
131 |
132 | UrlFormat:
133 | enabled: true
134 |
135 | UrlQuotes:
136 | enabled: true
137 |
138 | ZeroUnit:
139 | enabled: true
140 |
141 | Compass::*:
142 | enabled: false
--------------------------------------------------------------------------------
/client/admin.js:
--------------------------------------------------------------------------------
1 | if( !window.console ) { window.console = {}; }
2 | if( !console.log ) { console.log = function() {}; }
3 |
4 | var $ = require( 'jquery' ),
5 | ZeroClipboard = require( './vendor/ZeroClipboard.min' );
6 |
7 | // These export onto the jQuery namespace
8 | require( './vendor/jquery.tooltipster.min' );
9 | require( './vendor/stupidtable.min' );
10 |
11 | $('.table--expand-button').click( function(e){
12 | $(this).closest('tr').toggleClass('expanded');
13 | e.preventDefault();
14 | } );
15 |
16 | $( '#select-all' ).change(function() {
17 | var elems = $('.table--checkbox .form--element-control');
18 | if (this.checked) {
19 | elems.each(function() {
20 | this.checked = true;
21 | });
22 | } else {
23 | elems.each(function() {
24 | this.checked = false;
25 | });
26 | }
27 | });
28 |
29 |
30 | (function($) {
31 | var re = /([^&=]+)=?([^&]*)/g;
32 | var decode = function(str) {
33 | return decodeURIComponent(str.replace(/\+/g, ' '));
34 | };
35 | $.parseParams = function(query) {
36 | var params = {}, e;
37 | if (query) {
38 | if (query.substr(0, 1) == '?') {
39 | query = query.substr(1);
40 | }
41 |
42 | while (e = re.exec(query)) {
43 | var k = decode(e[1]);
44 | var v = decode(e[2]);
45 | if (params[k] !== undefined) {
46 | if (!$.isArray(params[k])) {
47 | params[k] = [params[k]];
48 | }
49 | params[k].push(v);
50 | } else {
51 | params[k] = v;
52 | }
53 | }
54 | }
55 | return params;
56 | };
57 | })(jQuery);
58 |
59 | function setMultipleActive(val) {
60 | var elems = $('.table--checkbox .form--element-control:checked');
61 | var keys = [];
62 | elems.each(function() {
63 | keys.push($(this).closest('tr').data('key'));
64 | });
65 | window.location = '/dashboard/multiple-active?val=' + (val ? 1 : 0) + '&keys=' + JSON.stringify(keys) + '&all=0';
66 | }
67 |
68 | $( '.button--mark-inactive' ).click(function() {
69 | setMultipleActive(false);
70 | });
71 |
72 | $( '.button--mark-active' ).click(function() {
73 | setMultipleActive(true);
74 | });
75 |
76 | var activeSorting = $('table.sortable th.sorting-no-icon');
77 |
78 | $( 'table.sortable th' ).each( function(idx, el) {
79 | var key = $(el).data('sort-key');
80 | if (!key) {
81 | return;
82 | }
83 |
84 | $(el).click(function() {
85 | var map = $.parseParams( document.location.search );
86 | map.sort = key;
87 | map.page = 1;
88 | if (key == activeSorting.data('sort-key')) {
89 | map.dir = activeSorting.hasClass('sorting-asc') ? 'desc' : 'asc';
90 | }
91 | window.location = '/dashboard?' + $.param(map);
92 | });
93 | });
94 |
95 | $( '.dashboard--copy' ).each( function( idx, el ) {
96 | $( el ).attr( 'data-clipboard-text',
97 | window.location.protocol.replace( ':', '' )
98 | + '://'
99 | + window.location.host
100 | + $( el ).data( 'url' )
101 | );
102 |
103 | ZeroClipboard.config( { swfPath: "/vendor/ZeroClipboard.swf" } );
104 | var client = new ZeroClipboard( el );
105 |
106 | client.on( 'ready', function( readyEvent ) {
107 | client.on( 'beforecopy', function( event ) {
108 | $( event.target ).removeClass( 'dashboard--zc-copied' );
109 | } );
110 |
111 | client.on( 'aftercopy', function( event ) {
112 | $( event.target ).addClass( 'dashboard--zc-copied' );
113 | } );
114 | } );
115 | } );
116 |
117 | //click handlers for table sorting
118 | $( 'main' ).show();
119 |
--------------------------------------------------------------------------------
/client/app.js:
--------------------------------------------------------------------------------
1 | if( !window.console ) { window.console = {}; }
2 | if( !console.log ) { console.log = function() {}; }
3 |
4 | var $ = require( 'jquery' ),
5 | d3 = require( './vendor/d3' ),
6 | Color = require( 'color' ),
7 |
8 | Spinner = require( './lib/spinner' ),
9 | modal = require( './lib/modal' ),
10 | sharemodal = require( './lib/sharemodal' ),
11 | jargonizer = require( './lib/jargonizer' ),
12 |
13 | surveyResults = require( './results/index' );
14 |
15 | // These export onto the jQuery namespace
16 | require( './vendor/jquery.tooltipster.min' );
17 | require( './vendor/parsley.min' );
18 | require( 'rangeslider.js' ); // this is just the name of the package
19 | require( './input' );
20 |
21 | console.log( 'Running app.js' );
22 |
23 | var spinner = new Spinner();
24 |
25 | $( '.tooltip' ).each( function() {
26 | $( this ).tooltipster( {
27 | contentAsHTML: true,
28 | theme: 'tooltipster-default ' + $( this ).attr( 'class' ).replace( /tooltip\s?/, '' )
29 | } );
30 | } );
31 |
32 | $( 'input[type="range"]' ).rangeslider( {
33 | polyfill: false
34 | } ).on( 'change', function() {
35 | var area = $(this).closest( '.form--element-control' );
36 | var left = area.find( '.rangeslider__handle' ).css( 'left' );
37 | var tip = area.find( '.range-value' );
38 | tip.css( 'left', left );
39 | tip.find( '.range-value-label' ).html( $( this ).val() );
40 | } );
41 |
42 |
43 | $( '.get_link' ).click( function( e ) {
44 | e.preventDefault();
45 | $( '.link-popup' ).toggleClass( 'visible' );
46 | $( '.link-popup input' ).focus().select();
47 | } );
48 |
49 | $( document ).on( 'click', function() {
50 | $( '.nav--toggle' ).removeClass( 'shown' );
51 | } );
52 |
53 | $( '.nav--toggle' ).on( 'click', function( e ) {
54 | e.stopPropagation();
55 | } );
56 |
57 | $( '.nav--toggle-button' ).on( 'click', function(e) {
58 | $( '.nav--toggle' ).toggleClass( 'shown' );
59 | } );
60 |
61 | // responsive tables
62 | $( '.table-wrapper' ).on( 'scroll', function() {
63 | $( '.row-header' ).css( 'left', $( this ).scrollLeft() );
64 | } );
65 |
66 | var chart = d3.selectAll( '.chart, .table, .filter' ),
67 | firstRender = true,
68 | charts = [];
69 |
70 | // bind click handlers to modal links
71 | $( '.modal--open' ).on( 'click', function() {
72 | modal( $( this ).attr( 'data-modalname' ) ).open();
73 | } );
74 |
75 | function getRandomArbitrary(min, max) {
76 | return Math.round(Math.random() * (max - min) + min);
77 | }
78 |
79 | function updateNumberPlaceholder(el, index) {
80 | if (!el.context.parentNode) {
81 | window.clearTimeout(dataPlaceholderIntervals[index]);
82 | return;
83 | }
84 |
85 | var rand = getRandomArbitrary(1, 100);
86 | el.html(rand);
87 | }
88 |
89 | var dataPlaceholderIntervals = {};
90 |
91 | function showDataPlaceholders() {
92 | $('.data-placeholder--number').each(function(i) {
93 | var el = $(this);
94 | dataPlaceholderIntervals[i] = window.setInterval(function() {
95 | updateNumberPlaceholder(el, i);
96 | }, 50);
97 | });
98 | }
99 |
100 | // Survey graphs
101 | $( function() {
102 | showDataPlaceholders();
103 |
104 | if( typeof survey !== 'undefined' ) {
105 | spinner.loading( 'survey' );
106 | surveyResults.init();
107 | jargonizer();
108 | }
109 | } );
110 |
--------------------------------------------------------------------------------
/client/form.js:
--------------------------------------------------------------------------------
1 | if( !window.console ) { window.console = {}; }
2 | if( !console.log ) { console.log = function() {}; }
3 |
4 | var $ = require( 'jquery' );
5 |
6 | // These export onto the jQuery namespace
7 | require( 'rangeslider.js' ); // this is just the name of the package
8 | require( './vendor/jquery.tooltipster.min' );
9 | require( './vendor/parsley.min' );
10 |
11 | require( './input.js' );
12 | require( './lib/jargonizer' )();
13 |
14 | $( function() {
15 | $( '.tooltip' ).each( function() {
16 | $( this ).tooltipster( {
17 | contentAsHTML: true,
18 | theme: 'tooltipster-default ' + $( this ).attr( 'class' ).replace( /tooltip\s?/, '' )
19 | } );
20 | } );
21 |
22 | $( 'input[type="range"]' ).rangeslider({
23 | polyfill: false,
24 | onSlide: function(position, value) {
25 | var area = $(this.$element).closest('.form--element-control');
26 | var left = area.find('.rangeslider__handle').css('left');
27 | var tip = area.find('.range-value');
28 | tip.css('left', left);
29 | tip.find('.range-value-label').html(value);
30 | }
31 | });
32 |
33 | $('.get_link').click( function(e){
34 | e.preventDefault();
35 | $('.link-popup').toggleClass('visible');
36 | $('.link-popup input').focus().select();
37 | });
38 |
39 | $(document).on('click', function(){
40 | $('.nav--toggle').removeClass('shown');
41 | });
42 |
43 | $('.nav--toggle').on('click', function(e){
44 | e.stopPropagation();
45 | })
46 |
47 | $('.nav--toggle-button').on( 'click', function(e) {
48 | $('.nav--toggle').toggleClass('shown');
49 | });
50 |
51 | //responsive tables
52 |
53 | $('.table-wrapper').scroll( function(){
54 | $('.row-header').css('left', $(this).scrollLeft());
55 | })
56 |
57 | //teamselect click handler
58 |
59 | $('.teamselect--dropdown').change(function() {
60 | if ($(this).find("option:selected").attr("value") === "newteam") {
61 | $(this).removeClass('is-shown').attr("name", "");
62 | $('.teamselect--text').addClass('is-shown').focus().attr("name", "team");
63 | };
64 | });
65 |
66 | $('.teamselect--text').blur(function() {
67 | if (!$(this).val()) {
68 | $(this).removeClass('is-shown');
69 | $('.teamselect--dropdown').addClass('is-shown').find('option').prop('selected', false);
70 | }
71 | });
72 | } );
73 |
--------------------------------------------------------------------------------
/client/graphs/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | bar: require( './bar' ),
3 | matrixplot: require( './matrixplot' ),
4 | numberline: require( './numberline' ),
5 | pie: require( './pie' ),
6 | radar: require( './radar' ),
7 | stack: require( './stack' ),
8 | line: require( './line' )
9 | };
10 |
--------------------------------------------------------------------------------
/client/graphs/numberline.js:
--------------------------------------------------------------------------------
1 | var $ = require( 'jquery' ),
2 | d3 = require( '../vendor/d3' );
3 |
4 | module.exports = function( width, data ) {
5 | function chart( div ) {
6 | div.each( function() {
7 | var scale = d3.scale.linear()
8 | .domain( [ 0, 1 ] )
9 | .range( [ 10, width - 10 ] ),
10 | el = d3.select( this ),
11 | g = el.select( 'g' );
12 |
13 | if( g.empty() ) {
14 | g = d3.select( this ).append( 'svg' )
15 | .attr( 'width', width )
16 | .attr( 'height', 120 )
17 | .append( 'g' );
18 |
19 | g.append( 'rect' )
20 | .attr( 'class', 'line' )
21 | .attr( 'width', width )
22 | .attr( 'height', 1 )
23 | .attr( 'x', 0 )
24 | .attr( 'y', 50 );
25 |
26 | var labels = [ [ 0, 'Strongly Disagree', 'start' ], [ 2, 'Neutral', 'middle' ], [ 4, 'Strongly Agree', 'end' ] ],
27 | labelItems = g.selectAll( 'text' )
28 | .data( labels )
29 | .enter()
30 | .append( 'text' )
31 | .attr( 'y', 80 )
32 | .attr( 'text-anchor', function( d ) { return d[ 2 ]; } );
33 |
34 | labelItems.append( 'tspan' )
35 | .text( function( d ) { return '(' + d[ 0 ] + ')'; } )
36 | .attr( 'x', function( d, i ) { return ( ( i / ( labels.length - 1 ) ) * 100 ) + '%'; } )
37 | .attr( 'dy', 0 );
38 |
39 | labelItems.append( 'tspan' )
40 | .text( function( d ) { return d[ 1 ]; } )
41 | .attr( 'x', function( d, i ) { return ( ( i / ( labels.length - 1 ) ) * 100 ) + '%'; } )
42 | .attr( 'dy', 20 );
43 |
44 | g.selectAll( 'circle' )
45 | .data( data )
46 | .enter()
47 | .append( 'circle' )
48 | .attr( 'class', function( d, i ) { return 'circle-' + ( data.length > 1 ? i : 'only' ); } )
49 | .attr( 'cx', function( d ) {
50 | return scale( d );
51 | })
52 | .attr( 'cy', 50 )
53 | .attr( 'r', 10 );
54 | } else {
55 | g.selectAll( 'circle' ).each( function( d, i ) {
56 | d3.select( this )
57 | .transition()
58 | .duration( 1000 )
59 | .attr( 'cx', scale( data[ i ] ) );
60 | } );
61 | }
62 | } );
63 | }
64 |
65 | return chart;
66 | };
67 |
--------------------------------------------------------------------------------
/client/lib/cookie.js:
--------------------------------------------------------------------------------
1 | // Cookie functions
2 | exports.createCookie = function(name,value,days) {
3 | var expires = "";
4 | if (days) {
5 | var date = new Date();
6 | date.setTime(date.getTime()+(days*24*60*60*1000));
7 | expires = "; expires="+date.toGMTString();
8 | }
9 | document.cookie = name+"="+value+expires+"; path=/";
10 | };
11 |
12 | exports.readCookie = function(name) {
13 | var nameEQ = name + "=";
14 | var ca = document.cookie.split(';');
15 | for(var i=0;i < ca.length;i++) {
16 | var c = ca[i];
17 | while (c.charAt(0)==' ') c = c.substring(1,c.length);
18 | if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
19 | }
20 | return null;
21 | };
22 |
23 | exports.eraseCookie = function(name) {
24 | createCookie(name,"",-1);
25 | };
26 |
27 | exports.cookiesEnabled = function() {
28 | var cookieEnabled = (navigator.cookieEnabled) ? true : false;
29 | if( typeof navigator.cookieEnabled == "undefined" && !cookieEnabled ) {
30 | document.cookie = "testcookie";
31 | cookieEnabled = ( document.cookie.indexOf( "testcookie" ) != -1 ) ? true : false;
32 | }
33 | return cookieEnabled;
34 | };
35 |
--------------------------------------------------------------------------------
/client/lib/jargonizer.js:
--------------------------------------------------------------------------------
1 | var $ = require( 'jquery' ),
2 | findAndReplaceDOMText = require( '../vendor/findAndReplaceDOMText' );
3 |
4 | /**
5 | * Pulls in the jargon.json file (from public) and replaces any instances of jargon
6 | * in the page with a tooltip-wrapped version that includes a hover-definition.
7 | * @return {null}
8 | */
9 | module.exports = function() {
10 | $.getJSON( '/data/jargon.json', function( jargon ) {
11 | var body = document.body;
12 |
13 | $.each( jargon, function( key, value ) {
14 | findAndReplaceDOMText( body, {
15 | find: new RegExp( '\\b' + key.replace( /([\[\]*.\\\/^$?!+\()])/g, '\$1' ), 'gi' ),
16 | replace: function( portion, match ) {
17 | var el = $( '' );
18 | el.html( portion.text );
19 | return el[ 0 ];
20 | }
21 | } );
22 | } );
23 |
24 | $( body ).find( '.jargon--tooltip[data-tip]' ).tooltipster( {
25 | functionBefore: function( origin, continueTooltip ) {
26 | origin.tooltipster( 'content', origin.data( 'tip' ) );
27 | continueTooltip();
28 | },
29 | theme: 'tooltipster-default'
30 | } );
31 | } );
32 | };
--------------------------------------------------------------------------------
/client/lib/modal.js:
--------------------------------------------------------------------------------
1 | var $ = require( 'jquery' );
2 |
3 | var modalList = {};
4 |
5 | var ModalObj = function(modalName) {
6 |
7 | this.name = modalName;
8 | this.object = $('.modal--' + this.name);
9 |
10 | this.open = function() {
11 | console.log('modal ' + this.name + ' opened');
12 | this.object.addClass('is-shown');
13 |
14 | $('.modal').on('click', $.proxy( function(e) {
15 | this.close();
16 | }, this));
17 |
18 | $('.modal--body').on('click', function(e) {
19 | e.stopPropagation();
20 | });
21 |
22 | $('.modal--close').on('click', $.proxy( function(e) {
23 | this.close();
24 | }, this));
25 | },
26 |
27 | this.close = function() {
28 | console.log('modal ' + this.name + ' closed');
29 | this.object.removeClass('is-shown');
30 | }
31 | };
32 |
33 | var modal = function(modalName) {
34 |
35 | if( !( modalName in modalList ) ) {
36 | modalList[ modalName ] = new ModalObj( modalName );
37 | };
38 |
39 | return modalList[ modalName ];
40 |
41 | };
42 |
43 | module.exports = modal;
--------------------------------------------------------------------------------
/client/lib/spinner.js:
--------------------------------------------------------------------------------
1 | var $ = require( 'jquery' );
2 |
3 | module.exports = function() {
4 | var $spinner = $( '.spinner' ),
5 | $gradient = $( '.nav--gradient' ),
6 | states = {};
7 |
8 | function onChange() {
9 | var showSpinner = false;
10 |
11 | for( var key in states ) {
12 | if( states[ key ] )
13 | showSpinner = true;
14 | }
15 |
16 | $spinner[ showSpinner ? 'removeClass' : 'addClass' ]('hidden');
17 | $gradient[ showSpinner ? 'addClass' : 'removeClass' ]('loading');
18 | }
19 |
20 | this.loading = function( key ) {
21 | states[ key ] = true;
22 | onChange();
23 | };
24 |
25 | this.done = function( key ) {
26 | delete states[ key ];
27 | onChange();
28 | };
29 | };
30 |
--------------------------------------------------------------------------------
/client/nav.js:
--------------------------------------------------------------------------------
1 | var $ = require( 'jquery' );
2 |
3 | $(document).ready( function() {
4 |
5 | var hoverTimer;
6 |
7 | // nav toggling
8 | $('.dropdown').on('click', function(e){
9 | e.stopPropagation();
10 | });
11 |
12 | $('.dropdown').on( 'mouseenter', function(e) {
13 | clearTimeout(hoverTimer);
14 | $(this).addClass('is-active');
15 | });
16 |
17 | $('.dropdown').on( 'mouseleave', function(e) {
18 | hoverTimer = setTimeout( function(){
19 | $('.dropdown').removeClass('is-active');
20 | }, 200);
21 | });
22 |
23 | });
24 |
--------------------------------------------------------------------------------
/client/results/graphs/bar.js:
--------------------------------------------------------------------------------
1 | var $ = require( 'jquery' ),
2 | d3 = require( '../../vendor/d3' ),
3 | bar = require( '../../graphs/bar' );
4 |
5 | module.exports = function( charts, data, responses, colors ) {
6 | $( '.chart.bar[data-col]' ).each( function() {
7 | var width = $( this ).width(),
8 | height = $( this ).data( 'height' ) || 200,
9 | col = $( this ).data( 'col' ),
10 | margins = ( $( this ).data( 'margins' ) || '' ).split( ',' ),
11 | barData = responses.dimension( function( d ) {
12 | return d[ col ];
13 | } ),
14 | barData2 = responses.dimension( function( d ) {
15 | return d[ col ];
16 | } );
17 |
18 | var chart = bar( width, height )
19 | .dimension( barData )
20 | .altDimension( barData2 )
21 | .color( colors[ 'graph-a' ], colors[ 'graph-a' ] );
22 | chart.margins.apply( chart, margins );
23 |
24 | charts.push( [ chart, this ] );
25 | $('.data-placeholder--box', this).remove();
26 | } );
27 | };
28 |
--------------------------------------------------------------------------------
/client/results/graphs/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | bar: require( './bar' ),
3 | matrixplot: require( './matrixplot' ),
4 | pie: require( './pie' ),
5 | stack: require( './stack' ),
6 | line: require( './line' ),
7 | numberResponses: require( './number-responses' ),
8 | numberIncomplete: require( './number-incomplete' ),
9 | numberPercentile: require( './number-percentile' ),
10 | numberScore: require( './number-score' ),
11 | numberlineAgreement: require( './numberline-agreement' ),
12 | numberlineDisagreement: require( './numberline-disagreement' ),
13 | responsivenessTable: require( './responsiveness-table' )
14 | };
15 |
--------------------------------------------------------------------------------
/client/results/graphs/line.js:
--------------------------------------------------------------------------------
1 | var $ = require( 'jquery' ),
2 | d3 = require( '../../vendor/d3' ),
3 | line = require( '../../graphs/line' );
4 |
5 | module.exports = function( charts, data, responses, colors ) {
6 | $( '.chart.line[data-cols]' ).each( function() {
7 | var width = $( this ).width(),
8 | height = 200,
9 | col = $( this ).data( 'col' ),
10 | lineData = responses.dimension( function( d ) {
11 | return d[ col ];
12 | } ),
13 | lineData2 = responses.dimension( function( d ) {
14 | return d[ col ];
15 | } );
16 |
17 | var chart = graphs.line( width, height )
18 | .dimension( lineData )
19 | .altDimension( lineData2 )
20 | .color( colors[ 'graph-a' ], colors[ 'graph-a' ] );
21 |
22 | charts.push( [ chart, this ] );
23 | } );
24 | };
25 |
--------------------------------------------------------------------------------
/client/results/graphs/matrixplot.js:
--------------------------------------------------------------------------------
1 | var $ = require( 'jquery' ),
2 | d3 = require( '../../vendor/d3' ),
3 | matrixplot = require( '../../graphs/matrixplot' );
4 |
5 | module.exports = function( charts, data, responses, colors ) {
6 | $( '.chart.matrixplot' ).each( function() {
7 | var width = $( this ).width(),
8 | height = $( this ).data( 'height' ) || width,
9 | colX = $( this ).data( 'col-x' ).toLowerCase(),
10 | colXValues = $( this ).data( 'x-values' ).split( ',' ),
11 | colY = $( this ).data( 'col-y' ).toLowerCase(),
12 | colYValues = $( this ).data( 'y-values' ).split( ',' ),
13 | plotData = responses.dimension( function( d ) {
14 | return [ ( d[ colX ] || '' ).toLowerCase(), ( d[ colY ] || '' ).toLowerCase() ];
15 | } ),
16 | chart = matrixplot( width, height, {
17 | xLabels: $( this ).data( 'x-labels' ).split( ',' ),
18 | xValues: colXValues,
19 | yLabels: $( this ).data( 'y-labels' ).split( ',' ),
20 | yValues: colYValues
21 | } )
22 | .color( colors[ 'graph-a' ], colors[ 'graph-a' ] )
23 | .dimension( plotData );
24 |
25 | charts.push( [ chart, this ] );
26 | $('.data-placeholder--box', this).remove();
27 | } );
28 | };
29 |
--------------------------------------------------------------------------------
/client/results/graphs/number-incomplete.js:
--------------------------------------------------------------------------------
1 | var $ = require( 'jquery' ),
2 | d3 = require( '../../vendor/d3' );
3 |
4 | module.exports = function( charts, data, responses, colors ) {
5 | $( '.number.incomplete' ).each( function() {
6 | var $number = $( this );
7 |
8 | var chart = function() {
9 | $number.html( data.incompleteCount );
10 | };
11 |
12 | charts.push( [ chart, this ] );
13 | $('.data-placeholder--box', this).remove();
14 | } );
15 | };
--------------------------------------------------------------------------------
/client/results/graphs/number-percentile.js:
--------------------------------------------------------------------------------
1 | var $ = require( 'jquery' ),
2 | d3 = require( '../../vendor/d3' );
3 |
4 | module.exports = function( charts, data, responses, colors ) {
5 | $( '.number.percentile' ).each( function() {
6 | var $number = $( this );
7 |
8 | var postfix = [ 'th', 'st', 'nd', 'rd' ];
9 |
10 | var chart = function( _, filters ) {
11 | // filter by distance from CEO (d['management_layers'])
12 | var filterData = responses.dimension( function( d ) { return d[ 'management_layers' ]; } ),
13 | filterNum = filterData.group().all().reduce( function( pV, cV ) { return pV + cV.value; }, 0 );
14 |
15 | if( filterNum < data.responses.length || filters.length ) {
16 | $number.addClass( 'is-hidden' );
17 | } else {
18 | $number.removeClass( 'is-hidden' );
19 | }
20 |
21 | filterData.dispose();
22 | };
23 |
24 | $number.addClass( 'is-hidden' );
25 | $.getJSON( '/view/' + data.key + '/percentile.json', function( pct ) {
26 | var percentile = Math.round( pct.percentile ),
27 | lastDigit = parseInt( percentile.toString().slice( -1 ) );
28 |
29 | $number.removeClass( 'is-hidden' ).html( percentile + ( postfix[ lastDigit ] || postfix[ 0 ] ) + ' percentile globally' );
30 | } );
31 |
32 | charts.push( [ chart, this ] );
33 | } );
34 | };
--------------------------------------------------------------------------------
/client/results/graphs/number-responses.js:
--------------------------------------------------------------------------------
1 | var $ = require( 'jquery' ),
2 | d3 = require( '../../vendor/d3' );
3 |
4 | module.exports = function( charts, data, responses, colors ) {
5 | $( '.number.responses' ).each( function() {
6 | var $number = $( this );
7 |
8 | var chart = function() {
9 | // filter by distance from CEO (d['management_layers'])
10 | var filterData = responses.dimension( function( d ) { return d[ 'management_layers' ]; } );
11 | var num = filterData.group().all().reduce( function( pV, cV ) { return pV + cV.value; }, 0 );
12 |
13 | console.log('Responses', num);
14 | $number.html( num );
15 | filterData.dispose();
16 | };
17 |
18 | var filterData = responses.dimension( function( d ) { return d[ 'management_layers' ]; } );
19 | var num = filterData.group().all().reduce( function( pV, cV ) { return pV + cV.value; }, 0 );
20 |
21 | console.log('Filter data:', filterData);
22 | console.log("UPDATING");
23 | $('.ovw-scoring--responses-total').html('of ' + num);
24 |
25 | charts.push( [ chart, this ] );
26 | $('.data-placeholder--box', this).remove();
27 | } );
28 | };
--------------------------------------------------------------------------------
/client/results/graphs/number-score.js:
--------------------------------------------------------------------------------
1 | var $ = require( 'jquery' ),
2 | d3 = require( '../../vendor/d3' );
3 |
4 | module.exports = function( charts, data, responses, colors, calculate ) {
5 | $( '.number.score' ).each( function() {
6 | var $score = $( this );
7 |
8 | var chart = function( _, filters ) {
9 | var sum = 0;
10 |
11 | data.survey.overalls.forEach( function( row, rdx ) {
12 | row.forEach( function( column, cdx ) {
13 | sum += calculate.columnScore( responses, column ) * 100;
14 | } );
15 | } );
16 |
17 | var average = Math.round( sum / ( data.survey.overalls.length * data.survey.columns.length ) );
18 | var oldAverage = $score.data('average');
19 | var diff = Math.round((average - oldAverage) / oldAverage * 100);
20 |
21 | var activeFilterNum = filters && filters.length;
22 |
23 | if( activeFilterNum ) {
24 | $('.ovw-scoring--responses-total').addClass('is-shown');
25 | $('.ovw-scoring--reset').addClass('is-shown');
26 |
27 | if (diff !== 0) {
28 | $('.ovw-scoring--score-diff').addClass('is-shown');
29 | $('.score-diff-num').addClass('is-shown').html(diff + '%');
30 | } else {
31 | $('.ovw-scoring--score-diff').removeClass('is-shown');
32 | }
33 |
34 | if (diff > 0) {
35 | $('.score-diff-up').addClass('is-shown');
36 | $('.score-diff-down').removeClass('is-shown');
37 | $('.score-diff-num').attr('class', 'score-diff-num score-diff-up is-shown');
38 | } else if (diff < 0) {
39 | $('.score-diff-up').removeClass('is-shown');
40 | $('.score-diff-down').addClass('is-shown');
41 | $('.score-diff-num').attr('class', 'score-diff-num score-diff-down is-shown');
42 | } else {
43 | $('.score-diff-up').removeClass('is-shown');
44 | $('.score-diff-down').removeClass('is-shown');
45 | $('.score-diff-num').attr('class', 'score-diff-num');
46 | }
47 | } else {
48 | $('.ovw-scoring--score-diff').removeClass('is-shown');
49 | $('.ovw-scoring--responses-total').removeClass('is-shown');
50 |
51 | $('.score-diff-num').removeClass('is-shown');
52 | $('.ovw-scoring--reset').removeClass('is-shown');
53 | $('.score-diff-up').removeClass('is-shown');
54 | $('.score-diff-down').removeClass('is-shown');
55 | }
56 |
57 | console.log('Diff', diff, activeFilterNum);
58 |
59 | $score.html( average );
60 | $score.data('average', average);
61 | };
62 |
63 | charts.push( [ chart, this ] );
64 | $('.data-placeholder--box', this).remove();
65 | } );
66 | };
67 |
--------------------------------------------------------------------------------
/client/results/graphs/numberline-agreement.js:
--------------------------------------------------------------------------------
1 | var $ = require( 'jquery' ),
2 | d3 = require( '../../vendor/d3' ),
3 | numberline = require( '../../graphs/numberline' );
4 |
5 | module.exports = function( charts, data, responses, colors, calculate ) {
6 | $( '.chart.numberline.agreement' ).each( function() {
7 | var $this = $( this ),
8 | domain = $( this ).data( 'domain' ),
9 | domainIdx,
10 | closest = {
11 | q: null,
12 | leadership: 0,
13 | team: 4
14 | };
15 |
16 | $.each( data.survey.columns, function( idx, col ) {
17 | if( col.title === domain ) {
18 | domainIdx = idx;
19 | }
20 | } );
21 |
22 | data.survey.overalls.forEach( function( row, rdx ) {
23 | // filter by distance from leadership (d['management_layers'])
24 | var leadershipDim = responses
25 | .dimension( function( d ) { return d[ 'management_layers' ]; } )
26 | .filter( function( d ) { return parseInt( d ) <= 2; } ),
27 | leadership = calculate.columnScore( responses, row[ domainIdx ] );
28 | leadershipDim.dispose();
29 |
30 | var teamDim = responses
31 | .dimension( function( d ) { return d[ 'management_layers' ]; } )
32 | .filter( function( d ) { return parseInt( d ) > 2; } ),
33 | team = calculate.columnScore( responses, row[ domainIdx ] );
34 | teamDim.dispose();
35 |
36 | if( Math.abs( leadership - team ) <= Math.abs( closest.leadership - closest.team ) ) {
37 | closest.leadership = leadership;
38 | closest.team = team;
39 | closest.q = row[ domainIdx ];
40 | }
41 | } );
42 |
43 | var closestQuestion;
44 | data.survey.pages.filter( function( page, pdx ) {
45 | return page.fields.filter( function( field, fdx ) {
46 | if( field.type === 'gridselect' ) {
47 | for( var key in field.options ) {
48 | if( key === closest.q ) {
49 | closestQuestion = field.options[ key ];
50 | return true;
51 | }
52 | }
53 |
54 | return false;
55 | }
56 | } );
57 | } );
58 |
59 | var $chartbox = $( '
' );
60 | $this.append( $chartbox );
61 | $this.append( $( '' ).html( closestQuestion ) );
62 |
63 | if( closest.q !== null ) {
64 | var agreementData = [ closest.leadership, closest.team ];
65 | if( Math.abs( agreementData[ 0 ] - agreementData[ 1 ] ) <= 0.01 ) {
66 | agreementData = [ ( agreementData[ 0 ] + agreementData[ 1 ] ) / 2 ];
67 | $( this ).prev( '.legend' ).find( '.agreement' ).addClass( 'show' );
68 | }
69 |
70 | var chart = numberline( $( this ).width(), agreementData );
71 | charts.push( [ chart, $chartbox[ 0 ] ] );
72 | } else {
73 | $('.data-placeholder--err', this).addClass( 'visible' );
74 | }
75 |
76 |
77 | $('.data-placeholder--box', this).remove();
78 | } );
79 | };
80 |
--------------------------------------------------------------------------------
/client/results/graphs/numberline-disagreement.js:
--------------------------------------------------------------------------------
1 | var $ = require( 'jquery' ),
2 | d3 = require( '../../vendor/d3' ),
3 | numberline = require( '../../graphs/numberline' );
4 |
5 | module.exports = function( charts, data, responses, colors, calculate ) {
6 | $( '.chart.numberline.disagreement' ).each( function() {
7 | var $this = $( this ),
8 | domain = $( this ).data( 'domain' ),
9 | domainIdx,
10 | furthest = {
11 | q: null,
12 | leadership: null,
13 | team: null
14 | };
15 |
16 | $.each( data.survey.columns, function( idx, col ) {
17 | if( col.title === domain ) {
18 | domainIdx = idx;
19 | }
20 | } );
21 |
22 | data.survey.overalls.forEach( function( row, rdx ) {
23 | // filter by distance from CEO (d['management_layers'])
24 | var leadershipDim = responses
25 | .dimension( function( d ) { return d[ 'management_layers' ]; } )
26 | .filter( function( d ) { return parseInt( d ) <= 2; } ),
27 | leadership = calculate.columnScore( responses, row[ domainIdx ] );
28 | leadershipDim.dispose();
29 |
30 | var teamDim = responses
31 | .dimension( function( d ) { return d[ 'management_layers' ]; } )
32 | .filter( function( d ) { return parseInt( d ) > 2; } ),
33 | team = calculate.columnScore( responses, row[ domainIdx ] );
34 | teamDim.dispose();
35 |
36 | if( Math.abs( leadership - team ) >= Math.abs( furthest.leadership - furthest.team ) ) {
37 | furthest.leadership = leadership;
38 | furthest.team = team;
39 | furthest.q = row[ domainIdx ];
40 | }
41 | } );
42 |
43 | var furthestQuestion;
44 | data.survey.pages.filter( function( page, pdx ) {
45 | return page.fields.filter( function( field, fdx ) {
46 | if( field.type === 'gridselect' ) {
47 | for( var key in field.options ) {
48 | if( key === furthest.q ) {
49 | furthestQuestion = field.options[ key ];
50 | return true;
51 | }
52 | }
53 |
54 | return false;
55 | }
56 | } );
57 | } );
58 |
59 | var $chartbox = $( '' );
60 | $this.append( $chartbox );
61 | $this.append( $( '' ).html( furthestQuestion ) );
62 |
63 | disagreementData = [ furthest.leadership, furthest.team ];
64 | if( Math.abs( disagreementData[ 0 ] - disagreementData[ 1 ] ) <= 0.01 )
65 | disagreementData = [ ( disagreementData[ 0 ] + disagreementData[ 1 ] ) / 2 ];
66 |
67 |
68 | if( furthest.q !== null ) {
69 | var chart = numberline( $( this ).width(), disagreementData );
70 | charts.push( [ chart, $chartbox[ 0 ] ] );
71 | } else {
72 | $('.data-placeholder--err', this).addClass( 'visible' );
73 | }
74 |
75 | $('.data-placeholder--box', this).remove();
76 | } );
77 | };
78 |
--------------------------------------------------------------------------------
/client/results/graphs/pie.js:
--------------------------------------------------------------------------------
1 | var $ = require( 'jquery' ),
2 | d3 = require( '../../vendor/d3' ),
3 | pie = require( '../../graphs/pie' );
4 |
5 | module.exports = function( charts, data, responses, colors ) {
6 | $( '.chart.pie' ).each( function() {
7 | var width = $( this ).width(),
8 | height = 200,
9 | col = $( this ).data( 'col' ),
10 | subCol = $( this ).data( 'sub-col' ),
11 | pieData = responses.dimension( function( d ) {
12 | return (subCol ? d[ col ][ subCol ] : d[ col ])
13 | } ),
14 | pieData2 = responses.dimension( function( d ) {
15 | return (subCol ? d[ col ][ subCol ] : d[ col ])
16 | } );
17 |
18 | var chart = pie( width, height )
19 | .dimension( pieData )
20 | .altDimension( pieData2 )
21 | .color( graphColorScheme );
22 |
23 | charts.push( [ chart, this ] );
24 | $('.data-placeholder--box', this).remove();
25 | } );
26 | };
27 |
--------------------------------------------------------------------------------
/client/results/graphs/stack.js:
--------------------------------------------------------------------------------
1 | var $ = require( 'jquery' ),
2 | d3 = require( '../../vendor/d3' ),
3 | stack = require( '../../graphs/stack' );
4 |
5 | module.exports = function( charts, data, responses, colors ) {
6 | $( '.chart.stacked' ).each( function() {
7 | var col = $( this ).data( 'col' ),
8 | stackedData = responses.dimension( function( d ) {
9 | return d[ col ];
10 | } ),
11 | stackedData2 = responses.dimension( function( d ) {
12 | return d[ col ];
13 | } ),
14 | stackedDataGroup = stackedData.group();
15 |
16 | var width = $( this ).width(),
17 | height = $( this ).data( 'height' ) || 200;
18 |
19 | var y = d3.scale.ordinal()
20 | .rangeRoundBands([0, height], 0.1, 1)
21 | .domain($.unique(data.responses.map(function(d) { return d[ col ] })).sort(function(a, b) { return parseInt(a) > parseInt(b); }));
22 |
23 | var chart = stack( width, height, data.responses )
24 | .dimension( stackedData )
25 | .altDimension( stackedData2 )
26 | .color( colors[ 'graph-a' ], colors[ 'graph-a' ] )
27 | .y( y );
28 |
29 | charts.push( [ chart, this ] );
30 | $('.data-placeholder--box', this).remove();
31 | } );
32 | };
33 |
--------------------------------------------------------------------------------
/client/results/index.js:
--------------------------------------------------------------------------------
1 | var $ = require( 'jquery' ),
2 | Color = require( 'color' ),
3 | d3 = require( '../vendor/d3' ),
4 | crossfilter = require( 'crossfilter' ),
5 |
6 | Spinner = require( '../lib/spinner' ),
7 | stickyheader = require( '../lib/stickyheader' ),
8 |
9 | graphs = require( './graphs/index' );
10 |
11 | require( '../vendor/jquery.tooltipster.min' );
12 |
13 | var renderers = {
14 | 'original': require( './renderers/original' ),
15 | 'even-over': require( './renderers/even-over' )
16 | };
17 |
18 | var spinner = new Spinner(),
19 | firstRender = true,
20 | charts = [],
21 | activeFilters = [];
22 |
23 | // Renders the specified chart or list.
24 | function render( d ) {
25 | if( d )
26 | d3.select( d[ 1 ] ).call( d[ 0 ], activeFilters );
27 | }
28 |
29 | exports.init = function() {
30 | if ($(window).width() > 640) {
31 | $('.sticky-nav').stick_in_parent({recalc_every: 50});
32 | $('.sticky-header').stick_in_parent({offset_top: 175});
33 | }
34 |
35 | spinner.loading( 'survey' );
36 | d3.json( '/view/' + survey + '/results.json', function( data ) {
37 | spinner.done( 'survey' );
38 |
39 | if( data.responses.length === 0 ) return;
40 |
41 | var survey = data.survey,
42 | responses = crossfilter( data.responses );
43 |
44 | var colors = {};
45 |
46 | // Graphing colors
47 | [
48 | 'green',
49 | 'red',
50 | 'graph-a',
51 | 'graph-b',
52 | 'blue'
53 | ].forEach( function( color ) {
54 | var obj = $( '' ).addClass( 'color-' + color.toLowerCase() ).appendTo( 'body' );
55 | colors[ color ] = Color( obj.css( 'color' ) );
56 | obj.remove();
57 | } );
58 |
59 | // Graphing color(s)
60 | var graphColorScheme = [
61 | colors[ 'graph-a' ],
62 | colors[ 'graph-b' ]
63 | ];
64 |
65 | // Initialize the appropriate renderer
66 | renderers[ survey.render ].init( data, responses, spinner, colors );
67 |
68 | function resetAll() {
69 | // Don't hate me.
70 | $('.reset').click();
71 | $('.ovw-scoring--responses-total').removeClass('is-shown');
72 | }
73 |
74 | $('.ovw-scoring--reset').on( 'click', function( evt ) {
75 | resetAll();
76 | return false;
77 | } );
78 |
79 | function renderAll() {
80 | charts.forEach( render );
81 | renderers[ survey.render ].render( activeFilters );
82 |
83 | $('body').trigger('sticky_kit:recalc');
84 | console.log('Completed render all');
85 | }
86 |
87 | $( 'body' ).on( 'filter', '.chart, .table', function( evt, data ) {
88 | console.log( 'Filter', data );
89 |
90 | if( data.value ) {
91 | var found = false;
92 | for( var i = 0; i < activeFilters.length; i++ ) {
93 | if( activeFilters[ i ].title === data.title ) {
94 | activeFilters[ i ].value = data.value;
95 | found = true;
96 | break;
97 | }
98 | }
99 |
100 | if( !found ) {
101 | activeFilters.push( data );
102 | }
103 | } else {
104 | var found = false;
105 | for( var i = 0; i < activeFilters.length; i++ ) {
106 | if( activeFilters[ i ].title === data.title ) {
107 | found = i;
108 | break;
109 | }
110 | }
111 |
112 | if( found !== false ) {
113 | activeFilters.splice( i, 1 );
114 | }
115 | }
116 |
117 | renderAll();
118 | } );
119 |
120 | [
121 | 'stack',
122 | 'pie',
123 | 'matrixplot',
124 | 'bar',
125 | 'line',
126 | 'numberResponses',
127 | 'numberIncomplete',
128 | 'numberPercentile'
129 | ].forEach( function( gr ) {
130 | graphs[ gr ]( charts, data, responses, colors );
131 | } );
132 |
133 | renderAll();
134 | } );
135 | };
136 |
--------------------------------------------------------------------------------
/client/shared.js:
--------------------------------------------------------------------------------
1 | // Fixes an issue with jquery.tooltipster not being AMD or CommonJS
2 | var $ = require( 'jquery' );
3 | global.jQuery = $;
4 |
5 | var cookie = require( './lib/cookie' );
6 | var nav = require( './nav' );
7 |
8 | if( !cookie.cookiesEnabled() ) {
9 | document.getElementById( 'cookies' ).className = 'cookies--not-enabled';
10 | }
11 |
--------------------------------------------------------------------------------
/client/vendor/stupidtable.min.js:
--------------------------------------------------------------------------------
1 | (function(d){d.fn.stupidtable=function(b){return this.each(function(){var a=d(this);b=b||{};b=d.extend({},d.fn.stupidtable.default_sort_fns,b);a.on("click.stupidtable","thead th",function(){var c=d(this),f=0,g=d.fn.stupidtable.dir;c.parents("tr").find("th").slice(0,c.index()).each(function(){var a=d(this).attr("colspan")||1;f+=parseInt(a,10)});var e=c.data("sort-default")||g.ASC;c.data("sort-dir")&&(e=c.data("sort-dir")===g.ASC?g.DESC:g.ASC);var l=c.data("sort")||null;null!==l&&(a.trigger("beforetablesort", {column:f,direction:e}),a.css("display"),setTimeout(function(){var h=[],m=b[l],k=a.children("tbody").children("tr");k.each(function(a,b){var c=d(b).children().eq(f),e=c.data("sort-value"),c="undefined"!==typeof e?e:c.text();h.push([c,b])});h.sort(function(a,b){return m(a[0],b[0])});e!=g.ASC&&h.reverse();k=d.map(h,function(a){return a[1]});a.children("tbody").append(k);a.find("th").data("sort-dir",null).removeClass("sorting-desc sorting-asc");c.data("sort-dir",e).addClass("sorting-"+e);a.trigger("aftertablesort", {column:f,direction:e});a.css("display")},10))})})};d.fn.stupidtable.dir={ASC:"asc",DESC:"desc"};d.fn.stupidtable.default_sort_fns={"int":function(b,a){return parseInt(b,10)-parseInt(a,10)},"float":function(b,a){return parseFloat(b)-parseFloat(a)},string:function(b,a){return b
a?1:0},"string-ins":function(b,a){b=b.toLowerCase();a=a.toLowerCase();return ba?1:0}}})(jQuery);
2 |
--------------------------------------------------------------------------------
/config.js:
--------------------------------------------------------------------------------
1 | // Set the current environment to true in the env object
2 | var currentEnv = process.env.NODE_ENV || 'development';
3 |
4 | // Import local environment variables from config/local.js
5 | var local = require('./config/local');
6 |
7 | exports.appName = 'ucros';
8 |
9 | exports.env = {
10 | production: false,
11 | staging: false,
12 | test: false,
13 | development: false
14 | };
15 |
16 | exports.env[ currentEnv ] = true;
17 |
18 | exports.log = {
19 | path: __dirname + '/var/log/app_#{currentEnv}.log'
20 | };
21 |
22 | exports.server = {
23 | port: process.env.PORT || 4600,
24 | // In staging and production, listen loopback. nginx listens on the network.
25 | ip: '127.0.0.1'
26 | };
27 |
28 | if( currentEnv != 'production' && currentEnv != 'staging' ) {
29 | exports.enableTests = true;
30 |
31 | // Listen on all IPs in dev/test (for testing from other machines)
32 | exports.server.ip = '0.0.0.0';
33 | }
34 |
35 | exports.db = {
36 | ip: '127.0.0.1',
37 | port: 27017,
38 | database: exports.appName + '_' + currentEnv
39 | };
40 | exports.db.url = 'mongodb://' + exports.db.ip + ':' + exports.db.port + '/' + exports.db.database;
41 |
42 | exports.auth = local.auth;
43 |
44 | exports.session = local.session;
45 |
46 | if( currentEnv === 'production' ) {
47 | exports.baseUrl = local.url.production;
48 | } else if( currentEnv === 'staging' ) {
49 | exports.baseUrl = local.url.staging;
50 | } else {
51 | exports.baseUrl = 'http://' + exports.server.ip + ':' + exports.server.port;
52 | }
53 |
54 | exports.surveyVersion = "20150331";
55 |
56 | exports.email = local.email;
57 |
--------------------------------------------------------------------------------
/config.rb:
--------------------------------------------------------------------------------
1 | require 'compass/import-once/activate'
2 | # Require any additional compass plugins here.
3 |
4 | require 'breakpoint'
5 | require 'susy'
6 |
7 | # Set this to the root of your project when deployed:
8 | http_path = "/"
9 | css_dir = "public/css"
10 | sass_dir = "sass"
11 | images_dir = "public/img"
12 | javascripts_dir = "public/js"
13 |
14 | # You can select your preferred output style here (can be overridden via the command line):
15 | # output_style = :expanded or :nested or :compact or :compressed
16 |
17 | # To enable relative paths to assets via compass helper functions. Uncomment:
18 | # relative_assets = true
19 |
20 | # To disable debugging comments that display the original location of your selectors. Uncomment:
21 | # line_comments = false
22 |
23 |
24 | # If you prefer the indented syntax, you might want to regenerate this
25 | # project again passing --syntax sass, or you can uncomment this:
26 | # preferred_syntax = :sass
27 | # and then run:
28 | # sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass
29 |
--------------------------------------------------------------------------------
/controllers/about.js:
--------------------------------------------------------------------------------
1 | var AboutController = function() {
2 | this.index = function( req, res ) {
3 | res.render( 'landing' );
4 | };
5 |
6 | this.terms = function( req, res ) {
7 | res.render( 'terms' );
8 | };
9 | };
10 |
11 | exports.setup = function( app ) {
12 | var about = new AboutController();
13 |
14 | app.get( '/about', about.index );
15 | app.get( '/terms', about.terms );
16 | };
17 |
--------------------------------------------------------------------------------
/controllers/admin.js:
--------------------------------------------------------------------------------
1 | var User = require( '../models/user' ),
2 | Survey = require( '../models/survey' ),
3 | SurveyResponse = require( '../models/survey_response' ),
4 | auth = require( './auth' );
5 |
6 | var AdminController = function() {
7 | this.users = function( req, res ) {
8 | var o = {};
9 | o.map = function() { emit( 'score', this.score ); };
10 | o.reduce = function( k, scores ) {
11 | var sum = 0, count = 0;
12 | for( var i = 0; i < scores.length; i++ ) {
13 | if( scores[ i ] > 0 ) {
14 | sum += scores[ i ];
15 | count++;
16 | }
17 | };
18 | return sum / count;
19 | }
20 |
21 | Survey.mapReduce( o, function( err, results ) {
22 | User.find( {}, function( err, users ) {
23 | SurveyResponse.count( {}, function( err, count ) {
24 | var totals = {
25 | users: users.length,
26 | responses: count,
27 | score: results[ 0 ].value
28 | };
29 |
30 | res.render( 'admin/users', {
31 | userList: users,
32 | totals: totals
33 | } );
34 | } );
35 | } );
36 | } );
37 | };
38 |
39 | this.viewUser = function( req, res ) {
40 | User.findOne( { _id: req.param( 'id' ) }, function( err, usr ) {
41 | res.render( 'admin/account', {
42 | accountUser: usr,
43 | csrfToken: req.csrfToken()
44 | } );
45 | } );
46 | };
47 |
48 | this.updateUser = function( req, res ) {
49 | User.findOne( { _id: req.param( 'id' ) }, function( err, usr ) {
50 | auth.updateUser( usr, req.body, function( status ) {
51 | usr.activated = parseInt( req.body.activated ) === 1 ? true : false;
52 |
53 | if( req.body.roles && !usr.hasRole( req.body.roles ) && usr.validRole( req.body.roles ) ) {
54 | usr.roles = [ req.body.roles ];
55 | }
56 |
57 | usr.save();
58 |
59 | res.send( status );
60 | } );
61 | } );
62 | };
63 | };
64 |
65 | exports.setup = function( app ) {
66 | var admin = new AdminController();
67 |
68 | app.get( '/admin/users', auth.requiresRole( 'admin' ), admin.users );
69 |
70 | app.get( '/admin/user/:id', auth.requiresRole( 'admin' ), admin.viewUser );
71 | app.post( '/admin/user/:id', auth.requiresRole( 'admin' ), admin.updateUser );
72 | };
73 |
--------------------------------------------------------------------------------
/controllers/export.js:
--------------------------------------------------------------------------------
1 | var auth = require( './auth' ),
2 | surveyTemplate = require( '../lib/survey_template' ),
3 | SurveyResponse = require( '../models/survey_response' );
4 |
5 | var ExportController = function() {
6 | this.csv = function( req, res ) {
7 | var now = new Date(),
8 | cleanTitle = req.survey.title.replace( /[^a-z0-9]/ig, '' ),
9 | key = req.param( 'key' ),
10 | today = now.getFullYear() + '_' + ( now.getMonth() + 1 ) + '_' + now.getDate(),
11 | flatSurvey = [],
12 | csvData = [],
13 | survey = surveyTemplate.getSurvey( req.survey.version );
14 |
15 | survey.pages.forEach( function( page ) {
16 | page.fields.forEach( function( q ) {
17 | if( q.type === 'gridselect' ) {
18 | for( var key in q.options ) {
19 | flatSurvey.push( { name: key, label: q.options[ key ] } );
20 | }
21 | } else {
22 | flatSurvey.push( { name: q.name, label: q.label } );
23 | }
24 | } );
25 | } );
26 |
27 | csvData.push( flatSurvey.map( function( q ) { return q.label; } ) );
28 |
29 | SurveyResponse.find( { surveyId: req.survey.id }, function( err, responses ) {
30 | if( responses && responses.length ) {
31 | responses.forEach( function( response ) {
32 | csvData.push( flatSurvey.map( function( q ) {
33 | try {
34 | return response.response[ q.name ];
35 | } catch( e ) {
36 | return '';
37 | }
38 | } ) );
39 | } );
40 | }
41 |
42 | res.header('Content-Disposition', 'attachment; filename=' + today + '_' + cleanTitle + '.csv');
43 | res.csv( csvData );
44 | } );
45 | };
46 | };
47 |
48 | exports.setup = function( app ) {
49 | var exporter = new ExportController();
50 |
51 | app.get( '/export/:key', auth.canAccessSurvey, exporter.csv );
52 | };
53 |
--------------------------------------------------------------------------------
/controllers/home.js:
--------------------------------------------------------------------------------
1 | var HomeController = function() {
2 | this.index = function( req, res ) {
3 | if( req.isAuthenticated() )
4 | return res.redirect( '/dashboard' );
5 |
6 | res.render( 'landing' );
7 | };
8 | };
9 |
10 | exports.setup = function( app ) {
11 | var home = new HomeController();
12 |
13 | app.get( '/', home.index );
14 | };
15 |
--------------------------------------------------------------------------------
/controllers/overview.js:
--------------------------------------------------------------------------------
1 | var fs = require( 'fs' ),
2 | auth = require( './auth' ),
3 | Survey = require( '../models/survey' ),
4 | surveyTemplate = require( '../lib/survey_template' );
5 |
6 | var OverviewController = function() {
7 | this.index = function( req, res ) {
8 | if( !req.survey ) {
9 | return res.redirect( '/404' );
10 | }
11 |
12 | res.locals.classes.push( 'overview' );
13 |
14 | var template = surveyTemplate.getSurvey( req.survey.version );
15 |
16 | res.render( 'survey/' + template.render + '/overview', {
17 | survey: req.survey,
18 | validPermissions: Survey.validPermissions(),
19 | csrfToken: req.csrfToken()
20 | } );
21 | };
22 |
23 | this.detail = function( req, res ) {
24 | var template = surveyTemplate.getSurvey( req.survey.version );
25 |
26 | fs.exists( __dirname + '/../views/survey/' + template.render + '/detail.jade', function( exists ) {
27 | if( exists ) {
28 | res.render( 'survey/' + template.render + '/detail', {
29 | survey: req.survey,
30 | validPermissions: Survey.validPermissions(),
31 | csrfToken: req.csrfToken()
32 | } );
33 | } else {
34 | // if no scores template exists, head back to the overview
35 | res.redirect( '/view/' + req.param( 'key' ) );
36 | }
37 | } );
38 | };
39 |
40 | this.domain = function( req, res ) {
41 | var template = surveyTemplate.getSurvey( req.survey.version ),
42 | domain = req.param( 'domain' );
43 | domain = domain[ 0 ].toUpperCase() + domain.slice( 1 );
44 |
45 | fs.exists( __dirname + '/../views/survey/' + template.render + '/scores.jade', function( exists ) {
46 | if( exists ) {
47 | res.render( 'survey/' + template.render + '/scores', {
48 | survey: req.survey,
49 | domain: domain,
50 | validPermissions: Survey.validPermissions(),
51 | csrfToken: req.csrfToken()
52 | } );
53 | } else {
54 | // if no scores template exists, head back to the overview
55 | res.redirect( '/view/' + req.param( 'key' ) );
56 | }
57 | } );
58 | };
59 | };
60 |
61 | exports.setup = function( app ) {
62 | var overview = new OverviewController();
63 |
64 | app.get( '/view/:key', auth.canAccessSurvey, overview.index );
65 | app.get( '/view/:key/detail', auth.canAccessSurvey, overview.detail );
66 | app.get( '/view/:key/scores/:domain', auth.canAccessSurvey, overview.domain );
67 | };
68 |
--------------------------------------------------------------------------------
/controllers/survey_data.js:
--------------------------------------------------------------------------------
1 | var extend = require( 'util' )._extend,
2 | request = require( 'request' ),
3 | auth = require( './auth' ),
4 | config = require( '../config.js' ),
5 | surveyTemplate = require( '../lib/survey_template' ),
6 | Survey = require( '../models/survey' ),
7 | SurveyResponse = require( '../models/survey_response' );
8 |
9 | var SurveyDataController = function() {
10 | this.data = function( req, res ) {
11 | var key = req.params.key,
12 | dummy = false;
13 |
14 | Survey.findOne( { key: key } ).exec( function( err, svy ) {
15 | if( !svy ) {
16 | dummy = true;
17 | svy = new Survey( {
18 | key: key,
19 | responses: [],
20 | responseCount: 0
21 | } );
22 | }
23 |
24 | var data = svy.toObject(),
25 | send = function() {
26 | // Remove list of shared users from object sent to user
27 | delete data.users;
28 |
29 | res.send( data );
30 | };
31 |
32 | // Add the survey template
33 | data.survey = surveyTemplate.getSurvey( svy.version );
34 |
35 | // Incomplete survey count
36 | data.incompleteCount = 0;
37 |
38 | if( !dummy ) {
39 | SurveyResponse.find( { surveyId: svy.id }, function( err, responses ) {
40 | data.responses = responses.filter( function( resp ) { return resp.submitted; } )
41 | .map( function( resp ) { return resp.response; } );
42 | data.incompleteCount = responses.filter( function( resp ) { return !resp.submitted; } ).length;
43 |
44 | send();
45 | } );
46 | } else {
47 | send();
48 | }
49 | } );
50 | };
51 |
52 | this.percentile = function( req, res ) {
53 | Survey.find( { responseCount: { $gt: 0 } }, { score: 1, key: 1 } ).exec( function( err, surveys ) {
54 | // Get the score for the current survey
55 | var keySurvey = surveys.filter( function( s ) { return s.key === req.params.key; } )[ 0 ],
56 | surveyCount = surveys.length;
57 | // Get a count of the number of surveys with scores less than this one
58 | var surveysBelow = surveys.filter( function( s ) {
59 | return s.score < keySurvey.score;
60 | } );
61 |
62 | // Get a count of the number of surveys with scores equal to this one
63 | var surveysEqual = surveys.filter( function( s ) {
64 | return s.score === keySurvey.score;
65 | } );
66 |
67 | /**
68 | * ( ( B + 0.5E ) / n ) * 100 = Percentile
69 | * Where:
70 | * B = number of scores below x
71 | * E = number of scores equal to x
72 | * n = number of scores
73 | * @type {Number}
74 | */
75 | res.send( { percentile: ( ( surveysBelow.length + ( 0.5 * surveysEqual.length ) ) / surveyCount ) * 100 } );
76 | } );
77 | };
78 | };
79 |
80 | exports.setup = function( app ) {
81 | var survey = new SurveyDataController();
82 |
83 | app.get( '/view/:key/results.json', auth.canAccessSurvey, survey.data );
84 | app.get( '/view/:key/percentile.json', auth.canAccessSurvey, survey.percentile );
85 | };
86 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var path = require( 'path' ),
2 | gulp = require( 'gulp' ),
3 | loadPlugins = require( 'gulp-load-plugins' ),
4 | includeAll = require( 'include-all' );
5 |
6 | var plugins = loadPlugins( {
7 | pattern: [ 'gulp-*', 'merge-*', 'run-*', 'main-*' ], // the glob to search for
8 | replaceString: /\bgulp[\-.]|run[\-.]|merge[\-.]|main[\-.]/, // remove from the name of the module when adding it to the context
9 | camelizePluginName: true,
10 | lazy: true // lazy-load plugins on demand
11 | } );
12 |
13 | /**
14 | * Loads Gulp configuration modules from the specified
15 | * relative path. These modules should export a function
16 | * that, when run, should either load/configure or register
17 | * a Gulp task.
18 | */
19 | var loadTasks = function( relPath ) {
20 | return includeAll( {
21 | dirname: path.resolve( __dirname, relPath ),
22 | filter: /(.+)\.js$/
23 | } ) || {};
24 | };
25 |
26 | var setupTasks = function( tasks ) {
27 | for( var taskName in tasks ) {
28 | if( tasks.hasOwnProperty( taskName ) ) {
29 | tasks[ taskName ]( gulp, plugins, path );
30 | }
31 | }
32 | };
33 |
34 | var tasks = loadTasks( './tasks' );
35 | setupTasks( tasks );
36 |
--------------------------------------------------------------------------------
/lib/calculations/even-over.js:
--------------------------------------------------------------------------------
1 | // Calculate an individual column score
2 | exports.columnScore = function( responses, column, filter ) {
3 | var values = responses.dimension( function( d ) { return d[ column ]; } ),
4 | numResponses = 0;
5 |
6 | if( filter )
7 | values.filter( d );
8 |
9 | var valueResults = values.group().all(),
10 | valueResultsByKey = {};
11 | valueResults.forEach( function( vr ) {
12 | if( vr.key !== -1 ) {
13 | valueResultsByKey[ vr.key ] = vr.value;
14 | numResponses += vr.value;
15 | }
16 | } );
17 |
18 | // Make room for more dimensions
19 | values.dispose();
20 |
21 | return (
22 | ( valueResultsByKey[ 1 ] || 0 ) +
23 | ( ( valueResultsByKey[ 2 ] || 0 ) * 2 ) +
24 | ( ( valueResultsByKey[ 3 ] || 0 ) * 3 ) +
25 | ( ( valueResultsByKey[ 4 ] || 0 ) * 4 )
26 | ) / ( Math.max( numResponses, 1 ) * 4 );
27 | };
28 |
29 | // Calculate overall scores
30 | exports.scores = function( data, responses ) {
31 | var columnTotal = [],
32 | overallScores = [];
33 |
34 | data.survey.overalls.forEach( function( row, rdx ) {
35 | var rowTotal = 0,
36 | rowData = [];
37 |
38 | row.forEach( function( column, cdx ) {
39 | var total = exports.columnScore( responses, column ),
40 | percentage = Math.round( total * 100 );
41 |
42 | // Tabulate row total
43 | rowTotal += total;
44 | rowData.push( total );
45 |
46 | // Tabulate column total
47 | if( !columnTotal[ cdx ] )
48 | columnTotal[ cdx ] = total;
49 | else
50 | columnTotal[ cdx ] += total;
51 | } );
52 |
53 | // Add row average
54 | rowTotal /= row.length;
55 | rowData.push( rowTotal );
56 |
57 | overallScores.push( rowData );
58 | } );
59 |
60 | columnTotal = columnTotal.map( function( a ) { return a / overallScores.length; } );
61 | columnTotal.push( columnTotal.reduce( function( a, b ) { return a + b; } ) / data.survey.columns.length );
62 |
63 | return columnTotal;
64 | };
65 |
66 | // Calculate scores and only return the total average
67 | exports.totalAverage = function( data, responses ) {
68 | var scores = exports.scores( data, responses );
69 |
70 | return scores[ scores.length - 1 ];
71 | };
72 |
--------------------------------------------------------------------------------
/lib/calculations/original.js:
--------------------------------------------------------------------------------
1 | // Calculate an individual column score
2 | exports.columnScore = function( responses, column, filter ) {
3 | var values = responses.dimension( function( d ) { return d[ column ]; } ),
4 | numResponses = 0;
5 |
6 | if( filter )
7 | values.filter( d );
8 |
9 | var valueResults = values.group().all(),
10 | valueResultsByKey = {};
11 | valueResults.forEach( function( vr ) {
12 | if( vr.key !== -1 ) {
13 | valueResultsByKey[ vr.key ] = vr.value;
14 | numResponses += vr.value;
15 | }
16 | } );
17 |
18 | // Make room for more dimensions
19 | values.dispose();
20 |
21 | return (
22 | ( valueResultsByKey[ 1 ] || 0 ) +
23 | ( ( valueResultsByKey[ 2 ] || 0 ) * 2 ) +
24 | ( ( valueResultsByKey[ 3 ] || 0 ) * 3 ) +
25 | ( ( valueResultsByKey[ 4 ] || 0 ) * 4 )
26 | ) / ( numResponses * 4 );
27 | };
28 |
29 | // Calculate overall scores
30 | exports.scores = function( data, responses ) {
31 | var columnTotal = [],
32 | overallScores = [];
33 |
34 | data.survey.overalls.forEach( function( row, rdx ) {
35 | var rowTotal = 0,
36 | rowData = [];
37 |
38 | row.forEach( function( column, cdx ) {
39 | var total = exports.columnScore( responses, column ),
40 | percentage = Math.round( total * 100 );
41 |
42 | // Tabulate row total
43 | rowTotal += total;
44 | rowData.push( total );
45 |
46 | // Tabulate column total
47 | if( !columnTotal[ cdx ] )
48 | columnTotal[ cdx ] = total;
49 | else
50 | columnTotal[ cdx ] += total;
51 | } );
52 |
53 | // Add row average
54 | rowTotal /= row.length;
55 | rowData.push( rowTotal );
56 |
57 | overallScores.push( rowData );
58 | } );
59 |
60 | columnTotal = columnTotal.map( function( a ) { return a / data.survey.rows.length; } );
61 | columnTotal.push( columnTotal.reduce( function( a, b ) { return a + b; } ) / data.survey.columns.length );
62 |
63 | overallScores.push( columnTotal );
64 |
65 | return overallScores;
66 | };
67 |
68 | // Calculate scores and only return the total average
69 | exports.totalAverage = function( data, responses ) {
70 | var scores = exports.scores( data, responses ),
71 | averagesRow = scores[ scores.length - 1 ];
72 |
73 | return averagesRow[ averagesRow.length - 1 ];
74 | };
75 |
--------------------------------------------------------------------------------
/lib/email_hash.js:
--------------------------------------------------------------------------------
1 | var crypto = require( 'crypto' );
2 |
3 | module.exports = function() {
4 | return function( req, res, next ) {
5 | // returns the current user's email address as an md5 hash
6 | res.locals.emailHash = function() {
7 | if( req.user ) {
8 | var md5 = crypto.createHash( 'md5' );
9 | md5.update( req.user.email.toLowerCase().trim() );
10 |
11 | return md5.digest( 'hex' );
12 | }
13 |
14 | return '';
15 | };
16 |
17 | next();
18 | };
19 | };
20 |
--------------------------------------------------------------------------------
/lib/random.js:
--------------------------------------------------------------------------------
1 | exports.defaultChars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
2 | exports.string = function( length, chars ) {
3 | var result = '';
4 | for( var i = length; i > 0; --i ) {
5 | result += chars[Math.round(Math.random() * (chars.length - 1))];
6 | }
7 | return result;
8 | };
9 |
10 | exports.seededNumber = function( seed, max, min ) {
11 | max = max || 1;
12 | min = min || 0;
13 |
14 | seed = ( seed * 9301 + 49297 ) % 233280;
15 | var rnd = seed / 233280;
16 |
17 | return {
18 | nextSeed: seed,
19 | value: min + rnd * (max - min)
20 | };
21 | };
22 |
--------------------------------------------------------------------------------
/lib/route_class.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | return function( req, res, next ) {
3 | var classes = req.path.toLowerCase()
4 | .replace( /^\/([^\/]+)\/?$/, '$1' ).trim()
5 | .replace( /[^A-Za-z0-9_\-\/]+/g, '-' )
6 | .replace( /[-]{2,}/g, '-' )
7 | .split( '/' );
8 |
9 | res.locals.classes = classes
10 | .filter( function( cls ) {
11 | return cls.length > 1;
12 | } )
13 | .map( function( cls ) {
14 | if( cls.match( /^\d/ ) )
15 | return '_' + cls;
16 |
17 | return cls;
18 | } );
19 |
20 | // usage:
21 | // is(['/view', 'active']) -> returns: 'active'
22 | // is(['/view', 'active'], [/toggle$/, 'thing']) -> if url contains /view and ends with toggle returns: 'active thing'
23 | res.locals.is = function() {
24 | var classes = [];
25 | Array.prototype.slice.call( arguments, 0 ).forEach( function( itm ) {
26 | if( typeof itm[ 0 ] === 'object' )
27 | var rxitm = itm[ 0 ];
28 | else
29 | var rxitm = new RegExp( itm[ 0 ].replace( /\//g, '\\/' ).replace( /([.?+*^$()|])/g, '[$1]' ), 'i' );
30 | if( req.path.match( rxitm ) )
31 | classes.push( itm[ 1 ] );
32 | } );
33 |
34 | return classes.join( ' ' );
35 | };
36 |
37 | next();
38 | };
39 | };
40 |
--------------------------------------------------------------------------------
/lib/survey_template.js:
--------------------------------------------------------------------------------
1 | var fs = require( 'fs' ),
2 | config = require( '../config' ),
3 | surveys = {};
4 |
5 | exports.requiredFields = [
6 | 'org_years',
7 | 'management_layers',
8 | 'org_position',
9 | 'org_traction',
10 | 'team'
11 | ];
12 |
13 | exports.getCurrentSurvey = function() {
14 | return exports.getSurvey( config.surveyVersion );
15 | };
16 |
17 | exports.getSurvey = function( id ) {
18 | if( !surveys[ id ] ) {
19 | try {
20 | surveys[ id ] = JSON.parse(
21 | fs.readFileSync( __dirname + '/../surveys/' + id + '.json', 'utf-8' )
22 | );
23 | } catch( err ) {
24 | console.warn( 'Unable to read survey template "' + id + '":', err );
25 | return {};
26 | }
27 | }
28 |
29 | return surveys[ id ];
30 | };
31 |
32 | exports.validateCurrentSurvey = function() {
33 | var fieldError = [],
34 | requiredFields = exports.requiredFields.slice(),
35 | survey = exports.getCurrentSurvey();
36 |
37 | survey.pages.forEach( function( page ) {
38 | page.fields.forEach( function( field ) {
39 | if( !field[ 'type' ] ) {
40 | fieldError.push( 'Field "' + field[ 'name' ] + '" missing required "type" attribute' );
41 | } else if( field.type !== 'gridselect' && !field[ 'name' ] ) {
42 | console.warn( '[Warning] Field in survey missing name in survey version', config.surveyVersion, '\n', field );
43 | } else if( requiredFields.indexOf( field.name ) > -1 ) {
44 | requiredFields.splice( requiredFields.indexOf( field.name ), 1 );
45 | } else if( field.type === "gridselect" ) {
46 | for( var key in field.options ) {
47 | if( requiredFields.indexOf( key ) > -1 ) {
48 | requiredFields.splice( requiredFields.indexOf( key ), 1 );
49 | }
50 | }
51 | }
52 | } );
53 | } );
54 |
55 | if( fieldError.length ) {
56 | fieldError.forEach( function( err ) {
57 | console.error( '[Error]', err, 'in survey version', config.surveyVersion );
58 | } );
59 | }
60 |
61 | if( requiredFields.length ) {
62 | console.error(
63 | '[Error] Missing required survey field' + ( requiredFields.length > 1 ? 's' : '' ),
64 | '"' + requiredFields.join( '", "' ) + '"',
65 | 'in survey version',
66 | config.surveyVersion
67 | );
68 | }
69 |
70 | return ( !fieldError.length && !requiredFields.length ) ? true : false;
71 | };
72 |
--------------------------------------------------------------------------------
/migrations/001-add-users.js:
--------------------------------------------------------------------------------
1 | var mongoose = require( 'mongoose' ),
2 | User = require( '../models/user' ),
3 | config = require( '../config' );
4 |
5 | // Connect to the MongoDB database
6 | mongoose.connect( config.db.url );
7 |
8 | exports.up = function(next){
9 | // Seed a user
10 | var user = new User( {
11 | email: 'global@test.io',
12 | password: '12345678',
13 | roles: [ 'admin' ]
14 | } );
15 |
16 | user.save( function(err) {
17 | if( err ) {
18 | console.log( err );
19 | } else {
20 | console.log( 'user: ' + user.email + ' saved.' );
21 | }
22 |
23 | next();
24 | } );
25 | };
26 |
27 | exports.down = function(next){
28 | User.findOneAndRemove( { email: 'global@test.io' }, function() {
29 | next()
30 | } );
31 | };
32 |
--------------------------------------------------------------------------------
/migrations/002-expiring-mongo-sessions.js:
--------------------------------------------------------------------------------
1 | var mongoose = require( 'mongoose' ),
2 | config = require( '../config' );
3 |
4 | // Connect to the MongoDB database
5 | var db = mongoose.createConnection( config.db.url );
6 |
7 | exports.up = function(next){
8 | // expire sessions after 2 years
9 | db.collection( 'sessions' ).ensureIndex(
10 | { "lastAccess": 1 },
11 | { expireAfterSeconds: 1000 * 60 * 60 * 24 * 365 * 2 },
12 | function() { next(); }
13 | );
14 | };
15 |
16 | exports.down = function(next){
17 | db.collection( 'sessions' ).dropIndex(
18 | { "lastAccess": 1 },
19 | {},
20 | function() { next(); }
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/migrations/003-elevate-aggregate-score.js:
--------------------------------------------------------------------------------
1 | var mongoose = require( 'mongoose' ),
2 | config = require( '../config' ),
3 | SurveyResponse = require( '../models/survey_response' ),
4 | Survey = require( '../models/survey' );
5 |
6 | // Connect to the MongoDB database
7 | var db = mongoose.createConnection( config.db.url );
8 |
9 | exports.up = function(next){
10 | // recalculate aggregate score for each survey
11 | Survey.find( {}, function( err, surveys ) {
12 | function pop() {
13 | var svy = surveys.pop();
14 | if (svy !== undefined && svy !== null) {
15 | if(!svy.orgLeaderRole || svy.orgLeaderRole === '') {
16 | svy.orgLeaderRole = 'Unknown';
17 | svy.orgLeader = 'Unknown';
18 | svy.industry = 'Unknown';
19 | svy.orgAge = 0;
20 | svy.orgSize = 'Not known';
21 | }
22 | svy.save( function( err ) {
23 | Survey.updateScore( svy._id, function() {
24 | if( surveys.length ) {
25 | process.nextTick( pop );
26 | } else {
27 | next();
28 | }
29 | } );
30 | } );
31 | } else {
32 | if( surveys.length ) {
33 | process.nextTick( pop );
34 | } else {
35 | next();
36 | }
37 | }
38 | };
39 |
40 | pop();
41 | } );
42 | };
43 |
44 | exports.down = function(next){
45 | next();
46 | };
47 |
--------------------------------------------------------------------------------
/migrations/004-survey-creators.js:
--------------------------------------------------------------------------------
1 | var mongoose = require( 'mongoose' ),
2 | config = require( '../config' ),
3 | SurveyResponse = require( '../models/survey_response' ),
4 | Survey = require( '../models/survey' );
5 |
6 | // Connect to the MongoDB database
7 | var db = mongoose.createConnection( config.db.url );
8 |
9 | exports.up = function(next){
10 | // recalculate aggregate score for each survey
11 | Survey.find( {}, function( err, surveys ) {
12 | function pop() {
13 | var svy = surveys.pop();
14 | if (svy !== undefined && svy !== null) {
15 | if( !svy.creator && svy.users.length ) {
16 | var creator = svy.users.filter( function( user ) {
17 | return user.permissions.indexOf( 'owner' );
18 | } )[ 0 ];
19 |
20 | if( creator ) {
21 | svy.creator = creator.user;
22 | }
23 | }
24 | svy.save( function( err ) {
25 | if( surveys.length ) {
26 | process.nextTick( pop );
27 | } else {
28 | next();
29 | }
30 | } );
31 | } else {
32 | if( surveys.length ) {
33 | process.nextTick( pop );
34 | } else {
35 | next();
36 | }
37 | }
38 | };
39 |
40 | pop();
41 | } );
42 | };
43 |
44 | exports.down = function(next){
45 | next();
46 | };
47 |
--------------------------------------------------------------------------------
/models/forgot_key.js:
--------------------------------------------------------------------------------
1 | var mongoose = require( 'mongoose' ),
2 | random = require( '../lib/random' ),
3 | Schema = mongoose.Schema;
4 |
5 | var forgotKeySchema = new Schema( {
6 | user: Schema.Types.ObjectId,
7 | created: {
8 | type: Number,
9 | default: Date.now
10 | },
11 | key: {
12 | type: String,
13 | default: function() {
14 | return random.string( 24, random.defaultChars );
15 | }
16 | }
17 | } );
18 |
19 | module.exports = mongoose.model( 'ForgotKey', forgotKeySchema );
20 |
--------------------------------------------------------------------------------
/models/survey_response.js:
--------------------------------------------------------------------------------
1 | var mongoose = require( 'mongoose' ),
2 | Schema = mongoose.Schema;
3 |
4 | var surveyResponseSchema = new Schema( {
5 | surveyId: Schema.Types.ObjectId,
6 | response: Schema.Types.Mixed,
7 | started: { type: Date, default: Date.now },
8 | submitted: { type: Date, default: null }
9 | } );
10 |
11 | surveyResponseSchema.post( 'save', function( resp ) {
12 | if( resp.submitted ) {
13 | mongoose.model( 'Survey' ).updateScore( resp.surveyId );
14 | }
15 | } );
16 |
17 | module.exports = mongoose.model( 'SurveyResponse', surveyResponseSchema );
18 |
--------------------------------------------------------------------------------
/models/user.js:
--------------------------------------------------------------------------------
1 | var mongoose = require( 'mongoose' ),
2 | MongooseError = require( 'mongoose/lib/error' ),
3 | bcrypt = require( 'bcrypt' ),
4 | uniqueValidator = require( 'mongoose-unique-validator' ),
5 | random = require( '../lib/random' ),
6 | Schema = mongoose.Schema,
7 | SALT_WORK_FACTOR = 10;
8 |
9 | var validateEmail = function( email ) {
10 | var re = /^[^@]*@.+(\.\w{2,})+$/;
11 | return re.test( email );
12 | };
13 |
14 | var validatePassword = function( password ) {
15 | return password.length >= 8;
16 | };
17 |
18 | var userSchema = mongoose.Schema( {
19 | email: {
20 | type: String,
21 | required: 'Email address is required.',
22 | unique: true,
23 | validate: [ validateEmail, 'Please enter a valid email address.' ]
24 | },
25 | password: {
26 | type: String,
27 | required: 'Please enter a password.',
28 | validate: [ validatePassword, 'Passwords must be at least 8 characters long.' ]
29 | },
30 | roles: [ String ],
31 | profile: {
32 | type: Schema.Types.Mixed,
33 | default: {}
34 | },
35 | activated: {
36 | type: Boolean,
37 | default: false
38 | },
39 | activationKey: String
40 | } );
41 |
42 | // Validate unique emails
43 | userSchema.plugin( uniqueValidator, { message: 'The email address you entered is already in use.' } );
44 |
45 | // Bcrypt middleware
46 | userSchema.pre( 'save', function( next, done ) {
47 | var user = this;
48 |
49 | if( user.isModified( 'password' ) ) {
50 | bcrypt.genSalt( SALT_WORK_FACTOR, function( err, salt ) {
51 | if( err ) return next( err );
52 |
53 | bcrypt.hash( user.password, salt, function( err, hash ) {
54 | if( err ) return next( err );
55 | user.password = hash;
56 | next();
57 | } );
58 | } );
59 | } else {
60 | next();
61 | }
62 | } );
63 |
64 | userSchema.pre( 'save', function( next, done ) {
65 | var user = this;
66 |
67 | if( user.isNew || user.isModified( 'activated' ) ) {
68 | if( !user.activated ) {
69 | user.activationKey = random.string( 24, random.defaultChars );
70 | } else {
71 | user.activationKey = null;
72 | }
73 | }
74 |
75 | next();
76 | } );
77 |
78 | // Role validation
79 | userSchema.method( 'hasRole', function( role ) {
80 | return this.roles.indexOf( role ) > -1;
81 | } );
82 |
83 | userSchema.method( 'validRole', function( role ) {
84 | return [ 'user', 'admin' ].indexOf( role ) > -1;
85 | } );
86 |
87 | // Password verification
88 | userSchema.method( 'comparePassword', function( candidatePassword, cb ) {
89 | bcrypt.compare( candidatePassword, this.password, function( err, isMatch ) {
90 | if( err ) return cb( err );
91 | cb( null, isMatch );
92 | });
93 | } );
94 |
95 | module.exports = mongoose.model( 'User', userSchema );
96 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "responsiveos",
3 | "version": "2.0.0",
4 | "description": "Responsive OS",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git://github.com/planetarycorp/UCROS.git"
12 | },
13 | "author": "Planetary",
14 | "license": "N/A",
15 | "bugs": {
16 | "url": "https://github.com/planetarycorp/UCROS/issues"
17 | },
18 | "homepage": "https://github.com/planetarycorp/UCROS",
19 | "dependencies": {
20 | "array.prototype.find": "^1.0.0",
21 | "array.prototype.findindex": "^1.0.0",
22 | "bcrypt": "^0.8.1",
23 | "body-parser": "^1.12.0",
24 | "brfs": "^1.4.0",
25 | "browserify": "^9.0.7",
26 | "bulkify": "^1.1.1",
27 | "color": "^0.8.0",
28 | "compression": "^1.4.2",
29 | "cookie-parser": "^1.3.4",
30 | "crossfilter": "^1.3.11",
31 | "csurf": "^1.7.0",
32 | "email-templates": "~1.2.0",
33 | "express": "^4.12.2",
34 | "express-csv": "^0.6.0",
35 | "express-flash": "0.0.2",
36 | "express-session": "^1.10.3",
37 | "express-session-mongo": "^0.1.0",
38 | "gulp-autoprefixer": "^2.1.0",
39 | "gulp-csso": "^1.0.0",
40 | "gulp-load-plugins": "^0.8.1",
41 | "gulp-notify": "^2.2.0",
42 | "gulp-sass": "^1.3.3",
43 | "gulp-sourcemaps": "^1.5.1",
44 | "hat": "~0.0.3",
45 | "include-all": "^0.1.6",
46 | "jade": "^1.9.2",
47 | "jquery": "^2.1.3",
48 | "juice2": "^1.3.1",
49 | "lodash": "^3.5.0",
50 | "marked": "^0.3.3",
51 | "md5": "^1.0.0",
52 | "moment": "~2.9.0",
53 | "mongoose": "^4.0.1",
54 | "mongoose-paginate": "~3.1.3",
55 | "mongoose-text-search": "0.0.2",
56 | "mongoose-unique-validator": "^0.4.1",
57 | "morgan": "^1.5.1",
58 | "node-cache": "^1.1.0",
59 | "node-sass": "~3.0.0-beta.4",
60 | "nodemailer": "^1.3.2",
61 | "nodemailer-html-to-text": "^1.0.0",
62 | "nodemailer-ses-transport": "^1.2.0",
63 | "on-headers": "^1.0.0",
64 | "parsleyjs": "~2.0.7",
65 | "passport": "^0.2.1",
66 | "passport-local": "^1.0.0",
67 | "rangeslider.js": "^1.1.0",
68 | "request": "^2.53.0",
69 | "vinyl-buffer": "^1.0.0",
70 | "vinyl-source-stream": "^1.1.0",
71 | "watchify": "^2.4.0"
72 | },
73 | "devDependencies": {
74 | "gulp": "~3.8.11",
75 | "gulp-jshint": "~1.9.2",
76 | "gulp-livereload": "~3.8.0",
77 | "gulp-minify-css": "~1.0.0",
78 | "gulp-nodemon": "~1.0.5",
79 | "gulp-scss-lint": "~0.1.10",
80 | "gulp-util": "~3.0.4",
81 | "tiny-lr": "~0.1.5"
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/processes.json:
--------------------------------------------------------------------------------
1 | {
2 | "apps" : [{
3 | "name": "pulse",
4 | "script": "app.js",
5 | "log_date_format": "YYYY-MM-DD HH:mm Z",
6 | "error_file": "logs/error.log",
7 | "out_file": "logs/server.log",
8 | "env_production": {
9 | "NODE_ENV": "production"
10 | },
11 | "env_development": {
12 | "NODE_ENV": "development"
13 | },
14 | "exec_mode": "cluster_mode",
15 | "instances": 0,
16 | "port": 4600
17 | }]
18 | }
19 |
--------------------------------------------------------------------------------
/public/data/jargon.json:
--------------------------------------------------------------------------------
1 | {
2 | "Purpose": "A written statement that guides your actions in absense of having a written plan or strategy",
3 | "Customer": "The person that you are responsible for making happy - this could be an actual customer or an internal stakeholder",
4 | "Role": "This is the set of responsibilities that you play. One person can have one title and still hold several roles. ",
5 | "Product and Service": "Physical goods or work performed by you or your team on behalf of others",
6 | "Real-Time Collaborative Software": "Computer programs like Google Docs where you can make a change on your screen, and a coworker will immediately see the change appear on their screen.",
7 | "Product": "Physical goods or work performed by you or your team on behalf of others",
8 | "Real-Time Feedback": "Immediate feedback that helps you understand what is working (or not working) at this moment",
9 | "Customer-Facing Tests": "Experiments you try with real customers (as opposed to making decisions and assuming you know what customers want or need)",
10 | "Success Theater": "Minimizing failures and exaggerating certain aspects of pet projects to make them look like successes and avoid embarassment",
11 | "Failures": "Information learned by serving others; used to change course or improve goods or services",
12 | "Default to Open": "Lean towards sharing information with your colleagues; make it easy for your coworkers to see what you are working on",
13 | "Ship": "Sharing, distributing, selling, or making available the products, services, or work you've performed for others",
14 | "Offering": "Goods, services, or work performed by you on behalf of others",
15 | "Superior": "Boss, manager, department head"
16 | }
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/favicon.ico
--------------------------------------------------------------------------------
/public/fonts/karla/Karla-Bold-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/fonts/karla/Karla-Bold-webfont.eot
--------------------------------------------------------------------------------
/public/fonts/karla/Karla-Bold-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/fonts/karla/Karla-Bold-webfont.ttf
--------------------------------------------------------------------------------
/public/fonts/karla/Karla-Bold-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/fonts/karla/Karla-Bold-webfont.woff
--------------------------------------------------------------------------------
/public/fonts/karla/Karla-Bold-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/fonts/karla/Karla-Bold-webfont.woff2
--------------------------------------------------------------------------------
/public/fonts/karla/Karla-BoldItalic-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/fonts/karla/Karla-BoldItalic-webfont.eot
--------------------------------------------------------------------------------
/public/fonts/karla/Karla-BoldItalic-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/fonts/karla/Karla-BoldItalic-webfont.ttf
--------------------------------------------------------------------------------
/public/fonts/karla/Karla-BoldItalic-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/fonts/karla/Karla-BoldItalic-webfont.woff
--------------------------------------------------------------------------------
/public/fonts/karla/Karla-BoldItalic-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/fonts/karla/Karla-BoldItalic-webfont.woff2
--------------------------------------------------------------------------------
/public/fonts/karla/Karla-Italic-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/fonts/karla/Karla-Italic-webfont.eot
--------------------------------------------------------------------------------
/public/fonts/karla/Karla-Italic-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/fonts/karla/Karla-Italic-webfont.ttf
--------------------------------------------------------------------------------
/public/fonts/karla/Karla-Italic-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/fonts/karla/Karla-Italic-webfont.woff
--------------------------------------------------------------------------------
/public/fonts/karla/Karla-Italic-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/fonts/karla/Karla-Italic-webfont.woff2
--------------------------------------------------------------------------------
/public/fonts/karla/Karla-Regular-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/fonts/karla/Karla-Regular-webfont.eot
--------------------------------------------------------------------------------
/public/fonts/karla/Karla-Regular-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/fonts/karla/Karla-Regular-webfont.ttf
--------------------------------------------------------------------------------
/public/fonts/karla/Karla-Regular-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/fonts/karla/Karla-Regular-webfont.woff
--------------------------------------------------------------------------------
/public/fonts/karla/Karla-Regular-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/fonts/karla/Karla-Regular-webfont.woff2
--------------------------------------------------------------------------------
/public/img/chosen/chosen-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/img/chosen/chosen-sprite.png
--------------------------------------------------------------------------------
/public/img/chosen/chosen-sprite@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/img/chosen/chosen-sprite@2x.png
--------------------------------------------------------------------------------
/public/img/hero.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/img/hero.jpg
--------------------------------------------------------------------------------
/public/img/landing_hero.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/img/landing_hero.png
--------------------------------------------------------------------------------
/public/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/img/logo.png
--------------------------------------------------------------------------------
/public/img/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
--------------------------------------------------------------------------------
/public/img/logo_gray.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
40 |
--------------------------------------------------------------------------------
/public/img/screenshots/dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/img/screenshots/dashboard.png
--------------------------------------------------------------------------------
/public/img/screenshots/results.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/img/screenshots/results.png
--------------------------------------------------------------------------------
/public/img/screenshots/survey.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/img/screenshots/survey.png
--------------------------------------------------------------------------------
/public/vendor/ZeroClipboard.swf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UCSoftware/responsive-pulse/96a352b00e57419b0f819db2065d331ffa1104b9/public/vendor/ZeroClipboard.swf
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # ROS Pulse
2 |
3 | ### Made with Love by [Planetary](https://planetary.io/)
4 |
5 | ## About
6 |
7 | The Pulse quantifies an organization’s operating system across 35 unique attributes, helping internal and external change agents track their impact.
8 |
9 | ## Is your organization prepared for the 21st century?
10 |
11 | The organizations we have today are unfit for the next century. Rigid hierarchies, inflexible matrix reporting structures, meeting-rich schedules, and decision-rights for only the most tenured members – these “features” prevent any real work from getting done.
12 |
13 | We’ve been studying groups that work in a new way and as a result have a bigger impact on the world than their competitors. We’ve been using this tool for that research and for our paying clients. It’s now available for free. Find out how your organization measures up.
14 |
15 | ## Measuring Up
16 |
17 | Administering the Responsive OS Pulse is easy. Once you create an account you'll be given a link you can distribute to your teams. Results can are analyzed in real-time and along a variety of dimensions.
18 |
19 | Intoning the principles measured by Responsive Pulse affords an organization greater responsiveness to competition, culture, technology, and all other forms of disruption. We believe these principles – and their associated implications on ways of working – dramatically improve even the largest and most traditional institutions – for employees, for customers, and for shareholders.
20 |
21 | ## Installation
22 |
23 | ### First, add a config/local.js file that looks like:
24 |
25 | ```javascript
26 | module.exports = {
27 | auth: {
28 | secret: 'a secret',
29 | adminPassword: 'an admin password'
30 | },
31 | session: {
32 | secret: 'another secret'
33 | },
34 | email: {
35 | from: 'an email address',
36 | accessKeyId: 'an access key id',
37 | secretAccessKey: 'a secret access key',
38 | rateLimit: a_number,
39 | region: 'a region'
40 | },
41 | url: {
42 | production: 'a url',
43 | staging: 'another url'
44 | }
45 | };
46 | ```
47 |
48 | ### Then run an install:
49 |
50 | 1. npm install -g gulp migrate
51 | 2. npm install
52 | 3. migrate
53 | 4. gulp
54 |
55 | ## License
56 |
57 | Copyright 2015 Undercurrent, LLC
58 |
59 | Licensed under the Apache License, Version 2.0 (the "License");
60 | you may not use this file except in compliance with the License.
61 | You may obtain a copy of the License at
62 |
63 | http://www.apache.org/licenses/LICENSE-2.0
64 |
65 | Unless required by applicable law or agreed to in writing, software
66 | distributed under the License is distributed on an "AS IS" BASIS,
67 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
68 | See the License for the specific language governing permissions and
69 | limitations under the License.
--------------------------------------------------------------------------------
/sass/components/_buttons.scss:
--------------------------------------------------------------------------------
1 | @mixin button-generator($color) {
2 | background: $color;
3 | color: color-contrast($color);
4 | border: 0;
5 |
6 | &:hover {
7 | background: lighten($color, 5%);
8 |
9 | color: contrast-color($color, $gray, $white);
10 | }
11 | }
12 |
13 | .button {
14 | @include button-generator($white);
15 |
16 | display: inline-block;
17 | padding: 0.75em 2em;
18 | text-align: center;
19 | text-decoration: none;
20 | text-shadow: none;
21 | font-family: $sans;
22 | font-size: inherit;
23 | cursor: pointer;
24 | line-height: 1rem;
25 |
26 | &:disabled,
27 | &.button--disabled {
28 | cursor: default;
29 | opacity: 0.5;
30 | }
31 |
32 | + .button {
33 | margin-left: 1em;
34 | }
35 |
36 | i {
37 | margin-right: 0.55em;
38 | margin-left: -0.55em;
39 | }
40 |
41 | &:focus {
42 | outline: none;
43 | }
44 | }
45 |
46 | .button--primary {
47 | @include button-generator($blue);
48 | text-shadow: none;
49 | color: $white;
50 | }
51 |
52 | .button--action {
53 | @include button-generator($green);
54 | text-shadow: none;
55 | color: $white;
56 | }
57 |
58 | .button--danger {
59 | @include button-generator($red);
60 | text-shadow: none;
61 | }
62 |
63 | .button--large {
64 | font-size: 1.2em;
65 | padding: 1em 2.2em;
66 | }
67 |
68 | .button--form-change {
69 | background: none;
70 | border: 0;
71 | display: inline;
72 | font: inherit;
73 | margin: 0;
74 | padding: 0;
75 | outline: none;
76 | outline-offset: 0;
77 | color: $gray-light;
78 | cursor: pointer;
79 | text-decoration: underline;
80 |
81 | &:hover {
82 | color: $gray;
83 | }
84 | }
85 |
86 | .form--invalid {
87 | .button--primary:disabled,
88 | .button--primary:disabled:hover {
89 | @include button-generator($gray-light);
90 | color: $gray-lighter;
91 | opacity: 1;
92 | }
93 | }
94 |
95 | .button--icon {
96 | padding: 0.75rem 1.5rem 0.75rem 1.25rem
97 | }
98 |
--------------------------------------------------------------------------------
/sass/components/_cards.scss:
--------------------------------------------------------------------------------
1 | $card-headroom: 1em;
2 | $card-inset: 1.5em;
3 |
4 | .card {
5 | position: relative;
6 |
7 | .card-body {
8 | display: inline-block;
9 | width: 100%;
10 | background: $white;
11 | border: 1px solid $gray-lighter;
12 | padding: $card-headroom $card-inset;
13 | margin-bottom: $card-headroom;
14 |
15 | &.card-body--no-border {
16 | border: none;
17 | }
18 |
19 | .card-breakout {
20 | clear: both;
21 | margin-left: -$card-headroom;
22 | margin-right: -$card-headroom;
23 |
24 | img {
25 | max-width: 100%;
26 | width: auto;
27 | height: auto;
28 | }
29 | }
30 |
31 | .card-intro {
32 | margin-bottom: $card-headroom * 2;
33 |
34 | }
35 |
36 | .data-controls {
37 | margin-bottom: 1em;
38 | text-align: right;
39 | }
40 |
41 | hr {
42 | height: 0;
43 | border: 0;
44 | border-top: 1px solid $off-white;
45 | margin: $card-headroom (-$card-inset);
46 | }
47 |
48 | .card-row {
49 | border-bottom: 1px solid $gray-lighter;
50 | padding: 2em 0;
51 | clear: both;
52 | max-width: 38em;
53 |
54 | form {
55 | display: inline-block;
56 | width: 100%;
57 | clear: both;
58 | }
59 |
60 | &:last-child {
61 | border-bottom: 0;
62 | }
63 | }
64 | }
65 |
66 | &.card-table {
67 | .card-body {
68 | padding: 0;
69 | }
70 | }
71 | }
72 |
73 | .card-title {
74 | font-size: $scale-medium;
75 | font-weight: 300;
76 | color: $gray-light;
77 | margin-bottom: $card-headroom / 2;
78 | }
79 |
80 | .card-responses,
81 | .card-score,
82 | .card-divergence {
83 | text-align: center;
84 | }
85 |
86 | .card--spacious {
87 | .card-body {
88 | padding: ($card-headroom * 1.5) ($card-inset * 1.5);
89 |
90 | .card-breakout {
91 | margin-left: -($card-inset * 1.5);
92 | margin-right: -($card-inset * 1.5);
93 | margin-top: ($card-headroom * 2);
94 | margin-bottom: ($card-headroom * 2);
95 | }
96 |
97 | }
98 |
99 | @include media((" span {
99 | float: none;
100 | text-align: center;
101 | display: inline-block;
102 | width: 100%;
103 | padding-top: 0;
104 | padding-bottom: 1em;
105 | }
106 | }
107 | }
108 |
109 | .dashboard--table {
110 | background: $white;
111 | }
112 |
113 | .table--list .no-responses {
114 | border-bottom: 0;
115 | }
116 |
117 | .survey-title {
118 | font-size: $scale-huge;
119 | color: $gray-darker;
120 | font-weight: 700;
121 | margin: 0;
122 | line-height: 1.33;
123 | }
124 |
125 | .dashboard--share {
126 | a {
127 | text-decoration: none;
128 | }
129 |
130 | &:hover,
131 | &.zeroclipboard-is-hover {
132 | &,
133 | a {
134 | color: $black;
135 | }
136 | }
137 | }
138 |
139 | .dashboard--copy,
140 | .dashboard--copy:hover {
141 | cursor: pointer;
142 | }
143 |
144 | .dashboard--zc-copied {
145 | animation: zc-copied 4s linear 0s;
146 | }
147 |
148 | @keyframes zc-copied {
149 | 0% {
150 | color: $green;
151 | }
152 |
153 | 100% {
154 | color: $gray-darker;
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/sass/layouts/_landing.scss:
--------------------------------------------------------------------------------
1 | .hero {
2 | height: 28rem;
3 | background: url("/img/hero.jpg");
4 | background-size: cover;
5 | background-position: center middle;
6 | margin-bottom: 3rem;
7 | }
8 |
9 | .hero--inner {
10 | @include container;
11 | padding: 6em 0;
12 | }
13 |
14 | .hero--text {
15 | color: $white;
16 | background: $green;
17 | display: inline-block;
18 | padding: 1rem 2rem;
19 | }
20 |
21 | .hero--cta {
22 | display: inline-block;
23 | margin: 1rem 0;
24 | border: 2px solid $white;
25 | border-radius: 3px;
26 | }
27 |
28 | .action {
29 | margin-top: 2em;
30 | }
31 |
32 | .placeholder {
33 | height: 20em;
34 | background: #eee;
35 | }
36 |
37 | .landing-screenshot {
38 | @include span(4);
39 |
40 | line-height: 0;
41 | height: auto;
42 | margin-bottom: $card-headroom;
43 |
44 | @include media(" span {
29 | display: block;
30 | width: 100%;
31 | color: $gray;
32 | border-bottom: 1px solid $gray-lighter;
33 | }
34 | }
35 |
36 | .ovw-scoring {
37 | float: right;
38 | text-align: center;
39 | }
40 |
41 | .ovw-scoring--inner {
42 | border-radius: $default-border-radius;
43 | background: $off-white;
44 | }
45 |
46 | .ovw-scoring--reset {
47 | color: $gray-light;
48 | margin: 0.5rem 0;
49 | display: none;
50 |
51 | &.is-shown {
52 | display: block;
53 | }
54 | }
55 |
56 | .ovw-scoring--percentile {
57 | display: block;
58 | margin: 0.5rem 0;
59 |
60 | &.is-hidden {
61 | display: none;
62 | }
63 | }
64 |
65 | .ovw-scoring--responses,
66 | .ovw-scoring--score {
67 | display: inline-block;
68 | vertical-align: middle;
69 | padding: 0.75rem 1rem 0.25rem;
70 | text-align: center;
71 |
72 | .number {
73 | font-size: $scale-xlarge;
74 | padding: 0.25rem 0;
75 | }
76 | }
77 |
78 | .ovw-scoring--responses {
79 | border-right: 1px solid $gray-lighter;
80 |
81 | .number {
82 | display: inline;
83 | }
84 | }
85 |
86 | .ovw-scoring--responses-total {
87 | color: $gray-light;
88 | padding-left: 0.25em;
89 | display: none;
90 |
91 | &.is-shown {
92 | display: inline-block;
93 | }
94 | }
95 |
96 | .ovw-scoring--score-diff {
97 | vertical-align: middle;
98 | padding-right: 1rem;
99 | padding-bottom: 0.25em;
100 | color: $gray;
101 | display: none;
102 |
103 | &.is-shown {
104 | display: inline-block;
105 | }
106 | }
107 |
108 | .score-diff-up,
109 | .score-diff-down {
110 | font-size: $scale-small;
111 | line-height: 1em;
112 |
113 | &.score-diff-num {
114 | font-size: $scale-normal;
115 | line-height: 1em;
116 | }
117 | }
118 |
119 | .score-diff-up,
120 | .score-diff-down,
121 | .score-diff-num {
122 | opacity: 0;
123 |
124 | &.is-shown {
125 | opacity: 1;
126 | }
127 | }
128 |
129 | .score-diff-up {
130 | color: $green;
131 | }
132 |
133 | .score-diff-down {
134 | color: $red;
135 | }
136 |
137 | .ovw-scoring--label {
138 | color: $gray-light;
139 | }
140 |
141 | .ovw-header {
142 | background: $white;
143 | border-left: 1px solid $gray-lighter;
144 | border-right: 1px solid $gray-lighter;
145 | z-index: 2000;
146 | }
147 |
148 | .ovw-header-inner {
149 | padding: 2em 1.5em 0;
150 | display: inline-block;
151 | width: 100%;
152 | }
153 |
154 | .is_stuck {
155 | z-index: 1000;
156 | }
157 |
158 | .verbatims {
159 | .verbatims--show-more {
160 | display: block;
161 | }
162 |
163 | .verbatims--show-less, .verbatims--list {
164 | display: none;
165 | }
166 |
167 | &.verbatims--expanded {
168 | .verbatims--list {
169 | display: block;
170 | }
171 |
172 | .verbatims--show-more {
173 | display: none;
174 | }
175 |
176 | .verbatims--show-less {
177 | display: block;
178 | }
179 | }
180 |
181 | &.verbatims--no-collapse {
182 | .verbatims--show-more, .verbatims--show-less {
183 | display: none;
184 | }
185 |
186 | .verbatims--list {
187 | display: block;
188 | }
189 | }
190 | }
191 |
192 | .results--section {
193 | padding-bottom: 3rem;
194 | }
195 |
196 | .structure--filter-team {
197 | text-align: right;
198 | color: $gray-light;
199 | margin: 1.5em 0 0;
200 | }
201 |
--------------------------------------------------------------------------------
/sass/layouts/_primary.scss:
--------------------------------------------------------------------------------
1 | body {
2 | background: $off-white;
3 | margin: 0;
4 | }
5 |
6 | main {
7 | padding: 0 1em;
8 |
9 | section {
10 | @include container;
11 | }
12 |
13 | }
14 |
15 | .overview, .detail {
16 | .no-responses {
17 | text-shadow: 0 1px 0 $white;
18 |
19 | text-align: center;
20 | font-size: $scale-xlarge;
21 | font-weight: 300;
22 | padding-top: 3em;
23 | color: $gray-light;
24 | margin-bottom: 2em;
25 |
26 | @include media((" abs($color-brightness - $dark-text-brightness), $light, $dark);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/sass/partials/_grid.scss:
--------------------------------------------------------------------------------
1 | /* AGS - Airframe grid system
2 | * v0.1 */
3 |
4 | // Grid configuration
5 | $grid-width: 60rem;
6 | $columns: 12;
7 | $gutter: 2%;
8 |
9 | $row-spacing: 1rem;
10 | $corner-radius: 4px;
11 | $r: $corner-radius;
12 |
13 | // The magic happens here
14 | @function cw($span-num, $total-num) {
15 | $ratio: ($total-num / $span-num);
16 | @return ((100% / ($ratio)) - (($ratio) - 1) * ($gutter / ($ratio)));
17 | }
18 |
19 | @mixin container {
20 | margin: 0 auto;
21 | width: $grid-width;
22 | }
23 |
24 | @mixin row {
25 | margin-bottom: $row-spacing;
26 |
27 | &:after {
28 | content: "";
29 | display: table;
30 | clear: both;
31 | }
32 | }
33 |
34 | @mixin span($span) {
35 | float: left;
36 | width: cw($span, $columns);
37 | margin-right: $gutter;
38 | }
39 |
40 | @mixin last {
41 | float: right;
42 | margin-right: 0;
43 | }
44 |
45 | @mixin visible-grid {
46 | background: transparentize($blue, 0.9);
47 | }
48 |
49 | // Helper classes
50 |
51 | .g--container {
52 | margin: 0 auto;
53 | width: $grid-width;
54 | }
55 |
56 | .g--row {
57 | @include row;
58 | }
59 |
60 | .g--visible-grid {
61 | @include visible-grid;
62 | }
63 |
64 | .g--full {
65 | @include span(12);
66 | }
67 |
68 | .g--half {
69 | @include span(6);
70 | }
71 |
72 | .g--third {
73 | @include span(4);
74 | }
75 |
76 | .g--quarter {
77 | @include span(3);
78 | }
79 |
80 | .g--last {
81 | @include last;
82 | }
--------------------------------------------------------------------------------
/sass/partials/_z.scss:
--------------------------------------------------------------------------------
1 | $z-top: 9999;
2 |
3 | $z-highest: 100;
4 | $z-high: 50;
5 | $z-ground: 0;
6 | $z-low: -50;
7 | $z-lowest: -100;
8 |
9 | $z-bottom: -9999;
--------------------------------------------------------------------------------
/survey_fields.md:
--------------------------------------------------------------------------------
1 | # Required Fields
2 |
3 | __`org_years`__
4 | *Recommended type:* `slider`
5 | Number of years at the organization
6 |
7 | __`management_layers`__
8 | *Recommended type:* `slider`
9 | Number of layers of management between [you] and [your] CEO
10 |
11 | __`org_position`__
12 | *Recommended type:* `radio`
13 | Organization's position in the market
14 |
15 | __`team`__
16 | *Recommended type:* `teamselect`
17 | Team and/or department do [you] work in
18 |
19 | __Verbatims__
20 | *Recommended type:* `textarea`
21 | `purpose_verbatims`
22 | `people_verbatims`
23 | `process_verbatims`
24 | `product_verbatims`
25 | `platform_verbatims`
26 |
27 | __Purpose__
28 | *Required type:* `gridselect`
29 | *Options:*
30 | `PuN`: *x* Serve Networks
31 | `PuA`: *x* Distribute Authority
32 | `PuS`: *x* Seek Simplicity
33 | `PuI`: *x* Process Information
34 | `PuD`: *x* Encourage Divergence
35 | `PuR`: *x* Enable Crossover
36 | `PuT`: *x* Embrace Uncertainty
37 |
38 | __People__
39 | *Required type:* `gridselect`
40 | *Options:*
41 | `PeN`: *x* Serve Networks
42 | `PeA`: *x* Distribute Authority
43 | `PeS`: *x* Seek Simplicity
44 | `PeI`: *x* Process Information
45 | `PeD`: *x* Encourage Divergence
46 | `PeR`: *x* Enable Crossover
47 | `PeT`: *x* Embrace Uncertainty
48 |
49 | __Process__
50 | *Required type:* `gridselect`
51 | *Options:*
52 | `PrN`: *x* Serve Networks
53 | `PrA`: *x* Distribute Authority
54 | `PrS`: *x* Seek Simplicity
55 | `PrI`: *x* Process Information
56 | `PrD`: *x* Encourage Divergence
57 | `PrR`: *x* Enable Crossover
58 | `PrT`: *x* Embrace Uncertainty
59 |
60 | __Product__
61 | *Required type:* `gridselect`
62 | *Options:*
63 | `PdN`: *x* Serve Networks
64 | `PdA`: *x* Distribute Authority
65 | `PdS`: *x* Seek Simplicity
66 | `PdI`: *x* Process Information
67 | `PdD`: *x* Encourage Divergence
68 | `PdR`: *x* Enable Crossover
69 | `PdT`: *x* Embrace Uncertainty
70 |
71 | __Platform__
72 | *Required type:* `gridselect`
73 | *Options:*
74 | `PlN`: *x* Serve Networks
75 | `PlA`: *x* Distribute Authority
76 | `PlS`: *x* Seek Simplicity
77 | `PlI`: *x* Process Information
78 | `PlD`: *x* Encourage Divergence
79 | `PlR`: *x* Enable Crossover
80 | `PlT`: *x* Embrace Uncertainty
81 |
--------------------------------------------------------------------------------
/tasks/build.js:
--------------------------------------------------------------------------------
1 | module.exports = function( gulp, plugins ) {
2 | gulp.task( 'build', [
3 | 'bundle:dev',
4 | 'styles'
5 | ] );
6 | };
7 |
--------------------------------------------------------------------------------
/tasks/bundle.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Compile JavaScript with Browserify.
3 | *
4 | * ---------------------------------------------------------------
5 | *
6 | * Compiles the js bundle from `src/js` into a single file and places
7 | * them into `build/js` directory.
8 | *
9 | */
10 | var path = require( 'path' );
11 | var source = require( 'vinyl-source-stream' );
12 | var buffer = require( 'vinyl-buffer' );
13 | var watchify = require( 'watchify' );
14 | var browserify = require( 'browserify' );
15 |
16 | module.exports = function( gulp, plugins, path ) {
17 | var errorHandler = function( err ) {
18 | plugins.notify.onError( {
19 | message: "<%= error.message %>"
20 | } ).apply( this, arguments );
21 |
22 | this.emit( 'end' );
23 | };
24 |
25 | var bundles = [
26 | {
27 | entryPoint: './app.js',
28 | require: [],
29 | output: 'bundle.js',
30 | external: [ 'jquery' ]
31 | },
32 | {
33 | entryPoint: './admin.js',
34 | require: [],
35 | output: 'admin.js',
36 | external: [ 'jquery' ]
37 | },
38 | {
39 | entryPoint: './form.js',
40 | require: [],
41 | output: 'form.js',
42 | external: [ 'jquery' ]
43 | },
44 | {
45 | entryPoint: './shared.js',
46 | require: [ 'jquery' ],
47 | output: 'shared.js',
48 | external: []
49 | }
50 | ];
51 |
52 | var args = watchify.args;
53 | args.debug = true;
54 | args.basedir = path.resolve( __dirname, '../client/' );
55 | args.fullPaths = false;
56 | args.insertGlobals = true;
57 |
58 | var bundlers = {};
59 |
60 | bundles.forEach( function( b ) {
61 | var bundler = browserify( b.entryPoint, args );
62 | // add any other browserify options or transforms here
63 | bundler.transform( 'brfs' );
64 | bundler.transform( 'bulkify' );
65 |
66 | if( b.require ) {
67 | b.require.forEach( function( r ) {
68 | bundler.require( r );
69 | } );
70 | }
71 |
72 | if( b.external ) {
73 | b.external.forEach( function( e ) {
74 | bundler.external( e );
75 | } );
76 | }
77 |
78 | bundlers[ b.output ] = bundler;
79 | } );
80 |
81 | gulp.task( 'bundle:dev', function() {
82 | bundles.forEach( function( b ) {
83 | bundle( b )();
84 | } );
85 | } ); // so you can run `gulp bundle:dev` to build the file
86 | gulp.task( 'bundle:watch', function() {
87 | bundles.forEach( function( b ) {
88 | var watcher = watchify( bundlers[ b.output ] );
89 | watcher.on( 'update', bundle( b ) ); // on any dep update, runs the bundler
90 | } );
91 | } );
92 |
93 | function bundle( b ) {
94 | return function() {
95 | bundlers[ b.output ].bundle()
96 | // log errors if they happen
97 | .on( 'error', errorHandler )
98 | .pipe( source( b.output ) )
99 | // optional, remove if you dont want sourcemaps
100 | .pipe( buffer() )
101 | .pipe( plugins.sourcemaps.init( { loadMaps: true } ) ) // loads map from browserify file
102 | .pipe( plugins.sourcemaps.write( './' ) ) // writes .map file
103 | //
104 | .pipe( gulp.dest( './public/js' ) )
105 | .pipe( plugins.notify( { message: b.output + ': browserify bundler task complete', onLast: true } ) );
106 | };
107 | }
108 | };
109 |
--------------------------------------------------------------------------------
/tasks/default.js:
--------------------------------------------------------------------------------
1 | module.exports = function( gulp, plugins ) {
2 | gulp.task( 'default', [
3 | 'build',
4 | 'watch',
5 | 'serve:dev'
6 | ] );
7 | };
8 |
--------------------------------------------------------------------------------
/tasks/js-lint.js:
--------------------------------------------------------------------------------
1 | module.exports = function( gulp, plugins ) {
2 | gulp.task( 'js-lint', function() {
3 | gulp.src( [
4 | 'controllers/**/*.js',
5 | 'lib/**/*.js',
6 | 'models/**/*.js'
7 | ] ).pipe( plugins.jshint() );
8 | } );
9 | };
10 |
--------------------------------------------------------------------------------
/tasks/scss-lint.js:
--------------------------------------------------------------------------------
1 | module.exports = function( gulp, plugins ) {
2 | var colors = plugins.util.colors;
3 |
4 | var linterErrors = function( file ) {
5 | if( !file.scsslint.success ) {
6 | plugins.util.log(colors.red(file.scsslint.issues.length + ' issues found in ' + file.path));
7 |
8 | file.scsslint.issues.forEach(function (issue) {
9 | var severity = issue.severity === 'warning' ? 'W' : 'E';
10 | var logMsg = colors.cyan(file.path) + ':' + colors.magenta(issue.line) + ' [' + severity + '] ' + issue.reason;
11 |
12 | plugins.util.log(logMsg);
13 | });
14 | } else {
15 | plugins.util.log(colors.green('Linting passed for ' + file.path));
16 | }
17 | };
18 |
19 | gulp.task( 'scss-lint', function() {
20 | gulp.src( [ './src/styles/**/*.scss', '!./src/styles/vendor/**/*' ] )
21 | .pipe( plugins.scssLint( {
22 | 'config': '.scss-lint.yml',
23 | 'customReport': linterErrors
24 | } ) );
25 | } );
26 | };
--------------------------------------------------------------------------------
/tasks/serve.js:
--------------------------------------------------------------------------------
1 | var extend = require( 'util' )._extend;
2 |
3 | module.exports = function( gulp, plugins, path ) {
4 | var options = {
5 | script: 'app.js',
6 | ext: [ 'js' ],
7 | ignore: [
8 | path.resolve( __dirname, '../client/**' ),
9 | path.resolve( __dirname, '../public/**' ),
10 | path.resolve( __dirname, '../tasks/**/*' )
11 | ]
12 | };
13 |
14 | gulp.task( 'serve:dev', function () {
15 | plugins.nodemon( extend( options, { env: { 'NODE_ENV': 'development' } } ) );
16 | } );
17 |
18 | gulp.task( 'serve:stage', function () {
19 | plugins.nodemon( extend( options, { env: { 'NODE_ENV': 'staging' } } ) );
20 | } );
21 |
22 | gulp.task( 'serve:prod', function () {
23 | plugins.nodemon( extend( options, { env: { 'NODE_ENV': 'production' } } ) );
24 | } );
25 | };
26 |
--------------------------------------------------------------------------------
/tasks/style.js:
--------------------------------------------------------------------------------
1 | //this will handle errors for us
2 | var handleError = function( err ) {
3 | console.log( err.toString() );
4 | this.emit( 'end' );
5 | };
6 |
7 | module.exports = function( gulp, plugins ) {
8 | //concat, minify css
9 | gulp.task( 'styles', function() {
10 | gulp.src(['./sass/**/*.scss'])
11 | .pipe( plugins.sass( {
12 | sourcemap: true,
13 | sourcemapPath: 'src/styles'
14 | } ) )
15 | .on( 'error', handleError )
16 | .pipe( plugins.csso() )
17 | .pipe( plugins.autoprefixer() )
18 | .pipe( gulp.dest( './public/css' ) )
19 | .pipe( plugins.notify( { message: 'scss compilation complete', onLast: true } ) );
20 | } );
21 | };
--------------------------------------------------------------------------------
/tasks/watch.js:
--------------------------------------------------------------------------------
1 | module.exports = function( gulp, plugins ) {
2 | gulp.task( 'watch:styles', function() {
3 | gulp.watch( 'sass/**/*.scss', [ 'scss-lint', 'styles' ] );
4 | } );
5 |
6 | gulp.task( 'watch:js-lint', function() {
7 | gulp.watch( [
8 | 'controllers/**/*.js',
9 | 'lib/**/*.js',
10 | 'models/**/*.js'
11 | ], [ 'js-lint' ] );
12 | } );
13 |
14 | gulp.task( 'watch', [
15 | 'watch:styles',
16 | 'watch:js-lint',
17 | 'bundle:watch'
18 | ] );
19 | };
20 |
--------------------------------------------------------------------------------
/views/404.jade:
--------------------------------------------------------------------------------
1 | extends /layouts/base
2 |
3 | block content
4 | main
5 | section
6 | .row
7 | .card.card-full.card--spacious.type-center
8 | .card-body
9 | .card-title Page not found.
10 | p Sorry, we couldn't find what you were looking for. Go back home.
11 |
--------------------------------------------------------------------------------
/views/admin/account.jade:
--------------------------------------------------------------------------------
1 | extends /layouts/auth_base
2 |
3 | include /partials/formcomponents
4 |
5 | block content
6 | main
7 | section
8 | .row
9 | .card.card-full.card--spacious
10 | .card-body
11 | .card-row
12 | +form-group(true)(action=("/admin/user/" + accountUser.id))
13 | h3 Basic Information
14 | +input-text("name", null, accountUser.profile.name || "", "Name", true)(required="")
15 | +input-text("organization", null, accountUser.profile.organization || "", "Organization", true)(required="")
16 | +input-email("email", null, accountUser.email, "E-mail", true)(required="")
17 | +input-password("password", null, "", "Password", true)
18 | input(type="hidden", name="_csrf", value=csrfToken)
19 | +form-buttons
20 | .card-row
21 | +form-group(true)(action=("/admin/user/" + accountUser.id))
22 | h3 Account State
23 | +input-select("activated", {"1": "Yes", "0": "No"}, "Activated", accountUser.activated ? "1" : "0")
24 | div
25 | +input-select("roles", {"admin": "Admin", "user": "User"}, "Role", accountUser.roles.indexOf( 'admin' ) > -1 ? "admin" : "user")
26 | input(type="hidden", name="_csrf", value=csrfToken)
27 | +form-buttons
--------------------------------------------------------------------------------
/views/admin/users.jade:
--------------------------------------------------------------------------------
1 | extends /layouts/auth_base
2 |
3 | block content
4 | main
5 | section
6 | .row
7 | .card.card-full.card-table
8 | .card-body
9 | table.table--list
10 | thead
11 | tr
12 | th User Count
13 | th Response Count
14 | th Average Score
15 | tbody
16 | tr
17 | td= totals.users
18 | td= totals.responses
19 | td= (totals.score * 100 ).toFixed( 2 ) + '%'
20 | .row
21 | .card.card-full.card-table
22 | .card-body
23 | table.table--list.sortable
24 | thead
25 | tr
26 | th(class="sorting-asc" data-sort="string-ins", data-sort-default="asc", data-sort-dir="asc") Email
27 | th(data-sort="string-ins", data-sort-default="desc") Name
28 | th(data-sort="string-ins", data-sort-default="desc") Organization
29 | th(data-sort="string-ins", data-sort-default="desc") Activated
30 | tbody
31 | each user in userList
32 | tr
33 | td
34 | a(href='/admin/user/' + user.id, title=user.email)= user.email
35 | td
36 | span= user.profile.name || ''
37 | td
38 | span= user.profile.organization || ''
39 | td
40 | span= user.activated ? 'Yes' : 'No'
--------------------------------------------------------------------------------
/views/email/activated/activated.html.jade:
--------------------------------------------------------------------------------
1 | include ../buttons
2 | extends ../layout
3 |
4 | block title
5 | h1 Your account has been activated.
6 |
7 | block content
8 | p Thank you for activating your account on Responsive Pulse. You can now login with your username #{to}.
9 |
10 | block actions
11 | +email-button('Sign in', baseUrl + '/signin')
12 |
--------------------------------------------------------------------------------
/views/email/buttons.jade:
--------------------------------------------------------------------------------
1 | mixin email-button(text, url)
2 |
3 |
8 | a.button(href=url, style="-webkit-font-smoothing:antialiased;background-color:#5998E1;border-radius:4px;color:#ffffff;display:inline-block;font-family:sans-serif;font-size:13px;font-weight:bold;line-height:40px;text-align:center;text-decoration:none;width:auto;padding: 0 25px;-webkit-text-size-adjust:none;")= text
9 |
13 |
14 |
--------------------------------------------------------------------------------
/views/email/forgotpassword/forgotpassword.html.jade:
--------------------------------------------------------------------------------
1 | include ../buttons
2 | extends ../layout
3 |
4 | block title
5 | h1 Password Reset
6 |
7 | block content
8 | if name
9 | p Hi #{name},
10 | p You've recently asked us to reset the password for your account at #{to}. To complete this process, please click the button below.
11 | p If you didn't make this request, please feel free to ignore this email.
12 |
13 | block actions
14 | if activated
15 | +email-button("Reset My Password", baseUrl + '/reset_password/' + key)
16 | else
17 | +email-button("Set My Password", baseUrl + '/activate?email=' + encodeURIComponent(to) + '&key=' + key)
--------------------------------------------------------------------------------
/views/email/layout.jade:
--------------------------------------------------------------------------------
1 |
2 | html
3 | head
4 | meta(http-equiv="Content-Type", content="text/html; charset=utf-8")
5 | meta(name="viewport", content="width=device-width")
6 | style(type="text/css")
7 | include style.css
8 | body
9 | table.body(cellpadding="0", cellspacing="0")
10 | tr
11 | td.header-gradient(bgcolor="#e6bbb6", style="background-color: #e6bbb6;height: 15px;background-image: url(http://i.imgur.com/rGNZrUx.png);background-size: 100%;background-image: -webkit-gradient(linear,0 50%,100% 50%,color-stop(0%,#cfd8ff),color-stop(100%,#ff9b65));background-image: -webkit-linear-gradient(left,#cfd8ff,#ff9b65);background-image: -webkit-linear-gradient(left, #cfd8ff, #ff9b65);background-image: linear-gradient(to right,#cfd8ff,#ff9b65);padding: 0;line-height: 0;margin: 0;")
12 |
17 |
18 |
19 |
23 | tr
24 | td.center
25 | table.container
26 | tr
27 | td
28 | a(href="http://pulse.responsive.org")
29 | img.logo(alt="Responsive Pulse", width="206", height="80" src="http://i.imgur.com/0lUWCu0.png")
30 | tr
31 | td.box-hold
32 | table
33 | tr
34 | td.box
35 | block title
36 | block content
37 | .actions
38 | block actions
39 | tr
40 | td.footer An Undercurrent Platform
41 | tr
42 | td.spacer
--------------------------------------------------------------------------------
/views/email/logo.html:
--------------------------------------------------------------------------------
1 |
37 |
--------------------------------------------------------------------------------
/views/email/registration/registration.html.jade:
--------------------------------------------------------------------------------
1 | include ../buttons
2 | extends ../layout
3 |
4 | block title
5 | h1 Your account has been created.
6 |
7 | block content
8 | p Thank you for creating an account with Responsive Pulse. You'll need to activate your account before you can sign in.
9 |
10 | block actions
11 | +email-button('Activate', baseUrl + '/reg_activate?email=' + encodeURIComponent(to) + '&key=' + key)
12 |
--------------------------------------------------------------------------------
/views/email/surveysharedhasaccount/surveysharedhasaccount.html.jade:
--------------------------------------------------------------------------------
1 | include ../buttons
2 | extends ../layout
3 |
4 | block title
5 | h1 Survey shared with you
6 |
7 | block content
8 | if invitee
9 | p Hi #{invitee},
10 | p #{inviter} has added you as a#{role.match(/^[aeiou]/) ? 'n' : ''} #{role} on their #{survey.title} survey. Click the button below to view.
11 |
12 | block actions
13 | +email-button('View Survey', baseUrl + '/view/' + survey.key)
14 |
--------------------------------------------------------------------------------
/views/email/surveysharednoaccount/surveysharednoaccount.html.jade:
--------------------------------------------------------------------------------
1 | include ../buttons
2 | extends ../layout
3 |
4 | block title
5 | h1 Survey shared with you
6 |
7 | block content
8 | p Hi there,
9 | p
10 | | #{inviter}
11 | if organization
12 | | at #{organization}
13 | | has added you as a#{role.match(/^[aeiou]/) ? 'n' : ''} #{role} on their #{survey.title} survey. Click the button below to create an account and start collaborating!
14 |
15 | block actions
16 | +email-button('Get Started', baseUrl + '/activate?email=' + encodeURIComponent(email) + '&key=' + inviteeKey)
17 |
--------------------------------------------------------------------------------
/views/form-welcome.jade:
--------------------------------------------------------------------------------
1 | extends layouts/base
2 | include partials/formcomponents
3 |
4 | block content
5 | main
6 | section
7 | .row
8 | .card.card-full
9 | .card-body
10 | +progress(page, totalPages, null, 'Welcome')
11 | form.form--group.form(method="post")
12 | .form--group
13 | if welcomeMessage
14 | p!= welcomeMessage
15 | else
16 | p Today's largest and most important organizations face two critical challenges: First, the world around them is changing faster than ever, and second, their own scale and complexity are working against them. To be successful in the future, we, as other organizations, need to strive to become more responsive – responsive to our customers, culture, competition, technology, regulation, and all other forces of disruption.
17 | p
18 | | To help us think about the future in a careful, deliberate way as we map out our vision for the future, we’re administering a brief survey to determine how responsive we are as an organization today. We think of it as taking our company’s ‘pulse.’
19 | strong The survey is fully anonymous
20 | | and will help us understand our strengths, biases, values, capabilities, and patterns of working.
21 | p
22 | | Our understanding requires everyone’s participation and honest input. So please participate,
23 | em it only takes five minutes to complete.
24 | .form--group
25 | input(type="hidden", name="_csrf", value=csrfToken)
26 | button.button.button--primary(type="submit") Begin
27 | +progress(page, totalPages, true)
28 |
29 | append scripts
30 | script(src="/js/form.js")
--------------------------------------------------------------------------------
/views/form.jade:
--------------------------------------------------------------------------------
1 | extends layouts/base
2 | include partials/formcomponents
3 |
4 | block content
5 | main
6 | section
7 | .row
8 | .card.card-full
9 | .card-body
10 | +progress(page, totalPages)
11 | .card-intro!= template.header || ''
12 | form.form--group.form(method="post")
13 | for field in template.fields
14 | .form--group
15 | case field.type
16 | when "text"
17 | +input-text(field.name, field.placeholder, form[ field.name ] || field.default || '', fillIn(field.label, surveyValues), false, true)(required="")
18 | when "textarea"
19 | +input-textarea(field.name, field.placeholder, form[ field.name ] || field.default || '', fillIn(field.label, surveyValues))
20 | when "email"
21 | +input-email(field.name, field.placeholder, form[ field.name ] || field.default || '', fillIn(field.label, surveyValues))(required="")
22 | when "radio"
23 | +input-radio(field.name, field.options, fillIn(field.label, surveyValues), form[ field.name ])(required="")
24 | when "select"
25 | +input-select(field.name, field.options, fillIn(field.label, surveyValues), form[ field.name ], true)
26 | when "teamselect"
27 | +input-teamselect(field.name, teams, fillIn(field.label, surveyValues), form[ field.name ])
28 | when "checkbox"
29 | if field.options
30 | +input-checkbox(field.name, field.options, fillIn(field.label, surveyValues), form[ field.name ])
31 | else
32 | +input-single-checkbox(field.name, field.value || '1', fillIn(field.label, surveyValues), form[ field.name ])
33 | when "gridselect"
34 | +input-gridselect(field.name, field.options, form)(required="")
35 | when "slider"
36 | +input-slider(field.name, fillIn(field.label, surveyValues), field.min.value, field.max.value, field.min.label, field.max.label, form[ field.name ], true)
37 | default
38 | .form--element-label Not yet implemented.
39 | .form--group
40 | input(type="hidden", name="_csrf", value=csrfToken)
41 | if page > 1
42 | a(href="./" + (page - 1)).button Back
43 | button.button.button--primary(type="submit")= (page == totalPages ? 'Submit' : 'Next')
44 | +progress(page, totalPages, true)
45 |
46 | append scripts
47 | script(src="/js/form.js")
48 |
--------------------------------------------------------------------------------
/views/forms.jade:
--------------------------------------------------------------------------------
1 | extends layouts/base
2 |
3 | include partials/formcomponents
4 |
5 | block content
6 | main
7 | section
8 | .row
9 | .card.card-full
10 | .card-body
11 | h2.card-title Form Elements
12 | .form
13 | .form--group
14 | label.form--element.form--element-text
15 | span.form--element-label Text Input
16 | input.form--element-control(type="text", placeholder="Placeholder")
17 | label.form--element.form--element-textarea
18 | span.form--element-label Text Area
19 | textarea.form--element-control
20 | .form--group
21 | .form--element-label Radio Buttons
22 | .form--element.form--element-radio
23 | input(type="radio", name="radio", value="r1").form--element-control#r1
24 | label(for="r1").span.form--element-value Radio Choice 1
25 | .form--element.form--element-radio
26 | input(type="radio", name="radio", value="r2").form--element-control#r2
27 | label(for="r2").span.form--element-value Radio Choice 2
28 | .form--group
29 | .form--element-label Checkboxes
30 | .form--element.form--element-checkbox
31 | input(type="checkbox", name="check", value="c1").form--element-control#c1
32 | label(for="c1").span.form--element-value Checkbox 1
33 | .form--element.form--element-checkbox
34 | input(type="checkbox", name="check", value="c2").form--element-control#c2
35 | label(for="c2").span.form--element-value Checkbox 2
36 | .form--group
37 | .form--element-label Sliders
38 | +input-slider("slider", 0, 50, "Low", "High")
39 | .form--group
40 | label.form--element.form--element-select
41 | span.form--element-label Select Box
42 | select.form--element-control
43 | option(selected) Option 1
44 | option Option 2
45 | option Option 3
46 | .form--group
47 | span.form--element-label Grid Select
48 | +input-gridselect("Grid Select", {
49 | "gs1": "Our purpose serves and mobilizes a community of users and partners.",
50 | "gs2": "Our purpose empowers my decision making."
51 | })
52 | .form--group
53 | span.form--element-label Buttons
54 | a(href="#").button Default Button
55 | a(href="#").button.button--primary Primary Button
56 | a(href="#").button.button--danger Danger Button
57 |
58 | block require
59 | | require(['main']);
60 |
--------------------------------------------------------------------------------
/views/landing.jade:
--------------------------------------------------------------------------------
1 | extends layouts/base
2 |
3 | block content
4 | main
5 | .hero
6 | .hero--inner
7 | .hero--text
8 | h1 Build a better organization.
9 | p Get your team ready for the 21st century.
10 | | Find the changes that will make the quickest impact.
11 | | Track changes towards responsiveness.
12 | .hero--cta
13 | a.button.button--action(href="/signup") Get started — it's free!
14 | section
15 | .card.card-full.card--spacious
16 | .card-body
17 | h1.title.title-xlarge Is your organization prepared for the 21st century?
18 | p.copy-large The organizations we have today are unfit for the next century. Rigid hierarchies, inflexible matrix reporting structures, meeting-rich schedules, and decision-rights for only the most tenured members – these “features” prevent any real work from getting done.
19 | p.copy-large We’ve been studying groups that work in a new way and as a result have a bigger impact on the world than their competitors. We’ve been using this tool for that research and for our paying clients. It’s now available for free. Find out how your organization measures up.
20 | section.landing-screenshots
21 | .row
22 | .landing-screenshot
23 | img(src="/img/screenshots/dashboard.png")
24 | .landing-screenshot
25 | img(src="/img/screenshots/survey.png")
26 | .landing-screenshot
27 | img(src="/img/screenshots/results.png")
28 | section
29 | .card.card-full.card--spacious
30 | .card-body
31 | h1.title.title-xlarge Measuring Up.
32 | p.copy-large Administering the Responsive OS Pulse is easy. Once you create an account you'll be given a link you can distribute to your teams. Results can are analyzed in real-time and along a variety of dimensions.
33 | p.copy-large Intoning the principles measured by Responsive Pulse affords an organization greater responsiveness to competition, culture, technology, and all other forms of disruption. We believe these principles – and their associated implications on ways of working – dramatically improve even the largest and most traditional institutions – for employees, for customers, and for shareholders.
34 | p.sub-title Your data will never be shared, sold, or made public. We promise to anonymize it if it's used to establish benchmarks or improve the tool's accuracy.
35 |
--------------------------------------------------------------------------------
/views/layouts/auth_base.jade:
--------------------------------------------------------------------------------
1 | extends base
2 |
3 | prepend scripts
4 | if survey
5 | script| var survey = '#{survey.key}';
6 | script(src="/js/bundle.js")
--------------------------------------------------------------------------------
/views/layouts/base.jade:
--------------------------------------------------------------------------------
1 | - function numberWithCommas(x) {
2 | - return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
3 | - }
4 | doctype html
5 | html
6 | head
7 | meta(charset='utf-8')
8 | meta(name='description', content='')
9 | meta(name='HandheldFriendly', content='True')
10 | meta(name='MobileOptimized', content='320')
11 | meta(name='viewport', content='width=device-width, initial-scale=1, minimal-ui')
12 | meta(http-equiv='cleartype', content='on')
13 | title Responsive OS Pulse — An Undercurrent Product
14 | //- Fonts
15 | link(href='http://fonts.googleapis.com/css?family=Merriweather:400,400italic,700,700italic&subset=latin,latin-ext', rel='stylesheet' type='text/css')
16 | //- FontAwesome
17 | link(href='//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css', rel='stylesheet')
18 | link(rel='stylesheet', href='/css/main.css')
19 | //- Pingdom Monitoring
20 | script.
21 | var _prum = [['id', '54aac1d6abe53dd673f771ae'],
22 | ['mark', 'firstbyte', (new Date()).getTime()]];
23 | (function() {
24 | var s = document.getElementsByTagName('script')[0]
25 | , p = document.createElement('script');
26 | p.async = 'async';
27 | p.src = '//rum-static.pingdom.net/prum.min.js';
28 | s.parentNode.insertBefore(p, s);
29 | })();
30 | block head
31 | body(class=classes.join(' '))
32 | include /partials/cookie-error
33 | include /partials/nav
34 |
35 | block content
36 |
37 | footer.footer
38 | .footer--credit
39 | | © Undercurrent LLC, 2015.
40 | .footer--legal
41 | a(href="/terms") Terms of Use
42 |
43 | script(src='/js/shared.js')
44 | block scripts
45 |
46 | script.
47 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
48 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
49 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
50 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
51 |
52 | ga('create', 'UA-59741696-1', 'auto');
53 | ga('send', 'pageview');
54 |
--------------------------------------------------------------------------------
/views/partials/cookie-error.jade:
--------------------------------------------------------------------------------
1 | #cookies
2 | p Cookies don't seem to be enabled in your browser. To use the Pulse, you must enable cookies.
3 | p
4 | | To learn how to manage your cookies, please visit your web browser's help center:
5 | a(href="https://support.google.com/chrome/answer/95647?hl=en", target="_blank") Chrome
6 | | ,
7 | a(href="http://windows.microsoft.com/en-us/internet-explorer/delete-manage-cookies#ie=ie-11", target="_blank") Internet Explorer
8 | | ,
9 | a(href="https://support.mozilla.org/en-US/kb/cookies-information-websites-store-on-your-computer", target="_blank") Firefox
10 | | ,
11 | a(href="https://support.apple.com/kb/PH19214?viewlocale=en_US&locale=en_US", target="_blank") Safari
--------------------------------------------------------------------------------
/views/partials/formcomponents.jade:
--------------------------------------------------------------------------------
1 | include formcomponents/email
2 | include formcomponents/password
3 | include formcomponents/text
4 | include formcomponents/textarea
5 | include formcomponents/select
6 | include formcomponents/radio
7 | include formcomponents/checkbox
8 | include formcomponents/single-checkbox
9 | include formcomponents/form
10 | include formcomponents/gridselect
11 | include formcomponents/progress
12 | include formcomponents/slider
13 | include formcomponents/teamselect
--------------------------------------------------------------------------------
/views/partials/formcomponents/checkbox.jade:
--------------------------------------------------------------------------------
1 | mixin input-checkbox(name, options, label, value)
2 | if label
3 | .form--element-label= label
4 | each val, key in options
5 | label.form--element.form--element-checkbox
6 | input.form--element-control(type="checkbox", name=name, value=key, checked=(value == key ? true : false))
7 | .form--element-value=val
--------------------------------------------------------------------------------
/views/partials/formcomponents/email.jade:
--------------------------------------------------------------------------------
1 | mixin input-email(name, placeholder, value, label, readonly)
2 | if messages.error
3 | - errors = messages.error[0];
4 | if typeof errors[name] !== 'undefined'
5 | - error = errors[name]
6 | else
7 | - error = undefined
8 |
9 | .form--area
10 | label.form--element.form--element-text(class=(error ? "form--element-invalid" : ""))
11 | if label
12 | .form--element-label= label
13 | if readonly
14 | +form-change-button()
15 | input.form--element-control(type="email", name=name, data-parsley-trigger="change", value=value, placeholder=placeholder, readonly=(readonly == true ? "readonly" : undefined))&attributes(attributes)
16 | if error
17 | .form--area-errors
18 | ul.form--element-invalid-message.filled
19 | li.parsley-required= error
20 | else
21 | .form--area-errors
22 |
--------------------------------------------------------------------------------
/views/partials/formcomponents/form.jade:
--------------------------------------------------------------------------------
1 | mixin form-group(readonly)
2 | form.form--group(class=(readonly ? "form--readonly" : undefined), method="POST")&attributes(attributes)
3 | block
4 |
5 | mixin form-buttons()
6 | .form-row
7 | .form-row-button
8 | button.button.button--clear-changes Clear Changes
9 | .form-row-button.last
10 | button.button.button--primary Save Changes
11 | .form-feedback
12 | .form-submitted-success-label
13 | i.fa.fa-check
14 | span Changes saved!
15 | .form-submitted-fail-label
16 | i.fa.fa-times
17 | span Could not save changes. Please try again.
18 |
19 | mixin form-change-button()
20 | .form--element-status
21 | span.form--element-edited-label edited
22 | button.button--form-change change
--------------------------------------------------------------------------------
/views/partials/formcomponents/password.jade:
--------------------------------------------------------------------------------
1 | mixin input-password(name, placeholder, value, label, readonly)
2 | if readonly
3 | .form--element.form--password
4 | .form--password-old
5 | label.form--element.form--element-text
6 | if label
7 | .form--element-label= label
8 | +form-change-button()
9 | .form--element-dummy.form--element-control(readonly=(readonly == true ? "readonly" : undefined))&attributes(attributes) ••••••••
10 | .form--password-confirm
11 | label.form--element.form--element-text
12 | .form--element-label Old Password
13 | input.form--element-control(type="password", name=name + "_old")&attributes(attributes)
14 | .form--password-reveal
15 | i.fa.fa-eye
16 | label.form--element.form--element-text
17 | .form--element-label New Password
18 | input.form--element-control(type="password", name=name)&attributes(attributes)
19 | .form--password-reveal
20 | i.fa.fa-eye
21 | else
22 | label.form--element.form--element-text
23 | if label
24 | .form--element-label= label
25 | input.form--element-control(type="password", name=name, value=value, placeholder=placeholder, readonly=(readonly == true ? "readonly" : undefined))&attributes(attributes)
--------------------------------------------------------------------------------
/views/partials/formcomponents/progress.jade:
--------------------------------------------------------------------------------
1 | mixin progress(page, totalPages, nav, title)
2 | .progress(class=nav ? " progress-nav" : "")
3 | .progress-bar
4 | .progress-bar-completed(style="width:" + (page / totalPages * 100) + "%")
5 | if nav
6 | .progress-pagination
7 | .progress-pagination-first
8 | span.pagination-link First
9 | .progress-pagination-last
10 | span.pagination-link Last
11 | .progress-pagination-center
12 | span="Page " + page + " of " + totalPages
13 | else
14 | .progress-label
15 | ="Page " + page + " of " + totalPages
16 | if template
17 | = template.title ? ": " + template.title : ""
18 | else if title
19 | = ": " + title
20 |
--------------------------------------------------------------------------------
/views/partials/formcomponents/radio.jade:
--------------------------------------------------------------------------------
1 | mixin input-radio(name, options, label, value)
2 | if messages.error
3 | - errors = messages.error[0];
4 | if typeof errors[name] !== 'undefined'
5 | - error = errors[name]
6 | else
7 | - error = undefined
8 |
9 | .form--area.form--area-stacked
10 | if label
11 | .form--element-label= label
12 | .form--element-radio-options
13 | each val, key in options
14 | label.form--element.form--element-radio
15 | input.form--element-control(type="radio", name=name, value=key, checked=((messages.field && messages.field[0][name] == key) || (value && value == key) ? true : false))&attributes(attributes)
16 | .form--element-value=val
17 | if error
18 | .form--area-errors
19 | ul.form--element-invalid-message.filled
20 | li.parsley-required= error
21 | else
22 | .form--area-errors
23 |
--------------------------------------------------------------------------------
/views/partials/formcomponents/select.jade:
--------------------------------------------------------------------------------
1 | mixin input-select-option(name, key, val, value)
2 | option(value=key, selected=((messages.field && messages.field[0][name] == key) || (value && value == key) ? true : false))=val
3 |
4 | mixin input-select(name, options, label, value, stacked)
5 | .form--area(class=(stacked == true ? "form--area-stacked" : ""))
6 | label.form--element.form--element-select(class=(stacked == true ? "form--element-select-stacked" : ""))
7 | if label
8 | .form--element-label= label
9 | select.form--chosen-select.form--element-control(name=name)&attributes(attributes)
10 | if options instanceof Array
11 | each val in options
12 | +input-select-option(name, val, val, value)
13 | else
14 | each val, key in options
15 | +input-select-option(name, key, val, value)
16 |
--------------------------------------------------------------------------------
/views/partials/formcomponents/single-checkbox.jade:
--------------------------------------------------------------------------------
1 | mixin input-single-checkbox(name, value, label, stateValue)
2 | .form--area
3 | if label
4 | label.form--element.form--element-checkbox
5 | input.form--element-control(type="checkbox", name=name, value=value, checked=(stateValue == value ? true : false))
6 | .form--element-value=label
7 |
--------------------------------------------------------------------------------
/views/partials/formcomponents/slider.jade:
--------------------------------------------------------------------------------
1 | mixin input-slider(name, label, min, max, minLabel, maxLabel, value, stacked)
2 | .form--area(class=(stacked == true ? "form--area-stacked" : ""))
3 | label.form--element.form--element-slider
4 | if label
5 | .form--element-label= label
6 | .form--element-control
7 | if minLabel || maxLabel
8 | .rangeslider-labels
9 | .rangeslider-label-min=minLabel
10 | .rangeslider-label-max=maxLabel
11 | .range-value
12 | .range-value-label= min
13 | .range-value-arrow
14 | input(type="range", name=name, min=min, max=max, value=(value ? value : min))&attributes(attributes)
15 |
--------------------------------------------------------------------------------
/views/partials/formcomponents/teamselect.jade:
--------------------------------------------------------------------------------
1 | mixin input-teamselect(name, teams, label, value)
2 | .form--area.form--area-stacked
3 | if !teams || teams.length === 0
4 | label.form--element.form--element-text.form--element-text-stacked.form--element-teamselect
5 | if label
6 | .form--element-label= label
7 | input(type="text", name="team").form--element-control
8 | else
9 | label.form--element.form--element-teamselect
10 | if label
11 | .form--element-label= label
12 | select.form--element-control(name="team")&attributes(attributes).teamselect--dropdown.is-shown
13 | each val in teams
14 | option(value=val, selected=((messages.field && messages.field[0][name] == val) || (value && value == val) ? true : false))=val
15 | option(value="-" disabled) -------------
16 | option(value="newteam") Add Team
17 | input(type="text").form--element-control.teamselect--text
--------------------------------------------------------------------------------
/views/partials/formcomponents/text.jade:
--------------------------------------------------------------------------------
1 | mixin input-text(name, placeholder, value, label, readonly, stacked)
2 | if messages.fieldErrors
3 | - errors = messages.fieldErrors[0]
4 | if errors[name]
5 | - error = errors[name].type
6 | else
7 | - error = undefined
8 |
9 | .form--area(class=(stacked == true ? "form--area-stacked" : ""))
10 | label.form--element.form--element-text(class=(stacked == true ? "form--element-text-stacked" : ""), class=(error ? "form--element-invalid" : ""))
11 | if label
12 | .form--element-label= label
13 | if readonly
14 | +form-change-button()
15 | input.form--element-control(type="text", name=name, value=(messages.field && messages.field[0][name] ? messages.field[0][name] : value), placeholder=placeholder, readonly=(readonly == true ? "readonly" : undefined))&attributes(attributes)
16 | if error
17 | .form--area-errors
18 | ul.form--element-invalid-message.filled
19 | li.parsley-required= ({ 'required': 'This field is required.' })[error]
20 | else
21 | .form--area-errors
22 |
--------------------------------------------------------------------------------
/views/partials/formcomponents/textarea.jade:
--------------------------------------------------------------------------------
1 | mixin input-textarea(name, placeholder, value, label, readonly, rows)
2 | .form--area
3 | label.form--element.form--element-textarea
4 | if label
5 | .form--element-label!= label
6 | if readonly
7 | +form-change-button()
8 | textarea.form--element-control(name=name, rows=rows || 5, placeholder=placeholder, readonly=(readonly == true ? "readonly" : undefined))&attributes(attributes)
9 | if block
10 | block
11 | else
12 | != messages.field && messages.field[0][name] ? messages.field[0][name] : value
--------------------------------------------------------------------------------
/views/partials/modal.jade:
--------------------------------------------------------------------------------
1 | mixin modal(name)
2 | .modal(class=name)
3 | .modal--body
4 | block
5 | a.modal--close Close
6 | .modal--overlay
--------------------------------------------------------------------------------
/views/partials/modals/share.jade:
--------------------------------------------------------------------------------
1 | .modal--section
2 | h3.modal--section-title Already shared with
3 | ul.share--list
4 | - var activeUser = survey.users.filter( function( u ) { try { return u.user.id == user.id; } catch( e ) { return false; } } )
5 | if activeUser.length
6 | - activeUser = activeUser[ 0 ];
7 | else
8 | - activeUser = { permissions: [] };
9 | - var ownerCount = survey.users.reduce( function( prev, cur ) { return cur.permissions.indexOf( 'owner' ) > -1 ? prev + 1 : prev; }, 0 );
10 | script.
11 | var activeSurveyUser = { perms: !{JSON.stringify(activeUser.permissions)}, roles: !{JSON.stringify(user.roles)} };
12 | if survey.users.filter( function( u ) { return u & u.user; } ).length
13 | for u in survey.users
14 | if u && u.user
15 | li.share--list-item(data-email=u.user.email)
16 | if u.user.profile && u.user.profile.name
17 | .share--list-name
18 | = u.user.profile.name
19 | span
20 | | (
21 | = u.user.email
22 | | )
23 | else
24 | .share--list-name= u.user.email
25 | .share--list-permissions
26 | span(class=(user.hasRole('admin') || activeUser.permissions.indexOf('owner') > -1 ? 'share--list-permissions--hidden' : ''))
27 | for perm in validPermissions
28 | if u.permissions.indexOf(perm) > -1
29 | = perm.split( ' ' ).map( function( p ) { return p[0].toUpperCase() + p.slice(1).toLowerCase() } ).join( ' ' )
30 | select(class=(!user.hasRole('admin') && activeUser.permissions.indexOf('owner') === -1 ? 'share--list-permissions--hidden' : ''))
31 | for perm in validPermissions
32 | option(value=perm, selected=(u.permissions.indexOf(perm) > -1))
33 | = perm.split( ' ' ).map( function( p ) { return p[0].toUpperCase() + p.slice(1).toLowerCase() } ).join( ' ' )
34 | - var surveyUserIsOwnerAndActiveUserIsNot = (activeUser.permissions.indexOf('owner') === -1 && u.permissions.indexOf('owner') > -1);
35 | - var activeUserIsOnlyOwner = (user.id === u.user.id && activeUser.permissions.indexOf('owner') > -1 && ownerCount === 1);
36 | if (surveyUserIsOwnerAndActiveUserIsNot || activeUserIsOnlyOwner) && !user.hasRole('admin')
37 | .share--list-remove.share--list-remove--hidden Remove
38 | else
39 | .share--list-remove Remove
40 | else
41 | li.share--list-nobody
42 | div: strong Nobody, yet!
43 | p Add someone using their email address below to share these results.
44 | li.share--list-item.share--list-item-template(data-email='')
45 | .share--list-name
46 | .share--list-permissions
47 | span(class=(user.hasRole('admin') || activeUser.permissions.indexOf('owner') > -1 ? 'share--list-permissions--hidden' : ''))
48 | select(class=(!user.hasRole('admin') && activeUser.permissions.indexOf('owner') === -1 ? 'share--list-permissions--hidden' : ''))
49 | for perm in validPermissions
50 | option(value=perm)
51 | = perm.split( ' ' ).map( function( p ) { return p[0].toUpperCase() + p.slice(1).toLowerCase() } ).join( ' ' )
52 | .share--list-remove Remove
53 | .modal--section
54 | h3.modal--section-title Add Collaborator
55 | .share--new
56 | .share--new-input
57 | input(type="text", name="email", placeholder="Email Address")
58 | input(type="hidden", name="_csrf", value=csrfToken)
59 | .share--new-submit
60 | a.button.button--primary Share
--------------------------------------------------------------------------------
/views/partials/nav.jade:
--------------------------------------------------------------------------------
1 | mixin nav
2 | if user
3 | .nav--menu
4 | .nav--menu-item.dropdown
5 | .dropdown--menu-parent
6 | = user.profile.name
7 | .dropdown--menu
8 | a.dropdown--menu-item(href="/dashboard", class=is(['/dashboard', 'is-active'])) My Surveys
9 | if user.hasRole( 'admin' )
10 | a.dropdown--menu-item(href="/admin/users", class=is(['/admin/user', 'is-active'])) All Users
11 | a.dropdown--menu-item(href="/account", class=is(['/account', 'is-active'])) My Account
12 | a.dropdown--menu-item(href="/logout") Sign Out
13 | else
14 | .nav--menu
15 | a.nav--menu-item(href="/login" class=is(['/login', 'is-active'])) Log In
16 | a.nav--menu-item.nav--menu-cta(href="/signup" class=is(['/signup', 'is--active'])) Sign Up
17 |
18 | nav.nav
19 | .nav--container
20 | a(href="/")
21 | .nav--logo
22 | .nav--logo-img
23 | img(src="/img/logo.svg")
24 | .nav--logo-title Responsive Pulse
25 | +nav
26 |
--------------------------------------------------------------------------------
/views/survey/about.jade:
--------------------------------------------------------------------------------
1 | extends /layouts/auth_base
2 |
3 | block content
4 | main
5 | section
6 | .row
7 | .card.card-full
8 | .card-body
9 | h2.card-title More About the Responsive OS Pulse
10 | .card-intro.voice-ros
11 | p
12 | object(type='application/x-shockwave-flash', style='width: 100%; height: 340px;', data='http://vimeo.com/moogaloop.swf?clip_id=65692182&server=vimeo.com&show_title=0&show_byline=0&show_portrait=0&color=ff9933&fullscreen=1', allowfullscreen='true', allowscriptaccess='always')
13 | param(name='movie', value='http://vimeo.com/moogaloop.swf?clip_id=65692182&server=vimeo.com&show_title=0&show_byline=0&show_portrait=0&color=ff9933&fullscreen=1', allowfullscreen='true', allowscriptaccess='always')
14 | p
15 | | Hello there, we're
16 | a(href='http://undercurrent.com') Undercurrent
17 | | , a management consultancy with offices in NY and LA. We help our clients thrive in the face of unprecedented uncertainty and rampant organizational complexity. We call it 'being responsive.' Our clients (which include GE, AMEX, Pepsi, Ford, and The Gates Foundation) call it speed, efficiency, or foresight. Regardless of how you label it, after our engagements our clients are faster, leaner, and yet more adaptive to changes in their markets.
18 | p
19 | strong We're obsessed with how organizations work and how to make them work harder.
20 | | We've spent the last 7 years studying the most ambitious organizations of our time — a group of over 80 firms, including Amazon, Tesla, Airbnb, Uber, Netflix, and Apple. Our work is also heavily influenced by the study of complex systems, an emerging scientific field that investigates the behavior of a large system of interacting agents (like a stock market, immune system, or hill of ants).
21 | p
22 | | Combining these two approaches, we've built, tested, and iterated our organizational model of everything — the Responsive Operating System — which guides and informs our work, whether we're helping to pick a new CEO, auditing a manufacturing system, devising a product strategy, or helping a firm re-write its mission.
23 |
--------------------------------------------------------------------------------
/views/survey/done.jade:
--------------------------------------------------------------------------------
1 | extends /layouts/base
2 |
3 | block content
4 | main
5 | section
6 | .card.card-full.card--spacious.type-center.card--success
7 | .card-body
8 | .card-title You're finished!
9 | p Now it's our turn to tabulate the responses.
10 | | Interested in learning more about responsive organizations? Visit Undercurrent.com.
--------------------------------------------------------------------------------
/views/survey/even-over/overview.jade:
--------------------------------------------------------------------------------
1 | extends /layouts/auth_base
2 | include /partials/modal
3 | include /survey/header
4 |
5 | block content
6 | +modal('modal--share')
7 | include /partials/modals/share
8 |
9 | main
10 | section
11 | +header
12 | .results--nav-menu
13 | if typeof survey !== 'undefined' && survey.responseCount
14 | a.results--nav-menu-item(href='/view/' + survey.key, class=is([new RegExp('/view/' + survey.key + '$'), 'is-active']))
15 | i.fa.fa-bookmark
16 | | Overview
17 | a.results--nav-menu-item(href='/view/' + survey.key + "/detail", class=is(['/view/' + survey.key + '/detail', 'is-active']))
18 | i.fa.fa-bar-chart
19 | | Detailed Results
20 | .ovw-container
21 | if typeof survey !== 'undefined' && survey.responseCount
22 | .g--row
23 | .ovw-card-rowHeader.sticky-header: span Responsiveness
24 | .chart.bar.responsiveness(data-margins=",,,40")
25 | .g--row
26 | .g--half.identifies-most
27 | script.template(type="text/html").
28 | Your organization identifies most with:
29 | <%= trait.fullTitle %>
30 | <%= marked(trait.blurb) %>
31 | .g--half.g--last.identifies-least
32 | script.template(type="text/html").
33 | Your organization identifies least with:
34 | <%= trait.fullTitle %>
35 | <%= marked(trait.blurb) %>
36 | else
37 | .g--row
38 | .g--full.no-responses
39 | h2 Waiting for responses…
40 | h3
41 | a(href='/about') Learn more
42 | | about Responsive OS.
43 |
--------------------------------------------------------------------------------
/views/survey/header.jade:
--------------------------------------------------------------------------------
1 | include nav
2 |
3 | mixin header
4 | .sticky-nav
5 | .results--nav
6 | +surveyNav
7 | if block
8 | block
9 | .ovw-header
10 | .ovw-header-inner
11 | if typeof survey !== 'undefined' && survey.responseCount
12 | .ovw-scoring
13 | .ovw-scoring--inner
14 | .ovw-scoring--responses
15 | .chart.number.responses
16 | .data-placeholder--number 0
17 | .ovw-scoring--responses-total
18 | | of 0
19 | .ovw-scoring--label
20 | | Responses
21 | .ovw-scoring--responses
22 | .chart.number.incomplete
23 | .data-placeholder--number 0
24 | .ovw-scoring--label
25 | | Incomplete
26 | .ovw-scoring--score
27 | .chart.number.score
28 | .data-placeholder--number 0
29 | .ovw-scoring--label
30 | | Score
31 | .ovw-scoring--score-diff
32 | .score-diff-up ▲
33 | .score-diff-num
34 | | 0%
35 | .score-diff-down ▼
36 | .ovw-scoring--percentile.number.percentile
37 | a.ovw-scoring--reset Reset Filters
38 | h2.survey-title= survey.title
39 | .survey-info
40 | if survey.createdAt
41 | | Created on
42 | = moment(survey.createdAt).format('MMM D, YYYY')
43 | | ·
44 | | Last updated
45 | span.date-underline(title = survey.lastUpdate ? moment(survey.lastUpdate).format('MMM D, YYYY') : 'Never')=moment(survey.lastUpdate).fromNow()
46 | .clearfix
47 |
--------------------------------------------------------------------------------
/views/survey/nav.jade:
--------------------------------------------------------------------------------
1 | mixin surveyNav
2 | if block
3 | block
4 | .results--nav-actions
5 | a.results--nav-menu-item.modal--open(data-modalname='share')
6 | i.fa.fa-fw.fa-share
7 | | Share
8 | a.results--nav-menu-item(href='/export/' + survey.key)
9 | i.fa.fa-fw.fa-download
10 | | Export
11 |
--------------------------------------------------------------------------------
/views/survey/new.jade:
--------------------------------------------------------------------------------
1 | extends /layouts/auth_base
2 |
3 | include /partials/formcomponents
4 |
5 | block content
6 | main
7 | section
8 | .row
9 | .card.card-half.card-center.card--create
10 | .card-body
11 | h1.card-title Create New Survey
12 | form.form(method="post")
13 | if messages.error
14 | ul.form--errors
15 | for error in messages.error
16 | li= error
17 | if messages.success
18 | ul.form--success
19 | for success in messages.success
20 | li= success
21 | .form--group
22 | label.form--element.form--element-text
23 | +input-text("title", "e.g., June Responsive Inquiry", form["title"] || "", "Survey Name", false, true)(autofocus="")
24 | .form--group
25 | label.form--element.form--element-select
26 | +input-select("orgSize", orgSizes, "How many people work within your organization?", "", true)
27 | label.form--element.form--element-text
28 | +input-text("orgAge", "in years", "", "How old is your organization?", false, true)(type="number")
29 | label.form--element.form--element-select
30 | +input-select("industry", industries, "What primary industry does your organization work within?", "", true)
31 | .form--group
32 | label.form--element.form--element-text
33 | +input-text("orgLeader", "e.g., John Smith", "", "What is the name of the person who leads this organization?", false, true)
34 | label.form--element.form--element-text
35 | +input-text("orgLeaderRole", "e.g., Director of Marketing", "", "What is their title?", false, true)
36 | +input-textarea("welcomeMessage", "Please take this survey...", "", "Provide a welcome message that users will see upon first visiting the survey (formatted using Markdown):", false, 16).
37 | Today's largest and most important organizations face two critical challenges: First, the world around them is changing faster than ever, and second, their own scale and complexity are working against them. To be successful in the future, we, as other organizations, need to strive to become more responsive – responsive to our customers, culture, competition, technology, regulation, and all other forces of disruption.
38 |
39 | To help us think about the future in a careful, deliberate way as we map out our vision for the future, we’re administering a brief survey to determine how responsive we are as an organization today. We think of it as taking our company’s ‘pulse.’ *The survey is fully anonymous* and will help us understand our strengths, biases, values, capabilities, and patterns of working.
40 |
41 | Our understanding requires everyone’s participation and honest input. So please participate, _it only takes five minutes to complete._
42 | .form--group
43 | label.form--element.form--element-textarea
44 | input(type="hidden", name="_csrf", value=csrfToken)
45 | button(type="submit").button.button--primary Create
--------------------------------------------------------------------------------
/views/survey/original/nav.jade:
--------------------------------------------------------------------------------
1 | .results--nav-menu
2 | a.results--nav-menu-item(href='/view/' + survey.key) Overview
3 | a.results--nav-menu-item(href='/view/' + survey.key + '/scores/purpose') Purpose
4 | a.results--nav-menu-item(href='/view/' + survey.key + '/scores/people') People
5 | a.results--nav-menu-item(href='/view/' + survey.key + '/scores/process') Process
6 | a.results--nav-menu-item(href='/view/' + survey.key + '/scores/product') Product
7 | a.results--nav-menu-item(href='/view/' + survey.key + '/scores/platform') Platform
--------------------------------------------------------------------------------
/views/survey/original/overview.jade:
--------------------------------------------------------------------------------
1 | extends /layouts/auth_base
2 | include /partials/modal
3 | include /survey/header
4 |
5 | block content
6 | +modal('modal--share')
7 | include /partials/modals/share
8 |
9 | main
10 | section
11 | if typeof survey !== 'undefined' && survey.responseCount
12 | +header
13 | include nav
14 | .ovw-container
15 | if typeof survey !== 'undefined' && survey.responseCount
16 | .ovw-card-row
17 | .ovw-card-rowHeader.sticky-header: span Structure
18 | .ovw-cardGroup.g--row
19 | .ovw-card.g--third
20 | .card-body
21 | h2.card-title Hierarchy
22 | .card-data
23 | .chart.stacked(data-title='Hierarchy',
24 | data-col='management_layers',
25 | data-y-label='Layers beneath CEO')
26 | .data-placeholder--box
27 | .ovw-card.g--third
28 | .card-body
29 | h2.card-title Position vs. Traction
30 | .card-data
31 | .chart.matrixplot(data-title='Position vs. Traction'
32 | data-col-x='org_position', data-col-y='org_traction',
33 | data-x-values='following,leading', data-y-values='advancing,declining',
34 | data-x-labels='Following,Leading', data-y-labels='Advancing,Declining')
35 | .data-placeholder--box
36 | .ovw-card.g--third.g--last
37 | .card-body
38 | h2.card-title Tenure
39 | .card-data
40 | .chart.bar.tenure(data-title='Tenure',
41 | data-col='org_years',
42 | data-x-label='in years',
43 | filterable='')
44 | .data-placeholder--box
45 | .ovw-card-row.g--row
46 | .ovw-card-rowHeader.sticky-header: span Responsiveness
47 | .ovw-cardGroup
48 | .ovw-card.card-full
49 | .chart.line.responsiveness
50 | .ovw-card-row.g--row
51 | .ovw-card-rowHeader.sticky-header: span Score Breakdown
52 | .ovw-cardGroup
53 | .ovw-card.card-full
54 | .card-body
55 | .card-intro
56 | p.voice-system
57 | | Select a team from the drop-down menu to view their scores compared to the organization overall.
58 | .overall.card-data
59 | .data-controls
60 | .button-group
61 | span Team:
62 | select.filter(data-col='team', data-min-value='2')
63 | .table-wrapper
64 | table.table.horizontal.responsive
65 | thead
66 | tr
67 | td.row-header
68 | td.tooltip.right-offset-arrow(title='Why we come to work') Purpose
69 | td.tooltip.right-offset-arrow(title='The teams that do the work') People
70 | td.tooltip.right-offset-arrow(title='What we deliver') Process
71 | td.tooltip.right-offset-arrow(title='How the work gets done') Product
72 | td.tooltip.right-offset-arrow(title='What the community creates on our behalf') Platform
73 | td.row-summary.col-header Total
74 | tbody
75 | else
76 | .ovw-card-row
77 | .ovw-card.card-full
78 | .no-responses
79 | h1 Waiting for responses…
80 | h2
81 | a(href='/about') Learn more
82 | | about Responsive OS.
83 |
--------------------------------------------------------------------------------
/views/survey/original/scores.jade:
--------------------------------------------------------------------------------
1 | extends /layouts/auth_base
2 | include /partials/modal
3 | include /survey/header
4 |
5 | block content
6 | +modal('modal--share')
7 | include /partials/modals/share
8 |
9 | main
10 | section
11 | if typeof survey !== 'undefined' && survey.responseCount
12 | +header
13 | include nav
14 | .ovw-container
15 | .ovw-card-row
16 | .card.card-half
17 | .card-body
18 | h2.card-title= domain
19 | .card-data
20 | table.domain-table(data-domain=domain)
21 | thead
22 | tr
23 | th.label
24 | th Score
25 | tbody
26 | .data-placeholder--box
27 |
28 | .card.card-half.last
29 | .card-body
30 | h2.card-title Highest Agreement
31 | .card-data
32 | .chart.numberline.agreement(data-domain=domain)
33 | .data-placeholder--box.data-placeholder--third
34 | .data-placeholder--err Not enough data.
35 | .legend
36 | .legend-item.leadership Leadership
37 | .legend-item.team Team
38 | .legend-item.agreement Leadership & Team Agree
39 | .card-body
40 | h2.card-title Highest Disagreement
41 | .card-data
42 | .chart.numberline.disagreement(data-domain=domain)
43 | .data-placeholder--box.data-placeholder--third
44 | .data-placeholder--err Not enough data.
45 | .legend
46 | .legend-item.leadership Leadership
47 | .legend-item.team Team
48 | .ovw-card-row
49 | .card.card-full
50 | .card-body
51 | h2.card-title Verbatims
52 | .card-data
53 | ol.verbatims--list.table(data-domain=domain)
54 |
--------------------------------------------------------------------------------
/views/user/account.jade:
--------------------------------------------------------------------------------
1 | extends /layouts/auth_base
2 |
3 | include /partials/formcomponents
4 |
5 | block content
6 | main
7 | section
8 | .row
9 | .card.card-full.card--spacious
10 | .card-body
11 | .card-row
12 | +form-group(true)(action="/account")
13 | h3 Basic Information
14 | +input-text("name", null, user.profile.name || "", "Name", true)(required="")
15 | +input-text("organization", null, user.profile.organization || "", "Organization", true)(required="")
16 | +input-email("email", null, user.email, "E-mail", true)(required="")
17 | +input-password("password", null, "", "Password", true)
18 | input(type="hidden", name="_csrf", value=csrfToken)
19 | +form-buttons
20 |
--------------------------------------------------------------------------------
/views/user/forgot.jade:
--------------------------------------------------------------------------------
1 | extends /layouts/base
2 |
3 | include /partials/formcomponents
4 |
5 | block content
6 | main
7 | section
8 | .auth--form
9 | h1.card-title Forgotten Password
10 | form.form(method="post")
11 | if messages.error
12 | ul.form--errors
13 | for error in messages.error
14 | li= error
15 | if messages.success
16 | .form--success= messages.success[0]
17 | .form--group
18 | label.form--element.form--element-text
19 | //- The 'email' field is named 'username' because that is what the
20 | //- passport library requires to validate on req.logIn.
21 | +input-email("email", "E-mail Address", messages.email)
22 | input(type="hidden", name="_csrf", value=csrfToken)
23 | button(type="submit").button.button--primary.login--submit Reset Password
24 | .login--signup Need an account? Sign up here
25 |
--------------------------------------------------------------------------------
/views/user/login.jade:
--------------------------------------------------------------------------------
1 | extends /layouts/base
2 |
3 | include /partials/formcomponents
4 |
5 | block content
6 | main
7 | section
8 | .auth--form
9 | h1 Log In
10 | form.form(method="post")
11 | if messages.error
12 | ul.form--errors
13 | for error in messages.error
14 | li= error
15 | if messages.success
16 | ul.form--success
17 | for success in messages.success
18 | li= success
19 | .form--group
20 | label.form--element.form--element-text
21 | //- The 'email' field is named 'username' because that is what the
22 | //- passport library requires to validate on req.logIn.
23 | +input-email("username", "E-mail Address", messages.email)
24 | label.form--element.form--element-text
25 | +input-password("password", "Password")
26 | a(href="/forgot").login--forgot Forgot?
27 | input(type="hidden", name="_csrf", value=csrfToken)
28 | button(type="submit").button.button--primary.login--submit Submit
29 | .login--signup Need an account? Sign up here
30 |
--------------------------------------------------------------------------------
/views/user/reset.jade:
--------------------------------------------------------------------------------
1 | extends /layouts/base
2 |
3 | include /partials/formcomponents
4 |
5 | block content
6 | main
7 | section
8 | .row
9 | .card.card-third.card-center.card--login
10 | .card-body
11 | h1.card-title Reset Password
12 | form.form(method="post")
13 | if messages.error
14 | ul.form--errors
15 | for error in messages.error
16 | li= error
17 | if messages.success
18 | .form--success= messages.success[0]
19 | .form--group
20 | label.form--element.form--element-bundled.form--element-text
21 | input(type="password", name="password", placeholder="New Password").form--element-control
22 | label.form--element.form--element-bundled.form--element-text
23 | input(type="password", name="validate_password" placeholder="Re-type New Password").form--element-control
24 | input(type="hidden", name="_csrf", value=csrfToken)
25 | button(type="submit").button.button--primary.login--submit Reset Password
26 |
--------------------------------------------------------------------------------
/views/user/signup.jade:
--------------------------------------------------------------------------------
1 | extends /layouts/base
2 |
3 | block content
4 | main
5 | section
6 | .auth--form
7 | h1 Sign Up
8 | form.form(method="post", action=(typeof activationKey !== 'undefined' ? '/activate' : '/signup'))
9 | if messages.error
10 | ul.form--errors
11 | for error in messages.error
12 | li= error
13 | .form--group
14 | label.form--element.form--element-bundled.form--element-text
15 | input(type="text", name="name", placeholder="First and Last Name").form--element-control
16 | label.form--element.form--element-bundled.form--element-text
17 | input(type="text", name="company", placeholder="Company").form--element-control
18 | label.form--element.form--element-bundled.form--element-text
19 | //- The 'email' field is named 'username' because that is what the
20 | //- passport library requires to validate on req.logIn.
21 | input(type="email", name="username",
22 | placeholder="E-mail Address",
23 | value=(typeof email !== 'undefined' ? email : ''),
24 | readonly=(typeof activationKey !== 'undefined' ? true : false)).form--element-control
25 | .form--group
26 | label.form--element.form--element-bundled.form--element-text
27 | input(type="password", name="password", placeholder="Password").form--element-control
28 | label.form--element.form--element-bundled.form--element-text
29 | input(type="password", name="validate_password" placeholder="Re-type Password").form--element-control
30 | .form--group
31 | .form--element.form--element-checkbox
32 | input(type="checkbox", name="check", value="c1").form--element-control#c1
33 | label(for="c1").span.form--element-value I agree to the Terms & Conditions
34 | input(type="hidden", name="_csrf", value=csrfToken)
35 | if typeof activationKey !== 'undefined'
36 | input(type="hidden", name="activationKey", value=activationKey)
37 | button(type="submit").button.button--primary.login--submit Submit
38 | .login--signup Already have an account? Log in here
39 |
--------------------------------------------------------------------------------