Frontend Technical Approach

Lea Bialachowski and Matthew Neil

Day one

Planning and not coding!

Repeated UI patterns extracted into core modules, approach to problems, layout used, spacing top or bottom for panel elements

UI Pattern Identification

List and link list, tabs, gradients

Example UI patterns

Folder architecture

Task Runner

Quicker than web essentials, easy to use, more configurable, does more things that are useful (image compression, concat without bundling), doesn't sudenly break or crash

/// <vs SolutionOpened='images' />
module.exports = function (grunt) {
	// load nom tasks from package.json
	require('load-grunt-tasks')(grunt);		

	// show timing of build tasks
	require('time-grunt')(grunt);

	// just in time load tasks / plugins faster
	require('jit-grunt')(grunt)({
		customTasksDir: 'grunt_tasks'
	});

	// global folder configuration
	var options = {
		config: {
			src: "grunt_tasks/*.js"
		},
		paths: {
			localDir: 'C:/inetpub/wwwroot/BurgesSalmon/Website',
			localHost: 'BurgesSalmon/',

			// css paths
			cssSrc: 'src/css',
			cssDist: 'dist/css',

			// scripts paths
			jsSrc: 'src/scripts',
			jsDist: 'dist/scripts',
			jsTest: 'src/scripts/specs',

			// image paths
			imgSrc: 'src/images',
			imgDist: 'dist/images',

			// Sitecore modules
			wffmSrc: 'src/css/modules/wffm',
			wffmDist: 'sitecore modules/Shell/Web Forms for Marketers/Themes',
			wffmCssFileName: 'burges-salmon'
		}
	};

	// Load grunt configurations automatically
	var configs = require('load-grunt-configs')(grunt, options);

	// Define the configuration for all the tasks
	grunt.initConfig(configs);

	// Default task(s).
	grunt.registerTask('default', ['watch']);
	grunt.registerTask('styles', ['less:main', 'postcss:dist', 'cssmin:main', 'newer:imagemin', 'copy:styles']);
	grunt.registerTask('scripts', ['newer:jshint:all', 'concat', 'uglify', 'copy:scripts']);
	grunt.registerTask('images', ['newer:imagemin', 'newer:svgmin', 'copy:images']);
	grunt.registerTask('test', ['jasmine']);
	grunt.registerTask('wffm', ['less:wffm', 'postcss:wffm', 'cssmin:wffm', 'copy:wffm']);

	// js debug
	grunt.registerTask('jsdebug', ['concat', 'uglify', 'copy:scripts']);

	grunt.registerTask('prod', [
	  'jshint:all',
	  'concat',
	  'uglify',
	  'less:main',
	  'postcss:dist',
	  'cssmin:main',
	  'less:wffm',
	  'postcss:wffm',
	  'cssmin:wffm',
	  'copy:wffm',
	  'imagemin',
	  'copy:prod'
	]);
};

Writing the code

  • extending WFFM styling
  • avoiding mixins
  • using variables for everything
  • using less functions such as darken when possible rather than creating new variables for a new HEX value

 

Code snippet

@import (reference) "../../core/_breakpoints.less";
@import (reference) "../../core/_variables.less";

.profile-card {
    box-sizing: border-box;
    color: @dark-grey;
    margin-top: @division-medium;
    width: 100%;
    display: flex;
    background-size: 100% 105px;
    position: relative;
    top: 0;
    transition: top 0.25s @cubic-1, box-shadow 0.3s @cubic-1;
    box-shadow: 0px 3px 10px rgba( 0, 0, 0, 0);

    &__content-container {
        width: 100%;
        background: linear-gradient( @grey-100, @grey-100) no-repeat 0 105px;
        position: relative;
        align-self: stretch;
        display: flex; 

        &:before {
            content: '';
            transition: all 1s;
            opacity: 1;
            background: @grey-150 linear-gradient( @grey-100, @grey-100) no-repeat 0 105px;
            position: absolute;
            top: 0;
            right: 0;
            bottom: 0;
            left: 0;
            z-index: 4;
        }

        &:hover {
            &:before {
                opacity: 0;
            }

            .profile-card__name:after {
                background: @bs-magenta;
            }
        }
    }


    &__content {
        position: relative;
        z-index: 5;
        padding: 30px 15px 90px 15px;
        align-self: stretch;
        width: 100%;
    }

    &__name {
        margin-bottom: 15px;
    }

    &__title {
        font-size: 75%;
        display: block;
    }

    &__list-label {
        padding-top: 10px;
        display: block;
        text-align: left;
        padding-left: 17px;
        font-size: 105%;
    }

    &__list {
        text-align: left;
        padding-top: 0;

        @media @tablet {
            li:nth-child(n+4){
                display: none;
            }
        }
    }

    &__button-container {
        position: absolute;
        bottom: 20px;
        left: 0;
        right: 0;
    }

}

Variables, darken, lighten

Code snippets

@import (reference) "../../core/_breakpoints.less";
@import (reference) "../../core/_grid.less";
@import (reference) "../../core/_utilities.less";

// Developer notes:
// The wffm selectors have been extended to the _forms.less file to reduce duplicated styles
// Required wffm selectors are then output to the sitecore modules folder...
// sitecore modules\Shell\Web Forms for Marketers\Themes

.scfForm {
	.page-editor &,
	.page-preview & {
		font-family: 'Frutiger Pro 45 Light';
	}

	.scfSectionBorderAsFieldSet {
		&:extend(fieldset);
		text-align: left;
	}

	.scfSectionLegend {
		&:extend(legend);

		.page-editor &,
		.page-preview & {
			font-family: inherit;
			font-weight: inherit;
			font-size: inherit;
			color: inherit;
		}
	}

	.scfTitleBorder {

		.page-editor &,
		.page-preview & {
			// Used to overide the default styles injected via page editor
			text-align: center;
			font-family: inherit;
		}
	}

	.scfDropListLabel,
	.scfEmailLabel,
	.scfMultipleLineTextLabel,
	.scfSingleLineTextLabel,
	.scfPasswordLabel,
	.scfNumberLabel,
	.scfDatePickerLabel,
	.scfDateLabel,
	.scfRadioButtonListLabel,
	.scfListBoxLabel,
	.scfFileUploadLabel,
	.scfDateSelectorLabel,
	.scfCreditCardLabel,
	.scfConfirmPasswordLabel,
	.scfCaptchaLabel,
	.scfTelephoneLabel,
	.scfSmsTelephoneLabel {
		&:extend(label);
		order: 0;

		.page-editor &,
		.page-preview & {
			// Used to overide the default styles injected via page editor
			&:extend(label);
			order: 0;
			width: auto;
		}
	}

	.scfCheckBoxListLabel {
		&:extend(label);
		order: 0;
	}

	.ui-icon-datepicker {
		display: none;
	}

	.scfCheckboxBorder {
		display: flex;
		flex-wrap: wrap;

		> .scfCheckBoxListGeneralPanel {
			width: auto;
		}

		.scfCheckboxUsefulInfo {

			+ .scfRequired {
				order: 4;
			}
		}
	}

	.scfCheckBoxListGeneralPanel {
		order: 2;
		width: 100%;
	}


	.scfRadioButtonList,
	.scfCheckBoxList {
		label {
			margin: @division-small / 2 0 0;
		}
	}

	.scfSingleLineTextBorder,
	.scfDropListBorder,
	.scfEmailBorder,
	.scfMultipleLineTextBorder,
	.scfSingleLineTextBorder,
	.scfPasswordBorder,
	.scfNumberBorder,
	.scfDateBorder,
	.scfRadioButtonListBorder,
	.scfListBoxBorder,
	.scfCheckBoxListBorder,
	.scfFileUploadBorder,
	.scfDateSelectorBorder,
	.scfCreditCardBorder,
	.scfConfirmPasswordBorder,
	.scfCaptchaBorder,
	.scfTelephoneBorder,
	.scfSmsTelephoneBorder {
		box-sizing:border-box;
		display: flex;
		flex-wrap: wrap;
		width: 100%;
		padding:0 @division-small/2;

	}

	.scfEmailGeneralPanel,
	.scfMultipleLineGeneralPanel,
	.scfSingleLineGeneralPanel,
	.scfPasswordGeneralPanel,
	.scfNumberGeneralPanel,
	.scfDateGeneralPanel,
	.scfRadioButtonListGeneralPanel,
	.scfFileUploadGeneralPanel,
	.scfDateSelectorGeneralPanel,
	.scfCreditCardGeneralPanel,
	.scfConfirmPasswordGeneralPanel,
	.scfCaptchaGeneralPanel,
	.scfTelephoneGeneralPanel,
	.scfSmsTelephoneGeneralPanel,
	.scfDropListGeneralPanel,
	.scfListBoxGeneralPanel,
	.scfDatePickerGeneralPanel {
		text-align: left;
		order: 3;
		width: 100%;
	}

	.scfDateSelectorGeneralPanel {
		label {
		}

		.scfDateSelectorShortLabelDay,
		.scfDateSelectorShortLabelMonth,
		.scfDateSelectorShortLabelYear,
		.scfDateSelectorYear,
		.scfDateSelectorMonth,
		.scfDateSelectorDay {
			display: inline-block;
			width: calc(100% / 3);
			border: 5px solid @white;
			box-sizing: border-box;

			.page-editor &,
			.page-preview & {
				color: inherit;
				font-size: inherit;
				float: none;
				line-height: inherit;
				padding: 0;
			}
		}

		.scfDateSelectorYear,
		.scfDateSelectorMonth,
		.scfDateSelectorDay {
			min-height: 60px;
		}
	}


	.scfListBox,
	.scfDropList {
	}

	select {
		&:extend(select);
	}

	textarea {
		&:extend(textarea);
	}

	input {
		&[type="text"],
		&[type="date"],
		&[type="email"],
		&[type="month"],
		&[type="number"],
		&[type="password"],
		&[type="tel"],
		&[type="time"],
		&[type="week"],
		&[type="url"] {
			&:extend(input[type="text"]);
		}
	}
	// form validation
	.scfValidatorRequired,
	.scfRequired {
		color: @dark-grey;
		order: 1;
		margin: 25px 0 0 10px;
	}

	.scfValidator {
	}

	.scfIntroBorder {
		clear: both;

		.page-editor &,
		.page-preview & {
			text-align: center;
		}
	}

	.scfFooterBorder {
		text-align: center;
	}


	.scfError,
	.scfValidationSummary,
	.scfValidatorRequired,
	.scfValidator {
		&:extend(.input-error);
	}

	.scfValidationSummary {
		border: 2px solid @bs-red;
		background: fade(@bs-red, 10%);
		margin-top: @division-small;

		ul {
			padding: @division-small;

			li {
				list-style: none;

				&:before {
					display: none;
				}
			}
		}
	}

	.scfCreditCardTextUsefulInfo,
	.scfConfirmPasswordUsefulInfo,
	.scfDateSelectorUsefulInfo,
	.scfCaptchaUsefulInfo,
	.scfTelephoneUsefulInfo,
	.scfSmsTelephoneUsefulInfo,
	.scfPasswordUsefulInfo,
	.scfSingleLineTextUsefulInfo,
	.scfMultipleLineTextUsefulInfo,
	.scfEmailUsefulInfo,
	.scfNumberUsefulInfo,
	.scfDatePickerUsefulInfo,
	.scfDropListUsefulInfo,
	.scfListBoxUsefulInfo,
	.scfRadioButtonListUsefulInfo,
	.scfCheckBoxListUsefulInfo,
	.scfFileUploadUsefulInfo,
	.scfCheckboxUsefulInfo {
		display: block;
		margin: 5px 0 0;
		color: fade(@bs-black, 50%);
		text-align: left;
		order: 5;
		width: 100%;
	}

	.scfSubmitButton:extend(.btn) {
		// Duplicated style as you cannot extend nested &--selector
		height: 60px;
		line-height: 62px;
		background: @bs-magenta url(/dist/images/icons/Icons_Arrow_White.svg) no-repeat center right 20px;
		color: @white;
		background-size: 10px;

		&:hover:extend(.btn:hover) {
			// this extends to the _forms.less file on compile
		}
	}

	.scfSubmitButtonBorder {
		.page-editor &,
		.page-preview & {
			text-align: center;
		}
	}

	.scfSectionContent {
		display: flex;
		flex-wrap: wrap;
	}
	/* BASE SITECORE CLASSES */
	/** Sitecore added classes applied to form elements **/
	// form 50/50 split columns sitecore set in webforms
	.halfAvailableWidth,
	.thirdAvailableWidth {
		box-sizing: border-box;
		display: flex;
		flex-wrap: wrap;
		float: none;
		padding: 0 @division-small/2;
		width: 100%;
	}


	.halfAvailableWidth {
		@media @tablet {
			width: 50%;
		}
	}

	.thirdAvailableWidth {
		@media @tablet {
			width: calc(100%/3);
		}
	}
	/* CUSTOM KAGOOL CLASSES */
	.two-column,
	.three-column {
		tbody {
			display: flex;
			flex-direction: row;
			flex-wrap: wrap;
		}

		tr {
			box-sizing: border-box;
			width: 100%;
		}
	}

	.two-column {
		tr {
			@media @tablet {
				width: 50%;
			}
		}
	}

	.three-column {
		tr {
			@media @tablet {
				width: calc(100% / 3);
			}
		}
	}
}

Extending and avoiding mixins

@import (reference) "_variables.less";
@import (reference) "_breakpoints.less";
@import (reference) "_typography.less";

// Mixin used as range slider requires specific browser selectors :(
.range-thumb() {
	background: @bs-magenta;
	border-radius: 20px;
	width: 20px;
	height: 20px;
	cursor: pointer;
}


/* BASE FORM */

fieldset {
	border:none;
	padding:0;
	margin:@division-small 0 0;
	
}

legend {
	.short-text();
	text-align:center;
	width:100%;
	&:after {
		&:extend(h2:after);
	}
}

label {
	display:block;
	text-align:left;
	font-size: @base-font-size * @p;
	margin:30px 0 0;
	line-height:1;	
}

input {	
	&[type="text"],
	&[type="email"],
	&[type="password"],
	&[type="date"],
	&[type="month"],
	&[type="number"],
	&[type="datetime"],
	&[type="datetime-local"],
	&[type="tel"],
	&[type="time"],
	&[type="week"],
	&[type="url"],
	&[type="search"] {
		box-sizing: border-box;
		border-radius:0;
		border: 2px solid @grey-100;
		background: @grey-100;
		color:@grey-80;
		min-height: 50px;
		margin: @division-small 0 0;
		padding: 10px @division-small;
		width: 100%;
		text-align: left;		
		-webkit-appearance: none; /* iOS input madness */

		&:focus {
			-webkit-appearance: none; /* iOS input madness */
			outline: none; /* remove chrome blue outline */
		}

		&:required {
			border-bottom-color: @bs-red;
		}
	}

	&[type="datetime"],
	&[type="datetime-local"] {
		padding: 10px 5px 10px @division-small;
	}

	&[type="file"] {
		margin: @division-small 0 0;
	}
	// RANGE ALL BROWSERS
	&[type="range"] {
		width: 100%;
		-webkit-appearance: none;

		&:focus {
			outline: none;
		}
		/* WEBKIT */
		&::-webkit-slider-thumb {
			-webkit-appearance: none;
			margin-top: -5px;
			.range-thumb();
		}

		&::-webkit-slider-runnable-track {
			animation: 0.2s;
			background: @grey-100;
			cursor: pointer;
			height: 8px;
			width: 100%;
			transition: background 0.25s @cubic-1;
		}
		/* FIREFOX */
		&::-moz-range-thumb {
			border: none;
			margin-top: -5px;
			.range-thumb();
		}

		&::-moz-range-track {
			animation: 0.2s;
			background: @grey-100;
			cursor: pointer;
			height: 8px;
			width: 100%;
			transition: background 0.25s @cubic-1;
		}
		/* IE */
		&::-ms-track {
			animate: 0.2s;
			width: 100%;
			height: 8px;
			cursor: pointer;
			background: transparent;
			border-color: transparent;
			border-width: 8px 0;
			color: transparent;
		}

		&::-ms-fill-upper,
		&::-ms-fill-lower,
		&:focus::-ms-fill-upper,
		&:focus::-ms-fill-lower {
			background: @grey-100;
		}

		&::-ms-thumb {
			border: none;
			.range-thumb();
		}
	}

	&[type="checkbox"] {
		display: none;

		+ label {
			position: relative;
		}

		&:checked {
			+ label:before {
				content: '\2713';
				color: @bs-green;
			}
		}

		+ label:before {
			box-sizing: border-box;
			content: '';
			cursor: pointer;
			width: 20px;
			height: 20px;
			border: 1px solid @dark-grey;
			display: inline-block;
			vertical-align: middle;
			margin-right: 10px;
			text-align: center;
		}
	}

	&[type="radio"] {
		display: none;

		+ label {
			position: relative;
		}

		&:checked {
			+ label:before {
				background: @dark-grey;
			}
		}

		+ label:before {
			content: '';
			width: 8px;
			height: 8px;
			display: inline-block;
			vertical-align: middle;
			margin: 0 16px 0 6px;
			text-align: center;
			border-radius: 50%;
			background: @white;
			box-shadow: 0 0 0 5px @white, 0 0 0 6px @dark-grey;
		}
	}

	&[type="search"] {
		background-image: url(/dist/images/icons/Icons_Search.svg);
		background-repeat: no-repeat;
		background-position: center right 10px;
		background-size: 20px;
	}

	&[type="submit"],
	&[type="reset"] {
		margin-left: @gutter-width-px;
		margin-right: @gutter-width-px;
	}
}

textarea {
	&:extend(input[type="text"]);
	resize: vertical;

	&:focus {
		-webkit-appearance: none; /* iOS input madness */
		outline: none; /* remove chrome blue outline */
	}
}

select {
	&:extend(input[type="text"]);
	-webkit-appearance: none;
	-moz-appearance: none;
	background-image: url(/dist/images/icons/Icons_Select_Arrow.svg);
	background-repeat: no-repeat;
	background-position: center right 10px;
	background-size: 12px;

	&[size] {
		background-image:none;
	}

	&::-ms-expand {
		border: none;
		color: transparent;
		background: transparent url(/dist/images/icons/Icons_Select_Arrow.svg) no-repeat center;
		background-size: 12px;
	}

	&:focus {
		-webkit-appearance: none; /* iOS input madness */
		outline: none; /* remove chrome blue outline */
	}
}

/* RADIO & CHECKBOX LIST */
.radio-list,
.checkbox-list {
	ul {
		padding: 0;
	}

	li {
		&:before {
			display:none;
		}
	}
}


/* VALIDATION */
.input-error {
	color:@bs-red;
}


/* PLACEHOLDER */
::-webkit-input-placeholder,
::-moz-placeholder,
:-ms-input-placeholder,
input:-moz-placeholder
 {
	color: fade(@bs-black, 50%);
}

.disabled {
    opacity: 0.25;
    pointer-events: none;
}

_web-forms.less

_forms.less

Sexy code right...

Dealing with Web forms styling

Would have normally had to override the webform styles or create a less file in the sitecore modules folder

module.exports = {
	// Less

	main: {
		options: {
			sourceMap: true,
			paths: ['<%= paths.cssSrc %>']				// scans for @import
		},
		files: [{
			expand: true,						// Enable dynamic expansion.
			cwd: '<%= paths.cssSrc %>',				// Src matches are relative to this path.
			src: ['**/main.less', '**/print.less'],			// Actual pattern(s) to match.
			dest: '<%= paths.cssDist %>',				// Destination path prefix.
			ext: '.css',						// Dest filepaths will have this extension.
		}],
	},
	wffm: {
		options: {
			sourceMap: false,
			paths: ['<%= paths.scModule %>']
		},
		files: {
			'<%= paths.wffmDist %>/<%= paths.wffmCssFileName %>.css': '<%= paths.wffmSrc %>/_web-forms.less'
		}
	}

};

Code snippet

Less build task

Solution explorer

Typography and Mixins


.define(@var) {
	@header: 'h@{var}';
	@header-mobile: 'mobile-h@{var}';
}
 
.h(@index) when (@index > 0) {
	h@{index} {
		.define(@index);
		font-size: @base-font-size * @@header-mobile;

		@media @tablet {
			font-size: @base-font-size * @@header;
		}
	}

	.h(@index - 1);
} 

.h(0) {
}

.h(5);

Dissecting modules into core styles

  • buttons
  • forms
  • gradients
  • grid
  • icons
  • link list
  • tables

Code snippet

@import (reference) "_breakpoints.less";
@import (reference) "_variables.less";


.btn {
	height: 50px;
	line-height: 52px;
	min-width: 165px;
	padding: 0 50px 0 20px;
	border: none;
	border-radius: 30px;
	box-sizing: border-box;
	display: inline-block;
	text-align: left;
	cursor: pointer;
	margin-top: @division-small;
	transition: opacity 0.25s @cubic-1;

	&:hover, 
	&:focus {
		opacity: 0.5;
	}

	&--base {
		background: fade(@dark-grey, 30%) url(/dist/images/icons/Icons_Arrow.svg) no-repeat center right 20px;
		background-size: 18px 18px;
	}

	&--cta {
		height: 60px;
		line-height: 62px;
		background: @bs-magenta url(/dist/images/icons/Icons_Arrow_White.svg) no-repeat center right 20px;
		color: @white;
		background-size: 18px 18px;
	}

	&--ghost {
		background: fade(@white, 30%) url(/dist/images/icons/Icons_Arrow_White.svg) no-repeat center right 20px;
		color: @white;
		background-size: 18px 18px;
	} 
}

Lessons learnt

  • Permissions
  • dist issues (check out all the things)
  • including images in the solution after copying through image task
  • JavaScript debugging task needs to be introduced.

Feedback styling approach into Foundations

Do Frontend tools need to be in Foundations solution?

Could this be stored in GitHub? Or separated out into its own project and Nuget package?

Any questions?

Thank you

Frontend Technical Approach

By Matthew Neil

Frontend Technical Approach

Frontend technical approach simplified

  • 36
Loading comments...

More from Matthew Neil