Skip to content

Instantly share code, notes, and snippets.

@JogoShugh
Last active December 11, 2015 17:49
Show Gist options
  • Save JogoShugh/4637576 to your computer and use it in GitHub Desktop.
Save JogoShugh/4637576 to your computer and use it in GitHub Desktop.
Use Backbone.js, Backbone Forms, and the VersionOne Data API to create a 75-line agile user story editor.
var StoryFormSchema = { // Backbone.Form will generate an HTML form based on this schema
Name: { validators: ['required'] }, // Name is required
Description: 'TextArea', // Since these next three are not required, we only need the data type
Benefits: 'TextArea',
Estimate: 'Number',
RequestedBy: {} // Defaults to 'Text'
};
var storyForm = null; // Instance of the schema declared above, created when we click 'Load Story'
var urlRoot = 'http://eval.versionone.net/platformtest/rest-1.v1/Data/Story/'; // V1 API URL base
var headers = { Authorization: 'Basic ' + btoa('admin:admin'), Accept: 'haljson' }; // Headers for auth and accept type format
Backbone.emulateHTTP = true; // Tells Backbone to issue a POST instead of a PUT HTTP method for updates
// Note that Models usually align with addressable HTTP resources, such as '/rest-1.v1/Data/Story/1154'
var StoryModel = Backbone.Model.extend({ // .extend comes from Underscore.js, for created an 'inherited' class
urlRoot: urlRoot, // Sets the root url to the VP API URL base
url: function () { // Override the built in url() for two cases:
if (this.hasChanged() && !this.isNew()) return this.urlRoot + this.id; // In this case, just use the id -- used for save() via POST
return this.urlRoot + this.id + '?sel=' + _.keys(storyForm.schema).join(','); // Otherwise, limit the attributes return to just what our form schema contains
}, // Note that _.keys is another Underscore goody that returns an array of key names from an object
fetch: function(options) { // Overrides the base fetch so we can customize behavior to be V1 API friendly
options || (options = {}); // When no options passed, default to an empty object
_.defaults(options, {dataType: 'json', headers: headers}); // Copies values from 2nd arg into the 1st if-and-only-if they don't exist already in the 1st
return Backbone.Model.prototype.fetch.call(this, options); // Delegate to the base implementation
},
save: function(attributes, options) { // Similar override of base save
options || (options = {});
_.defaults(options, {contentType: 'haljson', patch: true, headers: headers}); // See extended comment below...
return Backbone.Model.prototype.save.call(this, attributes, options);
} // patch: true tells Backbone.sync to send a partial representation, and makes it use the PATCH HTTP method,
}); // but, since we did Backbone.emulateHTTP = true, it uses POST and sets X-HTTP-Method: PATCH as a header
var storyModel = new StoryModel(); // Concrete instance of our StoryModel. Alive at last!
function createForm(model) { // Called to use Backbone.Form with our schema to build the form and add it to the DOM
var settings = {schema: StoryFormSchema}; // Gets passed to Backbone.Form constructor
var finish = function() { // Gets called below, either immediately if model is null, or asynchronously after fetch
storyForm = new Backbone.Form(settings); // Create concrete StoryForm instance
$('#editorFields').empty(); // Empty out the DOM element for our fields!
$('#editorFields').append(storyForm.render().el); // Construct the HTML for the form, and toss it into the DOM!
if (model) $('#editor').fadeIn(); // Oooo, ahhh animated fade in.
};
if (model) { // When called with a model instance:
model.fetch().done(function(data) { // Make the model fetch itself, and when done:
settings.model = model; // Assign a copy of the model into our settings hash, and:
finish(); // FINISH!
});
} else finish(); // When no model passed, just finish immediately WITHOUT a settings.model, resulting in an empty form
}; // Note that we don't have this case in the app, but if you'd like to make an 'Add' mode, you could rely on this
function storyLoad() { // Called when you click 'Load Story'
storyModel.id = $('#storyId').val(); // Extract the story id from the input field that we manually added
if (storyModel.id === '') { // If empty, then:
alert('Please enter a story id first'); // Warn, and:
return; // Get out of here...
}
createForm(storyModel); // Pass the model into createForm, causing the if (model) branch to run, causing
}; // model.fetch() to execute, causing Backbone.sync to the model from the V1 API, and
// causing finish() to execute, causing Backbone.Form and friends to execute and presto!
function storySave() { // Called when you click 'Save Story'
if (storyForm.validate() != null) return; // Backbone Forms validates the form based on the schema we gave it,
storyForm.commit();
storyModel.save(storyForm.getValue()).done(function(data) { // storyForm.getValue() gets data from the Backbone.Form instance, and .save() returns a jQuery deferred object, so we can pass a 'done' handler:
$('#error').hide(); // done gets called on SUCCESS, so hide the errors element
$('#message').text('Story saved!').fadeIn().delay(2500).fadeOut(); // More ooo, ahh animation for the success message
}).fail(function(jqXHR) { // If the HTTP POST operation fails, this gets called to handle the error
$('#message').hide(); // Get tid of the success message this time.
$('#error').text('Error during save! See console for details.').fadeIn().delay(5000).fadeOut(); // Boooo, hiss!
console.log(jqXHR); // Dump the raw jQuery XML HTTP Request object to the console
});
};
$(function() { // Configure jQuery's document ready handler and GO!
createForm(); // Create the form, without a model. Not terribly useful, really, because it will be hidden still
$('#storyLoad').click(storyLoad); // Wire up the storyLoad click handler to its corresponding button
$('#storySave').click(storySave); // Wire up storySave the same way
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment