diff --git a/.gitignore b/.gitignore index e508f25..5d4828b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ test/dummy/tmp/ test/dummy/.sass-cache/ test/tmp/**/* .idea/ +.rvmrc +.DS_Store +*.orig diff --git a/.rvmrc b/.rvmrc index f93238c..6e30686 100644 --- a/.rvmrc +++ b/.rvmrc @@ -1,47 +1 @@ -#!/usr/bin/env bash - -# This is an RVM Project .rvmrc file, used to automatically load the ruby -# development environment upon cd'ing into the directory - -# First we specify our desired [@], the @gemset name is optional. -environment_id="ruby-1.9.2-p180@backbone-rails" - -# -# First we attempt to load the desired environment directly from the environment -# file, this is very fast and efficicent compared to running through the entire -# CLI and selector. If you want feedback on which environment was used then -# insert the word 'use' after --create as this triggers verbose mode. -# -if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \ - && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]] ; then - \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id" -else - # If the environment file has not yet been created, use the RVM CLI to select. - rvm --create "$environment_id" -fi - -# -# If you use an RVM gemset file to install a list of gems (*.gems), you can have -# it be automatically loaded, uncomment the following and adjust the filename if -# necessary. -# -# filename=".gems" -# if [[ -s "$filename" ]] ; then -# rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d' -# fi - -# -# If you use bundler and would like to run bundle each time you enter the -# directory you can uncomment the following code. -# -# Ensure that Bundler is installed, install it if it is not. -if ! command -v bundle ; then - printf "The rubygem 'bundler' is not installed, installing it now.\n" - gem install bundler -fi -# -# # Bundle while redcing excess noise. -# printf "Bundling your gems this may take a few minutes on a fresh clone.\n" -# bundle | grep -v 'Using' | grep -v 'complete' | sed '/^$/d' -# - +rvm use 1.9.2@backbone-rails diff --git a/backbone-rails.gemspec b/backbone-rails.gemspec index abbdfaa..a324572 100644 --- a/backbone-rails.gemspec +++ b/backbone-rails.gemspec @@ -3,17 +3,17 @@ Gem::Specification.new do |s| s.name = "rails-backbone" s.version = "0.5.4" - s.authors = ["Ryan Fitzgerald", "Code Brew Studios"] + s.authors = ["Ryan Fitzgerald", "Code Brew Studios", "Jairo Vazquez"] s.email = ["ryan@codebrewstudios.com"] s.homepage = "http://github.com/codebrew/backbone-rails" - + s.summary = "Use backbone.js with rails 3.1" s.description = "Quickly setup backbone.js for use with rails 3.1. Generators are provided to quickly get started." s.files = Dir["lib/**/*"] + Dir["vendor/**/*"] + ["MIT-LICENSE", "Rakefile", "README.md"] - - s.add_dependency('rails', '~> 3.1.0') - s.add_dependency('coffee-script', '~> 2.2.0') - s.add_dependency('ejs', '~> 1.0.0') - + + s.add_dependency('rails', '~> 3.1') + s.add_dependency('coffee-script', '~> 2.2') + s.add_dependency('inherited_resources', '~> 1.3') + s.require_paths = ['lib'] end diff --git a/lib/generators/backbone/install/install_generator.rb b/lib/generators/backbone/install/install_generator.rb index 1b1f59a..c2d64d2 100644 --- a/lib/generators/backbone/install/install_generator.rb +++ b/lib/generators/backbone/install/install_generator.rb @@ -4,31 +4,133 @@ module Backbone module Generators class InstallGenerator < Rails::Generators::Base include Backbone::Generators::ResourceHelpers - + source_root File.expand_path("../templates", __FILE__) - + desc "This generator installs backbone.js with a default folder layout in app/assets/javascripts/backbone" - + class_option :skip_git, :type => :boolean, :aliases => "-G", :default => false, :desc => "Skip Git ignores and keeps" - + def inject_backbone inject_into_file "app/assets/javascripts/application.js", :before => "//= require_tree" do - "//= require underscore\n//= require backbone\n//= require backbone_rails_sync\n//= require backbone_datalink\n//= require backbone/#{application_name.underscore}\n" + [ + "//= require jquery.tmpl", + "//= require jquery.iframe-transport", + "//= require lib/jquery.ak.tools", + "//= require lib/jquery.livequery", + "//= require lib/jquery.maskedinput-1.3", + "//= require lib/jshashtable-2.1", + "//= require lib/jquery.numberformatter-1.2", + "//= require lib/jquery.formatting.tmpl", + "//= require underscore", + "//= require backbone", + "//= require backbone_rails_sync", + "//= require backbone_datalink", + "//= require backbone/#{application_name.underscore}\n" + ].join("\n") + end + end + + def inject_in_application_helper + inject_into_file "app/helpers/application_helper.rb", :after => "module ApplicationHelper" do + [ + "\n", + "def script_template(*args)", + " name = args.first", + " options = args.second || {}", + " id = options[:id] || \"backbone_templates_\#{controller_name}_\#{name}\"", + " locals = options[:locals] || {}", + " partial = options[:partial] || \"\#{controller_name}/\#{name}\"", + " id = id.camelize(:lower)", + " content_tag(:script, :type => \"text/template\", :id => id) do", + " render :partial => partial, :locals => locals", + " end", + "end\n", + "", + "def title_page", + " '#{application_name}'", + "end" + ].join("\n ") + end + end + + def inject_in_gemfile + append_file "Gemfile" do + [ + "", + "# Gems used for rails-backbone", + "gem 'inherited_resources'", + "gem 'will_paginate', '~> 3.0'", + "gem 'acts_as_api'", + "gem 'haml-rails'", + "gem 'less-rails-bootstrap'" + ].join("\n") end end - + def create_dir_layout - %W{routers models views templates}.each do |dir| - empty_directory "app/assets/javascripts/backbone/#{dir}" + %W{routers models views modules config}.each do |dir| + empty_directory "app/assets/javascripts/backbone/#{dir}" create_file "app/assets/javascripts/backbone/#{dir}/.gitkeep" unless options[:skip_git] end end - + def create_app_file template "app.coffee", "app/assets/javascripts/backbone/#{application_name.underscore}.js.coffee" end - + + def create_helpers_file + template "helpers.coffee", "app/assets/javascripts/backbone/helpers.js.coffee" + end + + def create_base_model_file + template "base_model.coffee", "app/assets/javascripts/backbone/models/base_model.js.coffee" + end + + def create_base_view_file + template "base_view.coffee", "app/assets/javascripts/backbone/views/base_view.js.coffee" + end + + def create_base_collection_file + template "base_collection.coffee", "app/assets/javascripts/backbone/models/base_collection.js.coffee" + end + + def create_dir_backbone_alerts + empty_directory "app/views/backbone_alerts" + template "alerts/_error_alert.haml", "app/views/backbone_alerts/_error_alert.html.haml" + template "alerts/_info_alert.haml", "app/views/backbone_alerts/_info_alert.html.haml" + template "alerts/_success_alert.haml", "app/views/backbone_alerts/_success_alert.html.haml" + template "alerts/_warning_alert.haml", "app/views/backbone_alerts/_warning_alert.html.haml" + end + + def create_pagination_template + template "alerts/_pagination.haml", "app/views/backbone_alerts/_pagination.html.haml" + end + + def create_backbone_responses + template "backbone_responses.rb", "lib/backbone_responses.rb" + end + + def create_module_files + %W{inheritance eip i18n ajax_requests number_helper pagination validations}.each do |module_name| + template "modules/#{module_name}.coffee", "app/assets/javascripts/backbone/modules/#{module_name}.js.coffee" + end + end + + def create_locale_files + empty_directory "app/assets/javascripts/backbone/config/locales" + template "config/locales/es-MX.coffee", "app/assets/javascripts/backbone/config/locales/es-MX.js.coffee" + end + + def create_config_files + template "config/string.coffee", "app/assets/javascripts/backbone/config/string.js.coffee" + end + + def create_base_router + template "base_router.coffee", "app/assets/javascripts/backbone/routers/base_router.js.coffee" + end + end end -end \ No newline at end of file +end diff --git a/lib/generators/backbone/install/templates/alerts/_error_alert.haml b/lib/generators/backbone/install/templates/alerts/_error_alert.haml new file mode 100644 index 0000000..1588743 --- /dev/null +++ b/lib/generators/backbone/install/templates/alerts/_error_alert.haml @@ -0,0 +1,7 @@ +.alert-message.error.block-message + %a.close{:href =>"#"} x + + {{each messages}} + %p + %strong ${name} ${message} + {{/each}} diff --git a/lib/generators/backbone/install/templates/alerts/_info_alert.haml b/lib/generators/backbone/install/templates/alerts/_info_alert.haml new file mode 100644 index 0000000..487d392 --- /dev/null +++ b/lib/generators/backbone/install/templates/alerts/_info_alert.haml @@ -0,0 +1,7 @@ +.alert-message.info.block-message + %a.close{:href =>"#"} x + + {{each messages}} + %p + %strong ${message} + {{/each}} diff --git a/lib/generators/backbone/install/templates/alerts/_pagination.haml b/lib/generators/backbone/install/templates/alerts/_pagination.haml new file mode 100644 index 0000000..07b6978 --- /dev/null +++ b/lib/generators/backbone/install/templates/alerts/_pagination.haml @@ -0,0 +1,26 @@ +.row + %hr + .pagination + %ul + + {{if paginatePrev}} + %li.prev + = link_to t('will_paginate.previous'), '${resources_path}?page=${paginatePrev}' + {{else}} + %li.prev.disabled + = link_to t('will_paginate.previous'), '#' + {{/if}} + + {{each pages}} + %li{:class => "${liKlass}"} + = link_to "${text}", '${path}' + {{/each}} + + {{if paginateNext}} + %li.next + = link_to t('will_paginate.next'), '${resources_path}?page=${paginateNext}' + {{else}} + %li.next.disabled + = link_to t('will_paginate.next'), '#' + {{/if}} + diff --git a/lib/generators/backbone/install/templates/alerts/_success_alert.haml b/lib/generators/backbone/install/templates/alerts/_success_alert.haml new file mode 100644 index 0000000..e31cf4a --- /dev/null +++ b/lib/generators/backbone/install/templates/alerts/_success_alert.haml @@ -0,0 +1,7 @@ +.alert-message.success.block-message + %a.close{:href =>"#"} x + + {{each messages}} + %p + %strong ${message} + {{/each}} diff --git a/lib/generators/backbone/install/templates/alerts/_warning_alert.haml b/lib/generators/backbone/install/templates/alerts/_warning_alert.haml new file mode 100644 index 0000000..c421a22 --- /dev/null +++ b/lib/generators/backbone/install/templates/alerts/_warning_alert.haml @@ -0,0 +1,7 @@ +.alert-message.warning.block-message + %a.close{:href =>"#"} x + + {{each messages}} + %p + %strong ${message} + {{/each}} diff --git a/lib/generators/backbone/install/templates/app.coffee b/lib/generators/backbone/install/templates/app.coffee index 7f2568d..8d65036 100644 --- a/lib/generators/backbone/install/templates/app.coffee +++ b/lib/generators/backbone/install/templates/app.coffee @@ -1,11 +1,24 @@ #= require_self -#= require_tree ./templates +#= require_tree ./config +#= require_tree ./modules +#= require ./helpers +#= require ./models/base_model +#= require ./models/base_collection #= require_tree ./models +#= require ./views/base_view #= require_tree ./views +#= require ./routers/base_router #= require_tree ./routers window.<%= js_app_name %> = Models: {} Collections: {} Routers: {} - Views: {} \ No newline at end of file + Views: {} + Locales: {} + Config: + default_locale:"es-MX" + locale: -> @default_locale + flashes: ["warning", "error", "success", "info"] + +window.Modules = {} diff --git a/lib/generators/backbone/install/templates/backbone_responses.rb b/lib/generators/backbone/install/templates/backbone_responses.rb new file mode 100644 index 0000000..ec411d6 --- /dev/null +++ b/lib/generators/backbone/install/templates/backbone_responses.rb @@ -0,0 +1,153 @@ +module BackboneResponses + + def self.included(base) + base.send :include, InstanceMethods + base.send :extend, ClassMethods + end + + module ClassMethods + # Add class methods here + end + + module InstanceMethods + # Add instance methods here + + def index + collection_public_attributes + + respond_to do |format| + format.html + format.json { render :json => instance_variable_get("@#{controller_name}") } + end + end + + def update + update! do |format| + format.json do + render :json => resource_public_attributes + end + end + end + + def create + create! do |format| + format.json do + render :json => resource_public_attributes + end + end + end + + def show + respond_to do |format| + format.json do + render :json => resource_public_attributes + end + end + end + + protected + + def collection + c = collection_scopes + resources = c.respond_to?(:scoped) ? c.scoped : c.all + instance_variable_set("@#{controller_name}" , resources) + end + + def collection_scopes + end_of_association_chain + end + + def resource_public_attributes + model = end_of_association_chain + object = reload_resource ? resource.reload : resource + + if model.respond_to?(:acts_as_api?) and model.acts_as_api? and model.respond_to?(:"api_accessible_#{api_resource_template}") + object.as_api_response(:"#{api_resource_template}") + else + object + end + end + + def collection_public_attributes + if paginate? + instance_variable_set("@#{controller_name}" , resources_with_pagination(collection)) + else + instance_variable_set("@#{controller_name}" , resources_without_pagination(collection)) + end + end + + end + + def resources_with_pagination(*args) + model = end_of_association_chain + resources = args.first.paginate(:page => (params[:page] || 1), :per_page => resources_per_page) + + options = args.second || {} + template = options[:template] || api_collection_template + path = options[:path] || "" + query_params = options[:params] || params #{} + + query_params.delete(:page) + + if model.respond_to?(:acts_as_api?) and model.acts_as_api? and model.respond_to?(:"api_accessible_#{template}") + allowed_keys = resources.as_api_response(:"#{template}") + else + allowed_keys = resources + end + + per_page = resources.per_page + total_entries = resources.total_entries + total_pages = (total_entries.to_f / per_page.to_f).ceil + + { + :resources => allowed_keys, + :pagination => { + :current_page => resources.current_page, + :per_page => per_page, + :total_entries => total_entries, + :total_pages => total_pages, + :params => query_params, + :path => path + } + } + end + + def resources_without_pagination(*args) + model = end_of_association_chain + resources = args.first + + options = args.second || {} + template = options[:template] || api_collection_template + path = options[:path] || "" + query_params = options[:params] || params #{} + + query_params.delete(:page) + + if model.respond_to?(:acts_as_api?) and model.acts_as_api? and model.respond_to?(:"api_accessible_#{template}") + allowed_keys = resources.as_api_response(:"#{template}") + else + allowed_keys = resources + end + end + + def api_collection_template + params[:api_template] || "list" + end + + def api_resource_template + params[:api_template] || "public" + end + + def paginate? + true + end + + def resources_per_page + 50 + end + + def reload_resource + false + end + +end diff --git a/lib/generators/backbone/install/templates/base_collection.coffee b/lib/generators/backbone/install/templates/base_collection.coffee new file mode 100644 index 0000000..7ea12a8 --- /dev/null +++ b/lib/generators/backbone/install/templates/base_collection.coffee @@ -0,0 +1,19 @@ +class <%= js_app_name %>.Collections.BaseCollection extends Backbone.Collection + + # The JSON representation of a Collection is an array of the + # models' attributes. + toJSON : (includeRelations = false) -> + @map( (model) -> model.toJSON(includeRelations) ) + + # When you have more items than you want to add or remove individually, + # you can reset the entire set with a new list of models, without firing + # any `added` or `removed` events. Fires `reset` when finished. + reset: (models = [], options = {}) -> + @pagination = models.pagination if models.pagination? + models = models.resources if models.resources? + + @each @_removeReference + @_reset() + @add models, silent: true + @trigger('reset', this, options) if (!options.silent) + this diff --git a/lib/generators/backbone/install/templates/base_model.coffee b/lib/generators/backbone/install/templates/base_model.coffee new file mode 100644 index 0000000..b7c9ef2 --- /dev/null +++ b/lib/generators/backbone/install/templates/base_model.coffee @@ -0,0 +1,339 @@ +class <%= js_app_name %>.Models.BaseModel extends Backbone.Model + + _.extend @, Modules.Inheritance + + @include Modules.Validations + + constructor: (attributes = {}, options) -> + + # Default Logic in the constructor of Backbone Model + # ========================================================= + if defaults = @defaults + defaults = defaults.call(this) if _.isFunction(defaults) + attributes = _.extend({}, defaults, attributes) + + @attributes = {} + @_escapedAttributes = {} + @cid = _.uniqueId('c') + @set(attributes, {silent : true}) + @_changed = false + @_previousAttributes = _.clone(@attributes) + @collection = options.collection if (options && options.collection) + # ========================================================= + + # Clone de original attributes + attributes = _.extend({}, _.clone(attributes)) + + # Callbacks + @bind("afterSave", (model, jqXHR) -> + _.each(@afterSave, (callback, key) -> callback?(model, jqXHR) ) + ) + + # belongsTo + @setBelongsTo(attributes, null) + + _.each(@belongsTo, (relation, key) => + relation = @buildBelonsToRelation(relation, key) + @bind("change:#{relation.foreignKey}", () -> + @setBelongsTo({}, key) + ) + ) + # hasMany + _.each(@hasMany, (relation, key) => + if relation.collection? + # Create a new collection object if not exist + unless @[key]? + @[key] = new <%= js_app_name %>.Collections[relation.collection] + @[key].url = "#{@urlRoot}/#{attributes.id}/#{key}" unless @isNew() + + @[key].reset attributes[key] if attributes[key]? + ) + + @initialize(attributes, options) + + calculatedAttributes: {} + + save: -> + $('.alert-message').remove() + super(arguments...) + + updateAttribute: (attribute, value, options = {}) -> + attrs = {} + attrs[attribute] = value + return false if !@set(attrs) + + data = {} + if @paramRoot? then data[@paramRoot] = attrs + else data = attrs + + _.extend data, {template: "base"} + + settings = + url : "#{@urlRoot}/#{@get('id')}" + type : "PUT" + data : data + + _.extend settings, options + <%= js_app_name %>.Helpers.ajax(settings) + + updateAttributes: (attrs, options = {}) -> + return false if !@set(attrs) + + data = {} + if @paramRoot? then data[@paramRoot] = attrs + else data = attrs + + _.extend data, {template: "base"} + + settings = + url : "#{@urlRoot}/#{@get('id')}" + type : "PUT" + data : data + + _.extend settings, options + <%= js_app_name %>.Helpers.ajax(settings) + + get : ( key ) -> + that = @ + + if @calculatedAttributes[key] + value = @calculatedAttributes[key] + + if typeof value != 'undefined' + return value() + + # fall back to the default backbone behavior + super(key) + + toJSON: ( includeRelations = false, includeCalculated = false ) -> + json = _.clone @attributes + + if includeRelations is true + json["#{@paramRoot}_cid"] = "backboneCid_#{@cid}" + + if includeCalculated is true + _.each(@calculatedAttributes, (attribute, key) => + json[key] = attribute(@) + ) + + # belongsTo + _.each(@belongsTo, + (relation, key) => + relation = @buildBelonsToRelation(relation, key) + + # include nesteds attributes for save with AJAX + if includeRelations is false + if @[key]? and relation.isNested is true + if relation.isPolymorphic isnt true + json["#{key}_attributes"] = @[key].toJSON(includeRelations) + + delete json[key] + + # include all values to use in Show view for example + else if @[key]? + json[key] = @[key].toJSON(includeRelations) + + # include delegates + delegate = @delegates[key] || {} + _.each(delegate.attributes, (name) -> + if delegate.prefix is true then keyName = "#{key}_#{name}" + else keyName = name + json[keyName] = json[key][name] + ) + + ) + # hasMany + _.each(@hasMany, + (relation, key) => + if includeRelations is false + + if @[key]? and relation.isNested is true + json["#{key}_attributes"] = @[key].toJSON(includeRelations) + + delete json[key] + + else if @[key]? + json[key] = @[key].toJSON(includeRelations) + ) + + # Attributes that are eliminated are not part of the model + # only used to display information with some custom format + if includeRelations is false + _.each(@removeWhenSaving, (value) -> + delete json[value] + ) + + # Attributes with null value are eliminated + for _key, _value of json + if _value is null and @hasChanged(_key) is false + delete json[_key] + + json + + setBelongsTo: (attributes, callbackKey) -> + # For reload association object when foreignKey has changed + if callbackKey? + relation = @belongsTo[callbackKey] + @createBelongsToRelation(attributes, relation, callbackKey, callbackKey) + + # For load association when and models is instantiated + else + _.each(@belongsTo, (relation, key) => + @createBelongsToRelation(attributes, relation, key, callbackKey) + ) + + createBelongsToRelation: (attributes, relation, key, callbackKey) -> + relation = @buildBelonsToRelation(relation, key) + + if relation.model? + + unless @[key]? + @[key] = new <%= js_app_name %>.Models[relation.model] attributes[key] + + # Retrieve values from database if foreignKey has changed + else if callbackKey? and relation.isNested isnt true + + if newValue = @get(relation.foreignKey) + if newValue isnt @[key].get("id") + url = "/#{relation.route}/#{newValue}" + template = relation.template || "base" + data = <%= js_app_name %>.Helpers.jsonData url, api_template: template + @[key].set(data, {silent: true}) if data? + + # clear attributes if foreignKey is null + else @[key].clear silent: true + + else + # Create a new Backbone Model for use toJSON function + @[key] = new Backbone.Model + + buildBelonsToRelation: (relation, key) -> + relation = _.clone relation + + # When belongsTo is a polymorphic association + if relation.isPolymorphic is true + relation = @polymorphicRelation(relation, key) + + else + # If route is not defined it's taken from model urlRoot + unless relation.route? + relation.route = <%= js_app_name %>.Models[relation.model].urlRoot + + # If foreignKey is not defined it's taken from model paramRoot more "_id" + unless relation.foreignKey? + relation.foreignKey = "#{<%= js_app_name %>.Models[relation.model].paramRoot}_id" + + relation + + polymorphicRelation: (relation, key) -> + polymorphicType = "#{key}_type" + relation.foreignKey = "#{key}_id" + + if (modelName = @get(polymorphicType))? + relation.route = <%= js_app_name %>.Models[modelName].urlRoot + relation.model = modelName + + relation + + prepareToEdit: () -> + window.router._editedModels ||= [] + index = _.indexOf(window.router._editedModels, @) + if index is -1 + @_originalAttributes = _.clone( @toJSON() ) + window.router._editedModels.push(@) + + resetToOriginValues: () -> + @set @_originalAttributes + + setAllValues: (url = @url()) -> + resettable = if _.isFunction(@isResettable) then @isResettable() else @isResettable + if resettable is true and @allValuesSeted is false + data = <%= js_app_name %>.Helpers.jsonData url + + if data? + @resetRelations data, true + @allValuesSeted = true + + allValuesSeted: false + + resetRelations: (data, setAllValues = false) -> + # Self Attributes + _.each(data, (value, key) => + if !@belongsTo[key]? or !@hasMany[key]? + @attributes[key] = data[key] + ) + + # belongsTo + _.each(@belongsTo, + (relation, key) => + values = data[key] + relation = @buildBelonsToRelation(relation, key) + if values? and ( relation.isNested is true or relation.resettable is true or setAllValues is true) + @[key] = new <%= js_app_name %>.Models[relation.model] values + ) + # hasMany + _.each(@hasMany, + (relation, key) => + values = data[key] + @[key].url = "#{@urlRoot}/#{@get('id')}/#{key}" unless @isNew() + @[key].reset values if values? + ) + + + validates: (attrs, validates = {}) -> + resultMessage = @_validates attrs, validates + + if _.isEmpty(resultMessage) then null + else + @attributes = _.extend(@attributes, attrs) + @errors = resultMessage + + isValid: () -> + resultValidation = @validate(@attributes) || {} + + # Run HasMany validations + for key, value of @hasMany when value.isNested is true + @[key].each((model) -> + nestedValidation = model.validate(model.attributes) || {} + resultValidation["#{key}.#{_key}"] = _value for _key, _value of nestedValidation + ) + + # Run BelongsTo validations + for key, value of @belongsTo when value.isNested is true + model = @[key] + nestedValidation = model.validate(model.attributes) || {} + resultValidation["#{key}.#{_key}"] = _value for _key, _value of nestedValidation + + if _.isEmpty resultValidation then true + else @errors = resultValidation + + includeCidInJson: false + + # Relations + hasMany: {} + + belongsTo: {} + + delegates: {} + + removeWhenSaving: [] + + isResettable: -> + _.isEmpty(@hasMany) is false + + resettableAttributes: [] + + # Callbacks + afterSave: {} + + # I18n support + modelName: () -> + @paramRoot + + humanName: () -> + Modules.I18n.t "activerecord.models.#{@paramRoot}" + + humanAttributeName: (name) -> + if name.split(".").length is 1 + Modules.I18n.t "activerecord.attributes.#{@paramRoot}.#{name}" + else Modules.I18n.t "#{name}" diff --git a/lib/generators/backbone/install/templates/base_router.coffee b/lib/generators/backbone/install/templates/base_router.coffee new file mode 100644 index 0000000..778003e --- /dev/null +++ b/lib/generators/backbone/install/templates/base_router.coffee @@ -0,0 +1,50 @@ +class <%= js_app_name %>.Routers.BaseRouter extends Backbone.Router + + _.extend @, Modules.Inheritance + + @include Modules.I18n + + # Manually bind a single named route to a callback. For example: + # @route('search/:query/p:num', 'search', (query, num) -> + + route : (route, name, callback) -> + Backbone.history || (Backbone.history = new Backbone.History) + + route = @._routeToRegExp(route) if (!_.isRegExp(route)) + + Backbone.history.route(route, _.bind( + (fragment) -> + args = this._extractParameters(route, fragment) + + @removeViews(name) + @resetModelsWithoutSaving() + @beforeFilter() + + callback.apply(this, args) + @trigger.apply(this, ['route:' + name].concat(args)) + + @renderPageTitle() + this) + ) + + beforeFilter: -> + + removeViews: (name) -> + @["#{name}_view"].remove() if @["#{name}_view"]? # index_view + @["#{name}View"].remove() if @["#{name}View"]? # customNameView + + resetModelsWithoutSaving: () -> + _.each(@_editedModels, (model) -> + model.resetToOriginValues() + ) + @_editedModels = [] + + renderPageTitle: -> + title_page = $("title").text() + title_h1 = $.trim $('.container h1:first').text() + title_page = title_page.split('|') + $('title').text("#{title_h1} | #{title_page[1]}") + + resourceNotFound: -> + @flash "warning", @t("errors.not_found") + window.location.hash = "/index" diff --git a/lib/generators/backbone/install/templates/base_view.coffee b/lib/generators/backbone/install/templates/base_view.coffee new file mode 100644 index 0000000..ab85d34 --- /dev/null +++ b/lib/generators/backbone/install/templates/base_view.coffee @@ -0,0 +1,190 @@ +class <%= js_app_name %>.Views.BaseView extends Backbone.View + + _.extend @, Modules.Inheritance + + @include Modules.Validations + @include Modules.NumberHelper + @include Modules.Pagination + @include Modules.AjaxRequests + @include Modules.I18n + + constructor: -> + @beforeInitialize() + super(arguments...) + @afterInitialize() + + # Callbacks + # ========================================================== + beforeInitialize: -> + _.each(@_beforeInitialize, (callback, key) => + callback?.apply(@) + ) + + afterInitialize: -> + _.each(@_afterInitialize, (callback, key) => + callback?.apply(@) + ) + + beforeRemove: -> + _.each(@_beforeRemove, (callback, key) => + callback?.apply(@) + ) + + # Defaults Events + # ========================================================== + events: + "click .destroy" : "destroy" + "click div.pagination a" : "pagination" + + allowAction: (element) -> + message = element.attr("data-confirm") || element.attr("confirm") + !message || confirm(message) + + destroy: (e, options = {}) -> + e.preventDefault() + + if @model? + link = $(e.currentTarget) + return false unless @allowAction(link) + + @model.destroy(options) + @remove() + + save: (e, options = {}) -> + e.preventDefault() + e.stopPropagation() + + form = $(e.currentTarget) + _model = options.model || @model + + options = _.extend( + success: (model) => + window.location.hash = "/#{_model.id}" + error: (model, jqXHR) => + @renderErrors( model, $.parseJSON( jqXHR.responseText ).errors ) + + options) + + if _model.isValid() is true + return false unless @allowAction(form) + + settings = + success: (model, jqXHR) => + window.router._editedModels = [] + _model.resetRelations(jqXHR) + _model.allValuesSeted = true + model.trigger("afterSave", model, jqXHR) + options.success(model, jqXHR) + error: options.error + + if !(_model.collection?) and @collection? + @collection.create _model, settings + else _model.save null, settings + + else @renderErrors(_model, _model.errors, form) + + update: (e, options = {}) -> + e.preventDefault() + e.stopPropagation() + + form = $(e.currentTarget) + _model = options.model || @model + + options = _.extend( + success : (model) => + window.location.hash = "/#{_model.id}" + error: (model, jqXHR) => + @renderErrors( model, $.parseJSON( jqXHR.responseText ).errors, form ) + + options) + + if _model.isValid() is true + return false unless @allowAction(form) + _model.save(null, + success: (model, jqXHR) => + window.router._editedModels = [] + _model.resetRelations(jqXHR) + _model.allValuesSeted = true + model.trigger("afterSave", model, jqXHR) + options.success(model, jqXHR) + error: options.error + ) + else @renderErrors(_model, _model.errors) + + # Progress bar + # ========================================================== + renderProgress: (schedule_id, callback) -> + <%= js_app_name %>.Helpers.renderProgress(schedule_id, callback) + + # Errors + # ========================================================== + renderErrors: (model, errors, alertsContainer = false) -> + alertsContainer = false if _.isEmpty alertsContainer + fullErrors = {} + + if errors? + _.each(errors, (messages, key) => + if model? + name = model.humanAttributeName(key) + else + alertsContainer ||= "#alerts_container" + name = @t("activerecord.attributes.#{key}") + + if _.isString messages + (fullErrors.messages ||= []).push({name: "", message: messages}) + else + for message in messages + name = "" if name is "base" + (fullErrors.messages ||= []).push({name: name, message: message}) + ) + else + alertsContainer ||= "#alerts_container" + (fullErrors.messages ||= []).push({name: "", message: @t("errors.default")}) + + <%= js_app_name %>.Helpers.renderError(fullErrors, alertsContainer) + + # Remove Callbacks in beforeRemove() function if needed + # ========================================================== + remove: -> + @beforeRemove() + super() + + delegateEvents: (events) -> + # Cached regex to split keys for `delegate`. + eventSplitter = /^(\S+)\s*(.*)$/ + + return if (!(events || (events = @events))) + events = events.call(this) if (_.isFunction(events)) + + waitingProxy = (func, thisObject) -> + (e) -> + trigger_object = $(e.currentTarget) + waiting = trigger_object.is(".disabled") + + disabled_object = $("a[href=\"#\"], input[type=\"submit\"].btn") + + if waiting then e.preventDefault() + else + trigger_object.addClass("disabled") + disabled_object.addClass("disabled") + + enableFunc = -> + trigger_object.removeClass("disabled") + disabled_object.removeClass("disabled") + + $.when(func.apply(thisObject, arguments)).then(enableFunc, enableFunc) + + $(@el).unbind(".delegateEvents#{@cid}") + + for key of events + method = this[events[key]] + throw new Error("Event #{events[key]} does not exist") unless method + + match = key.match(eventSplitter) + eventName = match[1] + selector = match[2] + method = waitingProxy(method, this) + eventName += ".delegateEvents#{@cid}" + + if selector is '' then $(@el).bind(eventName, method) + else $(@el).delegate(selector, eventName, method) diff --git a/lib/generators/backbone/install/templates/config/locales/es-MX.coffee b/lib/generators/backbone/install/templates/config/locales/es-MX.coffee new file mode 100644 index 0000000..f915121 --- /dev/null +++ b/lib/generators/backbone/install/templates/config/locales/es-MX.coffee @@ -0,0 +1,37 @@ +<%= js_app_name %>.Locales["es-MX"] = + errorsMessages: + inclusion: "no está incluído en la lista" + exclusion: "está reservado" + invalid: "es inválido" + record_invalid: "es inválido" + invalid_date: "es una fecha inválida" + confirmation: "no coincide con la confirmación" + blank: "no puede estar en blanco" + empty: "no puede estar vacío" + not_a_number: "no es un número" + taken: "ya ha sido tomado" + less_than: "debe ser menor que %{count}" + less_than_or_equal_to: "debe ser menor o igual que %{count}" + greater_than: "debe ser mayor que %{count}" + greater_than_or_equal_to: "debe ser mayor o igual que %{count}" + too_short: + one: "es demasiado corto (mínimo 1 caracter)" + other: "es demasiado corto (mínimo %{count} caracteres)" + too_long: + one: "es demasiado largo (máximo 1 caracter)" + other: "es demasiado largo (máximo %{count} caracteres)" + equal_to: "debe ser igual a %{count}" + wrong_length: + one: "longitud errónea (debe ser de 1 caracter)" + other: "longitud errónea (debe ser de %{count} caracteres)" + accepted: "debe ser aceptado" + even: "debe ser un número par" + odd: "debe ser un número non" + invalid_email: "no es una dirección de correo electrónico válida" + + activerecord: + models: {} # Human Model Name + + attributes: + model: {} # Model Attributes + diff --git a/lib/generators/backbone/install/templates/config/string.coffee b/lib/generators/backbone/install/templates/config/string.coffee new file mode 100644 index 0000000..e977fb6 --- /dev/null +++ b/lib/generators/backbone/install/templates/config/string.coffee @@ -0,0 +1,21 @@ +String.prototype.toUnderscore = () -> + @replace( /([A-Z])/g, ($1) -> "_"+$1.toLowerCase() ). + replace( /^_/, '' ) + +String.prototype.toCamelize = (type) -> + value = @replace(/(_| )/g, "-"). + replace(/(\-[a-z])/g, ($1) -> $1.toUpperCase().replace('-','') ). + replace( /^([a-z])/g, ($1) -> $1.toUpperCase() ) + + switch type + when "lower" then value = value.replace( /^([A-Z])/g, ($1) -> $1.toLowerCase() ) + else value + + value + +String.prototype.supplant = (o) -> + @replace(/\%{([^{}]*)}/g, + (a, b) -> + r = o[b] + if (typeof r is 'string' || typeof r is 'number') then r else a + ) diff --git a/lib/generators/backbone/install/templates/helpers.coffee b/lib/generators/backbone/install/templates/helpers.coffee new file mode 100644 index 0000000..7d761f4 --- /dev/null +++ b/lib/generators/backbone/install/templates/helpers.coffee @@ -0,0 +1,95 @@ +<%= js_app_name %>.Helpers = + + jsonData: (url, params = {}) -> + return if !url + responseDate = null + $.ajax( + url: url + async: false + dataType: "json" + data: params + beforeSend: (xhr) -> + token = $('meta[name="csrf-token"]').attr('content') + xhr.setRequestHeader('X-CSRF-Token', token) if token + success: (data, textStatus, jqXHR) -> responseDate = data + ) + responseDate + + jsonCallback: (url, callback, params = {}) -> + $.ajax + url: url + dataType: "json" + data: params + beforeSend: (xhr) -> + token = $('meta[name="csrf-token"]').attr('content') + xhr.setRequestHeader('X-CSRF-Token', token) if token + success: callback + + ajax: (options = {}) -> + return if !options.url + + complete = options.complete + options.complete = (jqXHR, textStatus) -> + <%= js_app_name %>.Helpers.showSubmitted() + + complete?() + + settings = + type: "GET" + data: {} + dataType: "json" + beforeSend: (xhr) -> + token = $('meta[name="csrf-token"]').attr('content') + xhr.setRequestHeader('X-CSRF-Token', token) if token + + _.extend settings, options + + $.ajax settings + + # Renders For Alers Messages + renderWarning: (data, alertsContainer = "#alerts_container") -> + $(alertsContainer).prepend( $("#backboneWarningAlert").tmpl(data) ) + + renderError: (data, alertsContainer = false) -> + $('.alert-message.error').remove() + + if alertsContainer is false then container = $(".columns form:first") + else if _.isString alertsContainer then container = $(alertsContainer) + else container = alertsContainer + + if container.offset()? + container.prepend( $("#backboneErrorAlert").tmpl(data) ) + $('html, body').animate({ scrollTop: container.offset().top - 45 }, 'slow') + else + $("#alerts_container").prepend( $("#backboneErrorAlert").tmpl(data) ) + + renderSuccess: (data, alertsContainer = "#alerts_container") -> + $(alertsContainer).prepend( $("#backboneSuccessAlert").tmpl(data) ) + + renderInfo: (data, alertsContainer = "#alerts_container") -> + $(alertsContainer).prepend( $("#backboneInfoAlert").tmpl(data) ) + + renderProgress: (schedule_id, callback) -> + if schedule_id? + $(".alert-message").remove() + progress = $("#progress_bar") + schedule = null + + progress.progressbar + value: 0 + complete: -> + clearInterval interval + callback?(schedule) + + interval = setInterval -> + $.getJSON "schedules/#{schedule_id}", (data) -> + schedule = data + count = (schedule.progress * 100) / schedule.total + progress.progressbar "option", "value", count + , 1500 + + showSubmitted: -> + $('.submitted').show() + $('.submitted').removeClass('submitted') + + diff --git a/lib/generators/backbone/install/templates/modules/ajax_requests.coffee b/lib/generators/backbone/install/templates/modules/ajax_requests.coffee new file mode 100644 index 0000000..b6eaf80 --- /dev/null +++ b/lib/generators/backbone/install/templates/modules/ajax_requests.coffee @@ -0,0 +1,74 @@ +Modules.AjaxRequests = + + instanceMethods: + + getJSON: (url, params = {}) -> + <%= js_app_name %>.Helpers.jsonData(url, params) + + doAjax: (settings = {}) -> + settings = _.extend { + error: (jqXHR) => + if /^\.Helpers.ajax(settings) + + # Remote Forms + remoteForm: (e, settings = {}) -> + e.preventDefault() + e.stopPropagation() + + form = $(e.currentTarget) + + if @isValidForm(form) is false + @renderErrors(null, @_formErrors) + else + url = form.prop("action") + + if url? + return false unless @allowAction(form) + type = form.prop("method") || "GET" + data = form.serializeArray() || {} + files = $(":file", form) + + if form.prop("enctype") is "multipart/form-data" and files.length > 0 + settings = _.extend { files: files, iframe: true, processData: false }, settings + + settings = _.extend { url: url, type: type, data: data }, settings + @doAjax settings + + _validateForm: (form) -> + return false unless form.is("form") + + attrs = {} + validations = {} + inputs = form.find("input[data-validations]") + + inputs.each -> + input = $(this) + value = input.val() + name = input.attr("name").replace("[", ".").replace("]", "") + validates = {} + arrayValidations = input.attr("data-validations").split(" ") + + for validation in arrayValidations + validates[validation] = true + + attrs[name] = value + validations[name] = validates + @_formErrors = @_validates(attrs, validations) + + isValidForm: (form) -> + result = @_validateForm(form) + return false if result is false + _.isEmpty result + + _getInputName: (el) -> + nestedName = el.attr("name").replace("[]", "") + nesteds = nestedName.split("[") + for nested, i in nesteds + nesteds[i] = nested.replace("]", "") + nesteds[nesteds.length-1] diff --git a/lib/generators/backbone/install/templates/modules/eip.coffee b/lib/generators/backbone/install/templates/modules/eip.coffee new file mode 100644 index 0000000..5016083 --- /dev/null +++ b/lib/generators/backbone/install/templates/modules/eip.coffee @@ -0,0 +1,226 @@ +Modules.EIP = (options = {}) -> + + # Callbacks + # ========================================================== + beforeInitialize: -> + @eipBuildFunctions() + _.extend @events, @eipEvents() + + afterInitialize: -> + @eipBindCallbacks() + @eipSetDefault() + + beforeRemove: -> + @eipUnbindCallbacks() + + instanceMethods: () -> + + options.eipNodes ||= {} + + _.extend options, + + # Events For EIP + # ================================================================ + eipEvents: -> + events = { + "click .eip-btn" : "eipPrintListBtn" + "click .eip-node" : "eipShowNode" + "click .delete-eip" : "eipDestroyNested" + "click .eip-delete-node" : "eipDestroyNested" + } + _.each(@eipNodes, (node, key) -> + events["click .add_#{key}"] = "eip_create_#{key}".toCamelize("lower") + ) + events + + # Print Functions For EIP + # ================================================================ + + # Print List + eipPrintList: (nodeName, eipAnimate = true) -> + container = this.$("#eip-container") + + if eipAnimate + $('html, body').animate({ scrollTop: container.offset().top }, 'slow') + + if nodeName? + eipListTemplate = @eipGetListTemplate(nodeName) + collection = @eipGetCollection(nodeName) + + container.html eipListTemplate + + collection.each(@eipPrintNode, @) + + # Print Node And Form + eipPrintNodeWithForm: (nested_model) -> + @eipPrintNode nested_model, true + @eipPrintForm nested_model + + # Print Node + eipPrintNode: (nested_model, nodeActive = false) -> + dom = @eipGetNodeTemplate(nested_model.paramRoot, nested_model) + + @eipBeforeRenderNode(dom, nested_model) + + if nested_model.eip_node_dom? and nested_model.eip_node_dom.is(":visible") + nested_model.eip_node_dom.replaceWith dom + else + @$("#eip-list").append dom + nested_model.prepareToEdit() + nested_model.unbind("change", @eipPrintNode, @) + nested_model.bind("change", @eipPrintNode, @) + + nested_model.eip_node_dom = dom + $(dom).data('nested_model', nested_model) + + @eipSetNodeActive dom if nodeActive + + # Print Form + eipPrintForm: (nested_model) -> + dom = @eipGetFormTemplate(nested_model.paramRoot, nested_model) + container = @$("#eip-form") + + @eipBeforeRenderForm(dom, nested_model) + + container.html dom.backboneLink(@model) + $('html, body').animate({ scrollTop: container.offset().top }, 'slow') + + @eipAfterRenderForm(dom, nested_model) + + nested_model.eip_form_dom = dom + dom.find(".delete-eip").data('nested_model', nested_model) + + eipShowNode: (e) -> + unless $(e.target).is("a.eip-delete-node") + node = $(e.currentTarget) + @eipSetNodeActive node + nested_model = node.data('nested_model') + @eipPrintForm nested_model + + eipSetNodeActive: (node) -> + @$('.eip-node').removeClass('active') + node.addClass('active') + + eipPrintListBtn: (e) -> + e.preventDefault() + link = $(e.currentTarget) + + if link.is(".info") + @eipPrintList null # Just run the animation + else + nodeName = @eipGetNodeName(link) + @eipPrintList nodeName + @eipSetBtnListActive(link) + + # Destroy Functions For EIP + # ================================================================ + eipDestroyNested: (e) -> + e.preventDefault() + + dom = $(e.currentTarget) + model_object = dom.data('nested_model') + + if model_object is undefined or model_object is null + model_object = dom.parents('.eip-node').data('nested_model') + + unless model_object.isNew() + msg = $(e.currentTarget).attr("data-confirm") + if msg? and !confirm(msg) then return false + + model_object.destroy({ + error: (model, jqXHR) => + @renderErrors( model, $.parseJSON( jqXHR.responseText ) ) + }) + + eipRemoveNestedFromDom: (nested_model) -> + nested_model.eip_form_dom?.remove?() + nested_model.eip_node_dom?.remove?() + + # Get Templates For EIP + # ================================================================ + eipGetNodeTemplate: (nodeName, nested_model) -> + templateName = "#{nodeName}_template".toCamelize() + @["eipNode#{templateName}"](nested_model.toJSON(true)) + + eipGetFormTemplate: (nodeName, nested_model) -> + templateName = "#{nodeName}_template".toCamelize() + @["eipForm#{templateName}"](nested_model.toJSON(true)) + + eipGetListTemplate: (nodeName) -> + templateName = "#{nodeName}_template".toCamelize() + @["eipList#{templateName}"]() + + eipGetCollection: (nodeName) -> + collection = @eipNodes[nodeName].collection + @model[collection] + + + # Build Functions For EIP + # ================================================================ + eipBuildFunctions: () -> + _.each(@eipNodes, (node, key) => + templateName = "#{key}_template".toCamelize() + functionName = "eip_create_#{key}".toCamelize("lower") + + # Templates + @["eipNode#{templateName}"] = (data) -> $("#eipNode#{templateName}").tmpl(data) + @["eipForm#{templateName}"] = (data) -> $("#eipForm#{templateName}").tmpl(data) + @["eipList#{templateName}"] = (data) -> $("#eipList#{templateName}").tmpl(data) + + # Functions + @[functionName] = (e) -> + e.preventDefault() + @eipGetCollection(key).add() if @eipBeforeAddNested() is true + ) + + # Bind Add Callback For Collections + # ================================================================ + eipBindCallbacks: -> + _.each(@eipNodes, (node, key) => + @eipGetCollection(key).bind("add", @eipPrintNodeWithForm, @) + @eipGetCollection(key).bind("destroy", @eipRemoveNestedFromDom, @) + @eipGetCollection(key).bind("error", @renderErrors, @) + ) + eipUnbindCallbacks: -> + _.each(@eipNodes, (node, key) => + @eipGetCollection(key).unbind("add", @eipPrintNodeWithForm, @) + @eipGetCollection(key).unbind("destroy", @eipRemoveNestedFromDom, @) + @eipGetCollection(key).unbind("error", @renderErrors, @) + ) + + # Other Functions + # ================================================================ + eipGetNodeName: (link) -> + name = null + _.each(@eipNodes, (node, key) => + name = key if link.is(".eip_#{key}") + ) + name + + eipSetDefault: () -> + if @eipDefault? + _this = @ + $("a.eip_#{@eipDefault}").livequery(() -> + link = $(this) + _this.eipSetBtnListActive(link) + _this.eipPrintList(_this.eipDefault, false) + ) + + eipSetBtnListActive: (link) -> + @$("#eip-buttons .eip-btn").removeClass("info") + link.addClass("info") + + eipBeforeRenderForm: -> + eipAfterRenderForm: -> + + eipBeforeRenderNode: -> + + eipBeforeAddNested: -> true + + + # Class Methods + # ================================================================ + + #classMethods: + + #class_method: -> diff --git a/lib/generators/backbone/install/templates/modules/i18n.coffee b/lib/generators/backbone/install/templates/modules/i18n.coffee new file mode 100644 index 0000000..1988980 --- /dev/null +++ b/lib/generators/backbone/install/templates/modules/i18n.coffee @@ -0,0 +1,31 @@ +Modules.I18n = + + locale : <%= js_app_name %>.Config.locale() + + messages : -> + <%= js_app_name %>.Locales[@locale] + + t: (route = "", options = {}) -> + messages = @messages() || {} + keys = route.split(".") + + for key in keys + if messages[key]? then messages = messages[key] + else messages = key + + messages = messages.supplant(options) unless _.isEmpty(options) + messages + + instanceMethods: + + t: (route = "", options = {}) -> + Modules.I18n.t route, options + + # Falsh Messages + flash: (type, messages = "", alertsContainer = "#alerts_container") -> + if _.include <%= js_app_name %>.Config.flashes, type + if _.isString messages + messages = {messages: [{message: messages}]} + + flashTemplate = "render_#{type}".toCamelize("lower") + <%= js_app_name %>.Helpers[flashTemplate]? messages, alertsContainer diff --git a/lib/generators/backbone/install/templates/modules/inheritance.coffee b/lib/generators/backbone/install/templates/modules/inheritance.coffee new file mode 100644 index 0000000..92cd261 --- /dev/null +++ b/lib/generators/backbone/install/templates/modules/inheritance.coffee @@ -0,0 +1,33 @@ +Modules.Inheritance = + + include: (obj) -> + inheritanceKeywords = ['included', 'beforeInitialize', 'afterInitialize', 'beforeRemove'] + + classMethods = obj.classMethods || {} + classMethods = classMethods() if _.isFunction classMethods + + for key, value of classMethods when key not in inheritanceKeywords + @[key] = value + + instanceMethods = obj.instanceMethods || {} + instanceMethods = instanceMethods() if _.isFunction instanceMethods + + for key, value of instanceMethods when key not in inheritanceKeywords + # Assign properties to the prototype + @::[key] = value + + obj.included?.apply(@) + + if obj.beforeInitialize? + @::_beforeInitialize ||= [] + @::_beforeInitialize.push obj.beforeInitialize + + if obj.afterInitialize? + @::_afterInitialize ||= [] + @::_afterInitialize.push obj.afterInitialize + + if obj.beforeRemove? + @::_beforeRemove ||= [] + @::_beforeRemove.push obj.beforeRemove + + this diff --git a/lib/generators/backbone/install/templates/modules/number_helper.coffee b/lib/generators/backbone/install/templates/modules/number_helper.coffee new file mode 100644 index 0000000..428ef43 --- /dev/null +++ b/lib/generators/backbone/install/templates/modules/number_helper.coffee @@ -0,0 +1,9 @@ +Modules.NumberHelper = + + instanceMethods: + + numberToCurrency: (number) -> + $.formatNumber( + parseFloat(number, 10) + format: "$ #,###.00", locale:"us" + ) diff --git a/lib/generators/backbone/install/templates/modules/pagination.coffee b/lib/generators/backbone/install/templates/modules/pagination.coffee new file mode 100644 index 0000000..53e10d1 --- /dev/null +++ b/lib/generators/backbone/install/templates/modules/pagination.coffee @@ -0,0 +1,52 @@ +Modules.Pagination = + + instanceMethods: + + pagination: (e, collection) -> + e.preventDefault() + link = $(e.currentTarget) + li = link.closest("li") + container = link.closest("#pagination-container") + + unless li.is(".active, .prev.disabled, .next.disabled") + unless container.attr("data-waiting") + container.attr("data-waiting", true) + href = link.attr("href") + <%= js_app_name %>.Helpers.jsonCallback(href, (data) -> + collection.reset data + container.removeAttr("data-waiting") + ) + + renderPagination: (collection) -> + # Clear Pagination Container + container = "#pagination-container" + @$(container).html "" + + if collection.pagination? and !collection.isEmpty() + pagination = _.clone(collection.pagination || {}) + + if pagination.total_pages > 1 + pagination.resources_path = pagination.path || collection.url + pagination.params = $.param(pagination.params) unless (_.isEmpty pagination.params) + pagination.pages = [] + + if (pagination.current_page > 1) + prevNumber = (pagination.current_page - 1) + pagination.paginatePrev = "#{pagination.resources_path}?page=#{prevNumber}" + pagination.paginatePrev += "&#{pagination.params}" unless (_.isEmpty pagination.params) + + if (pagination.current_page < pagination.total_pages) + nextNumber = (pagination.current_page + 1) + pagination.paginateNext = "#{pagination.resources_path}?page=#{nextNumber}" + pagination.paginateNext += "&#{pagination.params}" unless (_.isEmpty pagination.params) + + # builder pages + for number in [1..pagination.total_pages] + page = {} + page.liKlass = "active" if pagination.current_page is number + page.text = number + page.path = "#{pagination.resources_path}?page=#{number}" + page.path += "&#{pagination.params}" unless (_.isEmpty pagination.params) + pagination.pages.push(page) + + @$(container).html $("#backboneTemplatesPagination").tmpl(pagination) diff --git a/lib/generators/backbone/install/templates/modules/validations.coffee b/lib/generators/backbone/install/templates/modules/validations.coffee new file mode 100644 index 0000000..c81fc6d --- /dev/null +++ b/lib/generators/backbone/install/templates/modules/validations.coffee @@ -0,0 +1,74 @@ +Modules.Validations = + + instanceMethods: + + _validates: (attrs, validates = {}) -> + resultMessage = {} + messages = Modules.I18n.messages()?.errorsMessages || {} + + # Each for attributes of the model + _.each(attrs, (attrValue, attrKey) => + + for key, validation of validates[attrKey] + appliedValidations = true + action = validation.on + + appliedValidations = false if validates[attrKey].if is false + + if _.isFunction @isNew + if @isNew() is true and action is "update" + appliedValidations = false + + if @isNew() is false and action is "create" + appliedValidations = false + + if appliedValidations is true + + isEmpty = _.isEmpty( $.trim( attrValue ) ) + + + switch key + when "presence" + if isEmpty + (resultMessage[attrKey] ||= []).push(messages.blank) + + when "numericality" + if isEmpty is false and /^(\-)?\d+(\.\d+)?$/.test( attrValue ) is false + (resultMessage[attrKey] ||= []).push(messages.not_a_number) + + when "email" + if isEmpty is false and /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/.test( attrValue ) is false + (resultMessage[attrKey] ||= []).push(messages.invalid_email) + + when "rfc" + if isEmpty is false and /^([A-Z|a-z|&]{3}\d{2}((0[1-9]|1[012])(0[1-9]|1\d|2[0-8])|(0[13456789]|1[012])(29|30)|(0[13578]|1[02])31)|([02468][048]|[13579][26])0229)(\w{2})([A-Z|a-z|0-9])$|^([A-Z|a-z]{4}\d{2}((0[1-9]|1[012])(0[1-9]|1\d|2[0-8])|(0[13456789]|1[012])(29|30)|(0[13578]|1[02])31)|([02468][048]|[13579][26])0229)((\w{2})([A-Z|a-z|0-9])){0,3}$/.test( attrValue ) is false + (resultMessage[attrKey] ||= []).push(messages.invalid) + + when "zip_code" + if isEmpty is false and /^\d{5}$/.test( attrValue ) is false + (resultMessage[attrKey] ||= []).push(messages.invalid) + + when "length" + if isEmpty is false + min = validation.min + max = validation.max + attrLength = (attrValue || "").length + + if (min? and attrLength < min) + if min is 1 then lengthMessage = "one" else lengthMessage = "other" + message = (messages.too_short?[lengthMessage] || "").replace("%{count}", min) + (resultMessage[attrKey] ||= []).push(message) + + else if (max? and attrLength > max) + if max is 1 then lengthMessage = "one" else lengthMessage = "other" + message = (messages.too_long?[lengthMessage] || "").replace("%{count}", max) + (resultMessage[attrKey] ||= []).push(message) + + when "equalTo" + unless attrValue is @get(validation) + message = (messages.equal_to || "").replace("%{count}", @humanAttributeName(validation)) + (resultMessage[attrKey] ||= []).push(message) + + else false + ) + resultMessage diff --git a/lib/generators/backbone/model/model_generator.rb b/lib/generators/backbone/model/model_generator.rb index ee1d487..e934849 100644 --- a/lib/generators/backbone/model/model_generator.rb +++ b/lib/generators/backbone/model/model_generator.rb @@ -4,16 +4,21 @@ module Backbone module Generators class ModelGenerator < Rails::Generators::NamedBase include Backbone::Generators::ResourceHelpers - + source_root File.expand_path("../templates", __FILE__) - desc "This generator creates a backbone model" - + desc "This generator creates a backbone model and rails model with migration" + argument :attributes, :type => :array, :default => [], :banner => "field:type field:type" - + def create_backbone_model template "model.coffee", "#{backbone_path}/models/#{file_name}.js.coffee" end - + + def create_rails_model + attrs = attributes.collect{|a| "#{a.name}:#{a.type}" }.join(" ") + generate "model #{name} #{attrs}" + end + end end -end \ No newline at end of file +end diff --git a/lib/generators/backbone/model/templates/model.coffee b/lib/generators/backbone/model/templates/model.coffee index 930a0f8..fa17487 100644 --- a/lib/generators/backbone/model/templates/model.coffee +++ b/lib/generators/backbone/model/templates/model.coffee @@ -1,11 +1,22 @@ -class <%= model_namespace %> extends Backbone.Model - paramRoot: '<%= singular_table_name %>' +class <%= model_namespace %> extends <%= js_app_name %>.Models.BaseModel + paramRoot: '<%= singular_name %>' + urlRoot: '<%= plural_name %>' defaults: <% attributes.each do |attribute| -%> <%= attribute.name %>: null <% end -%> -class <%= collection_namespace %>Collection extends Backbone.Collection - model: <%= model_namespace %> - url: '<%= route_url %>' + validate: (attrs) -> + return @validates(attrs, { + # example + # : + # presence: true + }) + + @paramRoot : '<%= singular_name %>' + @urlRoot : '<%= plural_name %>' + +class <%= collection_namespace %>Collection extends <%= js_app_name %>.Collections.BaseCollection + model : <%= model_namespace %> + url : '<%= route_url %>' diff --git a/lib/generators/backbone/resource_helpers.rb b/lib/generators/backbone/resource_helpers.rb index a5e985d..f1a6d6f 100644 --- a/lib/generators/backbone/resource_helpers.rb +++ b/lib/generators/backbone/resource_helpers.rb @@ -1,43 +1,63 @@ module Backbone module Generators module ResourceHelpers - + def backbone_path "app/assets/javascripts/backbone" end - + + def backbone_tmpl_path + "app/views" + end + + def controllers_path + "app/controllers" + end + def model_namespace [js_app_name, "Models", class_name].join(".") end - + + def classify_model_name + singular_model_name.camelize + end + + def human_attribute_translate(name) + "#{classify_model_name}.human_attribute_name(:#{name})" + end + def singular_model_name uncapitalize singular_name.camelize end - + def plural_model_name uncapitalize(plural_name.camelize) end - + def collection_namespace [js_app_name, "Collections", plural_name.camelize].join(".") end - + def view_namespace [js_app_name, "Views", plural_name.camelize].join(".") end - + def router_namespace [js_app_name, "Routers", plural_name.camelize].join(".") end - + def jst(action) "backbone/templates/#{plural_name}/#{action}" end - + + def tmpl(action) + "backbone_templates_#{plural_name}_#{action}".camelize(:lower) + end + def js_app_name application_name.camelize end - + def application_name if defined?(Rails) && Rails.application Rails.application.class.name.split('::').first @@ -45,11 +65,11 @@ def application_name "application" end end - + def uncapitalize(str) str[0, 1].downcase + str[1..-1] end - + end end -end \ No newline at end of file +end diff --git a/lib/generators/backbone/router/router_generator.rb b/lib/generators/backbone/router/router_generator.rb index 2f5c045..161c8ad 100644 --- a/lib/generators/backbone/router/router_generator.rb +++ b/lib/generators/backbone/router/router_generator.rb @@ -4,17 +4,17 @@ module Backbone module Generators class RouterGenerator < Rails::Generators::NamedBase include Backbone::Generators::ResourceHelpers - + source_root File.expand_path("../templates", __FILE__) desc "This generator creates a backbone router with views and templates for the provided actions" - + argument :actions, :type => :array, :default => [], :banner => "action action" - + RESERVED_JS_WORDS = %W{ - break case catch continue debugger default delete do else finally for - function if in instanceof new return switch this throw try typeof var void while with + break case catch continue debugger default delete do else finally for + function if in instanceof new return switch this throw try typeof var void while with } - + def validate_no_reserved_words actions.each do |action| if RESERVED_JS_WORDS.include? action @@ -23,22 +23,22 @@ def validate_no_reserved_words end end end - - def create_router_files + + def create_router_files template 'router.coffee', File.join(backbone_path, "routers", class_path, "#{file_name}_router.js.coffee") end - + def create_view_files actions.each do |action| @action = action @view_path = File.join(backbone_path, "views", plural_name, "#{action}_view.js.coffee") - @jst_path = File.join(backbone_path,"templates", plural_name, "#{action}.jst.ejs") - + @tmpl_path = File.join(backbone_tmpl_path, plural_name, "_#{action}.html.haml") + template "view.coffee", @view_path - template "template.jst", @jst_path + template "template.haml", @tmpl_path end end end end -end \ No newline at end of file +end diff --git a/lib/generators/backbone/router/templates/router.coffee b/lib/generators/backbone/router/templates/router.coffee index f250a94..40798e0 100644 --- a/lib/generators/backbone/router/templates/router.coffee +++ b/lib/generators/backbone/router/templates/router.coffee @@ -8,7 +8,7 @@ class <%= router_namespace %>Router extends Backbone.Router <% actions.each do |action| -%> <%= action %>: -> - @view = new <%= "#{view_namespace}.#{action.camelize}View()" %> - $("#<%= plural_name %>").html(@view.render().el) + @<%= action %>_view = new <%= "#{view_namespace}.#{action.camelize}View()" %> + $("#<%= plural_name %>").html(@<%= action %>_view.render().el) <% end -%> diff --git a/lib/generators/backbone/router/templates/template.haml b/lib/generators/backbone/router/templates/template.haml new file mode 100644 index 0000000..5ebb621 --- /dev/null +++ b/lib/generators/backbone/router/templates/template.haml @@ -0,0 +1,2 @@ +%h1 <%= class_name %>#<%= @action %> +%p Find me in <%= @tmpl_path %> diff --git a/lib/generators/backbone/router/templates/template.jst b/lib/generators/backbone/router/templates/template.jst deleted file mode 100644 index d28b30f..0000000 --- a/lib/generators/backbone/router/templates/template.jst +++ /dev/null @@ -1,2 +0,0 @@ -

<%= class_name %>#<%= @action %>

-

Find me in <%= @jst_path %>

\ No newline at end of file diff --git a/lib/generators/backbone/router/templates/view.coffee b/lib/generators/backbone/router/templates/view.coffee index 1059706..aa4e0aa 100644 --- a/lib/generators/backbone/router/templates/view.coffee +++ b/lib/generators/backbone/router/templates/view.coffee @@ -1,8 +1,8 @@ <%= view_namespace %> ||= {} class <%= view_namespace %>.<%= @action.camelize %>View extends Backbone.View - template: JST["<%= jst @action %>"] + template: () -> $("#<%= tmpl @action %>").tmpl() render: -> $(@el).html(@template()) - return this + @ diff --git a/lib/generators/backbone/scaffold/scaffold_generator.rb b/lib/generators/backbone/scaffold/scaffold_generator.rb index 4ea2adc..dd585c5 100644 --- a/lib/generators/backbone/scaffold/scaffold_generator.rb +++ b/lib/generators/backbone/scaffold/scaffold_generator.rb @@ -3,29 +3,41 @@ module Backbone module Generators class ScaffoldGenerator < ModelGenerator - + source_root File.expand_path("../templates", __FILE__) desc "This generator creates the client side crud scaffolding" - - def create_router_files + + def inject_into_routes + inject_into_file "config/routes.rb", :after => "Application.routes.draw do\n" do + "\n resources :#{plural_name}\n" + end + end + + def create_router_files template 'router.coffee', File.join(backbone_path, "routers", class_path, "#{plural_name}_router.js.coffee") end - + def create_view_files available_views.each do |view| template "views/#{view}_view.coffee", File.join(backbone_path, "views", plural_name, "#{view}_view.js.coffee") - template "templates/#{view}.jst", File.join(backbone_path, "templates", plural_name, "#{view}.jst.ejs") + template "templates/#{view}.haml", File.join(backbone_tmpl_path, plural_name, "_#{view}.html.haml") end - + template "views/model_view.coffee", File.join(backbone_path, "views", plural_name, "#{singular_name}_view.js.coffee") - template "templates/model.jst", File.join(backbone_path, "templates", plural_name, "#{singular_name}.jst.ejs") + template "templates/model.haml", File.join(backbone_tmpl_path, plural_name, "_#{singular_name}.html.haml") + template "templates/form.haml", File.join(backbone_tmpl_path, plural_name, "_form.html.haml") + template "index.haml", File.join(backbone_tmpl_path, plural_name, "index.html.haml") + end + + def create_rails_controller + template "controller.rb", File.join(controllers_path, "#{plural_name}_controller.rb") end - + protected def available_views %w(index show new edit) end - + end end -end \ No newline at end of file +end diff --git a/lib/generators/backbone/scaffold/templates/controller.rb b/lib/generators/backbone/scaffold/templates/controller.rb new file mode 100644 index 0000000..a3dbb43 --- /dev/null +++ b/lib/generators/backbone/scaffold/templates/controller.rb @@ -0,0 +1,6 @@ +class <%= plural_name.camelize %>Controller < InheritedResources::Base + include BackboneResponses + + respond_to :json +end + diff --git a/lib/generators/backbone/scaffold/templates/index.haml b/lib/generators/backbone/scaffold/templates/index.haml new file mode 100644 index 0000000..641bf91 --- /dev/null +++ b/lib/generators/backbone/scaffold/templates/index.haml @@ -0,0 +1,14 @@ +#<%= plural_name %> + +:javascript + $(function() { + window.router = new <%= router_namespace %>Router({ <%= plural_model_name %>: #{ @<%= plural_name %>.to_json.html_safe } }); + Backbone.history.start(); + }); + +- content_for :templates do + = script_template("index") + = script_template("new") + = script_template("show") + = script_template("edit") + = script_template("<%= singular_name %>") diff --git a/lib/generators/backbone/scaffold/templates/model.coffee b/lib/generators/backbone/scaffold/templates/model.coffee index 930a0f8..fa17487 100644 --- a/lib/generators/backbone/scaffold/templates/model.coffee +++ b/lib/generators/backbone/scaffold/templates/model.coffee @@ -1,11 +1,22 @@ -class <%= model_namespace %> extends Backbone.Model - paramRoot: '<%= singular_table_name %>' +class <%= model_namespace %> extends <%= js_app_name %>.Models.BaseModel + paramRoot: '<%= singular_name %>' + urlRoot: '<%= plural_name %>' defaults: <% attributes.each do |attribute| -%> <%= attribute.name %>: null <% end -%> -class <%= collection_namespace %>Collection extends Backbone.Collection - model: <%= model_namespace %> - url: '<%= route_url %>' + validate: (attrs) -> + return @validates(attrs, { + # example + # : + # presence: true + }) + + @paramRoot : '<%= singular_name %>' + @urlRoot : '<%= plural_name %>' + +class <%= collection_namespace %>Collection extends <%= js_app_name %>.Collections.BaseCollection + model : <%= model_namespace %> + url : '<%= route_url %>' diff --git a/lib/generators/backbone/scaffold/templates/router.coffee b/lib/generators/backbone/scaffold/templates/router.coffee index 4320d9f..87cf111 100644 --- a/lib/generators/backbone/scaffold/templates/router.coffee +++ b/lib/generators/backbone/scaffold/templates/router.coffee @@ -1,4 +1,4 @@ -class <%= router_namespace %>Router extends Backbone.Router +class <%= router_namespace %>Router extends <%= js_app_name %>.Routers.BaseRouter initialize: (options) -> @<%= plural_model_name %> = new <%= collection_namespace %>Collection() @<%= plural_model_name %>.reset options.<%= plural_model_name %> @@ -11,21 +11,29 @@ class <%= router_namespace %>Router extends Backbone.Router ".*" : "index" new<%= class_name %>: -> - @view = new <%= "#{view_namespace}.NewView(collection: @#{plural_name})" %> - $("#<%= plural_name %>").html(@view.render().el) + @new_view = new <%= "#{view_namespace}.NewView(collection: @#{plural_model_name})" %> + $("#<%= plural_name %>").html(@new_view.render().el) index: -> - @view = new <%= "#{view_namespace}.IndexView(#{plural_name}: @#{plural_name})" %> - $("#<%= plural_name %>").html(@view.render().el) + @index_view = new <%= "#{view_namespace}.IndexView(#{plural_model_name}: @#{plural_model_name})" %> + $("#<%= plural_name %>").html(@index_view.render().el) show: (id) -> - <%= singular_name %> = @<%= plural_name %>.get(id) + <%= singular_name %> = @<%= plural_model_name %>.get(id) - @view = new <%= "#{view_namespace}.ShowView(model: #{singular_name})" %> - $("#<%= plural_name %>").html(@view.render().el) + if <%= singular_name %>? + <%= singular_name %>.setAllValues() + + @show_view = new <%= "#{view_namespace}.ShowView(model: #{singular_name})" %> + $("#<%= plural_name %>").html(@show_view.render().el) + else @resourceNotFound() edit: (id) -> - <%= singular_name %> = @<%= plural_name %>.get(id) + <%= singular_name %> = @<%= plural_model_name %>.get(id) + + if <%= singular_name %>? + <%= singular_name %>.setAllValues() - @view = new <%= "#{view_namespace}.EditView(model: #{singular_name})" %> - $("#<%= plural_name %>").html(@view.render().el) + @edit_view = new <%= "#{view_namespace}.EditView(model: #{singular_name})" %> + $("#<%= plural_name %>").html(@edit_view.render().el) + else @resourceNotFound() diff --git a/lib/generators/backbone/scaffold/templates/templates/edit.haml b/lib/generators/backbone/scaffold/templates/templates/edit.haml new file mode 100644 index 0000000..4ba791a --- /dev/null +++ b/lib/generators/backbone/scaffold/templates/templates/edit.haml @@ -0,0 +1,11 @@ +.row + .span16.columns + .page-header + %h1 + = t(".title") + ${<%= attributes.first.try(:name) %>} + %ul.tabs + %li + = link_to t("links.back"), "#/index" + += render :partial => "form", :locals => { :form_type => "edit" } diff --git a/lib/generators/backbone/scaffold/templates/templates/edit.jst b/lib/generators/backbone/scaffold/templates/templates/edit.jst deleted file mode 100644 index a21d672..0000000 --- a/lib/generators/backbone/scaffold/templates/templates/edit.jst +++ /dev/null @@ -1,17 +0,0 @@ -

Edit <%= singular_table_name %>

- -
-<% attributes.each do |attribute| -%> -
- - %> > -
- -<% end -%> -
- -
- -
- -Back \ No newline at end of file diff --git a/lib/generators/backbone/scaffold/templates/templates/form.haml b/lib/generators/backbone/scaffold/templates/templates/form.haml new file mode 100644 index 0000000..0432c1e --- /dev/null +++ b/lib/generators/backbone/scaffold/templates/templates/form.haml @@ -0,0 +1,13 @@ +.row + .span16.columns + = form_for <%= classify_model_name %>.new, :url => "", :html => { :id => "#{form_type}_<%= singular_name %>" } do |f| + %fieldset +<% attributes.each do |attribute| -%> + .clearfix + = f.label :<%= attribute.name %> + .input + = f.text_field :<%= attribute.name %>, :value => "${<%= attribute.name %>}", :class => "xlarge" + +<% end -%> + .actions + = f.submit t("helpers.submit.#{form_type}", :model => f.object.class.model_name.human), :class => "btn primary" diff --git a/lib/generators/backbone/scaffold/templates/templates/index.haml b/lib/generators/backbone/scaffold/templates/templates/index.haml new file mode 100644 index 0000000..f740c41 --- /dev/null +++ b/lib/generators/backbone/scaffold/templates/templates/index.haml @@ -0,0 +1,20 @@ +.row + .span16.columns + .page-header + %h1= t(".title") + %ul.tabs + %li + = link_to t("links.new", :resource => <%= classify_model_name %>.model_name.human), "#/new" + +.row + .span16.columns + %table#<%= plural_name %>_table.zebra-striped + %thead + %tr +<% attributes.each do |attribute| -%> + %th= <%= human_attribute_translate(attribute.name) %> +<% end -%> + %th + %tbody + +#pagination-container diff --git a/lib/generators/backbone/scaffold/templates/templates/index.jst b/lib/generators/backbone/scaffold/templates/templates/index.jst deleted file mode 100644 index a18f7f7..0000000 --- a/lib/generators/backbone/scaffold/templates/templates/index.jst +++ /dev/null @@ -1,16 +0,0 @@ -

Listing <%= plural_table_name %>

- - - -<% attributes.each do |attribute| -%> - -<% end -%> - - - - -
<%= attribute.human_name %>
- -
- -New <%= human_name %> \ No newline at end of file diff --git a/lib/generators/backbone/scaffold/templates/templates/model.haml b/lib/generators/backbone/scaffold/templates/templates/model.haml new file mode 100644 index 0000000..e38edad --- /dev/null +++ b/lib/generators/backbone/scaffold/templates/templates/model.haml @@ -0,0 +1,8 @@ +<% attributes.each do |attribute| -%> +%td ${<%= attribute.name %>} +<% end -%> + +%td.links + = link_to t("links.show"), "#/${id}".html_safe, :class => "show" + = link_to t("links.edit"), "#/${id}/edit".html_safe, :class => "edit" + = link_to t("links.destroy"), "#/${id}/destroy".html_safe, :class => "destroy", :confirm => t("links.confirm") diff --git a/lib/generators/backbone/scaffold/templates/templates/model.jst b/lib/generators/backbone/scaffold/templates/templates/model.jst deleted file mode 100644 index 207526a..0000000 --- a/lib/generators/backbone/scaffold/templates/templates/model.jst +++ /dev/null @@ -1,7 +0,0 @@ -<% attributes.each do |attribute| -%> -<%%= <%= attribute.name %> %> -<% end -%> - -Show -Edit -Destroy \ No newline at end of file diff --git a/lib/generators/backbone/scaffold/templates/templates/new.haml b/lib/generators/backbone/scaffold/templates/templates/new.haml new file mode 100644 index 0000000..ddc27ed --- /dev/null +++ b/lib/generators/backbone/scaffold/templates/templates/new.haml @@ -0,0 +1,9 @@ +.row + .span16.columns + .page-header + %h1= t(".title") + %ul.tabs + %li + = link_to t("links.back"), "#/index" + += render :partial => "form", :locals => { :form_type => "new" } diff --git a/lib/generators/backbone/scaffold/templates/templates/new.jst b/lib/generators/backbone/scaffold/templates/templates/new.jst deleted file mode 100644 index fd19bde..0000000 --- a/lib/generators/backbone/scaffold/templates/templates/new.jst +++ /dev/null @@ -1,17 +0,0 @@ -

New <%= singular_table_name %>

- -
-<% attributes.each do |attribute| -%> -
- - %> > -
- -<% end -%> -
- -
- -
- -Back \ No newline at end of file diff --git a/lib/generators/backbone/scaffold/templates/templates/show.haml b/lib/generators/backbone/scaffold/templates/templates/show.haml new file mode 100644 index 0000000..8abd3b6 --- /dev/null +++ b/lib/generators/backbone/scaffold/templates/templates/show.haml @@ -0,0 +1,26 @@ +.row + .span16.columns + .page-header + %h1= t(".title") + %ul.tabs + %li= link_to t("links.back"), "#/index" + %li= link_to t("links.edit"), "#/${id}/edit" + %li= link_to t("links.destroy"), "#/index", :class => "destroy", :confirm => t("links.confirm") + +.row + .span10.columns +<% attributes.each do |attribute| -%> + %p + %strong= <%= human_attribute_translate(attribute.name) %> + ${<%= attribute.name %>} + +<% end -%> + + .span6.columns + .dashboard + .page-header + %h3 + %p + + + diff --git a/lib/generators/backbone/scaffold/templates/templates/show.jst b/lib/generators/backbone/scaffold/templates/templates/show.jst deleted file mode 100644 index 06ff380..0000000 --- a/lib/generators/backbone/scaffold/templates/templates/show.jst +++ /dev/null @@ -1,9 +0,0 @@ -<% attributes.each do |attribute| -%> -

- <%= attribute.human_name %>: - <%%= <%= attribute.name %> %> -

- -<% end -%> - -Back \ No newline at end of file diff --git a/lib/generators/backbone/scaffold/templates/views/edit_view.coffee b/lib/generators/backbone/scaffold/templates/views/edit_view.coffee index 00d5172..19b3746 100644 --- a/lib/generators/backbone/scaffold/templates/views/edit_view.coffee +++ b/lib/generators/backbone/scaffold/templates/views/edit_view.coffee @@ -1,24 +1,22 @@ <%= view_namespace %> ||= {} -class <%= view_namespace %>.EditView extends Backbone.View - template : JST["<%= jst 'edit' %>"] +class <%= view_namespace %>.EditView extends <%= js_app_name %>.Views.BaseView + template: (data) -> $("#<%= tmpl 'edit' %>").tmpl(data) - events : - "submit #edit-<%= singular_name %>" : "update" + initialize: -> + @model.bind("error", @renderErrors) + @model.prepareToEdit() - update : (e) -> - e.preventDefault() - e.stopPropagation() - - @model.save(null, - success : (<%= singular_name %>) => - @model = <%= singular_name %> - window.location.hash = "/#{@model.id}" + events: + _.extend( _.clone(@__super__.events), + "submit #edit_<%= singular_name %>" : "update" ) - render : -> - $(this.el).html(this.template(@model.toJSON() )) - - this.$("form").backboneLink(@model) - + render: -> + $(@el).html( @template( @model.toJSON(true) ) ) + this.$("form#edit_<%= singular_name %>").backboneLink(@model) return this + + remove: -> + @model.unbind("error", @renderErrors) + super() diff --git a/lib/generators/backbone/scaffold/templates/views/index_view.coffee b/lib/generators/backbone/scaffold/templates/views/index_view.coffee index e51339d..8291b92 100644 --- a/lib/generators/backbone/scaffold/templates/views/index_view.coffee +++ b/lib/generators/backbone/scaffold/templates/views/index_view.coffee @@ -1,22 +1,35 @@ <%= view_namespace %> ||= {} -class <%= view_namespace %>.IndexView extends Backbone.View - template: JST["<%= jst 'index' %>"] +class <%= view_namespace %>.IndexView extends <%= js_app_name %>.Views.BaseView + template: (data) -> $("#<%= tmpl 'index' %>").tmpl(data) initialize: () -> _.bindAll(this, 'addOne', 'addAll', 'render') - @options.<%= plural_model_name %>.bind('reset', @addAll) + events: + _.extend( _.clone(@__super__.events), + {} + ) + addAll: () -> + @$("#<%= plural_name %>_table tbody").empty() + @renderPagination(@options.<%= plural_model_name %>) if @options.<%= plural_model_name %>.pagination? @options.<%= plural_model_name %>.each(@addOne) addOne: (<%= singular_model_name %>) -> view = new <%= view_namespace %>.<%= singular_name.camelize %>View({model : <%= singular_model_name %>}) - @$("tbody").append(view.render().el) + @$("#<%= plural_name %>_table tbody").append(view.render().el) render: -> - $(@el).html(@template(<%= plural_model_name %>: @options.<%= plural_model_name %>.toJSON() )) + $(@el).html(@template(<%= plural_model_name %>: @options.<%= plural_model_name %>.toJSON(true) )) @addAll() return this + + remove: -> + @options.<%= plural_model_name %>.unbind('reset', @addAll) + super() + + pagination: (e) -> + super(e, @options.<%= plural_model_name %>) diff --git a/lib/generators/backbone/scaffold/templates/views/model_view.coffee b/lib/generators/backbone/scaffold/templates/views/model_view.coffee index 3289537..15d65e5 100644 --- a/lib/generators/backbone/scaffold/templates/views/model_view.coffee +++ b/lib/generators/backbone/scaffold/templates/views/model_view.coffee @@ -1,19 +1,15 @@ <%= view_namespace %> ||= {} -class <%= view_namespace %>.<%= singular_name.camelize %>View extends Backbone.View - template: JST["<%= jst singular_name %>"] - - events: - "click .destroy" : "destroy" +class <%= view_namespace %>.<%= singular_name.camelize %>View extends <%= js_app_name%>.Views.BaseView + template: (data) -> $("#<%= tmpl singular_name %>").tmpl(data) tagName: "tr" - destroy: () -> - @model.destroy() - this.remove() - - return false + events: + _.extend( _.clone(@__super__.events), + {} + ) render: -> - $(this.el).html(@template(@model.toJSON() )) + $(@el).html( @template( @model.toJSON(true) ) ) return this diff --git a/lib/generators/backbone/scaffold/templates/views/new_view.coffee b/lib/generators/backbone/scaffold/templates/views/new_view.coffee index 1905439..b3093da 100644 --- a/lib/generators/backbone/scaffold/templates/views/new_view.coffee +++ b/lib/generators/backbone/scaffold/templates/views/new_view.coffee @@ -1,37 +1,19 @@ <%= view_namespace %> ||= {} -class <%= view_namespace %>.NewView extends Backbone.View - template: JST["<%= jst 'new' %>"] +class <%= view_namespace %>.NewView extends <%= js_app_name %>.Views.BaseView + template: (data) -> $("#<%= tmpl 'new' %>").tmpl(data) - events: - "submit #new-<%= singular_name %>": "save" - - constructor: (options) -> + initialize: (options) -> super(options) @model = new @collection.model() + @model.bind("error", @renderErrors) - @model.bind("change:errors", () => - this.render() - ) - - save: (e) -> - e.preventDefault() - e.stopPropagation() - - @model.unset("errors") - - @collection.create(@model.toJSON(), - success: (<%= singular_name %>) => - @model = <%= singular_name %> - window.location.hash = "/#{@model.id}" - - error: (<%= singular_name %>, jqXHR) => - @model.set({errors: $.parseJSON(jqXHR.responseText)}) + events: + _.extend( _.clone(@__super__.events), + "submit #new_<%= singular_name %>": "save" ) render: -> - $(this.el).html(@template(@model.toJSON() )) - - this.$("form").backboneLink(@model) - + $(@el).html( @template( @model.toJSON(true) ) ) + this.$("form#new_<%= singular_name %>").backboneLink(@model) return this diff --git a/lib/generators/backbone/scaffold/templates/views/show_view.coffee b/lib/generators/backbone/scaffold/templates/views/show_view.coffee index e26ca7d..227d460 100644 --- a/lib/generators/backbone/scaffold/templates/views/show_view.coffee +++ b/lib/generators/backbone/scaffold/templates/views/show_view.coffee @@ -1,8 +1,16 @@ <%= view_namespace %> ||= {} -class <%= view_namespace %>.ShowView extends Backbone.View - template: JST["<%= jst 'show' %>"] +class <%= view_namespace %>.ShowView extends <%= js_app_name %>.Views.BaseView + template: (data) -> $("#<%= tmpl 'show' %>").tmpl(data) + + events: + _.extend( _.clone(@__super__.events), + {} + ) render: -> - $(this.el).html(@template(@model.toJSON() )) + $(@el).html( @template( @model.toJSON(true) ) ) return this + + destroy: (e) -> + super(e, success: () -> window.location.hash = "") diff --git a/vendor/assets/javascripts/backbone_datalink.js b/vendor/assets/javascripts/backbone_datalink.js index 15683c6..a7bec03 100644 --- a/vendor/assets/javascripts/backbone_datalink.js +++ b/vendor/assets/javascripts/backbone_datalink.js @@ -1,21 +1,127 @@ (function($) { + + function getNestedName(el){ + var nestedName = el.attr("name").replace("[]", ""); + var nesteds = nestedName.split("["); + + for(var i in nesteds) nesteds[i] = nesteds[i].replace("]", "") + return nesteds[nesteds.length-1]; + } + + function getNestedObject(el, model){ + var nestedName = el.attr("name"); + var nesteds = nestedName.split("[") + var nestedObject = null; + + for (var i in nesteds) { + var value = nesteds[i].replace("]", ""); + nestedObject = nestedObject == null ? model : nestedObject; + if ( /^.+_attributes$/.test(value) ) { + value = value.replace("_attributes", ""); + nestedObject = nestedObject[value]; + } else if ( /^backboneCid_.+$/.test(value) ) { + value = value.replace("backboneCid_", ""); + nestedObject = nestedObject.getByCid(value); + } + } + return nestedObject; + } + + function setSelectedValueForInput(container, el, nestedObject){ + var name = getNestedName(el); + var value = nestedObject.get(name) + + if ( el.is("input:text") ) + el.val(value) + + if ( el.is("select") ) { + // SELECT MULTIPLE + if ( _.isArray(value) ){ + _.each(value, function(v){ + el.find("option[value=\""+v+"\"]").attr('selected', true); + }); + } else { + el.find("option[selected]").attr('selected', false); + el.find("option[value=\""+value+"\"]").attr('selected', true); + } + } + + if ( el.is("input:radio") ){ + var radioName = el.attr("name"); + container.find("input[name=\""+radioName+"\"][value=\""+value+"\"]:radio").attr('checked', true); + } + + if ( el.is("input:checkbox") ) { + // HABTM + if ( /_ids$/.test(name) ) { + var list = (value === null || value === undefined) ? [] : value; + var checkboxValue = parseInt(el.val(), 10); + + for(var i = 0, l = list.length; i < l; i++) { list[i] = parseInt(list[i], 10); } + if ( _.include(list, checkboxValue) ) + el.attr("checked", true); + } else { + var booleanValues = [true, "true", 1, "1"]; + var inputValue = _.include(booleanValues, el.val()); + var booleanValue = _.include(booleanValues, value); + var shouldMarked = false; + if (inputValue === true && booleanValue === true) shouldMarked = true; + el.attr("checked", shouldMarked) + } + } + } + + function setValues(container, el, nestedObject){ + var attrs = {}; + var name = getNestedName(el); + var inputValue = el.val(); + + if ( el.is("input:checkbox") ) { + // HABTM + if ( /_ids$/.test(name) ) { + var checkeds = container.find("input[name$=\"_ids][]\"]:checkbox:checked"); + inputValue = []; + for(var i = 0, l = checkeds.length; i < l; i++){ + var checkboxValue = parseInt($(checkeds[i]).val(), 10); + inputValue.push(checkboxValue); + } + } else { + if ( el.is(":checked") ) inputValue = true; + else inputValue = false; + } + } + + //if (inputValue === "") inputValue = null; + + attrs[name] = inputValue; + nestedObject.set(attrs); + return true; + } + + function bindChangeEvents(container, el, nestedObject){ + var name = getNestedName(el); + nestedObject.bind("change:" + name, function() { + return setSelectedValueForInput(container, el, nestedObject); + }); + } + return $.extend($.fn, { backboneLink: function(model) { - return $(this).find(":input").each(function() { - var el, name; - el = $(this); - name = el.attr("name"); - model.bind("change:" + name, function() { - return el.val(model.get(name)); - }); - return $(this).bind("change", function() { - var attrs; - el = $(this); - attrs = {}; - attrs[el.attr("name")] = el.val(); - return model.set(attrs); + var container = $(this); + container.find(":input:not(:checkbox.children_checkbox)").each(function() { + var el, nestedObject; + el = $(this); + nestedObject = getNestedObject(el, model); + + setSelectedValueForInput(container, el, nestedObject); + bindChangeEvents(container, el, nestedObject); + + return el.bind("change", function() { + return setValues(container, el, nestedObject); }); + }); + return container; } }); })(jQuery); diff --git a/vendor/assets/javascripts/jquery.iframe-transport.js b/vendor/assets/javascripts/jquery.iframe-transport.js new file mode 100755 index 0000000..211f568 --- /dev/null +++ b/vendor/assets/javascripts/jquery.iframe-transport.js @@ -0,0 +1,221 @@ +// This [jQuery](http://jquery.com/) plugin implements an `