diff --git a/README.md b/README.md index 87b1ee6..e237637 100644 --- a/README.md +++ b/README.md @@ -10,4 +10,5 @@ This repo has branches that represent the code for various blog posts. Here is * Introduction to Backbone.js with JQuery Mobile => master * Sorting collections with Backbone.js and Jquery Mobile => sort * From List to Details View using jQuery Mobile and Backbone.js => details-display -* Using jQuery Mobile and Backbone.js for handling forms => new-and-edit \ No newline at end of file +* Using jQuery Mobile and Backbone.js for handling forms => new-and-edit +* Using jQuery Mobile 1.4.0 and Backbone 1.1.0 => update-to-jqm14-bb11 diff --git a/index.html b/index.html index 39e4ee4..2d60568 100644 --- a/index.html +++ b/index.html @@ -3,19 +3,55 @@ Exercise - + + + - - - - - - - - + + + + @@ -26,12 +62,43 @@

Activities

- Add + Add
+
+
+ Edit +

Activity Details

+
+
+ +
+
+ +
+
+ Save +

New Activity

+
+
+ +
+
+
+ + + + + + + + + + + diff --git a/js/app.js b/js/app.js index d7e098a..246bb46 100644 --- a/js/app.js +++ b/js/app.js @@ -4,13 +4,69 @@ var exercise = {}; (function($){ - exercise.Activity = Backbone.Model.extend({ + defaults: { + date: new Date(), + type: '', + distance: '', + comments: '', + minutes: '' + }, + + set: function(attributes, options) { + var aDate; + if (attributes.date){ + //TODO future version - make sure date is valid format during input + console.log("attributes.date ", attributes.date); + aDate = new Date(attributes.date); + if ( Object.prototype.toString.call(aDate) === "[object Date]" && !isNaN(aDate.getTime()) ){ + attributes.date = aDate; + } + } + Backbone.Model.prototype.set.call(this, attributes, options); + }, + + dateInputType: function(){ + return exercise.formatDate(this.get('date'), "yyyy-mm-dd"); //https://github.com/jquery/jquery-mobile/issues/2755 + }, + + displayDate: function(){ + return exercise.formatDate(this.get('date'), "mm/dd/yyyy"); + }, + + toJSON: function(){ + var json = Backbone.Model.prototype.toJSON.call(this); + return _.extend(json, {dateInputType : this.dateInputType(), displayDate: this.displayDate()}); + } }); + exercise.formatDate = function(date, formatString){ + var yyyy, month, mm, day, dd, formatedDate; + + if (date instanceof Date){ + yyyy = date.getFullYear(); + month = date.getMonth() + 1; + mm = month < 10 ? "0" + month : month; + day = date.getDate(); + dd = day < 10 ? "0" + day : day; + + formatedDate = formatString.replace(/yyyy/i, yyyy); + formatedDate = formatedDate.replace(/mm/i, mm); + formatedDate = formatedDate.replace(/dd/i, dd); + }else{ + formatedDate = ""; + } + + return formatedDate; + }; + exercise.Activities = Backbone.Collection.extend({ model: exercise.Activity, - url: "exercise.json" + url: "exercise.json", + comparator: function(activity){ + var date = new Date(activity.get('date')); + return date.getTime(); + } }); exercise.ActivityListView = Backbone.View.extend({ @@ -18,8 +74,11 @@ var exercise = {}; id: 'activities-list', attributes: {"data-role": 'listview'}, - initialize: function() { - this.collection.bind('add', this.add, this); + initialize: function(options) { + this.options = options || {}; + this.collection.bind('add', this.render, this); + this.collection.bind('change', this.changeItem, this); + this.collection.bind('reset', this.render, this); this.template = _.template($('#activity-list-item-template').html()); }, @@ -29,21 +88,71 @@ var exercise = {}; template = this.template, listView = $(this.el); + container.empty(); $(this.el).empty(); activities.each(function(activity){ - listView.append(template(activity.toJSON())); - }); + this.renderItem(activity); + }, this); container.html($(this.el)); container.trigger('create'); return this; }, - add: function(item) { - var activitiesList = $('#activities-list'), - template = this.template; + renderItem: function(item) { + var template = this.template, + listView = $(this.el), + renderedItem = template(item.toJSON()), + $renderedItem = $(renderedItem); + + $renderedItem.jqmData('activityId', item.get('id')); + $renderedItem.bind('click', function(){ + //set the activity id on the page element for use in the details pagebeforeshow event + $('#activity-details').jqmData('activityId', $(this).jqmData('activityId')); //'this' represents the element being clicked + }); + + listView.append($renderedItem); + }, + + changeItem: function(item){ + this.collection.sort(); + this.render(); + } + }); + + exercise.ActivityDetailsView = Backbone.View.extend({ + //since this template will render inside a div, we don't need to specify a tagname + initialize: function(options) { + this.options = options || {}; + this.template = _.template($('#activity-details-template').html()); + }, + + render: function() { + var container = this.options.viewContainer, + activity = this.model, + renderedContent = this.template(this.model.toJSON()); + + container.html(renderedContent); + container.trigger('create'); + return this; + } + }); + + exercise.ActivityFormView = Backbone.View.extend({ + //since this template will render inside a div, we don't need to specify a tagname, but we do want the fieldcontain + attributes: {"data-role": 'fieldcontain'}, + + initialize: function(options) { + this.options = options || {}; + this.template = _.template($('#activity-form-template').html()); + }, + + render: function() { + var container = this.options.viewContainer, + renderedContent = this.template(this.model.toJSON()); - activitiesList.append(template(item.toJSON())); - activitiesList.listview('refresh'); + container.html(renderedContent); + container.trigger('create'); + return this; } }); @@ -54,7 +163,7 @@ var exercise = {}; }(jQuery)); -$('#activities').live('pageinit', function(event){ +$('#activities').on('pageinit', function(event){ var activitiesListContainer = $('#activities').find(":jqmData(role='content')"), activitiesListView; exercise.initData(); @@ -62,10 +171,66 @@ $('#activities').live('pageinit', function(event){ activitiesListView.render(); }); -$('#add-button').live('click', function(){ - var today = new Date(), - date; +$(document).ready(function(){ + + $('#add-button').on('click', function(){ + var activity = new exercise.Activity(), + activityForm = $('#activity-form-form'), + activityFormView; + + //clear any existing id attribute from the form page + $('#activity-details').jqmRemoveData('activityId'); + activityFormView = new exercise.ActivityFormView({model: activity, viewContainer: activityForm}); + activityFormView.render(); + }); + + $('#activity-details').on('pagebeforeshow', function(){ + console.log('activityId: ' + $('#activity-details').jqmData('activityId')); + var activitiesDetailsContainer = $('#activity-details').find(":jqmData(role='content')"), + activityDetailsView, + activityId = $('#activity-details').jqmData('activityId'), + activityModel = exercise.activities.get(activityId); + + activityDetailsView = new exercise.ActivityDetailsView({model: activityModel, viewContainer: activitiesDetailsContainer}); + activityDetailsView.render(); + }); + + $('#edit-activity-button').on('click', function() { + var activityId = $('#activity-details').jqmData('activityId'), + activityModel = exercise.activities.get(activityId), + activityForm = $('#activity-form-form'), + activityFormView; + + activityFormView = new exercise.ActivityFormView({model: activityModel, viewContainer: activityForm}); + activityFormView.render(); + }); - date = (today.getMonth() + 1) + "/" + today.getDate() + "/" + today.getFullYear(); - exercise.activities.add({id: 6, date: date, type: 'Walk', distance: '2 miles', comments: 'Wow...that was easy.'}); -}); \ No newline at end of file + $('#save-activity-button').on('click', function(){ + var activityId = $('#activity-details').jqmData('activityId'), + activity, + dateComponents, + formJSON = $('#activity-form-form').formParams(); + + //if we are on iOS and we have a date...convert it from yyyy-mm-dd back to mm/dd/yyyy + //TODO future version - for non-iOS, we would need to validate the date is in the expected format (mm/dd/yyyy) + if (formJSON.date && (navigator.userAgent.indexOf('iPhone') >= 0 || + navigator.userAgent.indexOf('iPad') >= 0 || + navigator.userAgent.indexOf('Chrome') >= 0 + ) + ){ + dateComponents = formJSON.date.split("-"); + formJSON.date = dateComponents[1] + "/" + dateComponents[2] + "/" + dateComponents[0]; + } + + if (activityId){ + //editing + activity = exercise.activities.get(activityId); + activity.set(formJSON); //not calling save since we have no REST backend...save in memory + }else{ + //new (since we have no REST backend, create a new model and add to collection to prevent Backbone making REST calls) + activity = new exercise.Activity(formJSON); + activity.set({'id': new Date().getTime()}); //create some identifier + exercise.activities.add(activity); + } + }); +}); diff --git a/js/vendor/backbone-min.js b/js/vendor/backbone-min.js new file mode 100644 index 0000000..3b2593d --- /dev/null +++ b/js/vendor/backbone-min.js @@ -0,0 +1,2 @@ +(function(){var t=this;var e=t.Backbone;var i=[];var r=i.push;var s=i.slice;var n=i.splice;var a;if(typeof exports!=="undefined"){a=exports}else{a=t.Backbone={}}a.VERSION="1.1.0";var h=t._;if(!h&&typeof require!=="undefined")h=require("underscore");a.$=t.jQuery||t.Zepto||t.ender||t.$;a.noConflict=function(){t.Backbone=e;return this};a.emulateHTTP=false;a.emulateJSON=false;var o=a.Events={on:function(t,e,i){if(!l(this,"on",t,[e,i])||!e)return this;this._events||(this._events={});var r=this._events[t]||(this._events[t]=[]);r.push({callback:e,context:i,ctx:i||this});return this},once:function(t,e,i){if(!l(this,"once",t,[e,i])||!e)return this;var r=this;var s=h.once(function(){r.off(t,s);e.apply(this,arguments)});s._callback=e;return this.on(t,s,i)},off:function(t,e,i){var r,s,n,a,o,u,c,f;if(!this._events||!l(this,"off",t,[e,i]))return this;if(!t&&!e&&!i){this._events={};return this}a=t?[t]:h.keys(this._events);for(o=0,u=a.length;o").attr(t);this.setElement(e,false)}else{this.setElement(h.result(this,"el"),false)}}});a.sync=function(t,e,i){var r=T[t];h.defaults(i||(i={}),{emulateHTTP:a.emulateHTTP,emulateJSON:a.emulateJSON});var s={type:r,dataType:"json"};if(!i.url){s.url=h.result(e,"url")||U()}if(i.data==null&&e&&(t==="create"||t==="update"||t==="patch")){s.contentType="application/json";s.data=JSON.stringify(i.attrs||e.toJSON(i))}if(i.emulateJSON){s.contentType="application/x-www-form-urlencoded";s.data=s.data?{model:s.data}:{}}if(i.emulateHTTP&&(r==="PUT"||r==="DELETE"||r==="PATCH")){s.type="POST";if(i.emulateJSON)s.data._method=r;var n=i.beforeSend;i.beforeSend=function(t){t.setRequestHeader("X-HTTP-Method-Override",r);if(n)return n.apply(this,arguments)}}if(s.type!=="GET"&&!i.emulateJSON){s.processData=false}if(s.type==="PATCH"&&E){s.xhr=function(){return new ActiveXObject("Microsoft.XMLHTTP")}}var o=i.xhr=a.ajax(h.extend(s,i));e.trigger("request",e,o,i);return o};var E=typeof window!=="undefined"&&!!window.ActiveXObject&&!(window.XMLHttpRequest&&(new XMLHttpRequest).dispatchEvent);var T={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};a.ajax=function(){return a.$.ajax.apply(a.$,arguments)};var k=a.Router=function(t){t||(t={});if(t.routes)this.routes=t.routes;this._bindRoutes();this.initialize.apply(this,arguments)};var S=/\((.*?)\)/g;var $=/(\(\?)?:\w+/g;var H=/\*\w+/g;var A=/[\-{}\[\]+?.,\\\^$|#\s]/g;h.extend(k.prototype,o,{initialize:function(){},route:function(t,e,i){if(!h.isRegExp(t))t=this._routeToRegExp(t);if(h.isFunction(e)){i=e;e=""}if(!i)i=this[e];var r=this;a.history.route(t,function(s){var n=r._extractParameters(t,s);i&&i.apply(r,n);r.trigger.apply(r,["route:"+e].concat(n));r.trigger("route",e,n);a.history.trigger("route",r,e,n)});return this},navigate:function(t,e){a.history.navigate(t,e);return this},_bindRoutes:function(){if(!this.routes)return;this.routes=h.result(this,"routes");var t,e=h.keys(this.routes);while((t=e.pop())!=null){this.route(t,this.routes[t])}},_routeToRegExp:function(t){t=t.replace(A,"\\$&").replace(S,"(?:$1)?").replace($,function(t,e){return e?t:"([^/]+)"}).replace(H,"(.*?)");return new RegExp("^"+t+"$")},_extractParameters:function(t,e){var i=t.exec(e).slice(1);return h.map(i,function(t){return t?decodeURIComponent(t):null})}});var I=a.History=function(){this.handlers=[];h.bindAll(this,"checkUrl");if(typeof window!=="undefined"){this.location=window.location;this.history=window.history}};var N=/^[#\/]|\s+$/g;var O=/^\/+|\/+$/g;var P=/msie [\w.]+/;var C=/\/$/;var j=/[?#].*$/;I.started=false;h.extend(I.prototype,o,{interval:50,getHash:function(t){var e=(t||this).location.href.match(/#(.*)$/);return e?e[1]:""},getFragment:function(t,e){if(t==null){if(this._hasPushState||!this._wantsHashChange||e){t=this.location.pathname;var i=this.root.replace(C,"");if(!t.indexOf(i))t=t.slice(i.length)}else{t=this.getHash()}}return t.replace(N,"")},start:function(t){if(I.started)throw new Error("Backbone.history has already been started");I.started=true;this.options=h.extend({root:"/"},this.options,t);this.root=this.options.root;this._wantsHashChange=this.options.hashChange!==false;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.options.pushState&&this.history&&this.history.pushState);var e=this.getFragment();var i=document.documentMode;var r=P.exec(navigator.userAgent.toLowerCase())&&(!i||i<=7);this.root=("/"+this.root+"/").replace(O,"/");if(r&&this._wantsHashChange){this.iframe=a.$('