July 24th, 2012 § Comments Off on The dude abides § permalink
Man, when did forms get so complex? Oh, right, always.
Backbone.js is great because it lets me structure my app how I see best and gives me the tools to do so efficiently. Well, most of the tools. It’s not very good at dealing with handling multiple forms submitting over AJAX and orchestrating those submissions under a parent view. Nor should it be. However, I needed that functionality so I wrote some code.
I give you Abide
Abide is an extension of a Backbone.View that has hooks for checking model validation, dealing with multiple jQuery.Deferred promises (be they AJAX or not) and orchestrating the whole submission process in one place.
Just have your view extend Abide instead of Backbone.View…
var parentView,
firstChild,
secondChild,
redhadedStepChild;
//Create up your parent view and extend Abide instead of Backbone.View
parentView = Backbone.Abide.extend({
promises: function() {
// Gather child view save promises
}
}
});
//Create child views as well. These can be regular views or more Abide parents.
firstChild = Backbone.View.extend({ /* ... */ });
secondChild = Backbone.View.extend({ /* ... */ });
redhadedStepChild = Backbone.View.extend({ /* ... */ });
//Then instantiate the parent, passing in which views it should parent
new parentView({ views: [firstChild, secondChild, redhadedStepChild] });
…and override the promises method to return your view’s promises…
var sweetAbideView = Backbone.Abide.extend({
promises: function() {
var promises = [],
self = this;
_.each(self.views, function(view) {
promises.push(view.model.save());
});
return promises;
}
});
…and you’re good to go!
There’s more information on github. Fork and submit pull requests!
June 21st, 2012 § Comments Off on How I used backbone.js in Lifesquare to make for a better signup progress § permalink
This year has been interesting. When not working the day job or finding time to brew I’ve been working hard on getting Lifesquare‘s consumer website off the ground. Lifesquare centers around QR codes that people attach to things that are always with them (e.g. bike helmet, wallet, iPhone, car insurance) and in the event of an accident an EMS team can use the Lifesquare app (available only to EMS professionals) to scan your QR code to find out any information they might need to know in an emergency. This is great because every second counts in a first responder situation and if you’re unable to communicate to the EMS team they might not know to avoid certain medications that could cause serious allergic reaction. It’s a fantastic idea and their product definitely adds to the betterment of humanity – two things that I pretty much require when working for someone.
All of the mobile app and server stuff was pretty much taken care of when I was brought on board; what was needed was a consumer side where people could sign up, create patient accounts and enter information about those patients. Sounds like something that has been done before but the team at Lifesquare threw in some fun twists that helps them stand out from generic, boring data entry workflows.
Level up backbone.js
I was given pretty much free reign on the front-end code so I based my work on backbone.js. The absolute best part of using backbone is that you don’t have to subscribe to any one way of doing anything. The tools are given to you to assemble an application in whatever way makes most sense for you. There are some basic things I like to do when starting any backbone-based app to augment the framework and create a bit cleaner architecture.
Organize further
Backbone provides structure but not much in the way of namespacing. A little bit of memoization takes care of that, however. I use a memoized base object that automatically adds a Views
object but nothing else. I’ve had projects that create multiple types of memoized objects from full backbone views/models/collection groups to plain object literals. I find the Views addition takes care of what’s needed for Lifesquare.
var module = function () {
var modules = {};
// Create a new module reference scaffold or load an existing module.
return function (name) {
if (modules[name]) {
return modules[name];
}
return modules[name] = { Views: {} };
};
}();
var Home = module('home');
Home.Views.SplashImages = Backbone.View.extend({ ... });
No matter where you are in the app’s code you simply call module(‘featureNameGroup’) and you get the fresh object or a reference to the one being used by everyone else. Especially handy when passing modules in to immediately invoked function expressions.
(function(Home) {
Home.Views.coolNewView = Backbone.View.extend({
...
initialize: function() { ... }
...
});
}(module('home'));
Add magic to getters/setters
While we all wait for ES6/Harmony and the getters and setters we deserve there’s Backbone.Mutators to do the heavy lifting for you. It gives you hooks for existing or arbitrary model values that can run transformations, mutations or whatever else you like on the get or set of any value.
My favorite use in Lifesquare was accepting a free text date field value, analyzing the string and finding the true date, creating a date mask to match the user input, saving that mask to a field and saving the full date in another field in the database and still retaining the user format in the field. This happens every time a date is entered into a field in Lifesquare. The storage of full date as YYYY-MM-DD HH:II:SS allows for better reporting and sorting on the admin side while the saved user mask allows us to display the date just as entered. This took some work to get right but it’s still not bug free. Free text dates are not a solved problem yet in JavaScript, despite moment.js and date.js.
A simple example is format conversion. Lifesquare is currently targeted toward American users but we store everything in metric so I needed to accept weight in pounds but convert to kilograms.
(function (Patient) {
"use strict";
Patient.Model = Backbone.Model.extend({
mutators: {
imperialWeight: {
get: function() {
var pounds = Math.round(this.weight * 2.20462262)
return pounds > 0 ? pounds : undefined;
},
set: function(key, value, options, set) {
var matches = value.match(/([0-9\.]+)/);
if(matches !== null) {
var pounds = value,
kilos = pounds * .45359237;
this.set('weight', kilos, options);
} else {
this.set('weight', '', options);
}
}
},
...
});
}(module('patient'));
Speaking of matching strings…
With all the input happening it’s inevitable that some validation was required. Nobody likes doing front end validation. Thankfully there’s backbone.validation to make things easier. It works with your model structure to provide hooks for default rules, custom regex tests and even custom validation functions. I use all three of those features extensively and it has been great.
What hasn’t been great is the validation execution path. Maybe I just don’t grok the intended workflow but it’s less than smooth in practice. What I ended up doing was the moment a view accepts a model the .validation
object is duplicated as validationRules
and the validation object is reset to an empty object literal. Then, as each field is blurred/selected the validation rule for that field is applied to the validation object (from the cloned validationRules) and that’s where the model-wide validation is run. Clunky, for sure, but it works pretty well in practice. Binding the validation rules to the fields, though, is a bummer. I ended up using data-
attributes and name
attributes. If anyone has integration tips I’m all ears.
Stick to the defaults
When development initially began the server interfaces would not accept requests if data had values outside of what was expected in the model. Given that the Backbone.Mutators were adding those fields to the model during JSON serialization (something for which I am eternally grateful) there were issues. Although the server rules eventually relaxed and just threw away the unnecessary fields I had to write a stripper in the interim. Pretty simple stuff but maybe someone will find it useful. Just plop this into your custom Backbone.Model extension or in any single model as it’s needed and call before/after sync()
.
//Removes values that shouldn't be sent back during a sync
evictIllegals: function() {
var self = this,
allowed = _.keys(this.defaults);
_.each(self.attributes, function(value, key) {
if(!!~allowed.indexOf(key) === false) {
delete self.attributes[key];
}
});
}
Wait for the big show
Okay, so now you have some great building blocks. Let’s write some data entry!
Wait. There are multiple groups of data on each page that need to go to different services and all must finish before you can consider the page “submtted”? No problem.
Enter Backbone.Abide
Abide is a small addition to backbone that I wrote during development of Lifesquare. What this dude does is orchestrates the submission of multiple AJAX requests, monitors their completion status and when everything is done will throw an event to anyone who cares to listen. He also will run validation on any models submitted, disable submit buttons and update their text with submitting/finishing/done status messages.
Right now there are hooks to warn the user if leaving a page where values have been modified and monitor the session timeout to throw “hey you’re about to time out and you haven’t submitted anything” events to subscribers but those will be refactored out in future revisions.
Just extend Abide when creating a view and subscribe to the events and you should be good to go.
(function(Home) {
Home.Views.AwesomeForm = Backbone.Abide.extend({
initialize: function(options) {
_.bindAll(this);
//I like to use "self" because it compresses well
var self = this;
self.on('validating', function() {
//Clear out previous validation warnings
}).on('validationFailed', function(e) {
//Tell the user there has been something wrong and identify where
}).on('done', function(e) {
//All AJAX done. Navigate the page somehow (pushState, document.location.href, change views, etc.)
}).on('fail', function(jqXHR, textStatus, errorThrown) {
//AJAX submission has failed somehow. Show error.
}).on('sessionExpired', self.renewSession).on('sessionTimeoutWarning', self.warnTimeout);
$(window).on('beforeunload', function(e) {
if(self._dirty === true) {
//Abide provides the _dirty flag if the data has been manipulated by the user. Warn about mods here
}
});
}
}(app.module('home'));
Who is this, really?
One of my favorite features to work on was the image uploading. Users can drop an image on the page (or, if you’re old school, pick one from the file picker) and crop the selection to the desired area. I did some really fun work on the cropper, making it intuitive and allowing re-edits. A picture is work a thousand words, right?
Of course it’s recommended you use an actual picture of your face so EMS professionals can correctly identify you. Unless you’re Worf. Then you use this picture.
The kitchen sink and more
I haven’t even gotten to occurance.js, the natural date parser helper I wrote to accompany moment.js, the autocomplete work with jQuery UI, how to deal with deeply nested backbone views sharing collections and more. All in good time.
What I missed
You can’t get everything right in an app and what I did miss this time around was using an event aggregator, like postal.js. This would have cleaned up a lot of the weird events I push and bubble through backbone and let me decouple things a bit better. In the future I’d like to spend more time getting Postal in but it works for now and like any startup it’s important to ship quickly and refactor on the go.
For now head on over to Lifesquare and check it out. Heck, you should sign up if you live in Marin County, CA. It really is a great idea that could save your life one day.
August 22nd, 2011 § Comments Off on Emulating the native shadow on UIMenuController § permalink
I’m an iOS developer! I still love and will continue doing front-end dev for web but I’m adding another feather to my cap in the way of working on some pretty cool iOS apps. One app I’ve worked on is Constellation, an eBook reader developed for Ashford Education. It’s pretty cool.
One of the things we do is emulate the UIMenuController with our own more powerful view controller. And one of the neat things about a UIMenuController is that you get a slick, subtle drop shadow wherever it is placed. Being the guy I am I spent some time to analyze the drop shadow in Photoshop and do my best to emulate it. Here’s what I came up with. Have a better solution? Tell me in the comments!
- (void) viewDidLoad
{
[super viewDidLoad];
self.view.layer.shadowColor = [[UIColor blackColor] CGColor];
self.view.layer.shadowOffset = CGSizeMake(0, 2.0f);
self.view.layer.shadowOpacity = .55f;
self.view.layer.shadowRadius = 2.0f;
self.view.layer.shouldRasterize = YES;
}
May 26th, 2011 § Comments Off on Using CSS transforms to mimic Safari’s find word selection § permalink
Here’s a quick one for you kids. I’ve just started playing with CSS3 and I needed something simple but useful to implement. Say what you will about Safari as a browser but I think its find feature has the best UI for showing matching results on the page and what item is currently selected. Wait, you don’t use Safari? Okay, check out the picture below.
See what I’m saying? Fade out what isn’t important, lighten all matches, put some special pizazz on the current item. As an added bonus each time you switch the current item it “pops” a bit to help pull your eye to where the next item is. Pretty slick.
I set out not to duplicate the styles but to get a feel for CSS3 animation and transitions. With that in mind the animation tweens and colors aren’t exact but they’ll work for the project I have in mind. If you have improvements go ahead and share them in the comments.
[Ninja edit] I forgot to mention that in order for CSS transformations to be hardware accelerated by devices that do so you have to include the “translate3d” property. If you don’t need to translate, use the meaningless zeroed out setting like I did below.
[Ninja edit 2} Also, I didn’t bother putting in all the different vendor prefixes. If you’re interested in this code your Google-fu will be strong enough to look up the different vendor settings and adjust accordingly.
@-webkit-keyframes pulseFocusedOverlay {
0%, 100% {
-webkit-transform: scale(1) translate3d(0px, 0px, 0px);;
}
15% {
-webkit-transform: scale(1.35) translate3d(0px, 0px, 0px);;
}
}
.searchMatch {
display: inline-block;
padding: 0 4px;
margin: 0 -3px;
-webkit-box-shadow: 0 1px 5px #333;
box-shadow: 0 1px 5px #333;
color: #222;
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(.5, rgb(222, 222, 222)), color-stop(1, rgb(255, 255, 255)));
}
.searchMatch_selected {
display: inline-block;
padding: 0 4px;
border-radius: 5px;
border: 1px solid #EFDB13;
margin: -1px -4px;
-webkit-box-shadow: 0 1px 3px #333;
box-shadow: 0 1px 5px #333;
color: #000;
-webkit-animation: pulseFocusedOverlay 185ms ease-in-out 0;
background-image: -webkit-gradient(linear, left bottom,left top,color-stop(.5, rgb(238, 207, 0)),color-stop(1, rgb(242, 237, 19)));
}
January 4th, 2010 § § permalink
This article is quite old. Please don’t use it for…anything. Thanks!
Since my upgrade to Snow Leopard I’ve been fighting a few very particular issues in my development environment. It should be noted that I think nearly all regular consumers and a large percentage of developers will be satisfied with the development environment provided by Apple. However, some developers need more.
While able to run in 64-bit, I was running Apache in 32-bit mode under my Leopard install. This is because the ODBC drivers I was running from Actual Technologies only supported 32-bit Apache. In Leopard land that was fine. Now that I’m in Snow Leopard and everything else is in 64-bit I thought my ODBC drivers ought be as well.
My problem is that I had the 32-bit Actual Technologies drivers, compiled in the FreeTDS drivers for my MSSQL support when compiling PHP and then installed a 64-bit beta build from Actual Technologies. Somewhere between the Snow Leopard upgrade and all these other installs things have gone a bit wonky. Best bet is to clean out all ODBC drivers and start anew.
Small hiccup: Actual Technologies does not provide an uninstaller. Well, not that you’d be able to find. I emailed their support and received a link to a script bundled up in a dmg. By request I’m not going to post a link to the script or the script itself and I think that’s fair. Looking at the script it is no more than a series of rm -rf
commands that blindly erase whole directories. Even existing iODBC data and settings you may have from other products or drivers.
I’m going to run the uninstaller, recompile FreeTDS, rebuild PHP and see where that gets me. Hopefully not towards a fresh Snow Leopard install.