├── 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 |
9 |
13 | {{employee.firstName}} {{employee.lastName}}
14 |
15 |
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 |
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 |
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 |
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 | [](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 |
fetch
247 |
248 |
249 |
254 | {{item.object.content | html}}
255 |
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);
--------------------------------------------------------------------------------