├── log └── .gitkeep ├── lib ├── tasks │ └── .gitkeep └── assets │ └── .gitkeep ├── public ├── favicon.ico ├── robots.txt ├── 422.html ├── 404.html └── 500.html ├── test ├── unit │ └── .gitkeep ├── fixtures │ └── .gitkeep ├── functional │ └── .gitkeep ├── integration │ └── .gitkeep ├── performance │ └── browsing_test.rb └── test_helper.rb ├── app ├── mailers │ └── .gitkeep ├── models │ ├── .gitkeep │ ├── employee.rb │ └── user.rb ├── helpers │ ├── employees_helper.rb │ ├── sessions_helper.rb │ └── application_helper.rb ├── assets │ ├── stylesheets │ │ ├── application.css.scss │ │ └── index.css.scss │ ├── images │ │ ├── valid.png │ │ ├── header.png │ │ └── invalid.png │ ├── javascripts │ │ ├── modules.coffee │ │ ├── controllers │ │ │ ├── mainController.coffee │ │ │ ├── headerController.coffee │ │ │ ├── loginController.coffee │ │ │ ├── createEmployeeController.coffee │ │ │ ├── editEmployeeController.coffee │ │ │ └── employeesController.coffee │ │ ├── services │ │ │ ├── employeesService.coffee │ │ │ ├── viewState.coffee │ │ │ ├── selectedEmployee.coffee │ │ │ └── sessionService.coffee │ │ ├── routes.coffee.erb │ │ └── application.js │ └── templates │ │ ├── employees.html │ │ ├── login.html │ │ └── employee.html ├── controllers │ ├── employees_controller.rb │ ├── application_controller.rb │ └── sessions_controller.rb └── views │ └── layouts │ └── application.html.erb ├── vendor ├── plugins │ └── .gitkeep └── assets │ ├── stylesheets │ └── .gitkeep │ └── javascripts │ └── angular-resource-1.1.4.js ├── Procfile ├── layout ├── header.png └── screenshots.png ├── .gitignore ├── wiki ├── cafetownsend-angular-rails-edit.png ├── cafetownsend-angular-rails-login.png ├── cafetownsend-angular-rails-overview.png └── cafetownsend-angular-rails-youtube.png ├── config.ru ├── config ├── environment.rb ├── boot.rb ├── initializers │ ├── mime_types.rb │ ├── inflections.rb │ ├── backtrace_silencers.rb │ ├── session_store.rb │ ├── secret_token.rb │ └── wrap_parameters.rb ├── locales │ └── en.yml ├── routes.rb ├── database.yml ├── environments │ ├── development.rb │ ├── test.rb │ └── production.rb └── application.rb ├── doc └── README_FOR_APP ├── db ├── migrate │ ├── 20120111190748_create_users.rb │ └── 20120112094632_create_employees.rb ├── seeds.rb └── schema.rb ├── Rakefile ├── script └── rails ├── Gemfile ├── README.md └── Gemfile.lock /log/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/tasks/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/unit/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/mailers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/plugins/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/functional/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/integration/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec rails server thin -p $PORT -------------------------------------------------------------------------------- /app/helpers/employees_helper.rb: -------------------------------------------------------------------------------- 1 | module EmployeesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/sessions_helper.rb: -------------------------------------------------------------------------------- 1 | module SessionsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/models/employee.rb: -------------------------------------------------------------------------------- 1 | class Employee < ActiveRecord::Base 2 | end 3 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css.scss: -------------------------------------------------------------------------------- 1 | @import 2 | "bourbon", 3 | "index"; -------------------------------------------------------------------------------- /layout/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ace/CafeTownsend-Angular-Rails/master/layout/header.png -------------------------------------------------------------------------------- /layout/screenshots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ace/CafeTownsend-Angular-Rails/master/layout/screenshots.png -------------------------------------------------------------------------------- /app/assets/images/valid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ace/CafeTownsend-Angular-Rails/master/app/assets/images/valid.png -------------------------------------------------------------------------------- /app/assets/images/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ace/CafeTownsend-Angular-Rails/master/app/assets/images/header.png -------------------------------------------------------------------------------- /app/assets/images/invalid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ace/CafeTownsend-Angular-Rails/master/app/assets/images/invalid.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | db/*.sqlite3 3 | log/*.log 4 | tmp/ 5 | .sass-cache/ 6 | 7 | .idea/* 8 | *.iml 9 | .DS_Store 10 | 11 | .rvmrc -------------------------------------------------------------------------------- /wiki/cafetownsend-angular-rails-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ace/CafeTownsend-Angular-Rails/master/wiki/cafetownsend-angular-rails-edit.png -------------------------------------------------------------------------------- /wiki/cafetownsend-angular-rails-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ace/CafeTownsend-Angular-Rails/master/wiki/cafetownsend-angular-rails-login.png -------------------------------------------------------------------------------- /wiki/cafetownsend-angular-rails-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ace/CafeTownsend-Angular-Rails/master/wiki/cafetownsend-angular-rails-overview.png -------------------------------------------------------------------------------- /wiki/cafetownsend-angular-rails-youtube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ace/CafeTownsend-Angular-Rails/master/wiki/cafetownsend-angular-rails-youtube.png -------------------------------------------------------------------------------- /app/controllers/employees_controller.rb: -------------------------------------------------------------------------------- 1 | class EmployeesController < InheritedResources::Base 2 | 3 | before_filter :ensure_authenticated 4 | 5 | respond_to :json 6 | 7 | end -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run CafeTownsendAngularRails::Application 5 | -------------------------------------------------------------------------------- /app/assets/javascripts/modules.coffee: -------------------------------------------------------------------------------- 1 | # register all modules 2 | angular.module 'cafeTownsend.services', ['ngResource', 'rails', 'ng-rails-csrf'] 3 | angular.module('cafeTownsend', ['cafeTownsend.services']) -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | attr_accessible :name, :password, :password_confirmation 3 | has_secure_password 4 | validates_presence_of :password, :on => :create 5 | end 6 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | CafeTownsendAngularRails::Application.initialize! 6 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | # Set up gems listed in the Gemfile. 4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 5 | 6 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) 7 | -------------------------------------------------------------------------------- /doc/README_FOR_APP: -------------------------------------------------------------------------------- 1 | Use this README file to introduce your application and point to useful places in the API for learning more. 2 | Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. 3 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" 6 | -------------------------------------------------------------------------------- /db/migrate/20120111190748_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration 2 | def change 3 | create_table :users do |t| 4 | t.string :name 5 | t.string :password_digest 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | # Add your own tasks in files placed in lib/tasks ending in .rake, 3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 4 | 5 | require File.expand_path('../config/application', __FILE__) 6 | 7 | CafeTownsendAngularRails::Application.load_tasks 8 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | CafeTownsendAngularRails::Application.routes.draw do 2 | 3 | 4 | #match "logout" => "sessions#destroy", :as => "logout" 5 | match "login" => "sessions#create", :as => "login" 6 | 7 | root :to => "sessions#create" 8 | 9 | resources :sessions 10 | resources :employees 11 | 12 | end 13 | -------------------------------------------------------------------------------- /script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /db/migrate/20120112094632_create_employees.rb: -------------------------------------------------------------------------------- 1 | class CreateEmployees < ActiveRecord::Migration 2 | def change 3 | create_table :employees do |t| 4 | t.string :first_name 5 | t.string :last_name 6 | t.string :email 7 | t.date :start_date 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/assets/javascripts/controllers/mainController.coffee: -------------------------------------------------------------------------------- 1 | angular.module('cafeTownsend').controller 'MainController' 2 | , ['$log', '$scope', 'SessionService', 'ViewState' 3 | , ($log, $scope, SessionService, ViewState) -> 4 | 5 | $scope.viewState = ViewState 6 | 7 | # ######################## 8 | # login status 9 | # ######################## 10 | 11 | $scope.authorized = -> 12 | SessionService.authorized() 13 | 14 | 15 | ] -------------------------------------------------------------------------------- /app/assets/javascripts/services/employeesService.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | RESTful service to handle data of employees 3 | using angularjs-rails-resource 4 | @see: http://ngmodules.org/modules/angularjs-rails-resource 5 | ### 6 | angular.module('cafeTownsend.services').factory 'EmployeesService' 7 | , ['$log', 'railsResourceFactory' 8 | , ($log, railsResourceFactory) -> 9 | resource = railsResourceFactory({url: '/employees', name: 'employee'}); 10 | ] -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | -------------------------------------------------------------------------------- /test/performance/browsing_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'rails/performance_test_help' 3 | 4 | class BrowsingTest < ActionDispatch::PerformanceTest 5 | # Refer to the documentation for all available options 6 | # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory] 7 | # :output => 'tmp/performance', :formats => [:flat] } 8 | 9 | def test_homepage 10 | get '/' 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /app/assets/javascripts/services/viewState.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | Simple Service to share data of current view state, which is acting as a domain model. 3 | The view state set by controllers. 4 | 5 | 6 | Current view states are: 7 | 'login' 8 | 'employees' 9 | 'edit' 10 | 'create' 11 | 12 | ### 13 | angular.module('cafeTownsend.services').factory 'ViewState' 14 | , ['$log' 15 | , ($log) -> 16 | 17 | current = '' 18 | 19 | { 20 | current 21 | } 22 | 23 | ] -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | 3 | protect_from_forgery 4 | 5 | layout nil 6 | 7 | before_filter :intercept_html_requests 8 | 9 | private 10 | 11 | def ensure_authenticated 12 | if session[:user_id].blank? 13 | render :json => { authorized: false } 14 | end 15 | end 16 | 17 | def intercept_html_requests 18 | render 'layouts/application' if request.format == Mime::HTML 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | CafeTownsendAngularRails::Application.config.session_store :cookie_store, key: '_CafeTownsend-Angular-Rails_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rails generate session_migration") 8 | # CafeTownsendAngularRails::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] = "test" 2 | require File.expand_path('../../config/environment', __FILE__) 3 | require 'rails/test_help' 4 | 5 | class ActiveSupport::TestCase 6 | # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. 7 | # 8 | # Note: You'll currently still have to declare fixtures explicitly in integration tests 9 | # -- they do not yet inherit this setting 10 | fixtures :all 11 | 12 | # Add more helper methods to be used by all tests here... 13 | end 14 | -------------------------------------------------------------------------------- /config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | CafeTownsendAngularRails::Application.config.secret_token = '7094a5e336ed6a515b7997e607189b0c1d2a11c7b833c2d7ca93ba2ec9dae6e159a8f1b5de4c546ac50700f63b22689335a52344e3d85c6f870eb8b5e6d6d255' 8 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # Disable root element in JSON by default. 12 | ActiveSupport.on_load(:active_record) do 13 | self.include_root_in_json = false 14 | end 15 | -------------------------------------------------------------------------------- /app/assets/javascripts/services/selectedEmployee.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | Simple Service to share data of an instance of selected employee (RailsResource) 3 | beetween controllers, which is acting as a domain model. 4 | For more information about RailsResource check http://ngmodules.org/modules/angularjs-rails-resource 5 | ### 6 | angular.module('cafeTownsend.services').factory 'SelectedEmployee' 7 | , ['$log', '$resource' 8 | , ($log, $resource) -> 9 | 10 | # @return An instance of selected employee, 11 | # which is behind the scenes an instance of RailsResource. 12 | # It is undefined by default. 13 | instance: undefined 14 | 15 | ] -------------------------------------------------------------------------------- /app/controllers/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class SessionsController < ApplicationController 2 | 3 | respond_to :json 4 | 5 | # POST /sessions 6 | def create 7 | user = User.find_by_name(params[:name]) 8 | if user && user.authenticate(params[:password]) 9 | session[:user_id] = user.id 10 | render :json => { user: user, authorized: 'true' } 11 | else 12 | #raise StandardError 13 | render :json => { authorized: 'false' } 14 | end 15 | end 16 | 17 | 18 | # DELETE /sessions/1 19 | def destroy 20 | session[:user_id] = nil 21 | render :json => { authorized: 'false' } 22 | end 23 | 24 | 25 | end 26 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | development: 7 | adapter: sqlite3 8 | database: db/development.sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | # Warning: The database defined as "test" will be erased and 13 | # re-generated from your development database when you run "rake". 14 | # Do not set this db to the same as development or production. 15 | test: 16 | adapter: sqlite3 17 | database: db/test.sqlite3 18 | pool: 5 19 | timeout: 5000 20 | 21 | production: 22 | adapter: sqlite3 23 | database: db/production.sqlite3 24 | pool: 5 25 | timeout: 5000 26 | -------------------------------------------------------------------------------- /app/assets/templates/employees.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 | 16 |
17 | 18 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The change you wanted was rejected.

23 |

Maybe you tried to change something you didn't have access to.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The page you were looking for doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

We're sorry, but something went wrong.

23 |

We've been notified about this issue and we'll take a look at it shortly.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /app/assets/javascripts/routes.coffee.erb: -------------------------------------------------------------------------------- 1 | # defining all routes 2 | angular.module('cafeTownsend').config ['$routeProvider', '$locationProvider', ($routeProvider, $locationProvider) -> 3 | 4 | $routeProvider 5 | .when '/login', 6 | templateUrl: '<%= asset_path("login.html") %>', 7 | controller: 'LoginController' 8 | 9 | .when '/employees', 10 | templateUrl: '<%= asset_path("employees.html") %>' 11 | controller: 'EmployeesController' 12 | 13 | .when '/employees/new', 14 | templateUrl: '<%= asset_path("employee.html") %>' 15 | controller: 'CreateEmployeeController' 16 | 17 | .when '/employees/:id/edit', 18 | templateUrl: '<%= asset_path("employee.html") %>' 19 | controller: 'EditEmployeeController' 20 | 21 | .otherwise redirectTo: '/login' 22 | 23 | # enabling html5Mode 24 | $locationProvider.html5Mode true 25 | ] -------------------------------------------------------------------------------- /app/assets/templates/login.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Login 4 |

{{message}}

5 | 15 | 23 | 24 | 25 |

Username: "Luke" Password: "Skywalker"

26 | 27 |
28 |
29 | -------------------------------------------------------------------------------- /app/assets/javascripts/controllers/headerController.coffee: -------------------------------------------------------------------------------- 1 | angular.module('cafeTownsend').controller 'HeaderController' 2 | , ['$log', '$scope', '$location', 'SessionService' 3 | , ($log, $scope, $location, SessionService) -> 4 | 5 | $scope.user = SessionService.getUser() 6 | 7 | # ######################## 8 | # login status 9 | # ######################## 10 | 11 | $scope.authorized = -> 12 | SessionService.authorized() 13 | 14 | # ######################## 15 | # logout 16 | # ######################## 17 | 18 | $scope.logout = -> 19 | if !!SessionService.authorized() 20 | SessionService.logout logoutResultHandler, logoutErrorHandler 21 | 22 | logoutResultHandler = -> 23 | # a succesfull log out results in {"authorized":"false"} sent from server side 24 | if !SessionService.authorized() 25 | $location.path '/login' 26 | 27 | logoutErrorHandler = (error)-> 28 | alert "Error trying to log out (error: #{error})" 29 | 30 | ] -------------------------------------------------------------------------------- /app/assets/javascripts/controllers/loginController.coffee: -------------------------------------------------------------------------------- 1 | angular.module('cafeTownsend').controller 'LoginController' 2 | , ['$log', '$scope', '$location', 'SessionService', 'ViewState' 3 | , ($log, $scope, $location, SessionService, ViewState) -> 4 | 5 | $scope.user = SessionService.getUser() 6 | # for debugging only 7 | # $scope.user.name = "Luke" 8 | # $scope.user.password = "Skywalker" 9 | 10 | ViewState.current = 'login' 11 | 12 | # ######################## 13 | # login 14 | # ######################## 15 | 16 | $scope.login = -> 17 | SessionService.login $scope.user, loginResultHandler, loginErrorHandler 18 | 19 | loginResultHandler = (result) -> 20 | r = result 21 | if !!SessionService.authorized() 22 | $location.path '/employees' 23 | else 24 | $scope.message = "Invalid username or password!" 25 | 26 | loginErrorHandler = (error) -> 27 | $scope.message = "Error: " + error 28 | 29 | #helper method called by view to show or hide a message 30 | $scope.showMessage = (message) -> 31 | $scope.message? and $scope.message.length 32 | 33 | ] -------------------------------------------------------------------------------- /app/assets/javascripts/services/sessionService.coffee: -------------------------------------------------------------------------------- 1 | angular.module('cafeTownsend.services').factory 'SessionService', ['$log', '$resource', ($log, $resource) -> 2 | 3 | service = $resource '/sessions/:param', {}, 4 | 'login': 5 | method: 'POST' 6 | 'logout': 7 | method: 'DELETE' 8 | 9 | user = {} 10 | 11 | authorized = -> 12 | user.authorized is "true" 13 | 14 | login = (newUser, resultHandler, errorHandler) -> 15 | service.login newUser 16 | , (result) -> 17 | user = result.user || {} 18 | user.authorized = result.authorized 19 | resultHandler(result) if angular.isFunction resultHandler 20 | , (error) -> 21 | errorHandler(error) if angular.isFunction errorHandler 22 | 23 | logout = (resultHandler, errorHandler) -> 24 | service.logout param: user.id 25 | , (result) -> 26 | user = {} 27 | resultHandler(result) if angular.isFunction resultHandler 28 | , (error) -> 29 | errorHandler(error) if angular.isFunction errorHandler 30 | 31 | getUser = -> 32 | user 33 | 34 | { 35 | login, 36 | logout, 37 | authorized, 38 | getUser 39 | } 40 | ] -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | CafeTownsendAngularRails::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Log error messages when you accidentally call methods on nil. 10 | config.whiny_nils = true 11 | 12 | # Show full error reports and disable caching 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger 20 | config.active_support.deprecation = :log 21 | 22 | # Only use best-standards-support built into browsers 23 | config.action_dispatch.best_standards_support = :builtin 24 | 25 | # Do not compress assets 26 | config.assets.compress = false 27 | 28 | # Expands the lines which load the assets 29 | config.assets.debug = true 30 | end 31 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into including all the files listed below. 2 | // Add new JavaScript/Coffee code in separate files in this directory and they'll automatically 3 | // be included in the compiled file accessible from http://example.com/assets/application.js 4 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 5 | // the compiled file. 6 | // 7 | // libraries 8 | //= require jquery 9 | //= require jquery_ujs 10 | //= require angular-1.1.4 11 | //= require angular-resource-1.1.4 12 | // require angular-1.0.5 13 | // require angular-resource-1.0.5 14 | //= require ng-rails-csrf 15 | //= require angularjs/rails/resource 16 | // 17 | // app related stuff 18 | //= require modules 19 | //= require services/sessionService 20 | //= require services/employeesService 21 | //= require services/selectedEmployee 22 | //= require services/viewState 23 | //= require controllers/mainController 24 | //= require controllers/headerController 25 | //= require controllers/loginController 26 | //= require controllers/employeesController 27 | //= require controllers/editEmployeeController 28 | //= require controllers/createEmployeeController 29 | //= require routes 30 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | 9 | 10 | 11 | # user name:string password:string 12 | User.create!(name: 'Luke', password: 'Skywalker') 13 | 14 | 15 | 16 | # employee first_name:string last_name:string email:string start_date:date 17 | Employee.create!([ 18 | { 19 | :first_name => 'Sue', 20 | :last_name => 'Hove', 21 | :email => 'shove@cafetownsend.com', 22 | :start_date => DateTime.new(2006,1,7) 23 | }, 24 | { 25 | :first_name => 'Matt', 26 | :last_name => 'Boles', 27 | :email => 'mboles@cafetownsend.com', 28 | :start_date => DateTime.new(2006,2,17) 29 | }, 30 | { 31 | :first_name => 'Mike', 32 | :last_name => 'Kollen', 33 | :email => 'mkollen@cafetownsend.com', 34 | :start_date => DateTime.new(2006,3,1) 35 | }, 36 | { 37 | :first_name => 'Jennifer', 38 | :last_name => 'Jaegel', 39 | :email => 'jjaegel@cafetownsend.com', 40 | :start_date => DateTime.new(2006,4,1) 41 | } 42 | ]) -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended to check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(:version => 20120112094632) do 15 | 16 | create_table "employees", :force => true do |t| 17 | t.string "first_name" 18 | t.string "last_name" 19 | t.string "email" 20 | t.date "start_date" 21 | t.datetime "created_at", :null => false 22 | t.datetime "updated_at", :null => false 23 | end 24 | 25 | create_table "users", :force => true do |t| 26 | t.string "name" 27 | t.string "password_digest" 28 | t.datetime "created_at", :null => false 29 | t.datetime "updated_at", :null => false 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem 'rails', '3.2.13' 4 | 5 | # Bundle edge Rails instead: 6 | # gem 'rails', :git => 'git://github.com/rails/rails.git' 7 | 8 | 9 | # Gems used only for assets and not required 10 | # in production environments by default. 11 | group :assets do 12 | gem 'sass-rails' 13 | gem 'coffee-rails' 14 | gem 'closure-compiler' 15 | end 16 | 17 | gem 'jquery-rails' 18 | 19 | # To use ActiveModel has_secure_password 20 | gem 'bcrypt-ruby' 21 | 22 | # inherit all restful actions into controllers 23 | # https://github.com/josevalim/inherited_resources 24 | gem 'inherited_resources' 25 | 26 | # Sass mixin library Bourbon 27 | gem 'bourbon' 28 | 29 | # Use unicorn as the web server 30 | # gem 'unicorn' 31 | 32 | # Deploy with Capistrano 33 | # gem 'capistrano' 34 | 35 | # To use debugger 36 | # gem 'ruby-debug19', :require => 'ruby-debug' 37 | 38 | group :test do 39 | # Pretty printed test output 40 | gem 'turn', :require => false 41 | end 42 | 43 | # Switch from SQLite to PostgreSQL for deployment to Heroku 44 | # http://railsapps.github.com/rails-heroku-tutorial.html 45 | group :development, :test do 46 | gem 'sqlite3' 47 | end 48 | group :production do 49 | gem 'pg' 50 | gem 'thin' 51 | end 52 | 53 | # AngularJS related stuff 54 | gem 'ng-rails-csrf', :git => "git://github.com/xrd/ng-rails-csrf.git" 55 | gem 'angularjs-rails-resource' 56 | -------------------------------------------------------------------------------- /app/assets/javascripts/controllers/createEmployeeController.coffee: -------------------------------------------------------------------------------- 1 | angular.module('cafeTownsend').controller 'CreateEmployeeController' 2 | , ['$log', '$scope', '$location', 'SessionService', 'EmployeesService', 'SelectedEmployee', 'ViewState' 3 | , ($log, $scope, $location, SessionService, EmployeesService, SelectedEmployee, ViewState) -> 4 | 5 | $scope.isCreateForm = true 6 | 7 | ViewState.current = 'create' 8 | 9 | # ######################## 10 | # create 11 | # ######################## 12 | 13 | create = -> 14 | employee = $scope.selectedEmployee 15 | employee.create() 16 | .then -> 17 | $scope.browseToOverview() 18 | , (error) -> 19 | alert "Error trying to save a new employee (error: " + error + ")" 20 | 21 | # ######################## 22 | # form 23 | # ######################## 24 | 25 | $scope.submit = -> 26 | create() 27 | 28 | # ######################## 29 | # navigation 30 | # ######################## 31 | 32 | $scope.browseToOverview = -> 33 | # clear reference to selected employee 34 | SelectedEmployee.instance = undefined 35 | $location.path '/employees' 36 | 37 | # ######################## 38 | # init 39 | # ######################## 40 | 41 | init = -> 42 | if !!SessionService.authorized() 43 | employee = new EmployeesService() 44 | # for debugging only 45 | # employee.email = "info@websector.de" 46 | # employee.firstName = "jens" 47 | # employee.lastName = "krause" 48 | # employee.startDate = "2013-03-30" 49 | 50 | # store new created instance 51 | # set reference to scope 52 | SelectedEmployee.instance = 53 | $scope.selectedEmployee = 54 | employee 55 | else 56 | $location.path '/login' 57 | 58 | init() 59 | 60 | ] -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CafeTownsend-AngularJS-Rails 5 | <%= stylesheet_link_tag "application" %> 6 | <%= csrf_meta_tags %> 7 | 8 | 9 | 10 | 11 | 12 |
18 |
19 |
24 |

Logout

25 |

Hello {{user.name}}

26 |
27 |
28 | 29 |
34 | 38 |
39 | 40 | 52 |
53 | <%= javascript_include_tag "application" -%> 54 | 55 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | CafeTownsendAngularRails::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Configure static asset server for tests with Cache-Control for performance 11 | config.serve_static_assets = true 12 | config.static_cache_control = "public, max-age=3600" 13 | 14 | # Log error messages when you accidentally call methods on nil 15 | config.whiny_nils = true 16 | 17 | # Show full error reports and disable caching 18 | config.consider_all_requests_local = true 19 | config.action_controller.perform_caching = false 20 | 21 | # Raise exceptions instead of rendering exception templates 22 | config.action_dispatch.show_exceptions = false 23 | 24 | # Disable request forgery protection in test environment 25 | config.action_controller.allow_forgery_protection = false 26 | 27 | # Tell Action Mailer not to deliver emails to the real world. 28 | # The :test delivery method accumulates sent emails in the 29 | # ActionMailer::Base.deliveries array. 30 | config.action_mailer.delivery_method = :test 31 | 32 | # Use SQL instead of Active Record's schema dumper when creating the test database. 33 | # This is necessary if your schema can't be completely dumped by the schema dumper, 34 | # like if you have constraints or database-specific column types 35 | # config.active_record.schema_format = :sql 36 | 37 | # Print deprecation notices to the stderr 38 | config.active_support.deprecation = :stderr 39 | end 40 | -------------------------------------------------------------------------------- /app/assets/templates/employee.html: -------------------------------------------------------------------------------- 1 | 4 | 7 |
8 |
9 | 10 | Edit 11 | 12 | 13 | Add 14 | 15 | 24 | 33 | 34 | 45 | 46 | 53 | 54 |
55 | 56 | 57 |

Delete

58 |
59 | 60 |
61 |
62 | -------------------------------------------------------------------------------- /app/assets/javascripts/controllers/editEmployeeController.coffee: -------------------------------------------------------------------------------- 1 | angular.module('cafeTownsend').controller 'EditEmployeeController' 2 | , ['$log', '$scope', '$location', '$routeParams', 'SessionService', 'EmployeesService', 'SelectedEmployee', 'ViewState' 3 | , ($log, $scope, $location, $routeParams, SessionService, EmployeesService, SelectedEmployee, ViewState) -> 4 | 5 | $scope.isCreateForm = false 6 | 7 | ViewState.current = 'edit' 8 | 9 | # ######################## 10 | # update 11 | # ######################## 12 | 13 | update = -> 14 | SelectedEmployee.instance.update(employee_id: SelectedEmployee.instance.id) 15 | .then -> 16 | $scope.browseToOverview() 17 | , (error) -> 18 | alert "Error trying to update an employee (error: #{error})" 19 | 20 | 21 | # ######################## 22 | # delete 23 | # ######################## 24 | 25 | $scope.deleteEmployee = -> 26 | employee = SelectedEmployee.instance 27 | if confirm("Are you sure you want to delete #{employee.firstName} #{employee.lastName}?") 28 | employee.delete(employee_id: employee.id) 29 | .then -> 30 | # clear reference to selected employee 31 | SelectedEmployee.instance = undefined 32 | # back to overview 33 | $scope.browseToOverview() 34 | , (error) -> 35 | alert "Error trying to delete an employee (error: #{error})" 36 | 37 | # ######################## 38 | # form 39 | # ######################## 40 | 41 | $scope.submit = -> 42 | update() 43 | 44 | # ######################## 45 | # navigation 46 | # ######################## 47 | 48 | $scope.browseToOverview = -> 49 | $location.path '/employees' 50 | 51 | # ######################## 52 | # init 53 | # ######################## 54 | 55 | init = -> 56 | unless SessionService.authorized() 57 | $location.path '/login' 58 | else 59 | $scope.selectedEmployee = SelectedEmployee.instance 60 | 61 | init() 62 | ] -------------------------------------------------------------------------------- /app/assets/javascripts/controllers/employeesController.coffee: -------------------------------------------------------------------------------- 1 | angular.module('cafeTownsend').controller 'EmployeesController' 2 | , ['$log', '$scope', '$location', 'SessionService', 'EmployeesService', 'SelectedEmployee', 'ViewState' 3 | , ($log, $scope, $location, SessionService, EmployeesService, SelectedEmployee, ViewState) -> 4 | 5 | 6 | ViewState.current = 'employees' 7 | 8 | # ######################## 9 | # selected employee 10 | # ######################## 11 | 12 | $scope.selectEmployee = (employee)-> 13 | # storing selected employee (domain model) 14 | # and set a reference to scope 15 | SelectedEmployee.instance = 16 | $scope.selectedEmployee = 17 | employee 18 | 19 | # ######################## 20 | # get 21 | # ######################## 22 | 23 | getEmployees = -> 24 | EmployeesService.query().then (employees) -> 25 | 26 | $scope.employees = employees 27 | 28 | # ######################## 29 | # edit 30 | # ######################## 31 | 32 | $scope.editEmployee = -> 33 | $location.path "/employees/#{$scope.selectedEmployee.id}/edit" 34 | 35 | # ######################## 36 | # create 37 | # ######################## 38 | 39 | $scope.createEmployee = -> 40 | $location.path "/employees/new" 41 | 42 | # ######################## 43 | # delete 44 | # ######################## 45 | 46 | 47 | $scope.deleteEmployee = -> 48 | employee = SelectedEmployee.instance 49 | if confirm("Are you sure you want to delete #{employee.firstName} #{employee.lastName}?") 50 | employee.delete(employee_id: employee.id) 51 | .then -> 52 | # clear reference to selected employee 53 | SelectedEmployee.instance = 54 | $scope.selectedEmployee = 55 | undefined 56 | # get employees again 57 | getEmployees() 58 | , (error) -> 59 | alert "Error trying to delete an employee (error: #{error})" 60 | 61 | # ######################## 62 | # init 63 | # ######################## 64 | 65 | init = -> 66 | if !!SessionService.authorized() 67 | $scope.selectedEmployee = SelectedEmployee.instance 68 | getEmployees() 69 | else 70 | $location.path '/login' 71 | 72 | init() 73 | 74 | ] -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | if defined?(Bundler) 6 | # If you precompile assets before deploying to production, use this line 7 | Bundler.require(*Rails.groups(:assets => %w(development test))) 8 | # If you want your assets lazily compiled in production, use this line 9 | # Bundler.require(:default, :assets, Rails.env) 10 | end 11 | 12 | module CafeTownsendAngularRails 13 | class Application < Rails::Application 14 | # Settings in config/environments/* take precedence over those specified here. 15 | # Application configuration should go into files in config/initializers 16 | # -- all .rb files in that directory are automatically loaded. 17 | 18 | # Custom directories with classes and modules you want to be autoloadable. 19 | # config.autoload_paths += %W(#{config.root}/extras) 20 | 21 | # Only load the plugins named here, in the order given (default is alphabetical). 22 | # :all can be used as a placeholder for all plugins not explicitly named. 23 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 24 | 25 | # Activate observers that should always be running. 26 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 27 | 28 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 29 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 30 | # config.time_zone = 'Central Time (US & Canada)' 31 | 32 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 33 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 34 | # config.i18n.default_locale = :de 35 | 36 | # Configure the default encoding used in templates for Ruby 1.9. 37 | config.encoding = "utf-8" 38 | 39 | # Configure sensitive parameters which will be filtered from the log file. 40 | config.filter_parameters += [:password] 41 | 42 | # Enable the asset pipeline 43 | config.assets.enabled = true 44 | 45 | # Version of your assets, change this if you want to expire all your assets 46 | config.assets.version = '1.0' 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | CafeTownsendAngularRails::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # Code is not reloaded between requests 5 | config.cache_classes = true 6 | 7 | # Full error reports are disabled and caching is turned on 8 | config.consider_all_requests_local = false 9 | config.action_controller.perform_caching = true 10 | 11 | # Disable Rails's static asset server (Apache or nginx will already do this) 12 | config.serve_static_assets = false 13 | 14 | # Compress JavaScripts and CSS 15 | config.assets.compress = true 16 | config.assets.js_compressor = :closure 17 | 18 | # Don't fallback to assets pipeline if a precompiled asset is missed 19 | config.assets.compile = false 20 | 21 | # Generate digests for assets URLs 22 | config.assets.digest = true 23 | 24 | # Defaults to Rails.root.join("public/assets") 25 | # config.assets.manifest = YOUR_PATH 26 | 27 | # Specifies the header that your server uses for sending files 28 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 29 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 30 | 31 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 32 | # config.force_ssl = true 33 | 34 | # See everything in the log (default is :info) 35 | # config.log_level = :debug 36 | 37 | # Use a different logger for distributed setups 38 | # config.logger = SyslogLogger.new 39 | 40 | # Use a different cache store in production 41 | # config.cache_store = :mem_cache_store 42 | 43 | # Enable serving of images, stylesheets, and JavaScripts from an asset server 44 | # config.action_controller.asset_host = "http://assets.example.com" 45 | 46 | # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) 47 | # config.assets.precompile += %w( search.js ) 48 | 49 | # Disable delivery errors, bad email addresses will be ignored 50 | # config.action_mailer.raise_delivery_errors = false 51 | 52 | # Enable threaded mode 53 | # config.threadsafe! 54 | 55 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 56 | # the I18n.default_locale when a translation can not be found) 57 | config.i18n.fallbacks = true 58 | 59 | # Send deprecation notices to registered listeners 60 | config.active_support.deprecation = :notify 61 | end 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #AngularJS (v.1.1.4) + Rails (v.3.2.13) port of Cafe Townsend application 2 | 3 | ##About 4 | [AngularJS](http://http://angularjs.org/) and [Rails](http://rubyonrails.org/) port of the famous Cafe Townsend application originally written in ActionScript. 5 | 6 | The application covers the following topics: 7 | 8 | * Angular and Rails 9 | * Angulars [Modules](http://docs.angularjs.org/guide/module) 10 | * Angulars [Services](http://docs.angularjs.org/guide/dev_guide.services) 11 | * Sharing data beetween controllers using services, which are acting as Domain Models 12 | * Angulars new animation directive [ngAnimate](http://code.angularjs.org/1.1.4/docs/api/ng.directive:ngAnimate) 13 | * Mixins by [Bourbon](http://bourbon.io) 14 | 15 | 16 | ##Demo (YouTube) 17 | [![Click on screen shot to see live demo on YouTube](https://github.com/sectore/CafeTownsend-Angular-Rails/raw/master/wiki/cafetownsend-angular-rails-youtube.png "Login")](https://www.youtube.com/watch?v=PCFUKOiThJA) 18 | 19 | ##Demo (Heroku) 20 | [http://cafetownsend-angular-rails.herokuapp.com](http://cafetownsend-angular-rails.herokuapp.com/) 21 | 22 | ##Local installation 23 | 1) Open Terminal 24 | 25 | git clone git://github.com/sectore/CafeTownsend-Angular-Rails.git 26 | cd CafeTownsend-Angular-Rails 27 | bundle install --without production 28 | rake db:migrate 29 | rake db:seed 30 | rails server 31 | 32 | 2) Open [http://localhost:3000](http://localhost:3000/) using [Chrome](https://www.google.com/chrome) 33 | 34 | ##License 35 | Copyright (c) 2012-2013 "sectore" Jens Krause // [WEBSECTOR.DE](http://www.websector.de) 36 | 37 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 38 | 39 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 40 | 41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 42 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git://github.com/xrd/ng-rails-csrf.git 3 | revision: e517c7e9c5c04d4c413122e4027ddc8de4dd1c1a 4 | specs: 5 | ng-rails-csrf (0.0.1) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | actionmailer (3.2.13) 11 | actionpack (= 3.2.13) 12 | mail (~> 2.5.3) 13 | actionpack (3.2.13) 14 | activemodel (= 3.2.13) 15 | activesupport (= 3.2.13) 16 | builder (~> 3.0.0) 17 | erubis (~> 2.7.0) 18 | journey (~> 1.0.4) 19 | rack (~> 1.4.5) 20 | rack-cache (~> 1.2) 21 | rack-test (~> 0.6.1) 22 | sprockets (~> 2.2.1) 23 | activemodel (3.2.13) 24 | activesupport (= 3.2.13) 25 | builder (~> 3.0.0) 26 | activerecord (3.2.13) 27 | activemodel (= 3.2.13) 28 | activesupport (= 3.2.13) 29 | arel (~> 3.0.2) 30 | tzinfo (~> 0.3.29) 31 | activeresource (3.2.13) 32 | activemodel (= 3.2.13) 33 | activesupport (= 3.2.13) 34 | activesupport (3.2.13) 35 | i18n (= 0.6.1) 36 | multi_json (~> 1.0) 37 | angularjs-rails-resource (0.1.2) 38 | ansi (1.4.3) 39 | arel (3.0.2) 40 | bcrypt-ruby (3.0.1) 41 | bourbon (3.1.1) 42 | sass (>= 3.2.0) 43 | thor 44 | builder (3.0.4) 45 | closure-compiler (1.1.8) 46 | coffee-rails (3.2.2) 47 | coffee-script (>= 2.2.0) 48 | railties (~> 3.2.0) 49 | coffee-script (2.2.0) 50 | coffee-script-source 51 | execjs 52 | coffee-script-source (1.6.2) 53 | daemons (1.1.9) 54 | erubis (2.7.0) 55 | eventmachine (1.0.3) 56 | execjs (1.4.0) 57 | multi_json (~> 1.0) 58 | has_scope (0.5.1) 59 | hike (1.2.1) 60 | i18n (0.6.1) 61 | inherited_resources (1.4.0) 62 | has_scope (~> 0.5.0) 63 | responders (~> 0.9) 64 | journey (1.0.4) 65 | jquery-rails (2.2.1) 66 | railties (>= 3.0, < 5.0) 67 | thor (>= 0.14, < 2.0) 68 | json (1.7.7) 69 | mail (2.5.3) 70 | i18n (>= 0.4.0) 71 | mime-types (~> 1.16) 72 | treetop (~> 1.4.8) 73 | mime-types (1.22) 74 | multi_json (1.7.2) 75 | pg (0.15.0) 76 | polyglot (0.3.3) 77 | rack (1.4.5) 78 | rack-cache (1.2) 79 | rack (>= 0.4) 80 | rack-ssl (1.3.3) 81 | rack 82 | rack-test (0.6.2) 83 | rack (>= 1.0) 84 | rails (3.2.13) 85 | actionmailer (= 3.2.13) 86 | actionpack (= 3.2.13) 87 | activerecord (= 3.2.13) 88 | activeresource (= 3.2.13) 89 | activesupport (= 3.2.13) 90 | bundler (~> 1.0) 91 | railties (= 3.2.13) 92 | railties (3.2.13) 93 | actionpack (= 3.2.13) 94 | activesupport (= 3.2.13) 95 | rack-ssl (~> 1.3.2) 96 | rake (>= 0.8.7) 97 | rdoc (~> 3.4) 98 | thor (>= 0.14.6, < 2.0) 99 | rake (10.0.4) 100 | rdoc (3.12.2) 101 | json (~> 1.4) 102 | responders (0.9.3) 103 | railties (~> 3.1) 104 | sass (3.2.7) 105 | sass-rails (3.2.6) 106 | railties (~> 3.2.0) 107 | sass (>= 3.1.10) 108 | tilt (~> 1.3) 109 | sprockets (2.2.2) 110 | hike (~> 1.2) 111 | multi_json (~> 1.0) 112 | rack (~> 1.0) 113 | tilt (~> 1.1, != 1.3.0) 114 | sqlite3 (1.3.7) 115 | thin (1.5.1) 116 | daemons (>= 1.0.9) 117 | eventmachine (>= 0.12.6) 118 | rack (>= 1.0.0) 119 | thor (0.18.1) 120 | tilt (1.3.6) 121 | treetop (1.4.12) 122 | polyglot 123 | polyglot (>= 0.3.1) 124 | turn (0.9.6) 125 | ansi 126 | tzinfo (0.3.37) 127 | 128 | PLATFORMS 129 | ruby 130 | 131 | DEPENDENCIES 132 | angularjs-rails-resource 133 | bcrypt-ruby 134 | bourbon 135 | closure-compiler 136 | coffee-rails 137 | inherited_resources 138 | jquery-rails 139 | ng-rails-csrf! 140 | pg 141 | rails (= 3.2.13) 142 | sass-rails 143 | sqlite3 144 | thin 145 | turn 146 | -------------------------------------------------------------------------------- /app/assets/stylesheets/index.css.scss: -------------------------------------------------------------------------------- 1 | // 2 | // constants 3 | // 4 | 5 | @import 'bourbon'; 6 | 7 | $MAIN_CONTENT_WIDTH: 480px; 8 | $MAIN_CONTENT_MARGIN: 0 auto; 9 | 10 | $MAIN_FONT: "Arial", "Helvetica", "sans-serif"; 11 | $MAIN_FONT_SIZE: 17px; 12 | 13 | $TRANSITION_DURATION: .5s; 14 | $TRANSITION_EASE_LINEAR: linear; 15 | // custom ease funtion 16 | // @see: http://matthewlein.com/ceaser/ 17 | $TRANSITION_EASE_IN_OUT: cubic-bezier(0.420, 0.000, 0.580, 1.000); 18 | $TRANSITION_EASE_OUT_SINE: cubic-bezier(0.390, 0.575, 0.565, 1.000); 19 | $TRANSITION_EASE_OUT_BACK: cubic-bezier(0.175, 0.885, 0.320, 1.275); 20 | 21 | // 22 | // global tags 23 | // 24 | *{ 25 | @include box-sizing(border-box); 26 | } 27 | 28 | body, 29 | html, 30 | p { 31 | padding: 0; 32 | margin: 0; 33 | } 34 | 35 | *:focus { 36 | outline: none; 37 | } 38 | 39 | 40 | body { 41 | line-height: 19px; 42 | font-size: $MAIN_FONT_SIZE; 43 | font-family: $MAIN_FONT; 44 | color: #363636; 45 | font-weight: normal; 46 | background-color: #FFF; 47 | } 48 | 49 | a { 50 | text-decoration: none; 51 | } 52 | 53 | 54 | ul { 55 | list-style: none; 56 | padding: 0; 57 | margin: 0; 58 | } 59 | 60 | .main-view{ 61 | width: $MAIN_CONTENT_WIDTH; 62 | margin: $MAIN_CONTENT_MARGIN; 63 | @include transition(padding-top $TRANSITION_DURATION $TRANSITION_EASE_OUT_SINE); 64 | padding-top: 160px; 65 | } 66 | 67 | .main-view-login{ 68 | padding-top: 160px; 69 | @include transition(padding-top $TRANSITION_DURATION $TRANSITION_EASE_OUT_SINE); 70 | } 71 | 72 | .main-view-employees{ 73 | padding-top: 100px; 74 | @include transition(padding-top $TRANSITION_DURATION $TRANSITION_EASE_OUT_SINE); 75 | } 76 | 77 | .main-view-edit, 78 | .main-view-create, 79 | { 80 | padding-top: 60px; 81 | @include transition(padding-top $TRANSITION_DURATION $TRANSITION_EASE_OUT_SINE); 82 | } 83 | 84 | .main-view-content { 85 | position: relative; /* needed to move header content */ 86 | box-shadow: 0 0 15px #888; 87 | @include size(480px 430px); 88 | background: #FFF image-url("header.png") no-repeat top left; 89 | padding-top: 140px; 90 | border: 10px solid #2c2c2c; 91 | 92 | @include transition(height $TRANSITION_DURATION $TRANSITION_EASE_OUT_SINE); 93 | } 94 | 95 | .main-view-content-login { 96 | height: 450px; 97 | @include transition(height $TRANSITION_DURATION $TRANSITION_EASE_OUT_SINE); 98 | } 99 | 100 | .main-view-content-employees { 101 | height: 465px; 102 | @include transition(height $TRANSITION_DURATION $TRANSITION_EASE_OUT_SINE); 103 | } 104 | 105 | .main-view-content-edit, 106 | .main-view-content-create{ 107 | height: 560px; 108 | @include transition(height $TRANSITION_DURATION $TRANSITION_EASE_OUT_SINE); 109 | } 110 | 111 | .main-view-content-enter-setup{ 112 | @include transition(opacity .3s .1s $TRANSITION_EASE_LINEAR); 113 | } 114 | 115 | .main-view-content-enter-setup{ 116 | opacity:0; 117 | } 118 | .main-view-content-enter-setup.main-view-content-enter-start{ 119 | opacity:1; 120 | } 121 | 122 | .error-message{ 123 | color: #FF3366; 124 | } 125 | 126 | header 127 | footer 128 | .content{ 129 | width: $MAIN_CONTENT_WIDTH; 130 | margin: $MAIN_CONTENT_MARGIN; 131 | } 132 | 133 | /********************************** 134 | header 135 | **********************************/ 136 | 137 | header{ 138 | 139 | height: 50px; 140 | 141 | div{ 142 | position: relative; 143 | height: 50px; 144 | } 145 | 146 | p#greetings{ 147 | @include position(absolute, 0 0 4px 40px); 148 | } 149 | 150 | p.main-button{ 151 | @include position(absolute, 0 15px -5px 0); 152 | } 153 | } 154 | 155 | 156 | .header-show-setup, 157 | .header-hide-setup { 158 | @include transition(top $TRANSITION_DURATION $TRANSITION_EASE_OUT_SINE); 159 | } 160 | 161 | .header-hide-setup { 162 | top:0; 163 | } 164 | .header-hide-setup.header-hide-start { 165 | top:50px; 166 | } 167 | 168 | .header-show-setup { 169 | top:50px; 170 | } 171 | .header-show-setup.header-show-start { 172 | top:0; 173 | } 174 | 175 | /********************************** 176 | footer 177 | **********************************/ 178 | 179 | footer{ 180 | padding-top: 8px; 181 | font-size: 15px; 182 | line-height: 21px; 183 | color: #888; 184 | text-align: center; 185 | text-shadow: white 1px 1px 1px; 186 | 187 | a{ 188 | text-decoration: underline; 189 | color: #888; 190 | &:hover{ 191 | color: #2c2c2c; 192 | } 193 | } 194 | } 195 | 196 | 197 | /* 198 | @overridden 199 | .ng-invalid 200 | .ng-valid 201 | are CSS classes used by angular 202 | http://docs-next.angularjs.org/guide/dev_guide.templates.css-styling 203 | */ 204 | form{ 205 | @include box-align(stretch); 206 | @include box-orient(vertical); 207 | padding: 20px; 208 | 209 | label{ 210 | @include box(horizontal, start, center); 211 | padding: 10px 0; 212 | } 213 | 214 | label span{ 215 | display: block; 216 | width: 100px; 217 | } 218 | 219 | p.error-message{ 220 | width: 100%; 221 | } 222 | 223 | input{ 224 | @include box-sizing(border-box); 225 | @include size(270px 35px); 226 | 227 | padding: 5px 10px; 228 | padding-right: 20px; 229 | 230 | box-shadow: 0px 0px 3px #ccc; 231 | 232 | border: 1px solid #AAA; 233 | border-radius: 0 10px 15px #eee; 234 | 235 | font-size: $MAIN_FONT_SIZE; 236 | 237 | &:focus{ 238 | background: #FFF; 239 | border: 1px solid #555; 240 | box-shadow: 0 0 3px #aaa; 241 | } 242 | 243 | &:focus.ng-invalid{ 244 | background: #FFF image-url("invalid.png") no-repeat 98% center !important; 245 | border-color: #FF3366; 246 | } 247 | } 248 | 249 | input.ng-valid{ 250 | background: #FFF image-url("valid.png") no-repeat 98% center; 251 | } 252 | 253 | div.formFooter{ 254 | @include box(horizontal, start, center); 255 | margin-top: 10px; 256 | } 257 | 258 | button[type="submit"].main-button{ 259 | margin: 10px 20px 0 100px; 260 | } 261 | 262 | p.main-button{ 263 | margin: 10px 20px 0 0; 264 | } 265 | 266 | p.bDelete, 267 | p.bCancel{ 268 | color: #888; 269 | text-decoration: underline; 270 | 271 | &:hover{ 272 | color: #363636; 273 | cursor: pointer; 274 | } 275 | } 276 | 277 | } 278 | 279 | 280 | legend{ 281 | display: none; 282 | } 283 | 284 | fieldset{ 285 | border: none; 286 | } 287 | 288 | #login-form{ 289 | margin-top: 10px; 290 | 291 | p.error-message{ 292 | padding-left: 100px; 293 | } 294 | p.info { 295 | padding-left: 100px; 296 | padding-top: 10px; 297 | font-size: 13px; 298 | color: #AAA; 299 | } 300 | } 301 | 302 | .main-button{ 303 | 304 | width: 125px; 305 | padding: 5px 0; 306 | 307 | background-color: #2c2c2c; 308 | border: 5px solid #FFF; 309 | box-shadow: 0 0 8px #888; 310 | 311 | font-size: 17px; 312 | text-align: center; 313 | color: #FFF; 314 | 315 | @include transition(background-color $TRANSITION_DURATION $TRANSITION_EASE_LINEAR); 316 | @include transition(color $TRANSITION_DURATION $TRANSITION_EASE_LINEAR); 317 | @include transition(border-color $TRANSITION_DURATION $TRANSITION_EASE_LINEAR); 318 | 319 | &:hover{ 320 | background-color: #FFF; 321 | border-color: #2c2c2c; 322 | color: #2c2c2c; 323 | cursor: pointer; 324 | } 325 | 326 | &:disabled{ 327 | opacity: .5; 328 | &:hover{ 329 | background-color: #7989D6; 330 | border-color: #FFF; 331 | color: #FFF; 332 | cursor: default; 333 | } 334 | } 335 | } 336 | 337 | .subButton{ 338 | display:block; 339 | 340 | width: 100px; 341 | padding: 5px 0; 342 | 343 | @include linear-gradient(#FFF, #F4F4F4); 344 | border: 1px solid #2c2c2c; 345 | 346 | color: #2c2c2c; 347 | font-size: 15px; 348 | 349 | text-decoration: none; 350 | text-align: center; 351 | 352 | cursor: pointer; 353 | 354 | &:hover{ 355 | opacity: .85; 356 | @include linear-gradient(#F4F4F4, #FFF); 357 | } 358 | 359 | &.disabled, 360 | &.disabled:hover{ 361 | opacity: .5; 362 | color: #363636; 363 | @include linear-gradient(#FFF, #F4F4F4); 364 | cursor: default; 365 | } 366 | } 367 | 368 | #employee-list-container{ 369 | overflow-y: auto; 370 | height: 245px; 371 | } 372 | 373 | #employee-list{ 374 | width: 100%; 375 | 376 | li{ 377 | padding: 15px 30px; 378 | @include transition(background-color $TRANSITION_DURATION $TRANSITION_EASE_LINEAR); 379 | 380 | color: #2c2c2c; 381 | 382 | &:hover{ 383 | background-color: #2c2c2c; 384 | color: #FFF; 385 | cursor: pointer; 386 | } 387 | 388 | &.active{ 389 | background-color: #2c2c2c; 390 | color: #FFF; 391 | } 392 | } 393 | } 394 | 395 | 396 | #sub-nav{ 397 | @include box(horizontal, start, center); 398 | height: 60px; 399 | background-color: #DEDEDE; 400 | 401 | li{ 402 | padding-left: 30px; 403 | } 404 | } 405 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/angular-resource-1.1.4.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.1.4 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) { 7 | 'use strict'; 8 | 9 | /** 10 | * @ngdoc overview 11 | * @name ngResource 12 | * @description 13 | */ 14 | 15 | /** 16 | * @ngdoc object 17 | * @name ngResource.$resource 18 | * @requires $http 19 | * 20 | * @description 21 | * A factory which creates a resource object that lets you interact with 22 | * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. 23 | * 24 | * The returned resource object has action methods which provide high-level behaviors without 25 | * the need to interact with the low level {@link ng.$http $http} service. 26 | * 27 | * # Installation 28 | * To use $resource make sure you have included the `angular-resource.js` that comes in Angular 29 | * package. You also can find this stuff in {@link http://code.angularjs.org/ code.angularjs.org}. 30 | * Finally load the module in your application: 31 | * 32 | * angular.module('app', ['ngResource']); 33 | * 34 | * and you ready to get started! 35 | * 36 | * @param {string} url A parametrized URL template with parameters prefixed by `:` as in 37 | * `/user/:username`. If you are using a URL with a port number (e.g. 38 | * `http://example.com:8080/api`), you'll need to escape the colon character before the port 39 | * number, like this: `$resource('http://example.com\\:8080/api')`. 40 | * 41 | * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in 42 | * `actions` methods. If any of the parameter value is a function, it will be executed every time 43 | * when a param value needs to be obtained for a request (unless the param was overridden). 44 | * 45 | * Each key value in the parameter object is first bound to url template if present and then any 46 | * excess keys are appended to the url search query after the `?`. 47 | * 48 | * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in 49 | * URL `/path/greet?salutation=Hello`. 50 | * 51 | * If the parameter value is prefixed with `@` then the value of that parameter is extracted from 52 | * the data object (useful for non-GET operations). 53 | * 54 | * @param {Object.=} actions Hash with declaration of custom action that should extend the 55 | * default set of resource actions. The declaration should be created in the format of {@link 56 | * ng.$http#Parameters $http.config}: 57 | * 58 | * {action1: {method:?, params:?, isArray:?, headers:?, ...}, 59 | * action2: {method:?, params:?, isArray:?, headers:?, ...}, 60 | * ...} 61 | * 62 | * Where: 63 | * 64 | * - **`action`** – {string} – The name of action. This name becomes the name of the method on your 65 | * resource object. 66 | * - **`method`** – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`, 67 | * and `JSONP`. 68 | * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of the 69 | * parameter value is a function, it will be executed every time when a param value needs to be 70 | * obtained for a request (unless the param was overridden). 71 | * - **`url`** – {string} – action specific `url` override. The url templating is supported just like 72 | * for the resource-level urls. 73 | * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array, see 74 | * `returns` section. 75 | * - **`transformRequest`** – `{function(data, headersGetter)|Array.}` – 76 | * transform function or an array of such functions. The transform function takes the http 77 | * request body and headers and returns its transformed (typically serialized) version. 78 | * - **`transformResponse`** – `{function(data, headersGetter)|Array.}` – 79 | * transform function or an array of such functions. The transform function takes the http 80 | * response body and headers and returns its transformed (typically deserialized) version. 81 | * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the 82 | * GET request, otherwise if a cache instance built with 83 | * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for 84 | * caching. 85 | * - **`timeout`** – `{number}` – timeout in milliseconds. 86 | * - **`withCredentials`** - `{boolean}` - whether to to set the `withCredentials` flag on the 87 | * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5 88 | * requests with credentials} for more information. 89 | * - **`responseType`** - `{string}` - see {@link 90 | * https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}. 91 | * 92 | * @returns {Object} A resource "class" object with methods for the default set of resource actions 93 | * optionally extended with custom `actions`. The default set contains these actions: 94 | * 95 | * { 'get': {method:'GET'}, 96 | * 'save': {method:'POST'}, 97 | * 'query': {method:'GET', isArray:true}, 98 | * 'remove': {method:'DELETE'}, 99 | * 'delete': {method:'DELETE'} }; 100 | * 101 | * Calling these methods invoke an {@link ng.$http} with the specified http method, 102 | * destination and parameters. When the data is returned from the server then the object is an 103 | * instance of the resource class. The actions `save`, `remove` and `delete` are available on it 104 | * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, 105 | * read, update, delete) on server-side data like this: 106 | *
107 |    var User = $resource('/user/:userId', {userId:'@id'});
108 |    var user = User.get({userId:123}, function() {
109 |           user.abc = true;
110 |           user.$save();
111 |         });
112 |    
113 | * 114 | * It is important to realize that invoking a $resource object method immediately returns an 115 | * empty reference (object or array depending on `isArray`). Once the data is returned from the 116 | * server the existing reference is populated with the actual data. This is a useful trick since 117 | * usually the resource is assigned to a model which is then rendered by the view. Having an empty 118 | * object results in no rendering, once the data arrives from the server then the object is 119 | * populated with the data and the view automatically re-renders itself showing the new data. This 120 | * means that in most case one never has to write a callback function for the action methods. 121 | * 122 | * The action methods on the class object or instance object can be invoked with the following 123 | * parameters: 124 | * 125 | * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` 126 | * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` 127 | * - non-GET instance actions: `instance.$action([parameters], [success], [error])` 128 | * 129 | * 130 | * The Resource instances and collection have these additional properties: 131 | * 132 | * - `$then`: the `then` method of a {@link ng.$q promise} derived from the underlying 133 | * {@link ng.$http $http} call. 134 | * 135 | * The success callback for the `$then` method will be resolved if the underlying `$http` requests 136 | * succeeds. 137 | * 138 | * The success callback is called with a single object which is the {@link ng.$http http response} 139 | * object extended with a new property `resource`. This `resource` property is a reference to the 140 | * result of the resource action — resource object or array of resources. 141 | * 142 | * The error callback is called with the {@link ng.$http http response} object when an http 143 | * error occurs. 144 | * 145 | * - `$resolved`: true if the promise has been resolved (either with success or rejection); 146 | * Knowing if the Resource has been resolved is useful in data-binding. 147 | * 148 | * @example 149 | * 150 | * # Credit card resource 151 | * 152 | *
153 |    // Define CreditCard class
154 |    var CreditCard = $resource('/user/:userId/card/:cardId',
155 |    {userId:123, cardId:'@id'}, {
156 |        charge: {method:'POST', params:{charge:true}}
157 |       });
158 | 
159 |    // We can retrieve a collection from the server
160 |    var cards = CreditCard.query(function() {
161 |        // GET: /user/123/card
162 |        // server returns: [ {id:456, number:'1234', name:'Smith'} ];
163 | 
164 |        var card = cards[0];
165 |        // each item is an instance of CreditCard
166 |        expect(card instanceof CreditCard).toEqual(true);
167 |        card.name = "J. Smith";
168 |        // non GET methods are mapped onto the instances
169 |        card.$save();
170 |        // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
171 |        // server returns: {id:456, number:'1234', name: 'J. Smith'};
172 | 
173 |        // our custom method is mapped as well.
174 |        card.$charge({amount:9.99});
175 |        // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
176 |      });
177 | 
178 |    // we can create an instance as well
179 |    var newCard = new CreditCard({number:'0123'});
180 |    newCard.name = "Mike Smith";
181 |    newCard.$save();
182 |    // POST: /user/123/card {number:'0123', name:'Mike Smith'}
183 |    // server returns: {id:789, number:'01234', name: 'Mike Smith'};
184 |    expect(newCard.id).toEqual(789);
185 |    * 
186 | * 187 | * The object returned from this function execution is a resource "class" which has "static" method 188 | * for each action in the definition. 189 | * 190 | * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and `headers`. 191 | * When the data is returned from the server then the object is an instance of the resource type and 192 | * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD 193 | * operations (create, read, update, delete) on server-side data. 194 | 195 |
196 |    var User = $resource('/user/:userId', {userId:'@id'});
197 |    var user = User.get({userId:123}, function() {
198 |        user.abc = true;
199 |        user.$save();
200 |      });
201 |    
202 | * 203 | * It's worth noting that the success callback for `get`, `query` and other method gets passed 204 | * in the response that came from the server as well as $http header getter function, so one 205 | * could rewrite the above example and get access to http headers as: 206 | * 207 |
208 |    var User = $resource('/user/:userId', {userId:'@id'});
209 |    User.get({userId:123}, function(u, getResponseHeaders){
210 |        u.abc = true;
211 |        u.$save(function(u, putResponseHeaders) {
212 |          //u => saved user object
213 |          //putResponseHeaders => $http header getter
214 |        });
215 |      });
216 |    
217 | 218 | * # Buzz client 219 | 220 | Let's look at what a buzz client created with the `$resource` service looks like: 221 | 222 | 223 | 243 | 244 |
245 | 246 | 247 |
248 |
249 |

250 | 251 | {{item.actor.name}} 252 | Expand replies: {{item.links.replies[0].count}} 253 |

254 | {{item.object.content | html}} 255 |
256 | 257 | {{reply.actor.name}}: {{reply.content | html}} 258 |
259 |
260 |
261 |
262 | 263 | 264 |
265 | */ 266 | angular.module('ngResource', ['ng']). 267 | factory('$resource', ['$http', '$parse', function($http, $parse) { 268 | var DEFAULT_ACTIONS = { 269 | 'get': {method:'GET'}, 270 | 'save': {method:'POST'}, 271 | 'query': {method:'GET', isArray:true}, 272 | 'remove': {method:'DELETE'}, 273 | 'delete': {method:'DELETE'} 274 | }; 275 | var noop = angular.noop, 276 | forEach = angular.forEach, 277 | extend = angular.extend, 278 | copy = angular.copy, 279 | isFunction = angular.isFunction, 280 | getter = function(obj, path) { 281 | return $parse(path)(obj); 282 | }; 283 | 284 | /** 285 | * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 286 | * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 287 | * segments: 288 | * segment = *pchar 289 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 290 | * pct-encoded = "%" HEXDIG HEXDIG 291 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 292 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 293 | * / "*" / "+" / "," / ";" / "=" 294 | */ 295 | function encodeUriSegment(val) { 296 | return encodeUriQuery(val, true). 297 | replace(/%26/gi, '&'). 298 | replace(/%3D/gi, '='). 299 | replace(/%2B/gi, '+'); 300 | } 301 | 302 | 303 | /** 304 | * This method is intended for encoding *key* or *value* parts of query component. We need a custom 305 | * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 306 | * encoded per http://tools.ietf.org/html/rfc3986: 307 | * query = *( pchar / "/" / "?" ) 308 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 309 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 310 | * pct-encoded = "%" HEXDIG HEXDIG 311 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 312 | * / "*" / "+" / "," / ";" / "=" 313 | */ 314 | function encodeUriQuery(val, pctEncodeSpaces) { 315 | return encodeURIComponent(val). 316 | replace(/%40/gi, '@'). 317 | replace(/%3A/gi, ':'). 318 | replace(/%24/g, '$'). 319 | replace(/%2C/gi, ','). 320 | replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 321 | } 322 | 323 | function Route(template, defaults) { 324 | this.template = template = template + '#'; 325 | this.defaults = defaults || {}; 326 | this.urlParams = {}; 327 | } 328 | 329 | Route.prototype = { 330 | setUrlParams: function(config, params, actionUrl) { 331 | var self = this, 332 | url = actionUrl || self.template, 333 | val, 334 | encodedVal; 335 | 336 | var urlParams = self.urlParams = {}; 337 | forEach(url.split(/\W/), function(param){ 338 | if (param && (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) { 339 | urlParams[param] = true; 340 | } 341 | }); 342 | url = url.replace(/\\:/g, ':'); 343 | 344 | params = params || {}; 345 | forEach(self.urlParams, function(_, urlParam){ 346 | val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; 347 | if (angular.isDefined(val) && val !== null) { 348 | encodedVal = encodeUriSegment(val); 349 | url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), encodedVal + "$1"); 350 | } else { 351 | url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match, 352 | leadingSlashes, tail) { 353 | if (tail.charAt(0) == '/') { 354 | return tail; 355 | } else { 356 | return leadingSlashes + tail; 357 | } 358 | }); 359 | } 360 | }); 361 | 362 | // set the url 363 | config.url = url.replace(/\/?#$/, '').replace(/\/*$/, ''); 364 | 365 | // set params - delegate param encoding to $http 366 | forEach(params, function(value, key){ 367 | if (!self.urlParams[key]) { 368 | config.params = config.params || {}; 369 | config.params[key] = value; 370 | } 371 | }); 372 | } 373 | }; 374 | 375 | 376 | function ResourceFactory(url, paramDefaults, actions) { 377 | var route = new Route(url); 378 | 379 | actions = extend({}, DEFAULT_ACTIONS, actions); 380 | 381 | function extractParams(data, actionParams){ 382 | var ids = {}; 383 | actionParams = extend({}, paramDefaults, actionParams); 384 | forEach(actionParams, function(value, key){ 385 | if (isFunction(value)) { value = value(); } 386 | ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value; 387 | }); 388 | return ids; 389 | } 390 | 391 | function Resource(value){ 392 | copy(value || {}, this); 393 | } 394 | 395 | forEach(actions, function(action, name) { 396 | action.method = angular.uppercase(action.method); 397 | var hasBody = action.method == 'POST' || action.method == 'PUT' || action.method == 'PATCH'; 398 | Resource[name] = function(a1, a2, a3, a4) { 399 | var params = {}; 400 | var data; 401 | var success = noop; 402 | var error = null; 403 | var promise; 404 | 405 | switch(arguments.length) { 406 | case 4: 407 | error = a4; 408 | success = a3; 409 | //fallthrough 410 | case 3: 411 | case 2: 412 | if (isFunction(a2)) { 413 | if (isFunction(a1)) { 414 | success = a1; 415 | error = a2; 416 | break; 417 | } 418 | 419 | success = a2; 420 | error = a3; 421 | //fallthrough 422 | } else { 423 | params = a1; 424 | data = a2; 425 | success = a3; 426 | break; 427 | } 428 | case 1: 429 | if (isFunction(a1)) success = a1; 430 | else if (hasBody) data = a1; 431 | else params = a1; 432 | break; 433 | case 0: break; 434 | default: 435 | throw "Expected between 0-4 arguments [params, data, success, error], got " + 436 | arguments.length + " arguments."; 437 | } 438 | 439 | var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data)); 440 | var httpConfig = {}, 441 | promise; 442 | 443 | forEach(action, function(value, key) { 444 | if (key != 'params' && key != 'isArray' ) { 445 | httpConfig[key] = copy(value); 446 | } 447 | }); 448 | httpConfig.data = data; 449 | route.setUrlParams(httpConfig, extend({}, extractParams(data, action.params || {}), params), action.url); 450 | 451 | function markResolved() { value.$resolved = true; } 452 | 453 | promise = $http(httpConfig); 454 | value.$resolved = false; 455 | 456 | promise.then(markResolved, markResolved); 457 | value.$then = promise.then(function(response) { 458 | var data = response.data; 459 | var then = value.$then, resolved = value.$resolved; 460 | 461 | if (data) { 462 | if (action.isArray) { 463 | value.length = 0; 464 | forEach(data, function(item) { 465 | value.push(new Resource(item)); 466 | }); 467 | } else { 468 | copy(data, value); 469 | value.$then = then; 470 | value.$resolved = resolved; 471 | } 472 | } 473 | 474 | (success||noop)(value, response.headers); 475 | 476 | response.resource = value; 477 | return response; 478 | }, error).then; 479 | 480 | return value; 481 | }; 482 | 483 | 484 | Resource.prototype['$' + name] = function(a1, a2, a3) { 485 | var params = extractParams(this), 486 | success = noop, 487 | error; 488 | 489 | switch(arguments.length) { 490 | case 3: params = a1; success = a2; error = a3; break; 491 | case 2: 492 | case 1: 493 | if (isFunction(a1)) { 494 | success = a1; 495 | error = a2; 496 | } else { 497 | params = a1; 498 | success = a2 || noop; 499 | } 500 | case 0: break; 501 | default: 502 | throw "Expected between 1-3 arguments [params, success, error], got " + 503 | arguments.length + " arguments."; 504 | } 505 | var data = hasBody ? this : undefined; 506 | Resource[name].call(this, params, data, success, error); 507 | }; 508 | }); 509 | 510 | Resource.bind = function(additionalParamDefaults){ 511 | return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); 512 | }; 513 | 514 | return Resource; 515 | } 516 | 517 | return ResourceFactory; 518 | }]); 519 | 520 | 521 | })(window, window.angular); --------------------------------------------------------------------------------