From 815af8934585fc6390c5ab953d22a66466381dbf Mon Sep 17 00:00:00 2001 From: Travis Grathwell Date: Wed, 15 May 2013 21:01:01 -0700 Subject: [PATCH] Lurching slightly closer to student sorting Adds handlebars_assets for backbone templates --- Gemfile | 1 + Gemfile.lock | 5 ++ app/assets/javascripts/application.js | 5 ++ app/assets/javascripts/views/base_view.js | 24 ++++++++ .../views/section_organizer_view.js | 31 ++++------ app/assets/javascripts/views/section_view.js | 30 ++++++++-- app/assets/javascripts/views/student_view.js | 8 --- .../stylesheets/_section_organizer.css.scss | 10 ++++ .../templates/section_organizer/section.hbs | 6 ++ .../section_organizer/section_organizer.hbs | 1 + .../section_organizer_spec.js | 31 ++++++---- vendor/assets/javascripts/backbone-super.js | 58 +++++++++++++++++++ 12 files changed, 169 insertions(+), 41 deletions(-) create mode 100644 app/assets/javascripts/views/base_view.js delete mode 100644 app/assets/javascripts/views/student_view.js create mode 100644 app/assets/templates/section_organizer/section.hbs create mode 100644 app/assets/templates/section_organizer/section_organizer.hbs create mode 100644 vendor/assets/javascripts/backbone-super.js diff --git a/Gemfile b/Gemfile index 884f9025d..a04c7ba19 100644 --- a/Gemfile +++ b/Gemfile @@ -19,6 +19,7 @@ group :production do end group :assets do + gem 'handlebars_assets' gem 'jquery-datatables-rails' gem 'sass-rails' gem 'coffee-rails' diff --git a/Gemfile.lock b/Gemfile.lock index 8570a1836..a0eb70a2e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -103,6 +103,10 @@ GEM guard-rspec (2.5.0) guard (>= 1.1) rspec (~> 2.11) + handlebars_assets (0.12.0) + execjs (>= 1.2.9) + sprockets (>= 2.0.3) + tilt hashie (1.2.0) hike (1.2.1) http_parser.rb (0.5.3) @@ -285,6 +289,7 @@ DEPENDENCIES gmaps4rails gravatar_image_tag guard-rspec + handlebars_assets jasmine jquery-datatables-rails jquery-rails diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index ab0870c0f..9428149dd 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -7,15 +7,20 @@ //= require jquery //= require jquery_ujs //= require jquery.ui.datepicker +//= require jquery.ui.sortable //= require twitter/bootstrap/modal //= require twitter/bootstrap/transition //= require select2 //= require modernizr +//= require handlebars.runtime //= require underscore //= require backbone +//= require backbone-super //= require bridgetroll +//= require_tree ../templates //= require_tree ./models //= require_tree ./collections +//= require ./views/base_view //= require_tree ./views //= require_tree . //= require jquery_nested_form diff --git a/app/assets/javascripts/views/base_view.js b/app/assets/javascripts/views/base_view.js new file mode 100644 index 000000000..7a8621ea1 --- /dev/null +++ b/app/assets/javascripts/views/base_view.js @@ -0,0 +1,24 @@ +Bridgetroll.Views.Base = Backbone.View.extend({ + postRender: $.noop, + context: $.noop, + + initialize: function () { + this.subViews = []; + }, + + render: function () { + this.$el.empty(); + if (this.template) { + var template = HandlebarsTemplates[this.template]; + this.$el.html(template(this.context())); + } + + this.postRender(); + this.delegateEvents(); + + _.each(this.subViews, function (view) { + view.render(); + this.$el.append(view.$el); + }, this); + } +}); diff --git a/app/assets/javascripts/views/section_organizer_view.js b/app/assets/javascripts/views/section_organizer_view.js index f660e23cd..792171bcf 100644 --- a/app/assets/javascripts/views/section_organizer_view.js +++ b/app/assets/javascripts/views/section_organizer_view.js @@ -1,30 +1,23 @@ -Bridgetroll.Views.SectionOrganizer = Backbone.View.extend({ - initialize: function (options) { - this.subViews = []; - this.students = options && options.students; +Bridgetroll.Views.SectionOrganizer = Bridgetroll.Views.Base.extend({ + template: 'section_organizer/section_organizer', - this.students.each(function (student) { - this.addStudent(student); - }, this); + events: { + 'click .add-section': 'addSection' }, - render: function () { - this.$el.empty(); - - _.each(this.subViews, function (view) { - view.render(); - this.$el.append(view.$el); - }, this); - }, + initialize: function (options) { + this._super('initialize', arguments); + this.students = options.students; + this.listenTo(this.students, 'change', this.render); - addStudent: function (student) { - var studentView = new Bridgetroll.Views.Student({model: student}); - this.subViews.push(studentView); + var section = new Bridgetroll.Views.Section({title: 'Unsorted Students', students: options.students}); + this.subViews.push(section); this.render(); }, addSection: function () { - var section = new Bridgetroll.Views.Section(); + var sectionStudents = new Bridgetroll.Collections.Student(); + var section = new Bridgetroll.Views.Section({title: 'New Section', students: sectionStudents}); this.subViews.push(section); this.render(); } diff --git a/app/assets/javascripts/views/section_view.js b/app/assets/javascripts/views/section_view.js index 763cc86fb..de03592e1 100644 --- a/app/assets/javascripts/views/section_view.js +++ b/app/assets/javascripts/views/section_view.js @@ -1,8 +1,30 @@ -Bridgetroll.Views.Section = Backbone.View.extend({ +Bridgetroll.Views.Section = Bridgetroll.Views.Base.extend({ className: 'bridgetroll-section', + template: 'section_organizer/section', - render: function () { - this.$el.empty(); - this.$el.append('i am a section'); + events: { + 'sortreceive .students': 'studentAdded', + 'sortremove .students': 'studentRemoved' + }, + + initialize: function (options) { + this._super('initialize', arguments); + + this.title = options.title; + this.students = options.students; + }, + + context: function () { + return { + title: this.title, + students: this.students.toJSON() + } + }, + + studentAdded: $.noop, + studentRemoved: $.noop, + + postRender: function () { + this.$('.students').sortable({connectWith: '.bridgetroll-section .students'}); } }); \ No newline at end of file diff --git a/app/assets/javascripts/views/student_view.js b/app/assets/javascripts/views/student_view.js deleted file mode 100644 index 5f2576e73..000000000 --- a/app/assets/javascripts/views/student_view.js +++ /dev/null @@ -1,8 +0,0 @@ -Bridgetroll.Views.Student = Backbone.View.extend({ - className: 'bridgetroll-student', - - render: function () { - this.$el.empty(); - this.$el.append(this.model.get('name')); - } -}); \ No newline at end of file diff --git a/app/assets/stylesheets/_section_organizer.css.scss b/app/assets/stylesheets/_section_organizer.css.scss index 772353b01..0d3ab400a 100644 --- a/app/assets/stylesheets/_section_organizer.css.scss +++ b/app/assets/stylesheets/_section_organizer.css.scss @@ -7,4 +7,14 @@ .bridgetroll-section { border: 1px solid green; + padding: 5px; + width: 200px; + + .students { + min-height: 50px; + } +} + +ul { + list-style-type: none; } \ No newline at end of file diff --git a/app/assets/templates/section_organizer/section.hbs b/app/assets/templates/section_organizer/section.hbs new file mode 100644 index 000000000..2c88d5fb4 --- /dev/null +++ b/app/assets/templates/section_organizer/section.hbs @@ -0,0 +1,6 @@ +

{{title}}

+ diff --git a/app/assets/templates/section_organizer/section_organizer.hbs b/app/assets/templates/section_organizer/section_organizer.hbs new file mode 100644 index 000000000..5323626aa --- /dev/null +++ b/app/assets/templates/section_organizer/section_organizer.hbs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/spec/javascripts/section_organizer/section_organizer_spec.js b/spec/javascripts/section_organizer/section_organizer_spec.js index 6aa8cc2fd..1cc1a1e0a 100644 --- a/spec/javascripts/section_organizer/section_organizer_spec.js +++ b/spec/javascripts/section_organizer/section_organizer_spec.js @@ -10,23 +10,34 @@ describe("SectionOrganizer", function() { sectionOrganizer = new Bridgetroll.Views.SectionOrganizer({students: students}); }); - it("renders each of the students from the original collection", function () { - sectionOrganizer.render(); - expect(sectionOrganizer.$el.text()).toContain('Lana Lang'); - expect(sectionOrganizer.$el.text()).toContain('Sue Storm'); - expect(sectionOrganizer.$el.text()).toContain('Ted Moesby'); + describe("after rendering", function () { + beforeEach(function () { + sectionOrganizer.render(); + }); + + it("contains each of the students from the original collection", function () { + expect(sectionOrganizer.$el.text()).toContain('Lana Lang'); + expect(sectionOrganizer.$el.text()).toContain('Sue Storm'); + expect(sectionOrganizer.$el.text()).toContain('Ted Moesby'); + }); + + describe("add section button", function () { + it("should invoke #addSection", function () { + spyOn(sectionOrganizer, 'addSection'); + sectionOrganizer.$('.add-section').click(); + sectionOrganizer.$('.add-section').click(); // TODO: not this + expect(sectionOrganizer.addSection).toHaveBeenCalled(); + }); + }); }); describe("#addSection", function () { it("adds a new section as a subview", function () { sectionOrganizer.render(); - expect(sectionOrganizer.$('.bridgetroll-section').length).toEqual(0); - - sectionOrganizer.addSection(); - expect(sectionOrganizer.$('.bridgetroll-section').length).toEqual(1); + var sectionCount = sectionOrganizer.$('.bridgetroll-section').length; sectionOrganizer.addSection(); - expect(sectionOrganizer.$('.bridgetroll-section').length).toEqual(2); + expect(sectionOrganizer.$('.bridgetroll-section').length).toEqual(sectionCount + 1); }); }); }); \ No newline at end of file diff --git a/vendor/assets/javascripts/backbone-super.js b/vendor/assets/javascripts/backbone-super.js new file mode 100644 index 000000000..805c644cf --- /dev/null +++ b/vendor/assets/javascripts/backbone-super.js @@ -0,0 +1,58 @@ +// This method gives you an easier way of calling super +// when you're using Backbone in plain javascript. +// It lets you avoid writing the constructor's name multiple +// times. You still have to specify the name of the method. +// +// So instead of having to write: +// +// User = Backbone.Model.extend({ +// save: function(attrs) { +// this.beforeSave(attrs); +// return User.__super__.save.apply(this, arguments); +// } +// }); +// +// You get to write: +// +// User = Backbone.Model.extend({ +// save: function(attrs) { +// this.beforeSave(attrs); +// return this._super("save", arguments); +// } +// }); +// + +;(function(Backbone) { + + // The super method takes two parameters: a method name + // and an array of arguments to pass to the overridden method. + // This is to optimize for the common case of passing 'arguments'. + function _super(methodName, args) { + + // Keep track of how far up the prototype chain we have traversed, + // in order to handle nested calls to _super. + this._superCallObjects || (this._superCallObjects = {}); + var currentObject = this._superCallObjects[methodName] || this, + parentObject = findSuper(methodName, currentObject); + this._superCallObjects[methodName] = parentObject; + + var result = parentObject[methodName].apply(this, args || []); + delete this._superCallObjects[methodName]; + return result; + } + + // Find the next object up the prototype chain that has a + // different implementation of the method. + function findSuper(methodName, childObject) { + var object = childObject; + while (object[methodName] === childObject[methodName]) { + object = object.constructor.__super__; + } + return object; + } + + _.each(["Model", "Collection", "View", "Router"], function(klass) { + Backbone[klass].prototype._super = _super; + }); + +})(Backbone);