Skip to content

Ecce Signum

Immanentize the Empathy

  • Home
  • About Me
  • Published Works and Literary Matters
  • Indexes
  • Laboratory
  • Notebooks
  • RSS Feed

Backbone/Marionette App Using Models, Collections and LocalStorage

2014-02-17 John Winkelman

This is an example of a simple library application which uses Backbone.js Models and Collections, and Marionette.js ItemViews, CollectionViews, and Layouts. It also uses the Backbone.js LocalStorage plugin, for saving data to the browser’s local storage system.

Here is the Javascript file, called “script.js”:

/* define the application */
var app = new Backbone.Marionette.Application();

/* define the region into which the app will be rendered */
app.addRegions({
	libraryRegion: '#libraryApp' // element in the HTML file
});

/* define the module we will use for this app */
app.module('Library',function(module, App, Backbone, Marionette, $, _){

	/* define the model used for a Genre */
	module.GenreModel = Backbone.Model.extend({
		defaults: {
			title: ''
		}
	});

	/* define the collection used for Genres */
	module.GenreCollection = Backbone.Collection.extend({

		/* set the model used in this collection */
		model: module.GenreModel,

		/* define localStorage container for this collection */
		localStorage: new Backbone.LocalStorage("LibraryGenreCollection")
	});

	/* define the model used for an Author */
	module.AuthorModel = Backbone.Model.extend({
		defaults: {
			firstName: '',
			lastName: ''
		}
	});

	/* define the collection used for Authors */
	module.AuthorCollection = Backbone.Collection.extend({
		/* set the model used in this collection */
		model: module.AuthorModel,

		/* define the localStorage container for his collection */
		localStorage: new Backbone.LocalStorage("LibraryAuthorCollection")
	});

	/* define the model used for a Book */
	module.BookModel = Backbone.Model.extend({
		defaults: {
			id: null,
			title: '',
			authorFirst: '',
			authorLast: '',
			genre: ''
		}
	});

	/* define the collection used for Books */
	module.BookCollection = Backbone.Collection.extend({

		/* set the model used in this collection */
		model: module.BookModel,
		localStorage: new Backbone.LocalStorage("LibraryCollection")
	});

	/* the main layout used for this app */
	module.LibraryView = Marionette.LayoutView.extend({

		/* local variables used to store/access collections */
		bookCollection: null,
		authorCollection: null,
		genreCollection: null,

		/* HTML template used for this view */
		template: '#layout-template',

		/* define mouse events */
		events: {
			'click #btnSave' : 'onSaveClicked',
			'click #btnClear' : 'onClearClicked',
			'click #btnClearCache' : 'onClearCacheClicked',
			'change #selectGenreRegion select' : 'onGenreSelected',
			'change #selectAuthorRegion select' : 'onAuthorSelected'
		},

		/* UI shortcuts */
		ui: {
			inputTitle: '#txtTitle', // title input field
			inputAuthorFirst: '#txtAuthorFirst', // author first name input field
			inputAuthorLast: '#txtAuthorLast', // author last name input field
			inputGenre: '#txtGenre', // genre input field
			selectGenre: '#selectGenre', // genre select list
			selectAuthor: '#selectAuthor' // author select list
		},

		/* regions into which other views will be rendered */
		regions: {
			authorRegion: '#selectAuthorRegion', // dropdown list of authors
			genreRegion: '#selectGenreRegion', // dropdown list of genres
			outputRegion: '#outputRegion' // list of books
		},

		/* called on click of SAVE button */
		onSaveClicked: function() {
			this.addBook();
		},

		/* called on click of CLEAR FORM button */
		onClearClicked: function() {
			this.clearForm();
		},

		/* called on click of CLEAR CACHE button.
			this will empty the localstorage cache for this app */
		onClearCacheClicked: function() {
			window.localStorage.clear();
			this.render();
		},

		/* called when a genre is selected from the dropdown menu */
		onGenreSelected: function() {
			var selectedGenre = $('#selectGenre').val();
			
			/* if the selected genre value is blank, do nothing */
			if(selectedGenre === '') return;

			/* update the genre input element with the value pulled from the select element */
			this.ui.inputGenre.val(selectedGenre);
		},

		/* called when an author is selected from the dropdown menu */
		onAuthorSelected: function() {
			
			/* set the values of the first and last name */
			var authorFirst = $('#selectAuthor').val().split(', ')[1];
			var authorLast = $('#selectAuthor').val().split(', ')[0];

			/* if the value is blank, do nothing */
			if(authorFirst === '' && authorLast === '') return;

			/* update the appropriate input elements with the values pulled from the select element */
			this.ui.inputAuthorFirst.val(authorFirst);
			this.ui.inputAuthorLast.val(authorLast);
		},

		/* add a book to the collection */
		addBook: function() {

			/* get values from the form elements */
			var newTitle = this.ui.inputTitle.val(),
				newAuthorFirst = this.ui.inputAuthorFirst.val(),
				newAuthorLast = this.ui.inputAuthorLast.val(),
				newGenre = this.ui.inputGenre.val();

			/* add a new model to the book collection.
				Automatically adds and instance of the 
				module.BookModel type because that is the 
				model associated with the BookCollection */
			this.bookCollection.add({
				id: new Date().getTime(),
				title: newTitle,
				authorFirst: newAuthorFirst,
				authorLast: newAuthorLast,
				genre: newGenre
			});

			/* iterate through the collection and save each model. 
				necessary to do it this way because of how
				Backbone.localStorage works. */
			this.bookCollection.each(function(oBook) {
				oBook.save();
			});

			/* clear the form elements */
			this.clearForm();

			/* add the author to the list of authors */
			this.addAuthor(newAuthorFirst,newAuthorLast);

			/* add the genre to the list of genres */
			this.addGenre(newGenre);
		},

		/* add a genre to the list of genres */
		addGenre: function(sGenre) {

			/* set a state variable */
			var exists = false;

			/* iterate through the collection to see if the genre
				we are attempting to add already exists */
			this.genreCollection.each(function(oGenre) {
				if(oGenre.get('title') === sGenre) exists = true;
			});

			/* if the genre already exists, exit and do nothing */
			if(exists) return;

			/* add the new genre to the collection. Defaults to an
				instance of module.GenreModel, because that is what
				is associated with the module.GenreCollection */
			this.genreCollection.add({
				title: sGenre
			});

			/* iterate through the genres in the collection and save each one */
			this.genreCollection.each(function(oGenre) {
				oGenre.save();
			});
		},

		/* add an author to the list of authors */
		addAuthor: function(sAuthFirst, sAuthLast) {
			/* set a state variable */
			var exists = false;

			/* iterate through the collection to see if the author
				we are attempting to add already exists */
			this.authorCollection.each(function(oAuthor) {
				if(oAuthor.get('firstName') === sAuthFirst && oAuthor.get('lastName') === sAuthLast) exists = true;
			});

			/* if the author already exists, exit and do nothing */
			if(exists) return;

			/* add the new author to the collection. Defaults to an
				instance of module.AuthorModel, because that is what
				is associated with the module.AuthorCollection */
			this.authorCollection.add({
				firstName: sAuthFirst,
				lastName: sAuthLast
			});

			/* iterate through the authors in the collection and save each one */
			this.authorCollection.each(function(oAuthor) {
				oAuthor.save();
			});
		},

		/* clear all of the imput elements in the form */
		clearForm: function() {
			this.ui.inputTitle.val('');
			this.ui.inputAuthorFirst.val('');
			this.ui.inputAuthorLast.val('');
			this.ui.inputGenre.val('');
		},

		/* called when the DOM for this view is ready for manipulation */
		onRender: function(){
			/* save a local reference to the view, for use in
				model and collection callbacks */
			var capturedThis = this;

			/* create a new instance of the book collection */
			this.bookCollection = new module.BookCollection();

			/* load the book collection from local storage */
			this.bookCollection.fetch()
				.fail(function(){ /* something went wrong */
					console.log('failed to load book collection');
				})
				.done(function(){
					/* when loaded, create a new collectionview using it */
					var bookCollectionView = new module.BookCollectionView({collection: capturedThis.bookCollection});

					/* render the collectionview in the appropriate region */
					capturedThis.outputRegion.show(bookCollectionView);
				});
			
			/* create a new instance of the genre collection */
			this.genreCollection = new module.GenreCollection();

			/* load the collection of genres from local storage */
			this.genreCollection.fetch()
				.fail(function() { /* something went wrong */
					console.log('failed to load genre collection');
				})
				.done(function() { /* loaded successfully */

					/* if the genre collection is empty, add an initial element to use as a label */
					if(capturedThis.genreCollection.length === 0) {
						capturedThis.genreCollection.add({
							title: ''
						});
					}
					/* Create a new collectionview using the genreCollection */
					var genreCollectionView = new module.GenreCollectionView({collection: capturedThis.genreCollection});

					/* render the collectionview in the appropriate region */
					capturedThis.genreRegion.show(genreCollectionView);
				});

			/* create a new instance of the author collection */
			this.authorCollection = new module.AuthorCollection();

			/* load the collection of authors from local storage */
			this.authorCollection.fetch()
				.fail(function() { /* something went wrong */
					console.log('failed to load author collection');
				})
				.done(function() { /* loaded succesfully */

					/* if the genre collection is empty, add an initial element to use as a label */
					if(capturedThis.authorCollection.length === 0) {
						capturedThis.authorCollection.add({
							firstName: '',
							lastName: ''
						});
					}
					/* when the collection is loaded, create a new collectionview using it */
					var authorCollectionView = new module.AuthorCollectionView({collection: capturedThis.authorCollection});

					/* render the collectionview in the appropriate region */
					capturedThis.authorRegion.show(authorCollectionView);
				});
		}
	});
	
	/* item view for individual book */
	module.BookItemView = Marionette.ItemView.extend({
		tagName: 'li',
		template: '#book-template',
		events: {
			'click .delete' : 'onDeleteClicked'
		},

		/* when the delete button is clicked, destroy this model
			this removes the model from the parent collection */
		onDeleteClicked: function(){
			this.model.destroy();
		}
	});

	/* collection view for the list of books */
	module.BookCollectionView = Marionette.CollectionView.extend({
		tagName: 'ul',
		childView: module.BookItemView,
		id: 'bookList',

		/* listen for changes in the collection or the models therein */
		modelEvents: {
			'change' : 'onModelChanged'
		},

		/* when the collection or one of the models therein broadcasts a change event,
			re-render this view. In this example, this is called when a book is added
			or removed from the collection. */
		onModelChanged: function() {
			this.render();
		}
	});

	/* item view for an individual genre */
	module.GenreItemView = Marionette.ItemView.extend({
		template: '#genre-template',
		tagName: 'option',

		/* add attributes to this element. Setting a return value as is done
			here allows for attributes to be set 'on the fly'; in this instance
			each element created will have a unique value derived from
			the model associated with this view. */
		attributes: function(){
			return {
				value: this.model.get('title')
			}
		}
	});

	/* define the genre collection view */
	module.GenreCollectionView = Marionette.CollectionView.extend({
		tagName: 'select',
		childView: module.GenreItemView,
		id: 'selectGenre'
	});

	module.AuthorItemView = Marionette.ItemView.extend({
		template: '#author-template',
		tagName: 'option',

		/* add attributes to this element. Setting a return value as is done
			here allows for attributes to be set 'on the fly'; in this instance
			each element created will have a unique value derived from
			the lastName and firstName attributes of the model associated with this view. */
		attributes: function(){
			var authName = this.model.get('lastName') + ', ' + this.model.get('firstName');
			return {
				value: authName
			}
		}
	});

	/* define the author collection view */
	module.AuthorCollectionView = Marionette.CollectionView.extend({
		tagName: 'select',
		childView: module.AuthorItemView,
		id: 'selectAuthor'
	});

	/* add a method which will fire on the initialization
		of this application. In this case, render
		the main view in the region which was defined
		up at the top of this file. */
	module.addInitializer(function(){
		app.libraryRegion.show(new module.LibraryView());
	});

});

/* once the DOM is ready, start the application */
$(document).ready(function() {app.start();});

Here is the HTML file:

<!doctype html>
<html>
	<head>
		<title>Library App Created in Backbone/Marionette Using LocalStorage</title>
		<link rel="stylesheet" href="style.css"/>
	</head>
	<body>
		<!-- Base element for app -->
		<!--
			Don't use the BODY element as the base element, because when the app renders in the BODY
			it will wipe out the template files before the views can pick them up.
		-->
		
		<div id="libraryApp"></div>

		<!-- TEMPLATES -->
		<!-- main layout template -->
		<script type="text/template" id="layout-template">
			<h1>Library App Using LocalStorage</h1>
			<div id="inputRegion">
				<div><label>Title</label><input id="txtTitle" type="text"/></div>
				<div class="author-row">
					<label>Author (last, first)</label><input id="txtAuthorLast" type="text"/><input id="txtAuthorFirst" type="text"/>
					<div id="selectAuthorRegion" class="selectContainer"></div>
				</div>
				<div>
					<label>Genre</label><input id="txtGenre" type="text"/>
					<div id="selectGenreRegion" class="selectContainer"></div>
				</div>
				<p class="buttons">
					<button id="btnSave">Save</button>
					<button id="btnClear">Clear</button>
					<button id="btnClearCache">Clear LocalStorage</button>
				</p>
			</div>
			<div id="outputRegion"></div>
		</script>

		<!-- book line item template -->
		<script type="text/template" id="book-template">
			<span>Title: <%- title %></span>
			<span>Author: <%- authorLast %>, <%- authorFirst %><br/></span>
			<span>Genre: <%- genre %><br/></span>
			<button class="delete">X</button>
		</script>

		<!-- genre option template -->
		<script type="text/template" id="genre-template">
		<%- title %>
		</script>

		<!-- author option template -->
		<script type="text/template" id="author-template">
		<%- lastName %>, <%- firstName %>
		</script>

		<!-- libraries -->
		<script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
		<script type="text/javascript" src="js/underscore.js"></script>
		<script type="text/javascript" src="js/backbone.js"></script>
		<script type="text/javascript" src="js/backbone.localStorage.js"></script>
		<script type="text/javascript" src="js/backbone.marionette.js"></script>

		<!-- app code -->
		<script type="text/javascript" src="js/script.js"></script>
	</body>
</html>

And here is the style sheet:

#libraryApp {
	width: 600px;
	margin: 0 auto;
}
h1 {
	float:left;
	text-align: center;
	width: 600px;
	margin: 0 auto 10px auto;
	border-bottom: 1px solid #cdcdcd;
}
#inputRegion,#outputRegion {
	float: left;
	width: 100%;
	margin: 0 0 10px 0;
}
#inputRegion > div {
	float: left;
	clear: both;
}
#inputRegion label {
	float: left;
	width: 120px;
}
#inputRegion input {
	float: left;
	width: 200px;
}
#inputRegion .author-row input {
	width: 98px;
}
#inputRegion .selectContainer {
	float: left;
}
#inputRegion select {
	float: left;
	margin-left: 20px;
	width: 200px;
}
.buttons {
	float: left;
	clear: both;
}
#outputRegion ul{
	list-style: none;
	margin: 0;
	padding: 0;
	float: left;
	width: 100%;
	border-top: 1px solid #ccc;
}
#outputRegion li {
	position: relative;
	float: left;
	width: 100%;
	height: 60px;
	border-bottom: 1px solid #ccc;
}
#outputRegion li span {
	float: left;
	width: 80%;
}
#outputRegion li button {
	position: absolute;
	right: 0;
	top: 5px;
}

These are the code libraries used in this demo:

  • jQuery.js
  • Underscore.js
  • Backbone.js
  • Backbone Localstorage plugin
  • Marionette.js

 

Posted in Programming comment on Backbone/Marionette App Using Models, Collections and LocalStorage

Simple Marionette.js Model, Collection and CollectionView Example

2014-02-09 John Winkelman

This is a simple example of using Backbone.js models and collections, and the Marionette.js ItemView and CollectionView. Here is the Javascript:

/* create a new instance of the Marionette app */
var app = new Backbone.Marionette.Application();

/* add the initial region which will contain the app */
app.addRegions({
	/* reference to container element in the HTML file */
	appRegion: '#AppBase'
});

/* define a module to keep the code modular */
app.module('App',function(module, App, Backbone, Marionette, $, _){
	
	/* definition for book model, with default example of data structure */
	module.BookModel = Backbone.Model.extend({
		defaults: {
			title: '',
			authorFirst: '',
			authorLast: ''
		}
	});

	/* definition for book collection */
	module.BookCollection = Backbone.Collection.extend({
		
		/* set model type used for this collection */
		model: module.BookModel,

		/* comparator determines how collection is sorted */
		comparator: 'authorLast'
	});

	/* definition for individual item view */
	module.BookItemView = Marionette.ItemView.extend({
		tagName: 'li',

		/* set the template used to display this view */
		template: '#itemView-template',

		/* used to show the order in which these method are called */
		initialize: function(){ console.log('BookItemView: initialize >>> ' + this.model.get('title')) },
		onRender: function(){ console.log('BookItemView: onRender >>> ' + this.model.get('title')) },
		onShow: function(){ console.log('BookItemView: onShow >>> ' + this.model.get('title')) }
	});

	/* definition for collection view */
	module.BookCollectionView = Marionette.CollectionView.extend({
		tagName: 'ul',

		/* explicitly set the childview (formerly 'itemView') used to display the models in this collection */
		childView: module.BookItemView,

		initialize: function(){ console.log('BookCollectionView: initialize') },
		onRender: function(){ console.log('BookCollectionView: onRender') },
		onShow: function(){ console.log('BookCollectionView: onShow') }
	});

	/* define a view; in this case a 'LayoutView' (formerly 'Layout') */
	module.AppLayoutView = Marionette.LayoutView.extend({
		
		/* the auto-generated element which contains this view */
		tagName: 'div',

		/* id attribute for the auto-generated container element */
		id: 'AppContainer',

		/* reference to the template which will be rendered for this view */
		template: '#layout-template',

		/* define the regions within this layout, into which we will load additional views */
		regions: {
			'RegionOne' : '#regionOne'
		},

		/* called when the view initializes, before it displays */
		initialize: function() {
			console.log('main layout: initialize');
		},

		/* called when view renders in the DOM. This is a good place to 
			add nested views, because the views need existing DOM elements
			into which to be rendered. */
		onRender: function() {
			console.log('main layout: onRender');
			
			/* create an array of books using anonymouse objects;
				the objects have the same structure as in the 'defaults'
				attribute of the module.BookModel definition */
			var bookArray = [];
			bookArray.push({title: 'Wolf',authorLast: 'Harrison', authorFirst: 'Jim'});
			bookArray.push({title: 'The Theory and Practice of Rivers', authorLast: 'Snyder', authorFirst: 'Gary'});
			bookArray.push({title: 'Weather Central',authorLast: 'Kooser', authorFirst: 'Ted'});
			bookArray.push({title: 'Losing Season',authorLast: 'Ridl', authorFirst: 'Jack'});
			bookArray.push({title: 'Mornings Like This',authorLast: 'Dillard', authorFirst: 'Annie'});

			/* create a collection using the array of anonymous objects */
			var bookCollection = new module.BookCollection(bookArray);

			/* create new instance of the collection view using the bookCollection */
			var bookCollectionView = new module.BookCollectionView({collection: bookCollection});

			/* display the collection view in region 1 */
			this.RegionOne.show(bookCollectionView);
		},

		/* called when the view displays in the UI */
		onShow: function() {
			console.log('main layout: onShow');
		}
	});

	/* Tell the module what to do when it is done loading */
	module.addInitializer(function(){
		/* create a new instance of the layout from the module */
		var layout = new module.AppLayoutView();

		/* display the layout in the region defined at the top of this file */
		app.appRegion.show(layout);
	});
});

/* once the DOM initializes, start the app */
$(document).ready(function() {app.start();});

And here is the HTML, including the templates used by the Marionette views.

<!doctype html>
<html>
	<head>
		<title>Backbone/Marionette basic setup with nested views</title>
		<style type="text/css">
		body {
			background: #ffffff;
		}
		#AppBase {
			width: 400px;
			margin: 10px auto;
			padding: 0;
			outline: 1px solid #808080;
		}
		#AppContainer {
			width: 400px;
			margin: 0;
			padding: 0;
		}
		h1 {
			width:100%;
			margin: 0;
			padding: 0;
			text-align: center;
			border-bottom: 1px solid #808080;
		}
		ul {
			margin: 1em 0 0 0;
			padding:0;
			list-style-type: none;
			width: 400px;
		}
		li {
			margin-bottom: .5em;
			padding: 0 0 .5em 1em;
			border-bottom: 1px solid #dedede;
		}
		</style>
	</head>
	<body>
		<!-- Base element for app -->
		<!--
			Dont use the BODY element as the base because when the app renders in the BODY
			it will wipe out the template files before the views can pick them up 
		-->
		<div id="AppBase"></div>

		<!-- TEMPLATES -->
		<!-- main layout template -->
		<script type="text/template" id="layout-template">
			<h1>Main Template</h1>
			<div id="regionOne"></div>
		</script>

		<!-- bookItemView template -->
		<!-- This used the Underscore templating system, which is built into Marionette.js -->
		<script type="text/template" id="itemView-template">
			<span><b>Title</b>: <%- title %></span><br/>
			<span><b>Author</b>: <%- authorLast %>, <%- authorFirst %></span>
		</script>

		<!-- libraries -->
		<script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
		<script type="text/javascript" src="js/underscore.js"></script>
		<script type="text/javascript" src="js/backbone.js"></script>
		<script type="text/javascript" src="js/backbone.marionette.js"></script>

		<!-- app code -->
		<script type="text/javascript" src="js/script.js"></script>
	</body>
</html>

Here are links to the code libraries used to create this example:

  • jQuery.js
  • Underscore.js
  • Backbone.js
  • Marionette.js
Posted in Programming comment on Simple Marionette.js Model, Collection and CollectionView Example

A Collection of Links Concerning Gary Snyder

2014-02-05 John Winkelman

This is a collection of links to articles about, and interviews with, poet and environmentalist Gary Snyder. They are in roughly chronological order, and will be updated as time and interest allows.

“The Wild Mind of Gary Snyder”, Shambhala Sun interview, May 1996.

“The Art of Poetry”, Paris Review interview with GS, 1996.

GS in 2009 at U.C. Berkeley on the 50th anniversary of publishing RipRap. (video)

The Oregonian interview with GS, June 2011.

NCTV Interview with GS in 2012. (video)

“The Man in the Clearing”, Ian Sinclair discusses GS, London Review of Books, May 2012. (essay and video)

Posted in Literary MattersTagged Gary Snyder comment on A Collection of Links Concerning Gary Snyder

Simple Backbone/Marionette Example With Nested Views

2014-02-02 John Winkelman

Here is the Javascript file:

/* create a new instance of the Marionette app */
var app = new Backbone.Marionette.Application();

/* add the initial region which will contain the app */
app.addRegions({
	/* reference to container element in the HTML file */
	appRegion: '#AppBase'
});

/* define a module to keep the code modular */
app.module('App',function(module, App, Backbone, Marionette, $, _){
	
	module.RegionOneLayoutView = Marionette.LayoutView.extend({
		
		/* specify element type of auto-generated container element */
		tagName: 'div',

		/* add ID attribute to the generated element */
		id: 'regionOneLayoutView',

		/* add class name to the generated element */
		template: '#layout-region-one',

		/* HTML template for this view */
		className: 'subLayout',
		initialize: function(){console.log('Region 1 Layout: initialize');},
		onRender: function(){console.log('Region 1 Layout: onRender');},
		onShow: function(){console.log('Region 1 Layout: onShow');}
	});

	module.RegionTwoLayoutView = Marionette.Layout.extend({
		
		/* specify element type of auto-generated container element */
		tagName: 'div',

		/* add ID attribute to the generated element */
		id: 'regionTwoLayoutView',

		/* add class name to the generated element */
		className: 'subLayout',

		/* HTML template for this view */
		template: '#layout-region-two',

		/* define UI events and assign event handlers */
		events: {
			'click .button1' : 'onButtonOneClicked',
			'click .button2' : 'onButtonTwoClicked',
			'click .button3' : 'onButtonThreeClicked'
		},

		/* click handlers for buttons */
		onButtonOneClicked: function(mouseEvent) {
			console.log('button one clicked');
		},
		onButtonTwoClicked: function(mouseEvent) {
			console.log('button two clicked');
		},
		onButtonThreeClicked: function(mouseEvent) {
			console.log('button three clicked');
		},
		initialize: function(){console.log('Region 2 Layout: initialize');},
		onRender: function(){console.log('Region 2 Layout: onRender');},
		onShow: function(){console.log('Region 2 Layout: onShow');}
	});

	/* define a view; in this case a 'Layout' */
	module.AppLayoutView = Marionette.Layout.extend({
		
		/* the auto-generated element which contains this view */
		tagName: 'div',

		/* id attribute for the auto-generated container element */
		id: 'AppContainer',

		/* reference to the template which will be rendered for this view */
		template: '#layout-template',

		/* define the regions within this layout, into which we will load additional views */
		regions: {
			'RegionOne' : '#regionOne',
			'RegionTwo' : '#regionTwo'
		},

		/* called when the view initializes, before it displays */
		initialize: function() {
			console.log('main layout: initialize');
		},

		/* called when view renders in the DOM. This is a good place to 
			add nested views, because the views need existing DOM elements
			into which to be rendered. */
		onRender: function() {
			console.log('main layout: onRender');
			/* create new instance of the region 1 layout view, and display it in Region1  */
			var regionOneLayoutView = new module.RegionOneLayoutView();
			this.RegionOne.show(regionOneLayoutView);

			/* create new instance of the region 2 layout view, and display it in Region2  */
			var regionTwoLayoutView = new module.RegionTwoLayoutView();
			this.RegionTwo.show(regionTwoLayoutView);
		},

		/* called when the view displays in the UI */
		onShow: function() {
			console.log('main layout: onShow');
		}
	});

	/* Tell the module what to do when it is done loading */
	module.addInitializer(function(){
		/* create a new instance of the layout from the module */
		var layout = new module.AppLayoutView();

		/* display the layout in the region defined at the top of this file */
		app.appRegion.show(layout);
	});
});

/* once the DOM initializes, start the app */
$(document).ready(function() {app.start();});

And here is the HTML document:

<!doctype html>
<html>
	<head>
		<title>Backbone/Marionette basic setup with nested views</title>
		<style type="text/css">
			body {
				background: #ffffff;
			}
			#AppBase {
				width: 100%;
				max-width: 960px;
				height: 480px;
				margin: 10px auto;
				padding: 0;
				float: left;
				outline: 1px solid #808080;
			}

			#AppContainer {
				margin: 0;
				padding: 0;
			}

			h1 {
				float:left;
				width:100%;
				margin:0;
				padding:0;
				text-align: center;
				height:100%;
			}
			.subLayout {
				float:left;
				width: 50%;
				height:200px;
				outline:1px solid #808080;
			}
			h2 {
				width:100%;
				text-align:center;
			}
			button {
				width: 75px;
				line-height:30px;
				text-align: center;
			}
		</style>
	</head>
	<body>
		<!-- Base element for app -->
		<!--
			Dont use the BODY element as the base because when the app renders in the BODY
			it will wipe out the template files before the views can pick them up 
		-->
		<div id="AppBase"></div>

		<!-- TEMPLATES -->
		<!-- main layout template -->
		<script type="text/template" id="layout-template">
			<h1>Main Template</h1>
			<div id="regionOne"></div>
			<div id="regionTwo"></div>
		</script>

		<!-- region 1 layout -->
		<script type="text/template" id="layout-region-one">
			<h2>Region 1 content</h2>
		</script>

		<!-- region 2 layout -->
		<script type="text/template" id="layout-region-two">
			<h2>Region 2 content</h2>
			<p>
				<button class="button1">One</button>
				<button class="button2">Two</button>
				<button class="button3">Three</button>
			</p>
		</script>
		<!-- libraries -->
		<script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
		<script type="text/javascript" src="js/underscore.js"></script>
		<script type="text/javascript" src="js/backbone.js"></script>
		<script type="text/javascript" src="js/backbone.marionette.js"></script>

		<!-- app code -->
		<script type="text/javascript" src="js/script.js"></script>
	</body>
</html>

Links to necessary code libraries:

  • jQuery
  • Underscore.js
  • Backbone.js
  • Marionette.js
Posted in Programming comment on Simple Backbone/Marionette Example With Nested Views

Lake Michigan, Mid-January 2014

2014-01-29 John Winkelman

[[{“type”:”media”,”view_mode”:”media_original”,”fid”:”1106″,”attributes”:{“alt”:””,”class”:”media-image”,”height”:”480″,”typeof”:”foaf:Image”,”width”:”640″}}]]

Four weeks after my Christmas Eve walk around Kirk Park I returned to Lake Michigan to see what had changed, and to try out my new camera.

[[{“type”:”media”,”view_mode”:”media_original”,”fid”:”1111″,”attributes”:{“alt”:””,”class”:”media-image”,”height”:”480″,”typeof”:”foaf:Image”,”width”:”640″}}]]

The extreme cold of the past few weeks increased the ice cover dramatically, and now the lake is covered out to near the horizon.

[[{“type”:”media”,”view_mode”:”media_original”,”fid”:”1116″,”attributes”:{“alt”:””,”class”:”media-image”,”height”:”480″,”typeof”:”foaf:Image”,”width”:”640″}}]]

The large piles of ice roughly correspond to where the sand bars or shallow regions near the beach appear in the summer.

[[{“type”:”media”,”view_mode”:”media_original”,”fid”:”1121″,”attributes”:{“alt”:””,”class”:”media-image”,”height”:”480″,”typeof”:”foaf:Image”,”width”:”640″}}]]

I walked out to the first large piles. I am certain the ice would have held me out much farther, but it was difficult walking; slick ice interspersed with patches of snow which might also be deep holes.

[[{“type”:”media”,”view_mode”:”media_original”,”fid”:”1126″,”attributes”:{“alt”:””,”class”:”media-image”,”height”:”480″,”typeof”:”foaf:Image”,”width”:”640″}}]]

After an hour here I drove up to Grand Haven State Park, where many people (comparatively) were enjoying themselves on the ice.

[[{“type”:”media”,”view_mode”:”media_original”,”fid”:”1131″,”attributes”:{“alt”:””,”class”:”media-image”,”height”:”480″,”typeof”:”foaf:Image”,”width”:”640″}}]]

The mouth of the channel was apparently the only open patch of water in this area, and therefore there were ducks in abundance.

[[{“type”:”media”,”view_mode”:”media_original”,”fid”:”1136″,”attributes”:{“alt”:””,”class”:”media-image”,”height”:”480″,”typeof”:”foaf:Image”,”width”:”640″}}]]

You can see the entire set here on Flickr.

Posted in Photography comment on Lake Michigan, Mid-January 2014

Barebones Backbone/Marionette File

2014-01-28 John Winkelman

I am posting this for two reasons: First, so that I have an easy place to go when I want to start a new Backbone/Marionette application and I don’t want to search around for a bare-bones template; and second, because when I started working with Backbone/Marionette I wish I could have found something like this; the equivalent of ‘Hello World’. Code follows, with comments therein.

/* create a new instance of the Marionette app */
var app = new Backbone.Marionette.Application();

/* add the initial region which will contain the app */
app.addRegions({
	/* reference to container element in the HTML file */
	appRegion: '#AppBase'
});

/* define a module to keep the code modular */
app.module('App',function(module, App, Backbone, Marionette, $, _){
	
	/* define a view; in this case a 'Layout' */
	module.GardenLayoutView = Marionette.LayoutView.extend({
		
		/* the auto-generated element which contains this view */
		tagName: 'div',

		/* id attribute for the auto-generated container element */
		id: 'AppContainer',

		/* reference to the template which will be rendered for this view */
		template: '#layout-template',

		/* called when the view initializes, before it displays */
		initialize: function() {
			console.log('initialize');
		},

		/* called when view renders in the DOM */
		onRender: function() {
			console.log('onRender');
		},

		/* called when the view displays in the UI */
		onShow: function() {
			console.log('onShow');
		}
	});

	/* Tell the module what to do when it is done loading */
	module.addInitializer(function(){
		/* create a new instance of the layout from the module */
		var layout = new module.GardenLayoutView();

		/* display the layout in the region defined at the top of this file */
		app.appRegion.show(layout);
	});
});


$(document).ready(function() {app.start();});

And here is the base HTML file, including references to all of the libraries which are required for creating a Backbone/Marionette application.

<!doctype html>
<html>
	<head>
		<title>Backbone/Marionette bare-bones setup</title>
		<link rel="stylesheet" href="style.css"/>
	</head>
	<body>
		<!-- Base element for app -->
		<!--
			Dont use the BODY element as the base because when the app renders in the BODY
			it will wipe out the template files before the views can pick them up 
		-->
		<div id="AppBase"></div>

		<!-- TEMPLATES -->
		<!-- main layout template -->
		<script type="text/template" id="layout-template">
			<h1>This is a rendered template</h1>
		</script>

		<!-- libraries -->
		<script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
		<script type="text/javascript" src="js/underscore.js"></script>
		<script type="text/javascript" src="js/backbone.js"></script>
		<script type="text/javascript" src="js/backbone.marionette.js"></script>

		<!-- app code -->
		<script type="text/javascript" src="js/script.js"></script>
	</body>
</html>

The libraries which load in before the code for the script can be found here:

  • jQuery
  • Underscore.js
  • Backbone
  • Marionette

Each link also has information necessary for learning and implementing the libraries.

Posted in Programming comment on Barebones Backbone/Marionette File

A Collection of Links Concerning Jim Harrison

2014-01-27 John Winkelman

This post is a central point where I can collect articles about, and interviews with, the American author Jim Harrison. I’m trying to keep them in roughly chronological order. One interesting effect of this is listening to his voice change through the years, and being able to hear him in my head while reading the print articles.

The Art of Fiction No. 104 – Paris Review interview, Summer 1988

Between Dog and Wolf (French documentary on JH), 1993

Writers and Company interview with JH from 1994. (audio)

“Will Write for Food”; NYT interview with JH, April 1994.

Excerpt from a documentary c. 1997. (video)

Wild Duck Review interview with JH from 1997.

Joseph Bednarik interviews Jim Harrison, October 2000.

JH reading at the Lensic Theater in Santa Fe, New Mexico – February 2002. (video)

JH conversation with Peter Lewis, February 2002 (video)

Robert Birnbaum interviews JH for The Morning News, June 2004.

Pleasures of the Hard-Worn Life – New York Times article 2007.

All Things Considered review of Returning to Earth, February 2007. (audio)

Interview for PlumTV, c. 2008. (video)

Article about Harrison’s Montana farm house; Wall Street Journal c. 2009.

PBS Newshour interview with JH, 2009. (video and transcript)

Daily Beast interview c. 2010.

Outside Magazine article about JH – “The Last Lion” c. 2011.

Interview with JH in Patagonia Arizona, February 2012. (video)

“Courage and Survival” essay by JH in Brick Magazine c. 2012.

Mario Batali interview Jim Harrison for Food & Wine Magazine, April 2013

“Four Meals with Jim Harrison” – HuffPost Books, May 2013

Q&A with Jim Harrison – Oregon Live, December 2013

“It Has to Come to You” – Jim Harrison discusses Theodor Roethke in The Atlantic, January 2014

“What I’ve Learned” – article in Esquire Magazine, August 2014

“A Prairie Prologue in Nebraska” – essay by JH in the New York Times, January 2015

“The Rodney Dangerfield of Literature” – essay in The Daily Beast, February 2015

“An Afternoon with Jim” – article in The Big Timber Pioneer, October 2015

The Gospel According to Jim – article in Angler’s Journal, February 2016

This page will be updated as I find more links.

Posted in Literary MattersTagged Jim Harrison comment on A Collection of Links Concerning Jim Harrison

Kirk Park, Christmas Eve 2013

2014-01-07 John Winkelman

By Christmas Eve the ice had stopped falling and we had an inch or so of snow taking the edge off. This made it fairly easy to drive out to Kirk Park in time to catch the sunset over Lake Michigan. I have the entire set available for viewing on Flickr.

[[{“type”:”media”,”view_mode”:”media_original”,”fid”:”1071″,”attributes”:{“alt”:””,”class”:”media-image”,”height”:”480″,”typeof”:”foaf:Image”,”width”:”640″}}]]

The dune path leading to the water was covered by a shell of ice at least two inches thick. This was not obvious until I stepped on it and slid, feet out front like on a toboggan, to the bottom of the dune. Anyone on an actual sled probably could have easily made it to the waterline.

[[{“type”:”media”,”view_mode”:”media_original”,”fid”:”1076″,”attributes”:{“alt”:””,”class”:”media-image”,”height”:”480″,”typeof”:”foaf:Image”,”width”:”640″}}]]

The clouds came in from the west in two distinct layers; the higher ones which flowed to the horizon, and the lower ones which formed over the water and dropped lake-effect snow farther inland.

[[{“type”:”media”,”view_mode”:”media_original”,”fid”:”1081″,”attributes”:{“alt”:””,”class”:”media-image”,”height”:”480″,”typeof”:”foaf:Image”,”width”:”640″}}]]

On the beach the ice made for treacherous footing. Out in the water I could see some small chunks of ice floating and growing, and tiny ice crystals forming where the cold air pulled the heat from the water.

[[{“type”:”media”,”view_mode”:”media_original”,”fid”:”1091″,”attributes”:{“alt”:””,”class”:”media-image”,”height”:”480″,”typeof”:”foaf:Image”,”width”:”640″}}]]

On the shoreline the beginning of what would become huge piles of ice began to form.

[[{“type”:”media”,”view_mode”:”media_original”,”fid”:”1101″,”attributes”:{“alt”:””,”class”:”media-image”,”height”:”480″,”typeof”:”foaf:Image”,”width”:”640″}}]]

As they day wound down the sun came through at an oblique angle and created spots of brilliant color in an otherwise muted landscape.

Posted in Photography comment on Kirk Park, Christmas Eve 2013

A Walk Around Pickerel Lake

2013-12-24 John Winkelman

The sky north of Grand Rapids looked blue, so I spent the late morning and noon hours wandering around Pickerel Lake. If you haven’t been, I recommend it highly. Pickerel Lake Park and Fred Meijer Nature Preserve is near the Cannonsburg Ski Area in the northeast part of Kent County.

I arrived at the park around 10:00; mine was the only car in the lot, and as far as I could tell, the first visitor of the day. There was some cloud cover, but it was thin, with the occasional shaft of light wandering across the lake. Everything was bright and shiny, but not blindingly so. And it was quiet. Barely any wind, and few cars to disturb the calm.

Pine trees along the northwest shore
From the boardwalk the ice was so bright against the trees on the north shore that the pines looked black.

Berries in ice
The only color to be had was that of the berries frozen to their branches and protected from the animals.

More ice
Many of the branches had ice coats so thick that the actual wood parts looked like imperfections in otherwise flawless crystal sculpture.

Looking southwest
The lake itself was perfectly smooth; the only details the remnants of dead trees and places where the wind had swept the ice clean of snow.

Southeast shore
About halfway around the lake the sun came out and turned the entire south shore to diamond.

Branches and ice
In among the trees the sun began melting and cracking the ice in the trees, and it sounded like wind chimes and electricity.

Ice and leaves
Instead of darkening the sky, the ice contrast made it the most brilliant blue imaginable.

See the entire set here on Flickr.

Posted in Photography comment on A Walk Around Pickerel Lake

Solving Display Refresh/Redraw/Repaint Issues in Webkit Browsers

2012-09-27 John Winkelman

So debugging on a phone is nowhere near as easy as debugging on a web application, if only because you can’t use the Chrome developer tools or Firebug effectively on a three-inch screen. Fortunately (for Android developers) there is a tool called the Dalvik Debug Monitor, which comes packaged with the Android SDK. It listens to any logs coming from the phone, assuming the phone on which you are testing an app is plugged into your computer via USB cable. Works pretty well, once you get the quirks sorted out.

Anyway: on the recently completed project I came across a bug where, when updating the text content of an element on the screen, the interface refused to update until another event fired in a nearby (DOM-wise) element. Debugging was a pain, because all of the traces I had running told me that yes, all of my Javascript functions had fired, and yes, the content of the element was correct. But the screen itself still showed the old content.

Turns out, this is a Known Issue for Webkit browsers (Chrome, Safari). Sometimes, when updating an inline element or style, the browser does not redraw/repaint the screen until a block-level change happens in the DOM. This bug  most often occurs when there is a lot going on in the page, and the browser seems to assume that, if the change is not happening at the block (which is to say, layout), level, then it is less important.

Fortunately, solving the issue is MUCH easier than diagnosing it. A simple, quick update to the offending element (or its parent) will force the browser to repaint the screen. Here are a few different ways to accomplish this:

/*	Silently append and remove a text node	
	This is the fix that worked for me in the Phonegap/Android application
	the setTimeout allows a 'tick' to happen in the browser refresh,
	giving the UI time to update
*/
var n = document.createTextNode(' ');
$('#myElement').append(n);
setTimeout(function(){n.parentNode.removeChild(n)}, 0);


/*	Hide and reshow the element	*/
document.getElementById('myElement').style.display='none';
document.getElementById('myElement').offsetHeight; // no need to store this anywhere, the reference is enough
document.getElementById('myElement').style.display='block';


/*	The same thing using jQuery	*/
$('#myElement').hide();
$('#myElement').get(0).offsetHeight; // no need to store this anywhere, the reference is enough
$('#myElement').show();


/*	Set the scale of the element to 1. 
	This doesn't actually change anything visually, 
	because by default everything in the browser 
	is set to scale 1	*/

document.getElementById('myElement').style.webkitTransform = 'scale(1)';

The first one is the one I used. I was using jQuery so this simplified things just a bit. All of the other fixes are valid, but the one I used was the only one that actually worked for me in the PhoneGap application.

For further reference, here are the resources I used while tracking down and solving the bug:

  • http://stackoverflow.com/questions/8840580/force-dom-redraw-refresh-on-chrome-mac
  • http://ajaxian.com/archives/forcing-a-ui-redraw-from-javascript
  • http://stackoverflow.com/questions/3485365/how-can-i-force-webkit-to-redraw-repaint-to-propagate-style-changes
  • http://mir.aculo.us/2009/09/25/force-redraw-dom-technique-for-webkit-based-browsers/
  • https://gist.github.com/1362093
Posted in Programming comment on Solving Display Refresh/Redraw/Repaint Issues in Webkit Browsers

Posts navigation

Older posts
Newer posts

Personal website of
John Winkelman

John Winkelman in closeup

Archives

Categories

Posts By Month

July 2025
S M T W T F S
 12345
6789101112
13141516171819
20212223242526
2728293031  
« Jun    

Links of Note

Reading, Writing
Tor.com
Locus Online
The Believer
File 770
IWSG

Watching, Listening
Writing Excuses Podcast
Our Opinions Are Correct
The Naropa Poetics Audio Archive

News, Politics, Economics
Naked Capitalism
Crooked Timber

Meta

  • Log in
  • Entries feed
  • Comments feed
  • WordPress.org

© 2025 Ecce Signum

Proudly powered by WordPress | Theme: x-blog by wpthemespace.com