Skip to content
Browse files

functional

  • Loading branch information...
1 parent e050a59 commit bbbc1352e15216372fe53e0754a6ab7901e08511 @davemo davemo committed Apr 9, 2012
Showing with 252 additions and 1 deletion.
  1. +5 −0 css/style.css
  2. +60 −1 index.html
  3. +15 −0 js/app.js
  4. +37 −0 js/models/user.model.js
  5. +12 −0 js/start.js
  6. +61 −0 js/views/form.validator.js
  7. +38 −0 js/views/user.info.form.js
  8. +24 −0 serve.rb
View
5 css/style.css
@@ -0,0 +1,5 @@
+.tooltip.right .tooltip-arrow {
+ border-right: 5px solid #B94A48;}
+
+.tooltip-inner {
+ background-color: #B94A48; }
View
61 index.html
@@ -8,12 +8,71 @@
</head>
<body>
+ <div class="container">
+ <div class="row">
+ <div class="span8">
+ <form id="user-info" class="form-horizontal">
+ <fieldset>
+ <legend>All About Me</legend>
+ <div class="control-group">
+ <label class="control-label" for="name">Your Name</label>
+ <div class="controls">
+ <input type="text" class="input" id="name" rel="tooltip" title="Your name cannot be Bob." placeholder="anything but Bob will work">
+ </div>
+ </div>
+ <div class="control-group">
+ <label class="control-label" for="age">Your Age</label>
+ <div class="controls">
+ <input type="number" class="input-small" id="age" rel="tooltip" title="Age must be between 1 and 100." min="0" max="100" step="1" placeholder="> 0 < 100">
+ </div>
+ </div>
+ <div class="control-group">
+ <label class="control-label" for="agreed">I agree to terms</label>
+ <div class="controls">
+ <label class="checkbox">
+ <input type="checkbox" id="agreed" rel="tooltip" title="You must agree. NOW!">
+ </label>
+ </div>
+ </div>
+ <div class="control-group">
+ <label class="control-label" for="best-pet">The best pet is:</label>
+ <div class="controls">
+ <select id="best-pet" rel="tooltip" title="Dogs are best!">
+ <option value="">something</option>
+ <option value="cat">Cat</option>
+ <option value="dog">Dog</option>
+ <option value="ferret">Ferret</option>
+ <option value="lizard">Lizard</option>
+ </select>
+ </div>
+ </div>
+ <div class="form-actions">
+ <button type="submit" class="btn btn-primary">Submit</button>
+ </div>
+ </fieldset>
+ </form>
+ </div>
+ </div>
+ </div>
-
+ <!-- vendor dependencies -->
<script type="text/javascript" src="js/vendor/jquery.min.js"></script>
<script type="text/javascript" src="js/vendor/underscore.min.js"></script>
<script type="text/javascript" src="js/vendor/backbone.min.js"></script>
<script type="text/javascript" src="js/vendor/bootstrap.min.js"></script>
+ <!-- app namespace -->
+ <script type="text/javascript" src="js/app.js"></script>
+
+ <!-- models -->
+ <script type="text/javascript" src="js/models/user.model.js"></script>
+
+ <!-- views -->
+ <script type="text/javascript" src="js/views/form.validator.js"></script>
+ <script type="text/javascript" src="js/views/user.info.form.js"></script>
+
+ <!-- the runtime -->
+ <script type="text/javascript" src="js/start.js"></script>
+
</body>
</html>
View
15 js/app.js
@@ -0,0 +1,15 @@
+(function(w) {
+
+ // a spot to store things
+ var APP = {
+ Views: {},
+ Models: {}
+ };
+
+ // for custom events
+ _.extend(APP, Backbone.Events);
+
+ // stick it on the global scope
+ w.APP = APP;
+
+})(this);
View
37 js/models/user.model.js
@@ -0,0 +1,37 @@
+(function($, APP, m) {
+
+ m.User = Backbone.Model.extend({
+
+ initialize: function() {
+ _.bindAll(this);
+ },
+
+ defaults: {
+ name: "Roy",
+ age: 21,
+ agreed: undefined,
+ bestPet: "dog"
+ },
+
+ validate: function(attrs) {
+ if(attrs.name.toLowerCase() === "bob") {
+ return "name";
+ }
+
+ if(attrs.age <= 0 || attrs.age > 100) {
+ return "age";
+ }
+
+ if(attrs.agreed === false) {
+ return "agreed";
+ }
+
+ if(attrs.bestPet !== "dog"){
+ return "bestPet";
+ }
+
+ APP.trigger("valid:input");
+ }
+ });
+
+})(jQuery, window.APP, window.APP.Models);
View
12 js/start.js
@@ -0,0 +1,12 @@
+(function(APP) {
+
+ // a runtime object to cache instances
+ var r = APP.Runtime = {};
+
+ // models
+ r.User = new APP.Models.User();
+
+ // views
+ r.UserInfo = new APP.Views.UserInfoForm({ model: r.User });
+
+})(this.APP);
View
61 js/views/form.validator.js
@@ -0,0 +1,61 @@
+(function($, v) {
+
+ v.FormValidator = Backbone.View.extend({
+
+ initialize: function() {
+ _.bindAll(this);
+ this.configureTooltips();
+ this.bindChangeables();
+ this.model.bind("error", this.showFormError);
+ APP.on("valid:input", this.clearFormValidationErrors);
+ },
+
+ bindChangeables: function() {
+ // As of Backbone 0.9.0 You can now bind and trigger multiple spaced-delimited events at once. For example: model.on("change:name change:age", ...)
+ // this emits a nice space-delimited string of multiple change events to bind at once.
+ var fields = _.map(this.fieldSelectorMap, function(val, key) {
+ return "change:%s".replace("%s", key);
+ }).join(" ");
+ this.model.bind(fields, this.updateFields);
+ this.model.bind(fields, this.enableSave);
+ },
+
+ clearFormValidationErrors: function() {
+ $('*[rel=tooltip]').tooltip('hide').closest('.control-group').removeClass('error');
+ },
+
+ configureTooltips: function() {
+ this.$('*[rel=tooltip]').tooltip({ placement: 'right', trigger: 'manual' });
+ },
+
+ showFormError: function(model, fieldInError) {
+ var mapping = this.fieldSelectorMap[fieldInError];
+ if(mapping) {
+ $(mapping.selector).tooltip('show').closest('.control-group').addClass('error');
+ }
+ },
+
+ updateTextField: function(selector, value) {
+ $(selector).val(value).tooltip('hide');
+ },
+
+ updateCheckboxField: function(selector, value) {
+ $(selector)[0].checked = value;
+ },
+
+ updateSelectField: function(selector, value) {
+ this.updateTextField(selector, value);
+ },
+
+ updateFields: function(m) {
+ _.each(m.changed, function(val, key) {
+ var mapping = this.fieldSelectorMap[key];
+ if(mapping) {
+ this[mapping.updater](mapping.selector, val);
+ }
+ }, this);
+ }
+
+ });
+
+})(jQuery, window.APP.Views);
View
38 js/views/user.info.form.js
@@ -0,0 +1,38 @@
+(function($, v) {
+
+ v.UserInfoForm = v.FormValidator.extend({
+
+ el: "#user-info",
+
+ events: {
+ "change #name" : "setName",
+ "change #age" : "setAge",
+ "change #agreed" : "setAgreed",
+ "change #best-pet" : "setBestPet"
+ },
+
+ fieldSelectorMap: {
+ "name" : { selector: "#name", updater: "updateTextField" },
+ "age" : { selector: "#age", updater: "updateTextField" },
+ "agreed" : { selector: "#agreed", updater: "updateCheckboxField" },
+ "bestPet" : { selector: "#best-pet", updater: "updateSelectField" }
+ },
+
+ setName: function(e) {
+ this.model.set("name", $(e.currentTarget).val());
+ },
+
+ setAge: function(e) {
+ this.model.set("age", parseFloat($(e.currentTarget).val(), 10));
+ },
+
+ setAgreed: function(e) {
+ this.model.set("agreed", e.currentTarget.checked);
+ },
+
+ setBestPet: function(e) {
+ this.model.set("bestPet", $(e.currentTarget).val());
+ }
+ });
+
+})(jQuery, window.APP.Views);
View
24 serve.rb
@@ -0,0 +1,24 @@
+#!/usr/bin/env ruby
+
+# thanks to jim weirich
+# run ./servefiles from the root directory
+
+require 'webrick'
+include WEBrick
+
+dir = Dir::pwd
+port = (ARGV.first || (12000 + (dir.hash % 1000))).to_i
+
+url = "http://#{Socket.gethostname}:#{port}"
+
+puts "Opening #{url}"
+`open #{url}`
+
+s = HTTPServer.new(
+ :Port => port,
+ :DocumentRoot => dir
+)
+
+trap("INT"){ s.shutdown }
+s.start
+

0 comments on commit bbbc135

Please sign in to comment.
Something went wrong with that request. Please try again.