Last active
December 11, 2015 17:49
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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