<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>johna's blog</title>
<link>https://johna.compoutpost.com/</link>
<description>...mostly about web development and programming, with a little bit of anything else related to the Internet, computers and technology.</description>
<item>
<title>tagInput: A simple jQuery plugin for tag entry using Bootstrap 4</title>
<link>https://johna.compoutpost.com/blog/1096/taginput-a-simple-jquery-plugin-for-tag-entry-using-bootstrap-4/</link>
<description>&lt;img alt=&quot;img1096_taginput.jpg&quot; src=&quot;/blog/uploads/img1096_taginput.jpg&quot; class=&quot;img-fluid&quot; /&gt;&lt;br&gt;&lt;br&gt;For a recent website project I needed a way to enter multiple tags.&lt;br&gt;&lt;br&gt;I tried a few open-source scripts but didn't find something that suited my needs. Some had browser compatibility, some had other issues, and some were huge!&lt;br&gt;&lt;br&gt;I just wanted something simple that I could easily modify to suit my own needs, so I wrote my own.&lt;br&gt;&lt;br&gt;My requirements:&lt;br&gt;&lt;br&gt;&#8226; Progressive enhancement (if JavaScript is not available or not working, revert to a simple input text for data entry instead)&lt;br&gt;&#8226; Not allow duplicate tags&lt;br&gt;&#8226; Use Bootstrap 4.x classes&lt;br&gt;&#8226; Validate entered tags&lt;br&gt;&#8226; Autocomplete functionality using HTML 5 datalist&lt;br&gt;&#8226; Autocomplete from an AJAX data source&lt;br&gt;&lt;br&gt;&lt;a href=&quot;/blog/uploads/att1096_demo.html&quot; target=&quot;_blank&quot; class=&quot;btn btn-primary&quot;&gt;Demo&lt;/a&gt;&lt;br&gt;&lt;br&gt;&lt;strong&gt;Update history&lt;/strong&gt;&lt;br&gt;&lt;br&gt;v1.0 - Initial release&lt;br&gt;v1.1 - Restricted minimum and maximum length of tags, handled tab key (can add tag, or if input text empty then moves to next field), added public method to add tags&lt;br&gt;v1.2 - Handled enter key or click on datalist option&lt;br&gt;&lt;br&gt;&lt;pre&gt;//tagInput by John Avis Oct 16, 2019&lt;br&gt;//v1.2&lt;br&gt;&lt;br&gt;(function ($, win) {&lt;br&gt;	$.fn.tagInput = function () {&lt;br&gt;		return this.each(function () {&lt;br&gt;&lt;br&gt;			//Single function used to add tags&lt;br&gt;			function addTag(tagName) {&lt;br&gt;				var cleanTagName = tagName.trim();&lt;br&gt;&lt;br&gt;				if (cleanTagName != '') {&lt;br&gt;					//Input validation (this example allows minimum 2 and maximum 50 upper and lower case letters and numbers - change if required)&lt;br&gt;					if (/^([a-zA-Z0-9]){2,50}$/.test(cleanTagName) === false) {&lt;br&gt;						return '2-50 letters or numbers only';&lt;br&gt;					}&lt;br&gt;&lt;br&gt;					//Check for duplicates (remove if not required)&lt;br&gt;					var tags = el.val().split(&quot;,&quot;);&lt;br&gt;					var rawTagName = rawTag(cleanTagName)&lt;br&gt;					for (var i = 0; i &amp;lt; tags.length; i++) {&lt;br&gt;						if (rawTag(tags[i]) === rawTagName) {&lt;br&gt;							return 'Duplicate tag';&lt;br&gt;						}&lt;br&gt;					}&lt;br&gt;&lt;br&gt;					//check for maximum number of tags reached&lt;br&gt;					if (tags.length &amp;gt;= 10) {&lt;br&gt;						return 'Only 10 tags allowed';&lt;br&gt;					}&lt;br&gt;&lt;br&gt;					//Add a tag to the list&lt;br&gt;					el.val(el.val() + (el.val() !== '' ? ',' : '') + cleanTagName);&lt;br&gt;					$('#' + id + '_form').before('&amp;lt;div class=&quot;float-left mr-1 mb-1&quot;&amp;gt;&amp;lt;span class=&quot;btn btn-secondary btn-sm&quot;&amp;gt;' + cleanTagName + ' &amp;lt;button type=&quot;button&quot; class=&quot;btn badge badge-light&quot; value=&quot;' + cleanTagName + '&quot;&amp;gt;&amp;times;&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;');&lt;br&gt;					return true;&lt;br&gt;				}&lt;br&gt;			}&lt;br&gt;&lt;br&gt;			function rawTag(tagName) {&lt;br&gt;				//Compare tags by their raw values only by converting to lower case//&lt;br&gt;				//You may also use this to remove any non alpha-numeric characters (if allowed) if some characters are ignored for comparisons&lt;br&gt;				return tagName.toLowerCase();&lt;br&gt;			}&lt;br&gt;&lt;br&gt;			//Global variables - the element (jquery object) itself, and the ID which is used as a prefix for dynamic elements&lt;br&gt;			var el = $(this);&lt;br&gt;			var id = el.attr('id');&lt;br&gt;&lt;br&gt;			//Add a reference to the object as data&lt;br&gt;			el.data('tagInput', this);&lt;br&gt;&lt;br&gt;			//Hide the original input text&lt;br&gt;			el.hide();&lt;br&gt;&lt;br&gt;			//Create the HTML elements for displaying the list of tags and for entering a new tag&lt;br&gt;			el.after('&amp;lt;div id=&quot;' + id + '_list&quot; class=&quot;clearfix border rounded pl-1 pt-1&quot;&amp;gt;' +&lt;br&gt;				'&amp;lt;div id=&quot;' + id + '_form&quot; class=&quot;float-left mb-1&quot;&amp;gt;' +&lt;br&gt;				'&amp;lt;input id=&quot;' + id + '_input&quot; type=&quot;text&quot; autocomplete=&quot;off&quot; class=&quot;form-control form-control-sm&quot; maxlength=&quot;50&quot; list=&quot;' + id + '_datalist&quot; placeholder=&quot;Enter a tag&quot; /&amp;gt;' +&lt;br&gt;				'&amp;lt;div id=&quot;' + id + '_error&quot; class=&quot;text-danger small&quot; style=&quot;display:none&quot;&amp;gt;&amp;lt;/div&amp;gt;' +&lt;br&gt;				'&amp;lt;button id=&quot;' + id + '_add&quot; class=&quot;btn btn-outline-secondary sr-only&quot; type=&quot;button&quot;&amp;gt;Add tag&amp;lt;/button&amp;gt;' +&lt;br&gt;				'&amp;lt;/div&amp;gt;' +&lt;br&gt;				'&amp;lt;/div&amp;gt;' +&lt;br&gt;				'&amp;lt;datalist id=&quot;' + id + '_datalist&quot;&amp;gt;&amp;lt;/datalist&amp;gt;');&lt;br&gt;&lt;br&gt;			//Attempt to add any existing comma separated values from the input text (can change separator if required)&lt;br&gt;			var tags = el.val().split(&quot;,&quot;);&lt;br&gt;			el.val('');&lt;br&gt;			for (var i = 0; i &amp;lt; tags.length; i++) {&lt;br&gt;				addTag(tags[i]);&lt;br&gt;			}&lt;br&gt;&lt;br&gt;			var lastVal = '';&lt;br&gt;&lt;br&gt;			//Watch for blur and keyup&lt;br&gt;			$('#' + id + '_input').bind('blur keyup', function (e) {&lt;br&gt;&lt;br&gt;				//Empty and hide any existing validation error&lt;br&gt;				$('#' + id + '_error').html('').hide();&lt;br&gt;&lt;br&gt;				//Execute if blur event or enter key pressed (could also trap other keystrokes if required)&lt;br&gt;				if (e.type === 'blur' || (e.type === 'keyup' &amp;&amp; (typeof e.keyCode === 'undefined' || e.keyCode === 13 || (e.shiftKey === false &amp;&amp; e.keyCode === 9)))) {&lt;br&gt;&lt;br&gt;					//Empty and hide any existing validation error&lt;br&gt;					$('#' + id + '_error').html('').hide();&lt;br&gt;&lt;br&gt;					//Attempt to add the tag and store the result&lt;br&gt;					var result = addTag($(this).val());&lt;br&gt;					if (result === true) {&lt;br&gt;&lt;br&gt;						//If was successful then clear the input text and autocomplete datalist&lt;br&gt;						$(this).val('');&lt;br&gt;						$('#' + id + '_datalist').html('');&lt;br&gt;&lt;br&gt;						//If enter or tab key was pressed then set focus back to input text&lt;br&gt;						if (e.type === 'keyup' &amp;&amp; (typeof e.keyCode === 'undefined' || e.keyCode === 13 || (e.shiftKey === false &amp;&amp; e.keyCode === 9))) {&lt;br&gt;							$(this).focus();&lt;br&gt;						}&lt;br&gt;					}&lt;br&gt;					else {&lt;br&gt;						$('#' + id + '_error').html(result).show();&lt;br&gt;					}&lt;br&gt;				}&lt;br&gt;				//Autocomplete datalist is only populated if at least one character has been entered (change if required)&lt;br&gt;				else if ($(this).val().length &amp;lt; 1) {&lt;br&gt;					//Less than 1 charcters so make sure the datalist is empty&lt;br&gt;					$('#' + id + '_datalist').html('');&lt;br&gt;				}&lt;br&gt;				//Execute if more than one character has been entered, will populate the datalist with matches&lt;br&gt;				else if (lastVal != $(this).val().trim()) {&lt;br&gt;					//Empty the datalist&lt;br&gt;					$('#' + id + '_datalist').html('');&lt;br&gt;&lt;br&gt;					//Populate the datalist with dummy data (the first letter entered plus numbers 0 to 50)&lt;br&gt;					//Replace with an AJAX request or remove if not required&lt;br&gt;					for (i = 0; i &amp;lt; 50; i++) {&lt;br&gt;						$('#' + id + '_datalist').append('&amp;lt;option&amp;gt;' + $(this).val().substring(0, 1) + i + '&amp;lt;/option&amp;gt;');&lt;br&gt;					}&lt;br&gt;				}&lt;br&gt;&lt;br&gt;				lastVal = $(this).val().trim();&lt;br&gt;			});&lt;br&gt;&lt;br&gt;			//Prevent the enter key from submitting the form, and prevent tab key default action if input text has data&lt;br&gt;			$('#' + id + '_input').bind('keydown', function (e) {&lt;br&gt;				if (e.keyCode === 13 || (e.shiftKey === false &amp;&amp; e.keyCode === 9 &amp;&amp; $(this).val().trim() !== '')) {&lt;br&gt;					e.preventDefault();&lt;br&gt;					return false;&lt;br&gt;				}&lt;br&gt;			});&lt;br&gt;&lt;br&gt;			//Watch for delete tag button pressed&lt;br&gt;			$('#' + id + '_list').on('click', '.badge', function () {&lt;br&gt;&lt;br&gt;				//Get current tags&lt;br&gt;				var tags = el.val().split(',');&lt;br&gt;&lt;br&gt;				//Clear current values, ready to add all but the deleted tag&lt;br&gt;				el.val('');&lt;br&gt;&lt;br&gt;				//Iterate through current tags and add all but the deleted tag&lt;br&gt;				for (var i = 0; i &amp;lt; tags.length; i++) {&lt;br&gt;					if (tags[i] != $(this).val()) {&lt;br&gt;						el.val(el.val() + (el.val() !== '' ? ',' : '') + tags[i]);&lt;br&gt;					}&lt;br&gt;				}&lt;br&gt;&lt;br&gt;				//Remove the deleted tag element&lt;br&gt;				$(this).parent().parent().remove();&lt;br&gt;&lt;br&gt;				//Set focus back to the input text&lt;br&gt;				el.focus();&lt;br&gt;			});&lt;br&gt;&lt;br&gt;			//A public method for adding extra tags&lt;br&gt;			this.addTags = function (tagNames) {&lt;br&gt;				var tags = tagNames.split(',');&lt;br&gt;				for (var i = 0; i &amp;lt; tags.length; i++) {&lt;br&gt;					addTag(tags[i]);&lt;br&gt;				}&lt;br&gt;			}&lt;br&gt;		});&lt;br&gt;	};&lt;br&gt;}(jQuery, window));&lt;/pre&gt;</description>
<comments>https://johna.compoutpost.com/blog/1096/taginput-a-simple-jquery-plugin-for-tag-entry-using-bootstrap-4/#comments</comments>
<pubDate>2019-10-15T12:00:00+10:00</pubDate>
<category>Jquery/Javascript</category>
<image>https://johna.compoutpost.com/blog/uploads/img1096_taginput.jpg</image>
<guid>https://johna.compoutpost.com/blog/1096</guid>
</item>
<item>
<title>Yet another enhancement of the simple Pinterest Style Grid Layout jQuery Plugin</title>
<link>https://johna.compoutpost.com/blog/837/yet-another-enhancement-of-the-simple-pinterest-style-grid-layout-jquery-plugin/</link>
<description>&lt;img alt=&quot;Pinterest style grid enhancement&quot; src=&quot;/blog/uploads/img837_pinterest-grid-enhancement.jpg&quot; class=&quot;img-fluid&quot; /&gt;&lt;br&gt;&lt;br&gt;The problem with the &lt;a href=&quot;/blog/828/enhanced-simple-pinterest-style-grid-layout-jquery-plugin/&quot;&gt;last version&lt;/a&gt; of Mediademons Simple jQuery Plugin To Create Pinterest Style Grid Layout - Pinterest Grid was that each item would be added consecutively in the next column or row.&lt;br&gt;&lt;br&gt;If one column had very long items then that column could be much longer than the other columns.&lt;br&gt;&lt;br&gt;A critical feature of this layout for my applications is that items should appear top to bottom in a specific order, often chronological.&lt;br&gt;&lt;br&gt;I've updated the script so that items are added to the shortest column. The result is that items may be placed out of order but they are always placed closest to the top.&lt;br&gt;&lt;br&gt;The updated script is below, and a &lt;a href=&quot;/blog/uploads/attt2227_Bootstrap 101 Template.html&quot; target=&quot;_blank&quot;&gt;demo&lt;/a&gt; is available.&lt;br&gt;&lt;br&gt;&lt;pre&gt;/*&lt;br&gt;	Pinterest Grid Plugin&lt;br&gt;	Copyright 2014 Mediademons&lt;br&gt;	@author smm 16/04/2014&lt;br&gt;		&lt;br&gt;	Modified by John Avis 16/11/2017&lt;br&gt;		&lt;br&gt;	usage:&lt;br&gt;		&lt;br&gt;		$(document).ready(function() {&lt;br&gt;		$('#blog-landing').pinterest_grid({&lt;br&gt;			no_columns: 4&lt;br&gt;		});&lt;br&gt;	});&lt;br&gt;*/&lt;br&gt;; (function ($, window, document, undefined) {&lt;br&gt;	var pluginName = 'pinterest_grid',&lt;br&gt;		defaults = {&lt;br&gt;			padding_x: 10,&lt;br&gt;			padding_y: 10,&lt;br&gt;			no_columns: 4,&lt;br&gt;			margin_bottom: 50,&lt;br&gt;			breakpoints: [&lt;br&gt;				[767, 1],&lt;br&gt;				[991, 2],&lt;br&gt;				[1199, 3]&lt;br&gt;			]&lt;br&gt;		},&lt;br&gt;		columns,&lt;br&gt;		$article,&lt;br&gt;		article_width;&lt;br&gt;&lt;br&gt;	function Plugin(element, options) {&lt;br&gt;		this.element = element;&lt;br&gt;		this.options = $.extend({}, defaults, options);&lt;br&gt;		this._defaults = defaults;&lt;br&gt;		this._name = pluginName;&lt;br&gt;		this.init();&lt;br&gt;	}&lt;br&gt;&lt;br&gt;	Plugin.prototype.init = function () {&lt;br&gt;		var self = this,&lt;br&gt;			resize_finish;&lt;br&gt;&lt;br&gt;		$(this.element).find(&quot;img&quot;).load(function () {&lt;br&gt;			$(window).resize();&lt;br&gt;		});&lt;br&gt;&lt;br&gt;		$(window).resize(function () {&lt;br&gt;			clearTimeout(resize_finish);&lt;br&gt;			resize_finish = setTimeout(function () {&lt;br&gt;				self.make_layout_change(self);&lt;br&gt;			}, 11);&lt;br&gt;		});&lt;br&gt;&lt;br&gt;		self.make_layout_change(self);&lt;br&gt;&lt;br&gt;		setTimeout(function () {&lt;br&gt;			$(window).resize();&lt;br&gt;		}, 500);&lt;br&gt;	};&lt;br&gt;&lt;br&gt;	Plugin.prototype.calculate = function (columns) {&lt;br&gt;		var self = this,&lt;br&gt;			tallest = 0,&lt;br&gt;			row = 0,&lt;br&gt;			$container = $(this.element),&lt;br&gt;			container_width = $container.width();&lt;br&gt;		$article = $(this.element).children();&lt;br&gt;&lt;br&gt;		if (columns === 1) {&lt;br&gt;			article_width = $container.width();&lt;br&gt;		} else {&lt;br&gt;			article_width = ($container.width() - self.options.padding_x * (columns - 1)) / columns;&lt;br&gt;		}&lt;br&gt;&lt;br&gt;		$article.each(function () {&lt;br&gt;			$(this).css('width', article_width);&lt;br&gt;		});&lt;br&gt;&lt;br&gt;		var columnTop = [];&lt;br&gt;		for (var i = 1; i &lt;= columns; i++) {&lt;br&gt;			columnTop[i] = 0;&lt;br&gt;		}&lt;br&gt;&lt;br&gt;		$article.each(function (index) {&lt;br&gt;			var current_column,&lt;br&gt;				left_out = 0,&lt;br&gt;				top = 0,&lt;br&gt;				$this = $(this);&lt;br&gt;&lt;br&gt;			current_column = 0;&lt;br&gt;			var minColumnTop = -1;&lt;br&gt;			for (var i = 1; i &lt;= columns; i++) {&lt;br&gt;				if (minColumnTop == -1 || columnTop[i] &lt; minColumnTop) {&lt;br&gt;					current_column = i;&lt;br&gt;					minColumnTop = columnTop[i];&lt;br&gt;				}&lt;br&gt;			}&lt;br&gt;&lt;br&gt;			top = columnTop[current_column];&lt;br&gt;&lt;br&gt;			columnTop[current_column] += $(this).outerHeight() + self.options.padding_y;&lt;br&gt;&lt;br&gt;			if (columns === 1) {&lt;br&gt;				left_out = 0;&lt;br&gt;			} else {&lt;br&gt;				left_out = ((current_column - 1) % columns) * (article_width + self.options.padding_x);&lt;br&gt;			}&lt;br&gt;&lt;br&gt;			$this.css({&lt;br&gt;				'left': left_out,&lt;br&gt;				'top': top&lt;br&gt;			});&lt;br&gt;		});&lt;br&gt;&lt;br&gt;		var paddingy = this.options.padding_y;&lt;br&gt;		largest = Math.max.apply(Math, columnTop) - paddingy;&lt;br&gt;		$container.css('height', largest + this.options.margin_bottom);&lt;br&gt;	};&lt;br&gt;&lt;br&gt;	Plugin.prototype.make_layout_change = function (_self) {&lt;br&gt;		columns = _self.options.no_columns;&lt;br&gt;&lt;br&gt;		for (var i = 0; i &lt; _self.options.breakpoints.length; i++) {&lt;br&gt;			if ($(window).width() &lt;= _self.options.breakpoints[i][0]) {&lt;br&gt;				columns = _self.options.breakpoints[i][1];&lt;br&gt;				break;&lt;br&gt;			}&lt;br&gt;		}&lt;br&gt;&lt;br&gt;		_self.calculate(columns);&lt;br&gt;	};&lt;br&gt;&lt;br&gt;	$.fn[pluginName] = function (options) {&lt;br&gt;		return this.each(function () {&lt;br&gt;			if (!$.data(this, 'plugin_' + pluginName)) {&lt;br&gt;				$.data(this, 'plugin_' + pluginName,&lt;br&gt;					new Plugin(this, options));&lt;br&gt;			}&lt;br&gt;		});&lt;br&gt;	}&lt;br&gt;&lt;br&gt;})(jQuery, window, document);&lt;/pre&gt;&lt;br&gt;&lt;br&gt;See also &lt;a href=&quot;/blog/799/simple-jquery-plugin-to-create-pinterest-style-grid-layout/&quot;&gt;my first post&lt;/a&gt; about this handy jQuery plugin I originally found on &lt;a href=&quot;http://www.jqueryscript.net/layout/Simple-jQuery-Plugin-To-Create-Pinterest-Style-Grid-Layout-Pinterest-Grid.html&quot; target=&quot;_blank&quot;&gt;www.jqueryscript.net&lt;/a&gt;.</description>
<comments>https://johna.compoutpost.com/blog/837/yet-another-enhancement-of-the-simple-pinterest-style-grid-layout-jquery-plugin/#comments</comments>
<pubDate>2017-11-16T12:00:00+10:00</pubDate>
<category>Jquery/Javascript</category>
<category>Responsive Web Design</category>
<image>https://johna.compoutpost.com/blog/uploads/img837_pinterest-grid-enhancement.jpg</image>
<guid>https://johna.compoutpost.com/blog/837</guid>
</item>
<item>
<title>Back to top button like Facebook app</title>
<link>https://johna.compoutpost.com/blog/834/back-to-top-button-like-facebook-app/</link>
<description>&lt;img alt=&quot;Facebook-like back to top&quot; src=&quot;/blog/uploads/img834_facebook-like-back-to-top.jpg&quot; class=&quot;img-fluid&quot; /&gt;&lt;br&gt;&lt;br&gt;The Facebook app on Windows phone has a nice and unobtrusive back to top button that only appears once you scroll past a certain point, and only when you start scrolling up the page.&lt;br&gt;&lt;br&gt;The following code offers similar functionality using jQuery.&lt;br&gt;&lt;br&gt;&lt;h2&gt;HTML&lt;/h2&gt;First you need to add the back to top button itself. In this example it's just the text &quot;TOP&quot;, but you can replace this with an image or icon font symbol or whatever you want.&lt;br&gt;&lt;br&gt;This element should be placed just before the closing &quot;body&quot; tag, and not inside any element with &quot;position:  relative&quot;, unless you want to change how it is positioned.&lt;br&gt;&lt;br&gt;&lt;pre&gt;&amp;lt;a id=&quot;scroll-top&quot; href=&quot;#&quot; style=&quot;display:none;&quot;&amp;gt;TOP&amp;lt;/a&amp;gt;&lt;/pre&gt;&lt;br&gt;Note the href attribute is set to &quot;#&quot;. The script below uses jQuery to animate scrolling to the top of the page, but you may wish to use an anchor reference instead here.&lt;br&gt;&lt;br&gt;&lt;h2&gt;CSS&lt;/h2&gt;To give the button fixed positioning and place it near the top right corner, you will need some styling. You can change the position if you like.&lt;br&gt;&lt;br&gt;I've also set the font size to 34 pixels in this example.&lt;br&gt;&lt;br&gt;&lt;pre&gt;#scroll-top {&lt;br&gt;	position: fixed;&lt;br&gt;	top: 10px;&lt;br&gt;	right: 10px;&lt;br&gt;	font-size: 34px&lt;br&gt;}&lt;/pre&gt;&lt;br&gt;&lt;h2&gt;JavaScript&lt;/h2&gt;Here's the JavaScript (using jQuery) that shows and hides the button as needed.&lt;br&gt;&lt;br&gt;I use setInterval so I can track when the user is scrolling up.&lt;br&gt;&lt;br&gt;Where you see the number &quot;100&quot;, this is the number of pixels down from, the top that the button will be displayed after. That way it won't be shown over your header and if the user is near the top of the page they don't really need a back to top button then anyway.&lt;br&gt;&lt;br&gt;&lt;pre&gt;$(function() {&lt;br&gt;	var lastPos = $(this).scrollTop();&lt;br&gt;&lt;br&gt;	setInterval(function() {&lt;br&gt;		var thisPos = $(this).scrollTop();&lt;br&gt;		if ($(this).scrollTop() &gt; 100 &amp;&amp; thisPos &lt; lastPos) {&lt;br&gt;			$(&quot;#scroll-top&quot;).show();&lt;br&gt;		} else {&lt;br&gt;			if ($(this).scrollTop() &lt;= 100 || thisPos &gt; lastPos) {&lt;br&gt;				$(&quot;#scroll-top&quot;).hide();&lt;br&gt;			}&lt;br&gt;		}&lt;br&gt;		lastPos = thisPos;&lt;br&gt;	}, 150);&lt;br&gt;&lt;br&gt;	$(&quot;#scroll-top&quot;).click(function() {&lt;br&gt;		$(&quot;html, body&quot;).animate({&lt;br&gt;			scrollTop: 0&lt;br&gt;		}, 500);&lt;br&gt;		return false;&lt;br&gt;	});&lt;br&gt;});&lt;/pre&gt;&lt;br&gt;&lt;h2&gt;Example&lt;/h2&gt;Want to see it in action?&lt;br&gt;&lt;br&gt;&lt;a href=&quot;https://jsfiddle.net/yj9L8squ/&quot; target=&quot;_blank&quot; class=&quot;btn btn-warning&quot;&gt;Example on JSFiddle&lt;/a&gt;</description>
<comments>https://johna.compoutpost.com/blog/834/back-to-top-button-like-facebook-app/#comments</comments>
<pubDate>2017-09-15T12:00:00+10:00</pubDate>
<category>Jquery/Javascript</category>
<image>https://johna.compoutpost.com/blog/uploads/img834_facebook-like-back-to-top.jpg</image>
<guid>https://johna.compoutpost.com/blog/834</guid>
</item>
<item>
<title>Enhanced simple Pinterest Style Grid Layout jQuery Plugin</title>
<link>https://johna.compoutpost.com/blog/828/enhanced-simple-pinterest-style-grid-layout-jquery-plugin/</link>
<description>&lt;strong&gt;UPDATE: There's a &lt;a href=&quot;/blog/837/yet-another-enhancement-of-the-simple-pinterest-style-grid-layout-jquery-plugin/&quot;&gt;new version&lt;/a&gt; available of this script available now that puts content in the shortest column rather than just in the next row/column position.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;In response to a request, I have made some improvements to the modified version of Mediademons &lt;a href=&quot;http://www.jqueryscript.net/layout/Simple-jQuery-Plugin-To-Create-Pinterest-Style-Grid-Layout-Pinterest-Grid.html&quot; target=&quot;_blank&quot;&gt;Simple jQuery Plugin To Create Pinterest Style Grid Layout - Pinterest Grid&lt;/a&gt;.&lt;br&gt;&lt;br&gt;This original script had a few bugs which I provided fixes for. See my previous post, &lt;a href=&quot;/blog/799/simple-jquery-plugin-to-create-pinterest-style-grid-layout/&quot;&gt; Simple jQuery Plugin To Create Pinterest Style Grid Layout&lt;/a&gt; for more details.&lt;br&gt;&lt;br&gt;Reader Maxim Usik asked to be able to have more than one breakpoint, so that it might have 3 columns on larger screens, 2 columns on a tablet, and 1 column on a mobile phone.&lt;br&gt;&lt;br&gt;I have made some changes to the script and you can now specify the number of columns at any number of breakpoints.&lt;br&gt;&lt;br&gt;The new option is called &quot;breakpoints&quot; and you pass it an array of values, for example:&lt;br&gt;&lt;br&gt;&lt;pre&gt;breakpoints: [  [ 767, 1 ], [ 991, 2 ], [ 1199, 3 ] ]&lt;/pre&gt;&lt;br&gt;For each breakpoint, the first value is the maximum width, and the second value is the number of columns.&lt;br&gt;&lt;br&gt;The default value for this option matches Bootstrap 3.x breakpoints.&lt;br&gt;&lt;br&gt;Also in this update script is small a fix for adjusting the screen as images load.&lt;br&gt;&lt;br&gt;Here is a &lt;a href=&quot;/blog/uploads/att828_pinterest-style-demo.html&quot; target=&quot;_blank&quot;&gt;demo&lt;/a&gt; using Bootstrap 3.x.&lt;br&gt;&lt;br&gt;And here is the updated script.&lt;br&gt;&lt;br&gt;&lt;pre&gt;/*&lt;br&gt;	Pinterest Grid Plugin&lt;br&gt;	Copyright 2014 Mediademons&lt;br&gt;	@author smm 16/04/2014&lt;br&gt;&lt;br&gt;	Modified by John Avis 13/04/2017&lt;br&gt;&lt;br&gt;	usage:&lt;br&gt;&lt;br&gt;		$(document).ready(function() {&lt;br&gt;		$('#blog-landing').pinterest_grid({&lt;br&gt;			no_columns: 4&lt;br&gt;		});&lt;br&gt;	});&lt;br&gt;*/&lt;br&gt;; (function ($, window, document, undefined) {&lt;br&gt;	var pluginName = 'pinterest_grid',&lt;br&gt;		defaults = {&lt;br&gt;			padding_x: 10,&lt;br&gt;			padding_y: 10,&lt;br&gt;			no_columns: 4,&lt;br&gt;			margin_bottom: 50,&lt;br&gt;			breakpoints: [&lt;br&gt;				[ 767, 1 ],&lt;br&gt;				[ 991, 2 ],&lt;br&gt;				[ 1199, 3 ]&lt;br&gt;			]&lt;br&gt;		},&lt;br&gt;		columns,&lt;br&gt;		$article,&lt;br&gt;		article_width;&lt;br&gt;&lt;br&gt;	function Plugin(element, options) {&lt;br&gt;		this.element = element;&lt;br&gt;		this.options = $.extend({}, defaults, options);&lt;br&gt;		this._defaults = defaults;&lt;br&gt;		this._name = pluginName;&lt;br&gt;		this.init();&lt;br&gt;	}&lt;br&gt;&lt;br&gt;	Plugin.prototype.init = function () {&lt;br&gt;		var self = this,&lt;br&gt;			resize_finish;&lt;br&gt;&lt;br&gt;		$(this.element).find(&quot;img&quot;).load(function () {&lt;br&gt;			$(window).resize();&lt;br&gt;		});&lt;br&gt;&lt;br&gt;		$(window).resize(function () {&lt;br&gt;			clearTimeout(resize_finish);&lt;br&gt;			resize_finish = setTimeout(function () {&lt;br&gt;				self.make_layout_change(self);&lt;br&gt;			}, 11);&lt;br&gt;		});&lt;br&gt;&lt;br&gt;		self.make_layout_change(self);&lt;br&gt;&lt;br&gt;		setTimeout(function () {&lt;br&gt;			$(window).resize();&lt;br&gt;		}, 500);&lt;br&gt;	};&lt;br&gt;&lt;br&gt;	Plugin.prototype.calculate = function (columns) {&lt;br&gt;		var self = this,&lt;br&gt;		tallest = 0,&lt;br&gt;		row = 0,&lt;br&gt;		$container = $(this.element),&lt;br&gt;			container_width = $container.width();&lt;br&gt;		$article = $(this.element).children();&lt;br&gt;&lt;br&gt;		if (columns === 1) {&lt;br&gt;			article_width = $container.width();&lt;br&gt;		} else {&lt;br&gt;			article_width = ($container.width() - self.options.padding_x * (columns - 1)) / columns;&lt;br&gt;		}&lt;br&gt;&lt;br&gt;		$article.each(function() {&lt;br&gt;			$(this).css('width', article_width);&lt;br&gt;		});&lt;br&gt;&lt;br&gt;		$article.each(function (index) {&lt;br&gt;			var current_column,&lt;br&gt;				left_out = 0,&lt;br&gt;				top = 0,&lt;br&gt;				$this = $(this),&lt;br&gt;				prevAll = $this.prevAll(),&lt;br&gt;				tallest = 0;&lt;br&gt;&lt;br&gt;			if (columns !== 1) {&lt;br&gt;				current_column = (index % columns);&lt;br&gt;			} else {&lt;br&gt;				current_column = 0;&lt;br&gt;			}&lt;br&gt;&lt;br&gt;			for (var t = 0; t &lt; columns; t++) {&lt;br&gt;				$this.removeClass('c' + t);&lt;br&gt;			}&lt;br&gt;&lt;br&gt;			if (index % columns === 0) {&lt;br&gt;				row++;&lt;br&gt;			}&lt;br&gt;&lt;br&gt;			$this.addClass('c' + current_column);&lt;br&gt;			$this.addClass('r' + row);&lt;br&gt;&lt;br&gt;			prevAll.each(function (index) {&lt;br&gt;				if ($(this).hasClass('c' + current_column)) {&lt;br&gt;					top += $(this).outerHeight() + self.options.padding_y;&lt;br&gt;				}&lt;br&gt;			});&lt;br&gt;&lt;br&gt;			if (columns === 1) {&lt;br&gt;				left_out = 0;&lt;br&gt;			} else {&lt;br&gt;				left_out = (index % columns) * (article_width + self.options.padding_x);&lt;br&gt;			}&lt;br&gt;&lt;br&gt;			$this.css({&lt;br&gt;				'left': left_out,&lt;br&gt;				'top': top&lt;br&gt;			});&lt;br&gt;		});&lt;br&gt;&lt;br&gt;		this.tallest($container);&lt;br&gt;	};&lt;br&gt;&lt;br&gt;	Plugin.prototype.tallest = function (_container) {&lt;br&gt;		var column_heights = [],&lt;br&gt;			largest = 0;&lt;br&gt;&lt;br&gt;		var paddingy = this.options.padding_y;&lt;br&gt;&lt;br&gt;		for (var z = 0; z &lt; columns; z++) {&lt;br&gt;			var temp_height = 0;&lt;br&gt;			_container.find('.c' + z).each(function () {&lt;br&gt;				temp_height += $(this).outerHeight() + paddingy;&lt;br&gt;			});&lt;br&gt;			column_heights[z] = temp_height;&lt;br&gt;		}&lt;br&gt;&lt;br&gt;		largest = Math.max.apply(Math, column_heights) - paddingy;&lt;br&gt;		_container.css('height', largest + this.options.margin_bottom);&lt;br&gt;	};&lt;br&gt;&lt;br&gt;	Plugin.prototype.make_layout_change = function (_self) {&lt;br&gt;		columns = _self.options.no_columns;&lt;br&gt;&lt;br&gt;		for (var i = 0; i &lt; _self.options.breakpoints.length; i++) {&lt;br&gt;			if ($(window).width() &lt;= _self.options.breakpoints[i][0]) {&lt;br&gt;				columns = _self.options.breakpoints[i][1];&lt;br&gt;				break;&lt;br&gt;			}&lt;br&gt;		}&lt;br&gt;&lt;br&gt;		_self.calculate(columns);&lt;br&gt;	};&lt;br&gt;&lt;br&gt;	$.fn[pluginName] = function (options) {&lt;br&gt;		return this.each(function () {&lt;br&gt;			if (!$.data(this, 'plugin_' + pluginName)) {&lt;br&gt;				$.data(this, 'plugin_' + pluginName,&lt;br&gt;				new Plugin(this, options));&lt;br&gt;			}&lt;br&gt;		});&lt;br&gt;	}&lt;br&gt;&lt;br&gt;})(jQuery, window, document);&lt;/pre&gt;</description>
<comments>https://johna.compoutpost.com/blog/828/enhanced-simple-pinterest-style-grid-layout-jquery-plugin/#comments</comments>
<pubDate>2017-04-13T12:00:00+10:00</pubDate>
<category>Jquery/Javascript</category>
<category>Responsive Web Design</category>
<guid>https://johna.compoutpost.com/blog/828</guid>
</item>
<item>
<title>Simple jQuery Plugin To Create Pinterest Style Grid Layout</title>
<link>https://johna.compoutpost.com/blog/799/simple-jquery-plugin-to-create-pinterest-style-grid-layout/</link>
<description>When I was looking for an alternative to &lt;a href=&quot;http://masonry.desandro.com/&quot; target=&quot;_blank&quot;&gt;Masonry&lt;/a&gt; to produce a responsive Pinterest style grid that was a lot simpler and lighter weight I came across this jQuery plugin on &lt;a href=&quot;http://www.jqueryscript.net/layout/Simple-jQuery-Plugin-To-Create-Pinterest-Style-Grid-Layout-Pinterest-Grid.html&quot; target=&quot;_blank&quot;&gt;www.jqueryscript.net&lt;/a&gt; that is very simple and works well.&lt;br&gt;&lt;br&gt;However, It has a few issues which I needed to fix before I could use it.&lt;br&gt;&lt;br&gt;1. The columns do not take up the full width of the container because the width calculation includes horizontal margin on all columns. It should exclude the margin on the final column.&lt;br&gt;&lt;br&gt;2. The height calculation is inaccurate as it does not include vertical padding on every element. I needs to include padding for each element except the last one.&lt;br&gt;&lt;br&gt;3. The function that runs to calculate and adjust sizes calls the window resize event handler every time it runs, which is not necessary, and results in the resize event running constantly. It is possible this was intended to handle loading of images, which effects the size calculation, which I deal with in issue 4.&lt;br&gt;&lt;br&gt;4. If images are not loaded when the script runs then it can't calculate the height required correctly. To accommodate this, I run the resize event each time an image is loaded within the container.&lt;br&gt;&lt;br&gt;Here's the final code. See the page on &lt;a href=&quot;http://www.jqueryscript.net/layout/Simple-jQuery-Plugin-To-Create-Pinterest-Style-Grid-Layout-Pinterest-Grid.html&quot; target=&quot;_blank&quot;&gt;www.jqueryscript.net&lt;/a&gt; for instructions on how to use it.&lt;br&gt;&lt;br&gt;&lt;b&gt;UPDATE 13 April 2017:&lt;/b&gt; I've made some further improvements and fixes to this script. The new script allows you to create multiple breakpoints each with a specific number of columns. See my new post, &lt;a href=&quot;/blog/828/enhanced-simple-pinterest-style-grid-layout-jquery-plugin/&quot;&gt;Enhanced simple Pinterest Style Grid Layout jQuery Plugin&lt;/a&gt;.&lt;br&gt;&lt;br&gt;&lt;b&gt;UPDATE 16 Nov 2017:&lt;/b&gt; There's a &lt;a href=&quot;/blog/837/yet-another-enhancement-of-the-simple-pinterest-style-grid-layout-jquery-plugin/&quot;&gt;new version&lt;/a&gt; available of this script available now that puts content in the shortest column rather than just in the next row/column position.&lt;br&gt;&lt;br&gt;Note that the comments in the code show changes I made.&lt;br&gt;&lt;br&gt;&lt;pre&gt; /*&lt;br&gt;    Pinterest Grid Plugin&lt;br&gt;    Copyright 2014 Mediademons&lt;br&gt;    @author smm 16/04/2014&lt;br&gt;&lt;br&gt;    usage:&lt;br&gt;&lt;br&gt;     $(document).ready(function() {&lt;br&gt;        $('#blog-landing').pinterest_grid({&lt;br&gt;            no_columns: 4&lt;br&gt;        });&lt;br&gt;    });&lt;br&gt;*/&lt;br&gt;; (function ($, window, document, undefined) {&lt;br&gt;	var pluginName = 'pinterest_grid',&lt;br&gt;        defaults = {&lt;br&gt;        	padding_x: 10,&lt;br&gt;        	padding_y: 10,&lt;br&gt;        	no_columns: 3,&lt;br&gt;        	margin_bottom: 50,&lt;br&gt;        	single_column_breakpoint: 700&lt;br&gt;        },&lt;br&gt;        columns,&lt;br&gt;        $article,&lt;br&gt;        article_width;&lt;br&gt;&lt;br&gt;	function Plugin(element, options) {&lt;br&gt;		this.element = element;&lt;br&gt;		this.options = $.extend({}, defaults, options);&lt;br&gt;		this._defaults = defaults;&lt;br&gt;		this._name = pluginName;&lt;br&gt;		this.init();&lt;br&gt;	}&lt;br&gt;&lt;br&gt;	Plugin.prototype.init = function () {&lt;br&gt;		var self = this,&lt;br&gt;            resize_finish;&lt;br&gt;&lt;br&gt;		$(&quot;.blog-landing img&quot;).load(function () {&lt;br&gt;			$(window).resize();&lt;br&gt;		});&lt;br&gt;&lt;br&gt;		$(window).resize(function () {&lt;br&gt;			clearTimeout(resize_finish);&lt;br&gt;			resize_finish = setTimeout(function () {&lt;br&gt;				self.make_layout_change(self);&lt;br&gt;			}, 11);&lt;br&gt;		});&lt;br&gt;&lt;br&gt;		self.make_layout_change(self);&lt;br&gt;&lt;br&gt;		setTimeout(function () {&lt;br&gt;			$(window).resize();&lt;br&gt;		}, 500);&lt;br&gt;	};&lt;br&gt;&lt;br&gt;	Plugin.prototype.calculate = function (single_column_mode) {&lt;br&gt;		var self = this,&lt;br&gt;            tallest = 0,&lt;br&gt;            row = 0,&lt;br&gt;            $container = $(this.element),&lt;br&gt;            container_width = $container.width();&lt;br&gt;		$article = $(this.element).children();&lt;br&gt;&lt;br&gt;		if (single_column_mode === true) {&lt;br&gt;			article_width = $container.width(); // - self.options.padding_x;&lt;br&gt;		} else {&lt;br&gt;			//article_width = ($container.width() - self.options.padding_x * self.options.no_columns) / self.options.no_columns;&lt;br&gt;			article_width = ($container.width() - self.options.padding_x * (self.options.no_columns - 1)) / self.options.no_columns;&lt;br&gt;		}&lt;br&gt;&lt;br&gt;		$article.each(function () {&lt;br&gt;			$(this).css('width', article_width);&lt;br&gt;		});&lt;br&gt;&lt;br&gt;		columns = self.options.no_columns;&lt;br&gt;&lt;br&gt;		$article.each(function (index) {&lt;br&gt;			var current_column,&lt;br&gt;                left_out = 0,&lt;br&gt;                top = 0,&lt;br&gt;                $this = $(this),&lt;br&gt;                prevAll = $this.prevAll(),&lt;br&gt;                tallest = 0;&lt;br&gt;&lt;br&gt;			if (single_column_mode === false) {&lt;br&gt;				current_column = (index % columns);&lt;br&gt;			} else {&lt;br&gt;				current_column = 0;&lt;br&gt;			}&lt;br&gt;&lt;br&gt;			for (var t = 0; t &lt; columns; t++) {&lt;br&gt;				$this.removeClass('c' + t);&lt;br&gt;			}&lt;br&gt;&lt;br&gt;			if (index % columns === 0) {&lt;br&gt;				row++;&lt;br&gt;			}&lt;br&gt;&lt;br&gt;			$this.addClass('c' + current_column);&lt;br&gt;			$this.addClass('r' + row);&lt;br&gt;&lt;br&gt;			prevAll.each(function (index) {&lt;br&gt;				if ($(this).hasClass('c' + current_column)) {&lt;br&gt;					top += $(this).outerHeight() + self.options.padding_y;&lt;br&gt;				}&lt;br&gt;			});&lt;br&gt;&lt;br&gt;			if (single_column_mode === true) {&lt;br&gt;				left_out = 0;&lt;br&gt;			} else {&lt;br&gt;				left_out = (index % columns) * (article_width + self.options.padding_x);&lt;br&gt;			}&lt;br&gt;&lt;br&gt;			$this.css({&lt;br&gt;				'left': left_out,&lt;br&gt;				'top': top&lt;br&gt;			});&lt;br&gt;		});&lt;br&gt;&lt;br&gt;		this.tallest($container);&lt;br&gt;		//$(window).resize();&lt;br&gt;	};&lt;br&gt;&lt;br&gt;	Plugin.prototype.tallest = function (_container) {&lt;br&gt;		var column_heights = [],&lt;br&gt;            largest = 0;&lt;br&gt;&lt;br&gt;		var paddingy = this.options.padding_y;&lt;br&gt;&lt;br&gt;		for (var z = 0; z &lt; columns; z++) {&lt;br&gt;			var temp_height = 0;&lt;br&gt;			_container.find('.c' + z).each(function () {&lt;br&gt;				//temp_height += $(this).outerHeight();&lt;br&gt;				temp_height += $(this).outerHeight() + paddingy;&lt;br&gt;			});&lt;br&gt;			column_heights[z] = temp_height;&lt;br&gt;		}&lt;br&gt;&lt;br&gt;		//largest = Math.max.apply(Math, column_heights);&lt;br&gt;		largest = Math.max.apply(Math, column_heights) - paddingy;&lt;br&gt;		//_container.css('height', largest + (this.options.padding_y + this.options.margin_bottom));&lt;br&gt;		_container.css('height', largest + this.options.margin_bottom);&lt;br&gt;	};&lt;br&gt;&lt;br&gt;	Plugin.prototype.make_layout_change = function (_self) {&lt;br&gt;		if ($(window).width() &lt; _self.options.single_column_breakpoint) {&lt;br&gt;			_self.calculate(true);&lt;br&gt;		} else {&lt;br&gt;			_self.calculate(false);&lt;br&gt;		}&lt;br&gt;	};&lt;br&gt;&lt;br&gt;	$.fn[pluginName] = function (options) {&lt;br&gt;		return this.each(function () {&lt;br&gt;			if (!$.data(this, 'plugin_' + pluginName)) {&lt;br&gt;				$.data(this, 'plugin_' + pluginName,&lt;br&gt;                new Plugin(this, options));&lt;br&gt;			}&lt;br&gt;		});&lt;br&gt;	}&lt;br&gt;&lt;br&gt;})(jQuery, window, document);&lt;/pre&gt;&lt;br&gt;&lt;br&gt;It's also easy to change the number of columns when it changes to &quot;single column&quot; mode by changing the Plugin.prototype.calculate method. Here's what the first part of this method should be changed to (comments show changes):&lt;br&gt;&lt;br&gt;&lt;pre&gt;Plugin.prototype.calculate = function (single_column_mode) {&lt;br&gt;	var self = this,&lt;br&gt;	tallest = 0,&lt;br&gt;	row = 0,&lt;br&gt;	$container = $(this.element),&lt;br&gt;		container_width = $container.width();&lt;br&gt;	$article = $(this.element).children();&lt;br&gt;&lt;br&gt;	//these next lines are new&lt;br&gt;	if (single_column_mode === true) {&lt;br&gt;		columns = 2;&lt;br&gt;		single_column_mode = false;&lt;br&gt;	}&lt;br&gt;	else {&lt;br&gt;		columns = self.options.no_columns;&lt;br&gt;	}&lt;br&gt;&lt;br&gt;	if(single_column_mode === true) {&lt;br&gt;		article_width = $container.width(); // - self.options.padding_x;&lt;br&gt;	} else {&lt;br&gt;		//this next line has been changed&lt;br&gt;		article_width = ($container.width() - self.options.padding_x * (columns - 1)) / columns;&lt;br&gt;	}&lt;br&gt;&lt;br&gt;	$article.each(function() {&lt;br&gt;		$(this).css('width', article_width);&lt;br&gt;	});&lt;br&gt;&lt;br&gt;	//this next line should be removed&lt;br&gt;	//columns = self.options.no_columns;&lt;/pre&gt;</description>
<comments>https://johna.compoutpost.com/blog/799/simple-jquery-plugin-to-create-pinterest-style-grid-layout/#comments</comments>
<pubDate>2016-03-09T12:00:00+10:00</pubDate>
<category>Jquery/Javascript</category>
<category>Responsive Web Design</category>
<guid>https://johna.compoutpost.com/blog/799</guid>
</item>
<item>
<title>Simple jQuery script for EU cookie law using jQuery and Bootstrap</title>
<link>https://johna.compoutpost.com/blog/784/simple-jquery-script-for-eu-cookie-law-using-jquery-and-bootstrap/</link>
<description>&lt;img alt=&quot;Simple jQuery script for EU cookie law using jQuery and Bootstrap&quot; src=&quot;/blog/uploads/img784_eu-cookie-law-message.jpg&quot; class=&quot;img-responsive&quot; /&gt;&lt;br&gt;&lt;br&gt;I recently needed to display an implied consent EU cookie law message on a website which uses Bootstrap 3.x and jQuery.&lt;br&gt;&lt;br&gt;With a little inspiration from another post, &lt;a href=&quot;https://www.creare.co.uk/js-eu-cookie-law-banner&quot; target=&quot;_blank&quot;&gt;Create a simple 'Implied Consent' EU Cookie Law Banner with JavaScript&lt;/a&gt;, I modified this to simplify it and adapt it for Bootstrap and jQuery.&lt;br&gt;&lt;br&gt;&lt;pre&gt;var cookiePromptTest = false; //change this to true to test the message&lt;br&gt;&lt;br&gt;$(function () {&lt;br&gt;	if (cookiePromptTest || checkCookie(&quot;cookiePrompt&quot;) != &quot;on&quot;) {&lt;br&gt;		//header is the id of the element the message will appear before&lt;br&gt;		$(&quot;#header&quot;).before('&amp;lt;div id=&quot;cookie-prompt&quot; class=&quot;alert alert-danger&quot;&amp;gt;&amp;lt;button type=&quot;button&quot; class=&quot;close&quot; aria-label=&quot;Close&quot; onclick=&quot;closeCookiePrompt()&quot;&amp;gt;&amp;lt;span aria-hidden=&quot;true&quot;&amp;gt;&amp;times;&amp;lt;/span&amp;gt;&amp;lt;/button&amp;gt;This website uses cookies. By continuing we assume your permission to deploy cookies, as detailed in our &amp;lt;a href=&quot;/cookies-policy/&quot; class=&quot;alert-link&quot; rel=&quot;nofollow&quot; title=&quot;Cookies Policy&quot;&amp;gt;cookies policy&amp;lt;/a&amp;gt;.&amp;lt;/div&amp;gt;');&lt;br&gt;	}&lt;br&gt;});&lt;br&gt;&lt;br&gt;function closeCookiePrompt() {&lt;br&gt;	if (!cookiePromptTest) {&lt;br&gt;		createCookie(&quot;cookiePrompt&quot;, &quot;on&quot;, 30); //don't show message for 30 days once closed (change if required)&lt;br&gt;	}&lt;br&gt;	$(&quot;#cookie-prompt&quot;).remove();&lt;br&gt;}&lt;br&gt;&lt;br&gt;function createCookie(name, value, days) {&lt;br&gt;	if (days) {&lt;br&gt;		var date = new Date();&lt;br&gt;		date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));&lt;br&gt;		var expires = &quot;; expires=&quot; + date.toGMTString();&lt;br&gt;	}&lt;br&gt;	else var expires = &quot;&quot;;&lt;br&gt;	document.cookie = name + &quot;=&quot; + value + expires + &quot;; path=/&quot;;&lt;br&gt;}&lt;br&gt;&lt;br&gt;function checkCookie(name) {&lt;br&gt;	var nameEQ = name + &quot;=&quot;;&lt;br&gt;	var ca = document.cookie.split(';');&lt;br&gt;	for (var i = 0; i &lt; ca.length; i++) {&lt;br&gt;		var c = ca[i];&lt;br&gt;		while (c.charAt(0) == ' ') c = c.substring(1, c.length);&lt;br&gt;		if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);&lt;br&gt;	}&lt;br&gt;	return null;&lt;br&gt;}&lt;br&gt;&lt;br&gt;function eraseCookie(name) {&lt;br&gt;	createCookie(name, &quot;&quot;, -1);&lt;br&gt;}&lt;/pre&gt;&lt;br&gt;See this script in action on &lt;a href=&quot;http://www.ghosttourbookings.co.uk/&quot; target=&quot;_blank&quot;&gt;this website&lt;/a&gt;.</description>
<comments>https://johna.compoutpost.com/blog/784/simple-jquery-script-for-eu-cookie-law-using-jquery-and-bootstrap/#comments</comments>
<pubDate>2015-08-11T12:00:00+10:00</pubDate>
<category>Jquery/Javascript</category>
<category>Bootstrap</category>
<guid>https://johna.compoutpost.com/blog/784</guid>
</item>
<item>
<title>Navigate away protection for ASP.NET Web Forms</title>
<link>https://johna.compoutpost.com/blog/768/navigate-away-protection-for-asp-net-web-forms/</link>
<description>I have wanted to add protection from vavigating away from some of my web forms but most of the solutions I have seen do not work well with ASP.NET with it's many possible ways of posting back and AJAX using Update Panels.&lt;br&gt;&lt;br&gt;For my situation I wanted to protect against the user navigating away using the website's main navigation, closing the browser, clicking back or forward or even going to a favourite website. I did not want to protect against the user clicking any Button, Link Button or any control with AutoPostBack enabled within my form.&lt;br&gt;&lt;br&gt;After a Stack Overflow question and investigation of many possible solutions I have created the following JavaScript/jQuery code that does this better than anything I found.&lt;br&gt;&lt;br&gt;Please note that this code does not check whether any changes were made to the values in the form, although this functionality could be added easily. In my situation I wanted to protect against navigating away because my system had a form of record locking in place that required the user to exit correctly (or wait for lock to timeout).&lt;br&gt;&lt;br&gt;&lt;pre&gt;var navigateAway = function (e) {&lt;br&gt;	e = e || window.event;&lt;br&gt;&lt;br&gt;	//This is the message that is shown when navigating away improperly&lt;br&gt;	var message = &quot;Exiting the form this way will result in this record being locked to you. Use cancel changes instead to exit correctly&quot;;&lt;br&gt;&lt;br&gt;	if (e) {&lt;br&gt;		e.returnValue = message;&lt;br&gt;	}&lt;br&gt;&lt;br&gt;	return message;&lt;br&gt;}&lt;br&gt;&lt;br&gt;function EnableUnloadEvent() {&lt;br&gt;	window.onbeforeunload = navigateAway;&lt;br&gt;}&lt;br&gt;&lt;br&gt;$(function () {&lt;br&gt;	$(&quot;#myform select&quot;).change(function () {&lt;br&gt;		window.onbeforeunload = null;&lt;br&gt;		window.setTimeout(&quot;EnableUnloadEvent()&quot;, 100);&lt;br&gt;	});&lt;br&gt;&lt;br&gt;	$(&quot;#myform&quot;).click(function (e) {&lt;br&gt;		window.onbeforeunload = null;&lt;br&gt;		window.setTimeout(&quot;EnableUnloadEvent()&quot;, 100);&lt;br&gt;	});&lt;br&gt;&lt;br&gt;	EnableUnloadEvent();&lt;br&gt;});&lt;/pre&gt;&lt;br&gt;&lt;br&gt;&lt;em&gt;#myform&lt;/em&gt; refers to the ID of the container (eg. DIV) that contains all of your form elements that can be clicked or changed without triggering the unload message. Anything outside of this container will trigger the message.&lt;br&gt;&lt;br&gt;Should you want to only show the message if one or more of the form elements anywhere on the page have been changed, you could add functionality to store the initial values, then compare these in the unload event (some code sourced from &lt;a href=&quot;http://www.codeproject.com/Articles/132445/Work-smarter-not-harder-Navigate-Away-feature-for&quot; target=&quot;_blank&quot;&gt;Code Project&lt;/a&gt;). Note that if you have anything on your page that causes a full PostBack (rather than a partial PostBack) it will set and compare the initial values from the most recent PostBack, so won't be accurate.&lt;br&gt;&lt;br&gt;&lt;pre&gt;var initialValue = &quot;&quot;;&lt;br&gt;&lt;br&gt;var navigateAway = function (e) {&lt;br&gt;&lt;br&gt;	if (initialValue != GetFormValues()) {&lt;br&gt;&lt;br&gt;		e = e || window.event;&lt;br&gt;&lt;br&gt;		var message = &quot;Exiting the form this way will result in this record being locked to you. Use cancel changes instead to exit correctly&quot;;&lt;br&gt;&lt;br&gt;		if (e) {&lt;br&gt;			e.returnValue = message;&lt;br&gt;		}&lt;br&gt;&lt;br&gt;		return message;&lt;br&gt;	}&lt;br&gt;}&lt;br&gt;&lt;br&gt;function EnableUnloadEvent() {&lt;br&gt;	window.onbeforeunload = navigateAway;&lt;br&gt;}&lt;br&gt;&lt;br&gt;function GetFormValues() {&lt;br&gt;	var formValues = &quot;&quot;;&lt;br&gt;	$.each($(&quot;form&quot;).serializeArray(), function (i, field) {&lt;br&gt;		if (field.name != &quot;__EVENTVALIDATION&quot;&lt;br&gt;		&amp;&amp; field.name != &quot;__EVENTTARGET&quot;&lt;br&gt;		&amp;&amp; field.name != &quot;__EVENTARGUMENT&quot;&lt;br&gt;		&amp;&amp; field.name != &quot;__VIEWSTATE&quot;&lt;br&gt;		&amp;&amp; field.name != &quot;__VIEWSTATEENCRYPTED&quot;) {&lt;br&gt;&lt;br&gt;			var inputField = $(&quot;[name=&quot; + field.name + &quot;]&quot;);&lt;br&gt;			var displayProperty = $(inputField).css(&quot;display&quot;);&lt;br&gt;&lt;br&gt;			if (displayProperty != &quot;none&quot;) {&lt;br&gt;				formValues = formValues + &quot;-&quot; + field.name + &quot;:&quot; + field.value;&lt;br&gt;			}&lt;br&gt;		}&lt;br&gt;	});&lt;br&gt;&lt;br&gt;	$(&quot;:checkbox&quot;).each(function () {&lt;br&gt;		formValues = formValues + &quot;-&quot; + $(this).attr(&quot;checked&quot;);&lt;br&gt;	});&lt;br&gt;&lt;br&gt;	$(&quot;:radio&quot;).each(function () {&lt;br&gt;		formValues = formValues + &quot;-&quot; + $(this).attr(&quot;checked&quot;);&lt;br&gt;	});&lt;br&gt;&lt;br&gt;	$(&quot;:file&quot;).each(function () {&lt;br&gt;		formValues = formValues + &quot;-&quot; + $(this).val();&lt;br&gt;	});&lt;br&gt;	return formValues;&lt;br&gt;}&lt;br&gt;&lt;br&gt;$(function () {&lt;br&gt;&lt;br&gt;	initialValue = GetFormValues();&lt;br&gt;&lt;br&gt;	$(&quot;#form select&quot;).change(function () {&lt;br&gt;		window.onbeforeunload = null;&lt;br&gt;		window.setTimeout(&quot;EnableUnloadEvent()&quot;, 100);&lt;br&gt;	});&lt;br&gt;&lt;br&gt;	$(&quot;#form&quot;).click(function (e) {&lt;br&gt;		window.onbeforeunload = null;&lt;br&gt;		window.setTimeout(&quot;EnableUnloadEvent()&quot;, 100);&lt;br&gt;	});&lt;br&gt;&lt;br&gt;	EnableUnloadEvent();&lt;br&gt;});&lt;/pre&gt;</description>
<comments>https://johna.compoutpost.com/blog/768/navigate-away-protection-for-asp-net-web-forms/#comments</comments>
<pubDate>2015-05-20T12:00:00+10:00</pubDate>
<category>ASP.NET Web Forms</category>
<category>Jquery/Javascript</category>
<guid>https://johna.compoutpost.com/blog/768</guid>
</item>
<item>
<title>Cross-browser responsive design without media queries</title>
<link>https://johna.compoutpost.com/blog/722/cross-browser-responsive-design-without-media-queries/</link>
<description>&lt;h2&gt;...Or an alternative to media queries for responsive design&lt;/h2&gt;&lt;br&gt;I really like the concept of responsive design, and I have read a lot of the positive stuff about it, and also read a lot of the negative too.&lt;br&gt;&lt;br&gt;Despite the negatives, I do think that responsive design is the way of the future (and now), but it seems that it is far from easy to get right.&lt;br&gt;&lt;br&gt;I'm taking my first steps in responsive design now, but one of the first issues I encountered is browser compatibility for CSS3 media queries. The Google Analytics statistics for most of the sites I maintain still show enough users with Internet Explorer 8 that I still need to consider these browsers during development.&lt;br&gt;&lt;br&gt;I have looked at some of the well-known fixes but the idea of requesting my CSS files by AJAX and parsing them doesn't sound too efficient, even though it will only happen with IE8 and below.&lt;br&gt;&lt;br&gt;An alternative is to use JavaScript or jQuery to handle the change of display size for all browsers, and I put something together quickly to do this. As the experimental work I have done so far has only involved using &quot;max-width&quot;, my jQuery example presented here works only that way.&lt;br&gt;&lt;br&gt;It works simply by applying classes to a specified tag (default is BODY tag). So if you have breakpoints set at 500 pixels and 300 pixels, it will apply no classes at 600 pixels, the 500 pixel class (which you specify) once the display width is less than or equal to 500 pixels, and both the 500 and 300 pixels classes when the display width is less than or equal to 300 pixels. It adjusts classes on document ready and when the browser is resized.&lt;br&gt;&lt;br&gt;Here's the code and a sample (jQuery required):&lt;br&gt;&lt;br&gt;&lt;pre&gt;&amp;lt;script type=&quot;text/javascript&quot;&amp;gt;&lt;br&gt;	(function ($) {&lt;br&gt;	    	jQuery.responsive = function (options) {&lt;br&gt;	    		var settings = $.extend({&lt;br&gt;	    			breakpoints: [],&lt;br&gt;	    			container: &quot;body&quot;&lt;br&gt;	    		}, options);&lt;br&gt;	    		$(window).resize(function () {&lt;br&gt;	    			respond();&lt;br&gt;	    		});&lt;br&gt;	    		respond();&lt;br&gt;	    		function respond() {&lt;br&gt;	    			for (var i = 0; i &lt; settings.breakpoints.length; i++) {&lt;br&gt;	    				if ($(window).width() &lt;= settings.breakpoints[i][0]) {&lt;br&gt;	    					$(settings.container).addClass(settings.breakpoints[i][1]);&lt;br&gt;	    				}&lt;br&gt;	    				else {&lt;br&gt;	    					$(settings.container).removeClass(settings.breakpoints[i][1]);&lt;br&gt;	    				}&lt;br&gt;	    			}&lt;br&gt;	    		};&lt;br&gt;	    	}&lt;br&gt;	})(jQuery);&lt;br&gt;&lt;br&gt;	//Sample usage - adds two breakpoints and applies class to BODY tag&lt;br&gt;	$(function () {&lt;br&gt;		$.responsive({&lt;br&gt;			//array with max-width in pixels, and class to apply&lt;br&gt;			breakpoints: [[980, &quot;mw980&quot;], [480, &quot;mw480&quot;]]&lt;br&gt;			//optional specify container to apply classes to, default is body&lt;br&gt;			//,container: &quot;#wrapper&quot;&lt;br&gt;		});&lt;br&gt;	});&lt;br&gt;&amp;lt;/script&amp;gt;&lt;/pre&gt;&lt;br&gt;I mocked up a couple of samples, one using media queries and my alternative using jQuery:&lt;br&gt;&lt;br&gt;&lt;a href=&quot;/blog/uploads/att722_media-queries.htm&quot; target=&quot;_blank&quot;&gt;Example using media queries&lt;/a&gt;&lt;br&gt;&lt;br&gt;&lt;a href=&quot;/blog/uploads/att722_jquery-alternative.htm&quot; target=&quot;_blank&quot;&gt;Example using jQuery&lt;/a&gt;&lt;br&gt;&lt;br&gt;They should appear and operate identically in all browsers, except IE8 and below where the media queries example will not work as designed.&lt;br&gt;&lt;br&gt;&lt;b&gt;Update:&lt;/b&gt; I have since redesigned my blog using this technique. I also added support for min-width. You can look at the source of this page to see updated code.&lt;br&gt;&lt;br&gt;&lt;b&gt;Update:&lt;/b&gt; I found that one of the disadvantages of using this technique is that the adding of classes does not happen until document ready. This means that there can be a delay in applying the classes which is visible to the user. To solve this problem I suggest adding a second script using native JavaScript to add the classes immediately after the BODY open tag (or whichever tag has the clases applied). Example (also applied to this website):&lt;br&gt;&lt;br&gt;&lt;pre&gt;var container = &quot;body&quot;; //must be ID&lt;br&gt;var breakpoints = [&lt;br&gt;	[&quot;max-width&quot;, 650, &quot;max650&quot;],&lt;br&gt;	[&quot;min-width&quot;, 986, &quot;min986&quot;]&lt;br&gt;];&lt;br&gt;for (var i = 0; i &lt; breakpoints.length; i++) {&lt;br&gt;	if (breakpoints[i][0] == &quot;max-width&quot;) {&lt;br&gt;		if (window.innerWidth &lt;= breakpoints[i][1]) {&lt;br&gt;			document.getElementById(container).className = breakpoints[i][2];&lt;br&gt;		}&lt;br&gt;	}&lt;br&gt;	else if (breakpoints[i][0] == &quot;min-width&quot;) {&lt;br&gt;		if (window.innerWidth &gt;= breakpoints[i][1]) {&lt;br&gt;			document.getElementById(container).className = breakpoints[i][2];&lt;br&gt;		}&lt;br&gt;	}&lt;br&gt;}&lt;/pre&gt;&lt;br&gt;&lt;br&gt;I've actually created some ASP.NET server controls to handle this in some of the projects I am working on. Let me know if you're interested in seeing that.&lt;br&gt;&lt;br&gt;&lt;b&gt;Update:&lt;/b&gt;&lt;br&gt;&lt;br&gt;I have created a pure JavaScript version of the cross-browser responsive media query alternative code. This version should be placed immediately after the opening tag for the element that classes are to be applied. The id of that element and the breakpoints need to be adjusted to your requirements. Here's an example:&lt;br&gt;&lt;br&gt;&lt;pre&gt;&amp;lt;script type=&quot;text/javascript&quot;&amp;gt;&lt;br&gt;	function hasClass(el, cls) {&lt;br&gt;		return el.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));&lt;br&gt;	}&lt;br&gt;	function addClass(el, cls) {&lt;br&gt;		if (!this.hasClass(el, cls)) el.className += &quot; &quot; + cls;&lt;br&gt;	}&lt;br&gt;	function removeClass(el, cls) {&lt;br&gt;		if (hasClass(el, cls)) {&lt;br&gt;			var reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');&lt;br&gt;			el.className = el.className.replace(reg, ' ');&lt;br&gt;		}&lt;br&gt;	}&lt;br&gt;&lt;br&gt;	var addEvent = function (elem, type, eventHandle) {&lt;br&gt;		if (elem == null || elem == undefined) return;&lt;br&gt;		if (elem.addEventListener) {&lt;br&gt;			elem.addEventListener(type, eventHandle, false);&lt;br&gt;		} else if (elem.attachEvent) {&lt;br&gt;			elem.attachEvent(&quot;on&quot; + type, eventHandle);&lt;br&gt;		} else {&lt;br&gt;			elem[&quot;on&quot; + type] = eventHandle;&lt;br&gt;		}&lt;br&gt;	};&lt;br&gt;&lt;br&gt;	function responsive() {&lt;br&gt;		var w = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;&lt;br&gt;&lt;br&gt;		for (var i = 0; i &lt; breakpoints.length; i++) {&lt;br&gt;			if (breakpoints[i][0] == &quot;max-width&quot;) {&lt;br&gt;				if (w &lt;= breakpoints[i][1]) {&lt;br&gt;					addClass(document.getElementById(id), breakpoints[i][2]);&lt;br&gt;				}&lt;br&gt;				else {&lt;br&gt;					removeClass(document.getElementById(id), breakpoints[i][2]);&lt;br&gt;				}&lt;br&gt;			}&lt;br&gt;			else if (breakpoints[i][0] == &quot;min-width&quot;) {&lt;br&gt;				if (w &gt;= breakpoints[i][1]) {&lt;br&gt;					addClass(document.getElementById(id), breakpoints[i][2]);&lt;br&gt;				}&lt;br&gt;				else {&lt;br&gt;					removeClass(document.getElementById(id), breakpoints[i][2]);&lt;br&gt;				}&lt;br&gt;			}&lt;br&gt;		}&lt;br&gt;	}&lt;br&gt;&lt;br&gt;	var resizeTimeoutId;&lt;br&gt;&lt;br&gt;	function resized() {&lt;br&gt;		window.clearTimeout(resizeTimeoutId);&lt;br&gt;		resizeTimeoutId = window.setTimeout('responsive();', 10);&lt;br&gt;	}&lt;br&gt;&lt;br&gt;	var id = &quot;body&quot;;&lt;br&gt;	var breakpoints = [[&quot;max-width&quot;, 630, &quot;max630&quot;], [&quot;min-width&quot;, 1890, &quot;min1890&quot;]];&lt;br&gt;&lt;br&gt;	addEvent(window, &quot;resize&quot;, resized);&lt;br&gt;&lt;br&gt;	responsive();&lt;br&gt;&amp;lt;/script&amp;gt;&lt;/pre&gt;</description>
<comments>https://johna.compoutpost.com/blog/722/cross-browser-responsive-design-without-media-queries/#comments</comments>
<pubDate>2013-06-07T12:00:00+10:00</pubDate>
<category>Responsive Web Design</category>
<category>Jquery/Javascript</category>
<guid>https://johna.compoutpost.com/blog/722</guid>
</item>
<item>
<title>Jquery/Javascript plugin to truncate text to fit container height and width</title>
<link>https://johna.compoutpost.com/blog/589/jquery-javascript-plugin-to-truncate-text-to-fit-container-height-and-width/</link>
<description>I recently needed a Javascript function to truncate text to fit a container such as a DIV, not just in width but in height. I found plenty of functions that truncate to width but couldn't find anything by height so I created this Jquery plugin that does the job. Please note that it does not handle HTML so if HTML content is truncated it may truncate mid-tag and break the display.&lt;br&gt;&lt;br&gt;To use the function use one the the following syntax:&lt;br&gt;&lt;pre&gt;$(&quot;#id&quot;).truncate();&lt;/pre&gt;&lt;i&gt;Truncates to size of container and adds &quot;...&quot; if the text requires truncation.&lt;/i&gt;&lt;br&gt;&lt;pre&gt;$(&quot;#id&quot;).truncate(&quot; &amp;lt;a href='#'&amp;gt;read more&amp;lt;/a&amp;gt;&quot;);&lt;/pre&gt;&lt;i&gt;Truncates to size of container and adds specified text (in this example a read more link) if the text requires truncation.&lt;/i&gt;&lt;br&gt;&lt;br&gt;&lt;a href=&quot;/blog/uploads/att589_example.htm&quot; target=&quot;_blank&quot;&gt;Click here for an example&lt;/a&gt;&lt;br&gt;&lt;br&gt;Your container should have a fixed height and &lt;i&gt;overflow&lt;/i&gt; set to &lt;i&gt;hidden&lt;/i&gt;. You will obviously also need to include the jQuery library.&lt;br&gt;&lt;br&gt;&lt;pre&gt;(function ($) {&lt;br&gt;       $.fn.truncate = function (options) {&lt;br&gt;              if (!options) options = &quot;...&quot;;&lt;br&gt;              return this.each(function (num) {&lt;br&gt;                     var height = parseInt($(this).css(&quot;height&quot;));&lt;br&gt;                     var content = $(this).html();&lt;br&gt;                     while (this.scrollHeight &gt; height) {&lt;br&gt;                           content = content.replace(/s+S*$/, &quot;&quot;);&lt;br&gt;                           $(this).html(content + options.more);&lt;br&gt;                     }&lt;br&gt;              })&lt;br&gt;       }&lt;br&gt;})(jQuery);&lt;/pre&gt;&lt;br&gt;&lt;br&gt;If you only want to truncate a string to a single line, and want a server-side solution, then I recommend you read the Code Project article &lt;a href=&quot;http://www.codeproject.com/Articles/108236/Truncating-a-text-string-in-ASP-NET-to-fit-within&quot; target=&quot;_blank&quot;&gt;Truncating a text string in ASP.NET to fit within a given pixel width&lt;/a&gt;.</description>
<comments>https://johna.compoutpost.com/blog/589/jquery-javascript-plugin-to-truncate-text-to-fit-container-height-and-width/#comments</comments>
<pubDate>2011-07-06T12:00:00+10:00</pubDate>
<category>Jquery/Javascript</category>
<guid>https://johna.compoutpost.com/blog/589</guid>
</item>
</channel>
</rss>
