From dffe30009c3a7d70145569b0ec46ead0318ab9e2 Mon Sep 17 00:00:00 2001
From: Shari Meggs
Date: Tue, 18 Oct 2016 13:56:35 -0700
Subject: [PATCH 001/144] Test gems and env stuff fully loaded
---
.gitignore | 8 +-
.ruby-gemset | 1 -
.ruby-version | 1 -
Gemfile | 21 ++++-
Gemfile.lock | 176 +++++++++++++++++++++++++++-------------
README.md | 194 --------------------------------------------
README.rdoc | 28 +++++++
bin/spring | 7 +-
config/database.yml | 78 ++----------------
config/secrets.yml | 4 +-
test/test_helper.rb | 3 +
11 files changed, 187 insertions(+), 334 deletions(-)
delete mode 100644 .ruby-gemset
delete mode 100644 .ruby-version
delete mode 100644 README.md
create mode 100644 README.rdoc
diff --git a/.gitignore b/.gitignore
index 6db3c9a5bc..e38a9978b9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,9 +7,13 @@
# Ignore bundler config.
/.bundle
+# Ignore the default SQLite database.
+/db/*.sqlite3
+/db/*.sqlite3-journal
+
# Ignore all logfiles and tempfiles.
/log/*
!/log/.keep
/tmp
-
-.DS_Store
+coverage
+.env
diff --git a/.ruby-gemset b/.ruby-gemset
deleted file mode 100644
index d5c660823f..0000000000
--- a/.ruby-gemset
+++ /dev/null
@@ -1 +0,0 @@
-betsy
diff --git a/.ruby-version b/.ruby-version
deleted file mode 100644
index 2bf1c1ccf3..0000000000
--- a/.ruby-version
+++ /dev/null
@@ -1 +0,0 @@
-2.3.1
diff --git a/Gemfile b/Gemfile
index c69f4ddde9..d89f616a42 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,10 +1,10 @@
source 'https://rubygems.org'
-ruby '2.3.1'
+
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
-gem 'rails', '4.2.6'
-# Use postgresql as the database for Active Record
-# gem 'pg', '~> 0.15'
+gem 'rails', '4.2.7'
+# Use sqlite3 as the database for Active Record
+gem 'sqlite3'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0'
# Use Uglifier as compressor for JavaScript assets
@@ -22,6 +22,8 @@ gem 'turbolinks'
gem 'jbuilder', '~> 2.0'
# bundle exec rake doc:rails generates the API under doc/api.
gem 'sdoc', '~> 0.4.0', group: :doc
+gem "omniauth"
+gem "omniauth-github"
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'
@@ -35,10 +37,21 @@ gem 'sdoc', '~> 0.4.0', group: :doc
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug'
+ gem 'dotenv-rails'
+end
+
+
+group :test do
+ gem 'minitest-reporters'
+ gem 'simplecov'
end
group :development do
# Access an IRB console on exception pages or by using <%= console %> in views
+
+ gem "better_errors"
+ gem "binding_of_caller"
+ gem "pry-rails"
gem 'web-console', '~> 2.0'
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
diff --git a/Gemfile.lock b/Gemfile.lock
index 20975578b8..c9a6ee3c34 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,46 +1,52 @@
GEM
remote: https://rubygems.org/
specs:
- actionmailer (4.2.6)
- actionpack (= 4.2.6)
- actionview (= 4.2.6)
- activejob (= 4.2.6)
+ actionmailer (4.2.7)
+ actionpack (= 4.2.7)
+ actionview (= 4.2.7)
+ activejob (= 4.2.7)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5)
- actionpack (4.2.6)
- actionview (= 4.2.6)
- activesupport (= 4.2.6)
+ actionpack (4.2.7)
+ actionview (= 4.2.7)
+ activesupport (= 4.2.7)
rack (~> 1.6)
rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (4.2.6)
- activesupport (= 4.2.6)
+ actionview (4.2.7)
+ activesupport (= 4.2.7)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- activejob (4.2.6)
- activesupport (= 4.2.6)
+ activejob (4.2.7)
+ activesupport (= 4.2.7)
globalid (>= 0.3.0)
- activemodel (4.2.6)
- activesupport (= 4.2.6)
+ activemodel (4.2.7)
+ activesupport (= 4.2.7)
builder (~> 3.1)
- activerecord (4.2.6)
- activemodel (= 4.2.6)
- activesupport (= 4.2.6)
+ activerecord (4.2.7)
+ activemodel (= 4.2.7)
+ activesupport (= 4.2.7)
arel (~> 6.0)
- activesupport (4.2.6)
+ activesupport (4.2.7)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
+ ansi (1.5.0)
arel (6.0.3)
+ better_errors (2.1.1)
+ coderay (>= 1.0.0)
+ erubis (>= 2.6.6)
+ rack (>= 0.9.0)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
builder (3.2.2)
- byebug (8.2.5)
+ byebug (9.0.6)
+ coderay (1.1.1)
coffee-rails (4.1.1)
coffee-script (>= 2.2.0)
railties (>= 4.0.0, < 5.1.x)
@@ -48,46 +54,84 @@ GEM
coffee-script-source
execjs
coffee-script-source (1.10.0)
- concurrent-ruby (1.0.1)
+ concurrent-ruby (1.0.2)
debug_inspector (0.0.2)
+ docile (1.1.5)
+ dotenv (2.1.1)
+ dotenv-rails (2.1.1)
+ dotenv (= 2.1.1)
+ railties (>= 4.0, < 5.1)
erubis (2.7.0)
- execjs (2.6.0)
- globalid (0.3.6)
+ execjs (2.7.0)
+ faraday (0.9.2)
+ multipart-post (>= 1.2, < 3)
+ globalid (0.3.7)
activesupport (>= 4.1.0)
+ hashie (3.4.6)
i18n (0.7.0)
- jbuilder (2.4.1)
+ jbuilder (2.6.0)
activesupport (>= 3.0.0, < 5.1)
multi_json (~> 1.2)
- jquery-rails (4.1.1)
+ jquery-rails (4.2.1)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (1.8.3)
+ jwt (1.5.6)
loofah (2.0.3)
nokogiri (>= 1.5.9)
mail (2.6.4)
mime-types (>= 1.16, < 4)
- mime-types (3.0)
+ method_source (0.8.2)
+ mime-types (3.1)
mime-types-data (~> 3.2015)
- mime-types-data (3.2016.0221)
- mini_portile2 (2.0.0)
- minitest (5.8.4)
- multi_json (1.11.3)
- nokogiri (1.6.7.2)
- mini_portile2 (~> 2.0.0.rc2)
+ mime-types-data (3.2016.0521)
+ mini_portile2 (2.1.0)
+ minitest (5.9.1)
+ minitest-reporters (1.1.11)
+ ansi
+ builder
+ minitest (>= 5.0)
+ ruby-progressbar
+ multi_json (1.12.1)
+ multi_xml (0.5.5)
+ multipart-post (2.0.0)
+ nokogiri (1.6.8.1)
+ mini_portile2 (~> 2.1.0)
+ oauth2 (1.2.0)
+ faraday (>= 0.8, < 0.10)
+ jwt (~> 1.0)
+ multi_json (~> 1.3)
+ multi_xml (~> 0.5)
+ rack (>= 1.2, < 3)
+ omniauth (1.3.1)
+ hashie (>= 1.2, < 4)
+ rack (>= 1.0, < 3)
+ omniauth-github (1.1.2)
+ omniauth (~> 1.0)
+ omniauth-oauth2 (~> 1.1)
+ omniauth-oauth2 (1.4.0)
+ oauth2 (~> 1.0)
+ omniauth (~> 1.2)
+ pry (0.10.4)
+ coderay (~> 1.1.0)
+ method_source (~> 0.8.1)
+ slop (~> 3.4)
+ pry-rails (0.3.4)
+ pry (>= 0.9.10)
rack (1.6.4)
rack-test (0.6.3)
rack (>= 1.0)
- rails (4.2.6)
- actionmailer (= 4.2.6)
- actionpack (= 4.2.6)
- actionview (= 4.2.6)
- activejob (= 4.2.6)
- activemodel (= 4.2.6)
- activerecord (= 4.2.6)
- activesupport (= 4.2.6)
+ rails (4.2.7)
+ actionmailer (= 4.2.7)
+ actionpack (= 4.2.7)
+ actionview (= 4.2.7)
+ activejob (= 4.2.7)
+ activemodel (= 4.2.7)
+ activerecord (= 4.2.7)
+ activesupport (= 4.2.7)
bundler (>= 1.3.0, < 2.0)
- railties (= 4.2.6)
+ railties (= 4.2.7)
sprockets-rails
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
@@ -97,40 +141,50 @@ GEM
rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
- railties (4.2.6)
- actionpack (= 4.2.6)
- activesupport (= 4.2.6)
+ railties (4.2.7)
+ actionpack (= 4.2.7)
+ activesupport (= 4.2.7)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
- rake (11.1.2)
+ rake (11.3.0)
rdoc (4.2.2)
json (~> 1.4)
+ ruby-progressbar (1.8.1)
sass (3.4.22)
- sass-rails (5.0.4)
- railties (>= 4.0.0, < 5.0)
+ sass-rails (5.0.6)
+ railties (>= 4.0.0, < 6)
sass (~> 3.1)
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
- sdoc (0.4.1)
+ sdoc (0.4.2)
json (~> 1.7, >= 1.7.7)
rdoc (~> 4.0)
- spring (1.7.1)
- sprockets (3.6.0)
+ simplecov (0.12.0)
+ docile (~> 1.1.0)
+ json (>= 1.8, < 3)
+ simplecov-html (~> 0.10.0)
+ simplecov-html (0.10.0)
+ slop (3.6.0)
+ spring (2.0.0)
+ activesupport (>= 4.2)
+ sprockets (3.7.0)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
- sprockets-rails (3.0.4)
+ sprockets-rails (3.2.0)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
+ sqlite3 (1.3.12)
thor (0.19.1)
thread_safe (0.3.5)
- tilt (2.0.2)
- turbolinks (2.5.3)
- coffee-rails
+ tilt (2.0.5)
+ turbolinks (5.0.1)
+ turbolinks-source (~> 5)
+ turbolinks-source (5.0.0)
tzinfo (1.2.2)
thread_safe (~> 0.1)
- uglifier (3.0.0)
+ uglifier (3.0.2)
execjs (>= 0.3.0, < 3)
web-console (2.3.0)
activemodel (>= 4.0)
@@ -142,20 +196,26 @@ PLATFORMS
ruby
DEPENDENCIES
+ better_errors
+ binding_of_caller
byebug
coffee-rails (~> 4.1.0)
+ dotenv-rails
jbuilder (~> 2.0)
jquery-rails
- rails (= 4.2.6)
+ minitest-reporters
+ omniauth
+ omniauth-github
+ pry-rails
+ rails (= 4.2.7)
sass-rails (~> 5.0)
sdoc (~> 0.4.0)
+ simplecov
spring
+ sqlite3
turbolinks
uglifier (>= 1.3.0)
web-console (~> 2.0)
-RUBY VERSION
- ruby 2.3.1p112
-
BUNDLED WITH
- 1.13.5
+ 1.13.1
diff --git a/README.md b/README.md
deleted file mode 100644
index 220a0423c4..0000000000
--- a/README.md
+++ /dev/null
@@ -1,194 +0,0 @@
-# bEtsy
-[b]Etsy will be an online store where a wide variety of products can be listed and sold by any user. In this project we will focus on reinforcing the major components of Rails, Model Validation, as well as introducing some more complex logic such as user authentication.
-
-## Project Learning Goals
-- Core comprehension of:
- - Routes
- - Controllers
- - Models
- - Views
-- User based application logic
-- User authentication
-- Agile practices
-- Feature branch management with Git
-- Group project ownership
-
-## Guidelines
-- Groups of three or four will collaborate in pairs or individually and will report to their assigned Project Manager (one of the instructors)
-- Use a task manager like [Trello](http://trello.com) to track your team's efforts
-- Build a logical user-flow that moves across multiple controllers and models
-- Use HTML/CSS to style your website
-
-### Restrictions
-- Do not use gems for user authentication (such as Devise)
-- You must use oAuth for user authentication.
-
-## Getting Started
-1. As a group decide on an app name (this may help lead the aesthetic)
-1. As a group decide on a team name (this will amuse your instructors)
-1. Have one person on your team fork/clone the project master as per usual
- 1. Add all other team members as collaborators
- 1. Each team member should clone the repo to their computer
-1. Figure out your workflow for the project, re: Git and Task management
- 1. Determine who will be the Stand Up Leader and Task Leader for the first week
-1. Create a Trello board and ensure that all team members and instructors have access
-1. Review the User Stories below and create Trello tasks to represent them
-1. Slack your team name, app name, and link to your trello board to your Project Manager
-
-## Expectations
-Build an online system for listing, selling, reviewing, and buying a wide variety of products listed by multiple merchants.
-
-### General Requirements
-- Unit tests and/or specs for
- - Controllers
- - Models
- - Routes
-- Test code coverage (using SimpleCov - remember me!)
- - 90% for all controller and model classes
-
-### User Stories
-#### Guest User (Unauthenticated)
-As a guest to the website (not signed in) I **can**:
-
-- Browse all products
-- Browse products by category
-- Browse products by merchant (users)
-- View any individual product with additional details
-- Leave a review for a product providing:
- - A Text review
- - A rating out of 5
-- Add in-stock products to my cart
-- Remove products from my cart
-- Change the quantity of an existing product in my cart
-- Purchase the items in my cart, providing:
- - Email Address
- - Mailing Address
- - Name on credit card
- - Credit card number
- - Credit cart expiration
- - Credit Card CVV (security code)
- - Billing zip code
-- Purchasing an order makes the following changes:
- - Reduces the number of inventory for each product
- - Changes the order state from "pending" to "paid"
- - Clears the current cart
-- After purchasing an order, I can view a confirmation screen including:
- - Each item in the order with a quantity and line-item subtotal
- - A link to the item description page
- - Order total price
- - DateTime the order was placed
- - The current status of the order
-- Sign up to be a merchant using OAuth
- - Every merchant must have a username
-- Sign in to my merchant account using OAuth
-
-As a guest I **cannot**:
-
-- Add products to the cart that are out of stock
-- View any link or page to manage any products
-- View any of the account pages
-
-#### Authenticated Users
-As a signed-in user, I **can**:
-
-- Do everything a guest user can do except for sign up and sign in
-- Sign out
-- Create new categories (categories are shared between all merchants)
-- Create a new product providing:
- - name
- - description
- - price
- - photo URL
- - stock
-- Assign my products to any number of categories
-- Retire a product from being sold, which hides it from browsing
-- View an account page to edit/update my existing products
-- View an account page showing my order fulfillment
-- On the order fulfillment page:
- - Total Revenue
- - Total Revenue by status
- - Total number of orders by status
- - Filter orders displayed by status
- - Link to each individual order
- - A list of orders including at least one of my products:
- - Each order item sold by me with a quantity and line-item subtotal
- - A link to the item description page
- - DateTime the order was placed
- - Link to transition the order item to marked as shipped
- - The current status of the order ("pending", "paid", "complete", "cancelled")
-- View an individual order to see the user's:
- - Name
- - Email address
- - Mailing address
- - Last four digits of their credit card
- - Credit card expiration date
-
-As a signed-in user, I **cannot**:
-
-- Review my own products
-- View order items from a shared order that belong to another merchant
-- View another users private data (i.e. order fulfillment or product management)
-
-### Model Validations
-Many of our models will have attributes that are required for our application to use and display data consistently. Each model will have attributes with requirements for a valid record. The requirements are summarized below:
-
-#### Merchant
-- Username must be present
-- Username must be unique
-- Email Address must be present
-- Email Address must be unique
-
-#### Product
-- Name must be present
-- Name must be unique
-- Price must be present
-- Price must be a number
-- Price must be greater than 0
-- Product must belong to a User
-
-#### Order
-- An Order must have one or more Order Items
-
-#### OrderItem
-- Must belong to a Product
-- Must belong to an Order
-- Quantity must be present
-- Quantity must be an integer
-- Quantity must be greater than 0
-
-#### Review
-- Rating must be present
-- Rating must be an integer
-- Rating must be between 1 and 5
-
-## Submission Guidelines
-Your final project must be deployed to [Heroku](http://heroku.com). Your team will open a single pull request for the entire project. Include the link to your Heroku deployment in the PR's description, as well as the team name and the names of all members.
-
-## Team Leaders
-Each team will have team leaders who are responsible for keeping track of each team member's contributions. Rotate leader roles at the beginning of each week; every team member should be in at least one leader role during the project.
-
-- Stand Up Leader
- - Notifies team members about meeting schedule and ensures that everyone is present and ready
- - Takes notes about each person's daily report in Stand Up
- - Keeps the meeting moving
-- Task Leader
- - Leads discussion on task assignment
- - Decide if a task should be completed alone or in a pair
- - Assign tasks based on...
- - Individual comfort
- - Desire
- - Ability
- - Ensures the task list stays up to date
-
-## Stand Up Meetings
-The Project Manager for your team will determine the timing for all Stand Up meetings. Because PMs are managing multiple projects at once this time will be different for each team, and may change from day to day. The meeting schedule will be communicated to the Stand Up Leader as soon as it is determined.
-
-At the end of each day, your team's assigned Project Manager will review the Trello board for all tasks discussed during that day's Stand Up meeting.
-
-## Weekly Demos
-In a real world work environment, a team's success is measured by their product as opposed to each individual's contribution.
-
-Each team will present their progress and respond to questions from their Project Manager each Friday. Every team member will participate in these demos; the PM will ask specific questions regarding
-1. The team's progress and plan for completing the project
-1. The technical decisions and implementation
-1. Every team member's understanding of the underlying technical structures.
diff --git a/README.rdoc b/README.rdoc
new file mode 100644
index 0000000000..dd4e97e22e
--- /dev/null
+++ b/README.rdoc
@@ -0,0 +1,28 @@
+== README
+
+This README would normally document whatever steps are necessary to get the
+application up and running.
+
+Things you may want to cover:
+
+* Ruby version
+
+* System dependencies
+
+* Configuration
+
+* Database creation
+
+* Database initialization
+
+* How to run the test suite
+
+* Services (job queues, cache servers, search engines, etc.)
+
+* Deployment instructions
+
+* ...
+
+
+Please feel free to use a different markup language if you do not plan to run
+rake doc:app .
diff --git a/bin/spring b/bin/spring
index 7fe232c3aa..9bc076b9ea 100755
--- a/bin/spring
+++ b/bin/spring
@@ -7,9 +7,10 @@ unless defined?(Spring)
require 'rubygems'
require 'bundler'
- if (match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m))
- Gem.paths = { 'GEM_PATH' => [Bundler.bundle_path.to_s, *Gem.path].uniq.join(Gem.path_separator) }
- gem 'spring', match[1]
+ lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read)
+ if spring = lockfile.specs.detect { |spec| spec.name == "spring" }
+ Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
+ gem 'spring', spring.version
require 'spring/binstub'
end
end
diff --git a/config/database.yml b/config/database.yml
index 04f24be2ba..1c1a37ca8d 100644
--- a/config/database.yml
+++ b/config/database.yml
@@ -1,85 +1,25 @@
-# PostgreSQL. Versions 8.2 and up are supported.
+# SQLite version 3.x
+# gem install sqlite3
#
-# Install the pg driver:
-# gem install pg
-# On OS X with Homebrew:
-# gem install pg -- --with-pg-config=/usr/local/bin/pg_config
-# On OS X with MacPorts:
-# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config
-# On Windows:
-# gem install pg
-# Choose the win32 build.
-# Install PostgreSQL and put its /bin directory on your path.
-#
-# Configure Using Gemfile
-# gem 'pg'
+# Ensure the SQLite 3 gem is defined in your Gemfile
+# gem 'sqlite3'
#
default: &default
- adapter: postgresql
- encoding: unicode
- # For details on connection pooling, see rails configuration guide
- # http://guides.rubyonrails.org/configuring.html#database-pooling
+ adapter: sqlite3
pool: 5
+ timeout: 5000
development:
<<: *default
- database: betsy_development
-
- # The specified database role being used to connect to postgres.
- # To create additional roles in postgres see `$ createuser --help`.
- # When left blank, postgres will use the default role. This is
- # the same name as the operating system user that initialized the database.
- #username: betsy
-
- # The password associated with the postgres role (username).
- #password:
-
- # Connect on a TCP socket. Omitted by default since the client uses a
- # domain socket that doesn't need configuration. Windows does not have
- # domain sockets, so uncomment these lines.
- #host: localhost
-
- # The TCP port the server listens on. Defaults to 5432.
- # If your server runs on a different port number, change accordingly.
- #port: 5432
-
- # Schema search path. The server defaults to $user,public
- #schema_search_path: myapp,sharedapp,public
-
- # Minimum log levels, in increasing order:
- # debug5, debug4, debug3, debug2, debug1,
- # log, notice, warning, error, fatal, and panic
- # Defaults to warning.
- #min_messages: notice
+ database: db/development.sqlite3
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
<<: *default
- database: betsy_test
+ database: db/test.sqlite3
-# As with config/secrets.yml, you never want to store sensitive information,
-# like your database password, in your source code. If your source code is
-# ever seen by anyone, they now have access to your database.
-#
-# Instead, provide the password as a unix environment variable when you boot
-# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
-# for a full rundown on how to provide these environment variables in a
-# production deployment.
-#
-# On Heroku and other platform providers, you may have a full connection URL
-# available as an environment variable. For example:
-#
-# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
-#
-# You can use this database configuration with:
-#
-# production:
-# url: <%= ENV['DATABASE_URL'] %>
-#
production:
<<: *default
- database: betsy_production
- username: betsy
- password: <%= ENV['BETSY_DATABASE_PASSWORD'] %>
+ database: db/production.sqlite3
diff --git a/config/secrets.yml b/config/secrets.yml
index 6442868fc1..d26d6727cf 100644
--- a/config/secrets.yml
+++ b/config/secrets.yml
@@ -11,10 +11,10 @@
# if you're sharing your code publicly.
development:
- secret_key_base: eaebee6b0ce07b1e5b22743dacf6e6aaec01bf01c88370f9f86c3ae0d8592b4313c5e9da09754cd613eda8d206168bd74d5ed9910230b3957725352c70e2cf80
+ secret_key_base: 6ecbe27cf12a699c0f0842fcb1ce4add1a562a92f96d08f03fa31192a94cde8c5fa571fc9fdc4e96ad64b9e3aa11134cd85f4c7b0065a1ecbf26d3de3f8acb62
test:
- secret_key_base: f9c5bd6c383573755e5f8cf214402a51eb5ce8c7d0df9860dbf980f03fbd6c508e97412644b6a80d7034774602b05e05988bf7ab8ff594912950fdb590589a4d
+ secret_key_base: 81722cec9809519a189142b7cc0ab478412efc5da7e93a2490c862734470561356f2d1dfe64a2934052e123f02bf8d8954875d87d2bdf3a677efadf855489423
# Do not keep production secrets in the repository,
# instead read values from the environment.
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 92e39b2d78..2c5373e528 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -1,10 +1,13 @@
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
+require 'simplecov'
+SimpleCov.start 'rails'
class ActiveSupport::TestCase
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
+ Minitest::Reporters.use!
# Add more helper methods to be used by all tests here...
end
From 2bc96722e5655ede4e178d7e7b60e2916a2bdd8b Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Tue, 18 Oct 2016 15:54:25 -0700
Subject: [PATCH 002/144] Added .DS_Store to .gitignore and saved the routes we
drafted together
---
.gitignore | 1 +
config/routes.rb | 85 ++++++++++++++++++------------------------------
2 files changed, 32 insertions(+), 54 deletions(-)
diff --git a/.gitignore b/.gitignore
index e38a9978b9..c43c6b0bf2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,4 @@
/tmp
coverage
.env
+.DS_Store
diff --git a/config/routes.rb b/config/routes.rb
index 3f66539d54..17e043cf12 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,56 +1,33 @@
Rails.application.routes.draw do
- # The priority is based upon order of creation: first created -> highest priority.
- # See how all your routes lay out with "rake routes".
-
- # You can have the root of your site routed with "root"
- # root 'welcome#index'
-
- # Example of regular route:
- # get 'products/:id' => 'catalog#view'
-
- # Example of named route that can be invoked with purchase_url(id: product.id)
- # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase
-
- # Example resource route (maps HTTP verbs to controller actions automatically):
- # resources :products
-
- # Example resource route with options:
- # resources :products do
- # member do
- # get 'short'
- # post 'toggle'
- # end
- #
- # collection do
- # get 'sold'
- # end
- # end
-
- # Example resource route with sub-resources:
- # resources :products do
- # resources :comments, :sales
- # resource :seller
- # end
-
- # Example resource route with more complex sub-resources:
- # resources :products do
- # resources :comments
- # resources :sales do
- # get 'recent', on: :collection
- # end
- # end
-
- # Example resource route with concerns:
- # concern :toggleable do
- # post 'toggle'
- # end
- # resources :posts, concerns: :toggleable
- # resources :photos, concerns: :toggleable
-
- # Example resource route within a namespace:
- # namespace :admin do
- # # Directs /admin/products/* to Admin::ProductsController
- # # (app/controllers/admin/products_controller.rb)
- # resources :products
- # end
+
+ root 'ffc#index'
+
+ resources :merchants, only: [:show] do
+ resources :products
+ resources :orders, except: [:new, :create, :delete]
+ # NESTED? do
+ # resources :order_items, only: [:update]
+ # end
+ end
+
+ resources :products, only: [:index, :show] do
+ resources :reviews, only: [:new, :create]
+ end
+
+ resources :categories, only: [:new, :create, :show]
+
+# We're going to talk about this more if any of us needs to edit this. :)
+ resources :orders, only: [:new, :create, :show] do
+ resources :order_items, except [:index, :show]
+ end
+
+# Sessions routes - can be further flushed out...
+ get '/auth/:provider/callback' => 'sessions#create'
+
+ # get "/sessions/login_failure", to: "sessions#login_failure", as: "login_failure"
+
+ get '/sessions', to: 'sessions#index', as: 'sessions'
+
+ delete '/sessions', to: 'sessions#destroy'
+
end
From 194de3d3cde4c2b9accbfe3d0d222a37e5576bce Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Tue, 18 Oct 2016 16:22:57 -0700
Subject: [PATCH 003/144] fixed a route, colon was missing
---
config/routes.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/config/routes.rb b/config/routes.rb
index 17e043cf12..2d0b71e807 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -18,7 +18,7 @@
# We're going to talk about this more if any of us needs to edit this. :)
resources :orders, only: [:new, :create, :show] do
- resources :order_items, except [:index, :show]
+ resources :order_items, except: [:index, :show]
end
# Sessions routes - can be further flushed out...
From 12047204232ec2916c7dd08f7d490602b4a3f8d6 Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Tue, 18 Oct 2016 16:31:46 -0700
Subject: [PATCH 004/144] created REVIEW model
---
app/models/review.rb | 2 ++
db/migrate/20161018233022_create_reviews.rb | 8 ++++++++
test/fixtures/reviews.yml | 11 +++++++++++
test/models/review_test.rb | 7 +++++++
4 files changed, 28 insertions(+)
create mode 100644 app/models/review.rb
create mode 100644 db/migrate/20161018233022_create_reviews.rb
create mode 100644 test/fixtures/reviews.yml
create mode 100644 test/models/review_test.rb
diff --git a/app/models/review.rb b/app/models/review.rb
new file mode 100644
index 0000000000..2fbac34e64
--- /dev/null
+++ b/app/models/review.rb
@@ -0,0 +1,2 @@
+class Review < ActiveRecord::Base
+end
diff --git a/db/migrate/20161018233022_create_reviews.rb b/db/migrate/20161018233022_create_reviews.rb
new file mode 100644
index 0000000000..39c5709271
--- /dev/null
+++ b/db/migrate/20161018233022_create_reviews.rb
@@ -0,0 +1,8 @@
+class CreateReviews < ActiveRecord::Migration
+ def change
+ create_table :reviews do |t|
+
+ t.timestamps null: false
+ end
+ end
+end
diff --git a/test/fixtures/reviews.yml b/test/fixtures/reviews.yml
new file mode 100644
index 0000000000..937a0c002e
--- /dev/null
+++ b/test/fixtures/reviews.yml
@@ -0,0 +1,11 @@
+# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+# This model initially had no columns defined. If you add columns to the
+# model remove the '{}' from the fixture names and add the columns immediately
+# below each fixture, per the syntax in the comments below
+#
+one: {}
+# column: value
+#
+two: {}
+# column: value
diff --git a/test/models/review_test.rb b/test/models/review_test.rb
new file mode 100644
index 0000000000..11aa5204f0
--- /dev/null
+++ b/test/models/review_test.rb
@@ -0,0 +1,7 @@
+require 'test_helper'
+
+class ReviewTest < ActiveSupport::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end
From 045f86f73365dae484d95290198b21b6aef2b83c Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Tue, 18 Oct 2016 20:28:48 -0700
Subject: [PATCH 005/144] model: review: set-up model table columns in the db
migrate... file; set-up a few preliminarty fixtures for testing; and pasted
commented text into my review test model file to begin testing customization
---
app/models/review.rb | 11 +++
db/migrate/20161018233022_create_reviews.rb | 18 +++--
test/fixtures/reviews.yml | 23 ++++---
test/models/review_test.rb | 74 ++++++++++++++++++++-
4 files changed, 108 insertions(+), 18 deletions(-)
diff --git a/app/models/review.rb b/app/models/review.rb
index 2fbac34e64..7084174a22 100644
--- a/app/models/review.rb
+++ b/app/models/review.rb
@@ -1,2 +1,13 @@
class Review < ActiveRecord::Base
+ validates :rating, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: 5 }
+ validates :description, presence: true, length: { maximum: 400 }
+ validates :product_id, presence: true
+
+ belongs_to :product
end
+
+# Reviews:
+# Rating: integer default to 1
+# Description: string
+# Product_ID
+# (belongs to a product)
diff --git a/db/migrate/20161018233022_create_reviews.rb b/db/migrate/20161018233022_create_reviews.rb
index 39c5709271..b395d0d607 100644
--- a/db/migrate/20161018233022_create_reviews.rb
+++ b/db/migrate/20161018233022_create_reviews.rb
@@ -1,8 +1,18 @@
class CreateReviews < ActiveRecord::Migration
- def change
- create_table :reviews do |t|
+ def change
+ create_table :reviews do |t|
+ t.integer :rating
+ t.string :description
+ t.integer :product_id
- t.timestamps null: false
+ t.timestamps null: false
+ end
end
- end
end
+
+
+# Reviews:
+# Rating: integer default to 1
+# Description: string
+# Product_ID
+# (belongs to a product)
diff --git a/test/fixtures/reviews.yml b/test/fixtures/reviews.yml
index 937a0c002e..79da576c84 100644
--- a/test/fixtures/reviews.yml
+++ b/test/fixtures/reviews.yml
@@ -1,11 +1,12 @@
-# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
-
-# This model initially had no columns defined. If you add columns to the
-# model remove the '{}' from the fixture names and add the columns immediately
-# below each fixture, per the syntax in the comments below
-#
-one: {}
-# column: value
-#
-two: {}
-# column: value
+one_star:
+ rating: 1
+ description: This product is awesome.
+four_star:
+ rating: 4
+ description: Highly recommend purchasing this item.
+five_star:
+ rating: 5
+ description: Another description...
+long_description:
+ rating: 4
+ description: Here is a review of a book for sale. It is a great book, worth reading and possibly adding to your personal library. You will not be disappointed. Check this book out from your local library so you can form your own opinion and upvote it here. klsfjshrjdf lkajsf jej faiosejf esj oiasejf lsdjf ;oasjef ojsdfkl jasefj sodf lsdfj oisejf SDJf lKSDJFo iejw lKDSFJ l;SEF SIefj l;KSZDf sd eddjjrhdm fydowkf
diff --git a/test/models/review_test.rb b/test/models/review_test.rb
index 11aa5204f0..c8444c57f7 100644
--- a/test/models/review_test.rb
+++ b/test/models/review_test.rb
@@ -1,7 +1,75 @@
require 'test_helper'
class ReviewTest < ActiveSupport::TestCase
- # test "the truth" do
- # assert true
- # end
+ # test "Can create an album with only a name provided" do
+ # assert albums(:valid_data).valid?
+ # end
+ #
+ # test "Cannot create an album without a name" do
+ # album = Album.new
+ # assert_not album.valid?
+ # end
+ #
+ # test "Create two albums with different titles" do
+ # album1 = albums(:valid_data)
+ # album2 = albums(:another_album)
+ # assert album2.valid?
+ # end
+ #
+ # test "Cannot create two albums with the same title" do
+ # album1 = albums(:valid_data)
+ # album2 = Album.new(name: "Hello")
+ # assert_not(album2.valid?)
+ # end
+ #
+ # test "Can create an album with a description 808 characters long" do
+ # album1 = albums(:valid_data)
+ # album2 = Album.new(name: "Hello")
+ # assert_not(album2.valid?)
+ # end
+ #
+ # test "Cannot create an album with a description 810 characters long" do
+ # album_too_long = Album.new(name: "Too Long", description: "Here is a description of Book Title #12. It is a great book, worth reading and possibly adding to your personal library. You will not be disappointed. Check this book out from your local library so you can form your own opinion and upvote it here. klsjdf lkajsf jej faiosejf esj oiasejf lsdjf ;oasjef ojsdfkl jasefj sodf lsdfj oisejf SDJf lKSDJFo iejw lKDSFJ l;SEF SIefj l;KSZDf os;IF ;KLSDJf l;SJf;ijsefl j SDKL:fj es f;oSJDF l;kSDJFio SJefm LS:Dfj ;djf ;LKSDFj ;Ldjs ;ILEJSF l;SDJF SDIJfp oSfj ko;SDfopPSEfj PAEIOSfj lsidfj IOSEfj KL o;iSDfj efj DFIJS IOSEf oIAEJSf oDJS fo;IJSDF ;oIJDF ioSdfj dios;f jo;ISJf io;SDFJ ;SDKOFj ;Sdfj ;Jf :IOSDFJ ads o;iASdfj AKLSdj O:IADJ ;AOId oSDFJ S:OIDF :OSjm S:DFJ oiDJSf L:DFSZ O:IDSZJF ;oISJDf ;OSDFJ L:SDIFj L:SDKFJ O:ISDJf ;DFJS ;sIOJF ;oDFJS IO:SEfjDJSF ;SDFJ ioSfjUH")
+ # assert_not(album_too_long.valid?)
+ # end
+ #
+ # test "Creating a new album will instantiate vote count at zero" do
+ # b = Album.new
+ # assert_equal(0, b.votes)
+ # end
+ #
+ # test "Calling upvote_one increments votes by 1 correctly when starting with a new album" do
+ # b = Album.new
+ # b.upvote_one
+ # assert_equal(1, b.votes)
+ # end
+ #
+ # test "Calling upvote_one increments votes by 1 correctly with any integer higher than zero" do
+ # b = Album.new
+ # b.votes = 5
+ # b.upvote_one
+ # assert_equal(6, b.votes)
+ # end
+ #
+ # test "Cannot set votes to a negative integer" do
+ # b = Album.new(name: "yellow")
+ # b.votes = -4
+ # assert(b.invalid?)
+ # assert_includes(b.errors, :votes)
+ # end
+ #
+ # test "Votes cannot be set to nil" do
+ # b = Album.new(name: "yellow")
+ # b.votes = nil
+ # assert(b.invalid?)
+ # assert_includes(b.errors, :votes)
+ # end
end
+
+# on rails console, object.new goes straight to the model, bypassing the controller completely.
+
+# Reviews:
+# Rating: integer default to 1
+# Description: string
+# Product_ID
+# (belongs to a product)
From 769b9847baf5d52a91d4b4aa89b11747d71c3d43 Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Tue, 18 Oct 2016 20:29:49 -0700
Subject: [PATCH 006/144] model: review: set up validations and belongs_to
relationship in the review model file.
---
app/models/review.rb | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/models/review.rb b/app/models/review.rb
index 7084174a22..b0786530c1 100644
--- a/app/models/review.rb
+++ b/app/models/review.rb
@@ -6,6 +6,7 @@ class Review < ActiveRecord::Base
belongs_to :product
end
+
# Reviews:
# Rating: integer default to 1
# Description: string
From 30832a4a3c6be0033ebbeaa4906bb8aeedcd0063 Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Tue, 18 Oct 2016 20:31:46 -0700
Subject: [PATCH 007/144] raked db migrate
---
db/schema.rb | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
create mode 100644 db/schema.rb
diff --git a/db/schema.rb b/db/schema.rb
new file mode 100644
index 0000000000..8c4cd49d99
--- /dev/null
+++ b/db/schema.rb
@@ -0,0 +1,24 @@
+# encoding: UTF-8
+# This file is auto-generated from the current state of the database. Instead
+# of editing this file, please use the migrations feature of Active Record to
+# incrementally modify your database, and then regenerate this schema definition.
+#
+# Note that this schema.rb definition is the authoritative source for your
+# database schema. If you need to create the application database on another
+# system, you should be using db:schema:load, not running all the migrations
+# from scratch. The latter is a flawed and unsustainable approach (the more migrations
+# you'll amass, the slower it'll run and the greater likelihood for issues).
+#
+# It's strongly recommended that you check this file into your version control system.
+
+ActiveRecord::Schema.define(version: 20161018233022) do
+
+ create_table "reviews", force: :cascade do |t|
+ t.integer "rating"
+ t.string "description"
+ t.integer "product_id"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+end
From 9b46e77d7d894988f6f511b2476ae407cbfc6021 Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Tue, 18 Oct 2016 20:52:50 -0700
Subject: [PATCH 008/144] wrote 5 tests confirming reviews--model--rating
validations
---
test/fixtures/reviews.yml | 4 ++++
test/models/review_test.rb | 42 ++++++++++++++++++++++++++------------
2 files changed, 33 insertions(+), 13 deletions(-)
diff --git a/test/fixtures/reviews.yml b/test/fixtures/reviews.yml
index 79da576c84..02c94cc1f3 100644
--- a/test/fixtures/reviews.yml
+++ b/test/fixtures/reviews.yml
@@ -1,12 +1,16 @@
one_star:
rating: 1
description: This product is awesome.
+ product_id: 123
four_star:
rating: 4
description: Highly recommend purchasing this item.
+ product_id: 123
five_star:
rating: 5
description: Another description...
+ product_id: 123
long_description:
rating: 4
description: Here is a review of a book for sale. It is a great book, worth reading and possibly adding to your personal library. You will not be disappointed. Check this book out from your local library so you can form your own opinion and upvote it here. klsfjshrjdf lkajsf jej faiosejf esj oiasejf lsdjf ;oasjef ojsdfkl jasefj sodf lsdfj oisejf SDJf lKSDJFo iejw lKDSFJ l;SEF SIefj l;KSZDf sd eddjjrhdm fydowkf
+ product_id: 123
diff --git a/test/models/review_test.rb b/test/models/review_test.rb
index c8444c57f7..c78a23be2d 100644
--- a/test/models/review_test.rb
+++ b/test/models/review_test.rb
@@ -1,14 +1,35 @@
require 'test_helper'
class ReviewTest < ActiveSupport::TestCase
- # test "Can create an album with only a name provided" do
- # assert albums(:valid_data).valid?
- # end
- #
- # test "Cannot create an album without a name" do
- # album = Album.new
- # assert_not album.valid?
- # end
+ test "Creating a new review will instantiate rating as nil" do
+ r = Review.new
+ assert_equal(nil, r.rating)
+ end
+
+ test "Cannot save a nil rating to the database" do
+ no_rating = Review.new(description: "whatever", product_id: 123)
+ assert_not no_rating.valid?
+ end
+
+ test "Can create reviews with ratings between 1 and 5" do
+ assert reviews(:one_star).valid?
+ assert reviews(:four_star).valid?
+ assert reviews(:five_star).valid?
+ end
+
+ test "Can only create reviews with integer ratings" do
+ float_rating = Review.new(rating: 3.2, description: "whatever", product_id: 123)
+ string_rating = Review.new(rating: "excellent", description: "whatever", product_id: 123)
+ assert_not float_rating.valid?
+ assert_not string_rating.valid?
+ end
+
+ test "Cannot create reviews with ratings less than 1 or greater than 5" do
+ too_low_rating = Review.new(rating: 0, description: "whatever", product_id: 123)
+ too_high_rating = Review.new(rating: 6, description: "whatever", product_id: 123)
+ assert_not too_low_rating.valid?
+ assert_not too_high_rating.valid?
+ end
#
# test "Create two albums with different titles" do
# album1 = albums(:valid_data)
@@ -33,11 +54,6 @@ class ReviewTest < ActiveSupport::TestCase
# assert_not(album_too_long.valid?)
# end
#
- # test "Creating a new album will instantiate vote count at zero" do
- # b = Album.new
- # assert_equal(0, b.votes)
- # end
- #
# test "Calling upvote_one increments votes by 1 correctly when starting with a new album" do
# b = Album.new
# b.upvote_one
From fc1a55b3fe8d8d267a1d25925f6d59c0fcaf8f54 Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Tue, 18 Oct 2016 21:01:20 -0700
Subject: [PATCH 009/144] wrote 2 tests confirming reviews--model--description
validations
---
test/fixtures/reviews.yml | 2 +-
test/models/review_test.rb | 12 ++++++++++++
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/test/fixtures/reviews.yml b/test/fixtures/reviews.yml
index 02c94cc1f3..71935fdf51 100644
--- a/test/fixtures/reviews.yml
+++ b/test/fixtures/reviews.yml
@@ -10,7 +10,7 @@ five_star:
rating: 5
description: Another description...
product_id: 123
-long_description:
+character_400:
rating: 4
description: Here is a review of a book for sale. It is a great book, worth reading and possibly adding to your personal library. You will not be disappointed. Check this book out from your local library so you can form your own opinion and upvote it here. klsfjshrjdf lkajsf jej faiosejf esj oiasejf lsdjf ;oasjef ojsdfkl jasefj sodf lsdfj oisejf SDJf lKSDJFo iejw lKDSFJ l;SEF SIefj l;KSZDf sd eddjjrhdm fydowkf
product_id: 123
diff --git a/test/models/review_test.rb b/test/models/review_test.rb
index c78a23be2d..4c2652e2b7 100644
--- a/test/models/review_test.rb
+++ b/test/models/review_test.rb
@@ -30,6 +30,18 @@ class ReviewTest < ActiveSupport::TestCase
assert_not too_low_rating.valid?
assert_not too_high_rating.valid?
end
+
+ test "Review creation requires a description [string]" do
+ no_description = Review.new(rating: 0, product_id: 123)
+ assert_not no_description.valid?
+ assert reviews(:one_star).valid?
+ end
+
+ test "Can only create reviews with descriptions 400 characters or less" do
+ assert reviews(:character_400).valid?
+ character_401 = Review.new(rating: 0, product_id: 123, description: "Here is a review of a book for sale. It is a great book, worth reading and possibly adding to your personal library. You will not be disappointed. Check this book out from your local library so you can form your own opinion and upvote it here. klsfjshrjdf lkajsf jej faiosejf esj oiasejf lsdjf ;oasjef ojsdfkl jasefj sodf lsdfj oisejf SDJf lKSDJFo iejw lKDSFJ l;SEF SIefj l;KSZDf sd eddjjrhdm fydowkf!")
+ assert_not character_401.valid?
+ end
#
# test "Create two albums with different titles" do
# album1 = albums(:valid_data)
From e18bb9a3ae6161b362413f00a5df599c885a51ff Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Tue, 18 Oct 2016 21:16:36 -0700
Subject: [PATCH 010/144] wrote 1 test confirming reviews--model--product_id
validations
---
app/models/review.rb | 7 -----
test/models/review_test.rb | 64 +++++---------------------------------
2 files changed, 8 insertions(+), 63 deletions(-)
diff --git a/app/models/review.rb b/app/models/review.rb
index b0786530c1..e094e15fec 100644
--- a/app/models/review.rb
+++ b/app/models/review.rb
@@ -5,10 +5,3 @@ class Review < ActiveRecord::Base
belongs_to :product
end
-
-
-# Reviews:
-# Rating: integer default to 1
-# Description: string
-# Product_ID
-# (belongs to a product)
diff --git a/test/models/review_test.rb b/test/models/review_test.rb
index 4c2652e2b7..31b0bd8d69 100644
--- a/test/models/review_test.rb
+++ b/test/models/review_test.rb
@@ -42,62 +42,14 @@ class ReviewTest < ActiveSupport::TestCase
character_401 = Review.new(rating: 0, product_id: 123, description: "Here is a review of a book for sale. It is a great book, worth reading and possibly adding to your personal library. You will not be disappointed. Check this book out from your local library so you can form your own opinion and upvote it here. klsfjshrjdf lkajsf jej faiosejf esj oiasejf lsdjf ;oasjef ojsdfkl jasefj sodf lsdfj oisejf SDJf lKSDJFo iejw lKDSFJ l;SEF SIefj l;KSZDf sd eddjjrhdm fydowkf!")
assert_not character_401.valid?
end
- #
- # test "Create two albums with different titles" do
- # album1 = albums(:valid_data)
- # album2 = albums(:another_album)
- # assert album2.valid?
- # end
- #
- # test "Cannot create two albums with the same title" do
- # album1 = albums(:valid_data)
- # album2 = Album.new(name: "Hello")
- # assert_not(album2.valid?)
- # end
- #
- # test "Can create an album with a description 808 characters long" do
- # album1 = albums(:valid_data)
- # album2 = Album.new(name: "Hello")
- # assert_not(album2.valid?)
- # end
- #
- # test "Cannot create an album with a description 810 characters long" do
- # album_too_long = Album.new(name: "Too Long", description: "Here is a description of Book Title #12. It is a great book, worth reading and possibly adding to your personal library. You will not be disappointed. Check this book out from your local library so you can form your own opinion and upvote it here. klsjdf lkajsf jej faiosejf esj oiasejf lsdjf ;oasjef ojsdfkl jasefj sodf lsdfj oisejf SDJf lKSDJFo iejw lKDSFJ l;SEF SIefj l;KSZDf os;IF ;KLSDJf l;SJf;ijsefl j SDKL:fj es f;oSJDF l;kSDJFio SJefm LS:Dfj ;djf ;LKSDFj ;Ldjs ;ILEJSF l;SDJF SDIJfp oSfj ko;SDfopPSEfj PAEIOSfj lsidfj IOSEfj KL o;iSDfj efj DFIJS IOSEf oIAEJSf oDJS fo;IJSDF ;oIJDF ioSdfj dios;f jo;ISJf io;SDFJ ;SDKOFj ;Sdfj ;Jf :IOSDFJ ads o;iASdfj AKLSdj O:IADJ ;AOId oSDFJ S:OIDF :OSjm S:DFJ oiDJSf L:DFSZ O:IDSZJF ;oISJDf ;OSDFJ L:SDIFj L:SDKFJ O:ISDJf ;DFJS ;sIOJF ;oDFJS IO:SEfjDJSF ;SDFJ ioSfjUH")
- # assert_not(album_too_long.valid?)
- # end
- #
- # test "Calling upvote_one increments votes by 1 correctly when starting with a new album" do
- # b = Album.new
- # b.upvote_one
- # assert_equal(1, b.votes)
- # end
- #
- # test "Calling upvote_one increments votes by 1 correctly with any integer higher than zero" do
- # b = Album.new
- # b.votes = 5
- # b.upvote_one
- # assert_equal(6, b.votes)
- # end
- #
- # test "Cannot set votes to a negative integer" do
- # b = Album.new(name: "yellow")
- # b.votes = -4
- # assert(b.invalid?)
- # assert_includes(b.errors, :votes)
- # end
- #
- # test "Votes cannot be set to nil" do
- # b = Album.new(name: "yellow")
- # b.votes = nil
- # assert(b.invalid?)
- # assert_includes(b.errors, :votes)
- # end
+
+ test "Review creation requires a product_id [integer]" do
+ no_product_id = Review.new(rating: 0, description: "hello")
+ assert_not no_product_id.valid?
+ assert reviews(:one_star).valid?
+ end
end
-# on rails console, object.new goes straight to the model, bypassing the controller completely.
+# note: have not written any tests on the belongs_to relationship, as we haven't set-up the product_id in the controller using params yet. I think this relationship can be tested in the controller instead of the model tests?
-# Reviews:
-# Rating: integer default to 1
-# Description: string
-# Product_ID
-# (belongs to a product)
+# fyi from earlier project: on rails console, object.new goes straight to the model, bypassing the controller completely.
From b25f4ee2e26af66ada57efd135faef2bf786c0e7 Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Tue, 18 Oct 2016 22:33:47 -0700
Subject: [PATCH 011/144] wrote 3 order item MODEL tests, one is not passing,
too tired to figure this out right now--all these tests relate to the
quantity validations in the order item model
---
app/models/order_item.rb | 12 ++++
.../20161019043527_create_order_items.rb | 12 ++++
db/schema.rb | 11 ++-
test/fixtures/order_items.yml | 10 +++
test/models/order_item_test.rb | 71 +++++++++++++++++++
test/models/review_test.rb | 21 ++++--
6 files changed, 132 insertions(+), 5 deletions(-)
create mode 100644 app/models/order_item.rb
create mode 100644 db/migrate/20161019043527_create_order_items.rb
create mode 100644 test/fixtures/order_items.yml
create mode 100644 test/models/order_item_test.rb
diff --git a/app/models/order_item.rb b/app/models/order_item.rb
new file mode 100644
index 0000000000..3192d4dab1
--- /dev/null
+++ b/app/models/order_item.rb
@@ -0,0 +1,12 @@
+class OrderItem < ActiveRecord::Base
+ validates :quantity, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 1 }
+ # less_than_or_equal_to: Product.find(self.product_id).quantity }
+ # I'd like this last line of code to work in the numericality hash--it's not working right now. Maybe it will work once the Product model has been created in this version of our app?
+ # Also, need to confirm what the quantity/stock variable is in the Product model and update the last word of this commented out code in line 3.
+ validates :product_id, presence: true
+ validates :order_id, presence: true
+ validates :shipped?, presence: true
+
+ belongs_to :product
+ belongs_to :order
+end
diff --git a/db/migrate/20161019043527_create_order_items.rb b/db/migrate/20161019043527_create_order_items.rb
new file mode 100644
index 0000000000..c3be31db3b
--- /dev/null
+++ b/db/migrate/20161019043527_create_order_items.rb
@@ -0,0 +1,12 @@
+class CreateOrderItems < ActiveRecord::Migration
+ def change
+ create_table :order_items do |t|
+ t.integer :quantity, :default => 1
+ t.integer :product_id
+ t.integer :order_id
+ t.boolean :shipped?, :default => false
+
+ t.timestamps null: false
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 8c4cd49d99..0ff0936d58 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,16 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20161018233022) do
+ActiveRecord::Schema.define(version: 20161019043527) do
+
+ create_table "order_items", force: :cascade do |t|
+ t.integer "quantity", default: 1
+ t.integer "product_id"
+ t.integer "order_id"
+ t.boolean "shipped?", default: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
create_table "reviews", force: :cascade do |t|
t.integer "rating"
diff --git a/test/fixtures/order_items.yml b/test/fixtures/order_items.yml
new file mode 100644
index 0000000000..ea52eb6faf
--- /dev/null
+++ b/test/fixtures/order_items.yml
@@ -0,0 +1,10 @@
+one_unit:
+ quantity: 1
+ product_id: 123
+ order_id: 456
+ shipped?: false
+four_unit:
+ quantity: 4
+ product_id: 123
+ order_id: 456
+ shipped?: false
diff --git a/test/models/order_item_test.rb b/test/models/order_item_test.rb
new file mode 100644
index 0000000000..90544ec1a5
--- /dev/null
+++ b/test/models/order_item_test.rb
@@ -0,0 +1,71 @@
+require 'test_helper'
+
+class OrderItemTest < ActiveSupport::TestCase
+ test "Cannot create an order item with nil quantity" do
+ nil_quantity = OrderItem.new(quantity: nil, product_id: 123, order_id: 456)
+ assert_equal(nil, nil_quantity.quantity)
+ assert_not nil_quantity.valid?
+ assert_includes(nil_quantity.errors, :quantity)
+ end
+
+ test "Order item quantity is always instantiated at 1" do
+ default_quantity = OrderItem.new(product_id: 123, order_id: 456)
+ assert_equal(1, default_quantity.quantity)
+ end
+
+ test "Order item quantity must be a positive (non-zero) integer (LESS THAN OR EQUAL TO THE PRODUCT'S QUANTITY/STOCK, WOULD lIKE TO BUILD IN THIS PART OF THE TEST)" do
+ assert order_items(:four_unit).valid?
+ assert order_items(:one_unit).valid?
+
+ zero_quantity = OrderItem.new(quantity: 0, product_id: 123, order_id: 456)
+ assert_not zero_quantity.valid?
+ assert_includes(zero_quantity.errors, :quantity)
+
+ negative_quantity = OrderItem.new(quantity: -6, product_id: 123, order_id: 456)
+ assert_not negative_quantity.valid?
+ assert_includes(negative_quantity.errors, :quantity)
+ end
+
+
+
+ # test "Can only create reviews with integer ratings" do
+ # float_rating = Review.new(rating: 3.2, description: "whatever", product_id: 123)
+ # string_rating = Review.new(rating: "excellent", description: "whatever", product_id: 123)
+ # assert_not float_rating.valid?
+ # assert_not string_rating.valid?
+ # end
+ #
+ # test "Cannot create reviews with ratings less than 1 or greater than 5" do
+ # too_low_rating = Review.new(rating: 0, description: "whatever", product_id: 123)
+ # too_high_rating = Review.new(rating: 6, description: "whatever", product_id: 123)
+ # assert_not too_low_rating.valid?
+ # assert_not too_high_rating.valid?
+ # end
+ #
+ # test "Review creation requires a description [string]" do
+ # no_description = Review.new(rating: 0, product_id: 123)
+ # assert_not no_description.valid?
+ # assert reviews(:one_star).valid?
+ # end
+ #
+ # test "Can only create reviews with descriptions 400 characters or less" do
+ # assert reviews(:character_400).valid?
+ # character_401 = Review.new(rating: 0, product_id: 123, description: "Here is a review of a book for sale. It is a great book, worth reading and possibly adding to your personal library. You will not be disappointed. Check this book out from your local library so you can form your own opinion and upvote it here. klsfjshrjdf lkajsf jej faiosejf esj oiasejf lsdjf ;oasjef ojsdfkl jasefj sodf lsdfj oisejf SDJf lKSDJFo iejw lKDSFJ l;SEF SIefj l;KSZDf sd eddjjrhdm fydowkf!")
+ # assert_not character_401.valid?
+ # end
+ #
+ # test "Review creation requires a product_id [integer]" do
+ # no_product_id = Review.new(rating: 0, description: "hello")
+ # assert_not no_product_id.valid?
+ # assert reviews(:one_star).valid?
+ # end
+ #
+ # test "Creating a new review will instantiate rating as nil" do
+ # r = Review.new
+ # assert_equal(nil, r.rating)
+ # end
+end
+
+ # note: have not written any tests on the belongs_to relationship, as we haven't set-up the product_id in the controller using params yet. I think this relationship can be tested in the controller instead of the model tests?
+
+ # fyi from earlier project: on rails console, object.new goes straight to the model, bypassing the controller completely.
diff --git a/test/models/review_test.rb b/test/models/review_test.rb
index 31b0bd8d69..27a82295bd 100644
--- a/test/models/review_test.rb
+++ b/test/models/review_test.rb
@@ -9,6 +9,7 @@ class ReviewTest < ActiveSupport::TestCase
test "Cannot save a nil rating to the database" do
no_rating = Review.new(description: "whatever", product_id: 123)
assert_not no_rating.valid?
+ assert_includes(no_rating.errors, :rating)
end
test "Can create reviews with ratings between 1 and 5" do
@@ -19,34 +20,46 @@ class ReviewTest < ActiveSupport::TestCase
test "Can only create reviews with integer ratings" do
float_rating = Review.new(rating: 3.2, description: "whatever", product_id: 123)
- string_rating = Review.new(rating: "excellent", description: "whatever", product_id: 123)
assert_not float_rating.valid?
+ assert_includes(float_rating.errors, :rating)
+
+ string_rating = Review.new(rating: "excellent", description: "whatever", product_id: 123)
assert_not string_rating.valid?
+ assert_includes(string_rating.errors, :rating)
end
test "Cannot create reviews with ratings less than 1 or greater than 5" do
too_low_rating = Review.new(rating: 0, description: "whatever", product_id: 123)
- too_high_rating = Review.new(rating: 6, description: "whatever", product_id: 123)
assert_not too_low_rating.valid?
+ assert_includes(too_low_rating.errors, :rating)
+
+ too_high_rating = Review.new(rating: 6, description: "whatever", product_id: 123)
assert_not too_high_rating.valid?
+ assert_includes(too_high_rating.errors, :rating)
end
test "Review creation requires a description [string]" do
+ assert reviews(:one_star).valid?
+
no_description = Review.new(rating: 0, product_id: 123)
assert_not no_description.valid?
- assert reviews(:one_star).valid?
+ assert_includes(no_description.errors, :description)
end
test "Can only create reviews with descriptions 400 characters or less" do
assert reviews(:character_400).valid?
+
character_401 = Review.new(rating: 0, product_id: 123, description: "Here is a review of a book for sale. It is a great book, worth reading and possibly adding to your personal library. You will not be disappointed. Check this book out from your local library so you can form your own opinion and upvote it here. klsfjshrjdf lkajsf jej faiosejf esj oiasejf lsdjf ;oasjef ojsdfkl jasefj sodf lsdfj oisejf SDJf lKSDJFo iejw lKDSFJ l;SEF SIefj l;KSZDf sd eddjjrhdm fydowkf!")
assert_not character_401.valid?
+ assert_includes(character_401.errors, :description)
end
test "Review creation requires a product_id [integer]" do
+ assert reviews(:one_star).valid?
+
no_product_id = Review.new(rating: 0, description: "hello")
assert_not no_product_id.valid?
- assert reviews(:one_star).valid?
+ assert_includes(no_product_id.errors, :product_id)
end
end
From b8fccf47467711e8495d50463d15f2f1daba7755 Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Tue, 18 Oct 2016 22:41:48 -0700
Subject: [PATCH 012/144] wrote 3 more tests for the remaining order item model
validations--however two of these tests are not yet passing, need to come
back to these with FRESH EYES. :)
---
test/models/order_item_test.rb | 56 ++++++++++++----------------------
1 file changed, 20 insertions(+), 36 deletions(-)
diff --git a/test/models/order_item_test.rb b/test/models/order_item_test.rb
index 90544ec1a5..6125b55eb6 100644
--- a/test/models/order_item_test.rb
+++ b/test/models/order_item_test.rb
@@ -26,44 +26,28 @@ class OrderItemTest < ActiveSupport::TestCase
assert_includes(negative_quantity.errors, :quantity)
end
+ test "Order item creation requires a product_id [integer]" do
+ no_product_id = OrderItem.new(quantity: 2, order_id: 456)
+ assert_not no_product_id.valid?
+ assert_includes(no_product_id.errors, :product_id)
+ assert order_items(:one_unit).valid?
+ assert order_items(:four_unit).valid?
+ end
- # test "Can only create reviews with integer ratings" do
- # float_rating = Review.new(rating: 3.2, description: "whatever", product_id: 123)
- # string_rating = Review.new(rating: "excellent", description: "whatever", product_id: 123)
- # assert_not float_rating.valid?
- # assert_not string_rating.valid?
- # end
- #
- # test "Cannot create reviews with ratings less than 1 or greater than 5" do
- # too_low_rating = Review.new(rating: 0, description: "whatever", product_id: 123)
- # too_high_rating = Review.new(rating: 6, description: "whatever", product_id: 123)
- # assert_not too_low_rating.valid?
- # assert_not too_high_rating.valid?
- # end
- #
- # test "Review creation requires a description [string]" do
- # no_description = Review.new(rating: 0, product_id: 123)
- # assert_not no_description.valid?
- # assert reviews(:one_star).valid?
- # end
- #
- # test "Can only create reviews with descriptions 400 characters or less" do
- # assert reviews(:character_400).valid?
- # character_401 = Review.new(rating: 0, product_id: 123, description: "Here is a review of a book for sale. It is a great book, worth reading and possibly adding to your personal library. You will not be disappointed. Check this book out from your local library so you can form your own opinion and upvote it here. klsfjshrjdf lkajsf jej faiosejf esj oiasejf lsdjf ;oasjef ojsdfkl jasefj sodf lsdfj oisejf SDJf lKSDJFo iejw lKDSFJ l;SEF SIefj l;KSZDf sd eddjjrhdm fydowkf!")
- # assert_not character_401.valid?
- # end
- #
- # test "Review creation requires a product_id [integer]" do
- # no_product_id = Review.new(rating: 0, description: "hello")
- # assert_not no_product_id.valid?
- # assert reviews(:one_star).valid?
- # end
- #
- # test "Creating a new review will instantiate rating as nil" do
- # r = Review.new
- # assert_equal(nil, r.rating)
- # end
+ test "Order item creation requires an order_id [integer]" do
+ no_order_id = OrderItem.new(quantity: 2, product_id: 456)
+ assert_not no_order_id.valid?
+ assert_includes(no_order_id.errors, :order_id)
+
+ assert order_items(:one_unit).valid?
+ assert order_items(:four_unit).valid?
+ end
+
+ test "Creating an order item instantiate :shipped? as false" do
+ o = OrderItem.new
+ assert_equal(false, o.shipped?)
+ end
end
# note: have not written any tests on the belongs_to relationship, as we haven't set-up the product_id in the controller using params yet. I think this relationship can be tested in the controller instead of the model tests?
From eac1b0282829fe8170cd5b4629de8fa15cd7cf03 Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Tue, 18 Oct 2016 22:42:21 -0700
Subject: [PATCH 013/144] wrote 3 more tests for the remaining order item model
validations--however two of these tests are not yet passing, need to come
back to these with FRESH EYES.
---
test/models/order_item_test.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/models/order_item_test.rb b/test/models/order_item_test.rb
index 6125b55eb6..ac7973fe02 100644
--- a/test/models/order_item_test.rb
+++ b/test/models/order_item_test.rb
@@ -50,6 +50,6 @@ class OrderItemTest < ActiveSupport::TestCase
end
end
- # note: have not written any tests on the belongs_to relationship, as we haven't set-up the product_id in the controller using params yet. I think this relationship can be tested in the controller instead of the model tests?
+ # note: have not written any tests on the belongs_to relationships, as we haven't set-up the product_id or order_id in the controller using params yet. I think these relationships can be tested in the controller instead of the model tests?
# fyi from earlier project: on rails console, object.new goes straight to the model, bypassing the controller completely.
From 8ce365d521d563dee9e65ab95a7615c7056628f3 Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Wed, 19 Oct 2016 08:46:19 -0700
Subject: [PATCH 014/144] wrote Product model and tests
---
app/models/product.rb | 6 ++
db/migrate/20161019045258_create_products.rb | 14 ++++
db/schema.rb | 27 ++++++++
test/fixtures/products.yml | 12 ++++
test/models/product_test.rb | 72 ++++++++++++++++++++
5 files changed, 131 insertions(+)
create mode 100644 app/models/product.rb
create mode 100644 db/migrate/20161019045258_create_products.rb
create mode 100644 db/schema.rb
create mode 100644 test/fixtures/products.yml
create mode 100644 test/models/product_test.rb
diff --git a/app/models/product.rb b/app/models/product.rb
new file mode 100644
index 0000000000..6887aee930
--- /dev/null
+++ b/app/models/product.rb
@@ -0,0 +1,6 @@
+class Product < ActiveRecord::Base
+ has_and_belongs_to_many :categories
+ belongs_to :merchant
+ validates :name, presence: true, uniqueness: true
+ validates :price, presence: true, numericality: {only_integer: true, greater_than: 0}
+end
diff --git a/db/migrate/20161019045258_create_products.rb b/db/migrate/20161019045258_create_products.rb
new file mode 100644
index 0000000000..4929487895
--- /dev/null
+++ b/db/migrate/20161019045258_create_products.rb
@@ -0,0 +1,14 @@
+class CreateProducts < ActiveRecord::Migration
+ def change
+ create_table :products do |t|
+ t.string :name
+ t.string :description
+ t.integer :stock
+ t.integer :price
+ t.string :photo_url
+ t.belongs_to :merchant
+
+ t.timestamps null: false
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
new file mode 100644
index 0000000000..023c2295a4
--- /dev/null
+++ b/db/schema.rb
@@ -0,0 +1,27 @@
+# encoding: UTF-8
+# This file is auto-generated from the current state of the database. Instead
+# of editing this file, please use the migrations feature of Active Record to
+# incrementally modify your database, and then regenerate this schema definition.
+#
+# Note that this schema.rb definition is the authoritative source for your
+# database schema. If you need to create the application database on another
+# system, you should be using db:schema:load, not running all the migrations
+# from scratch. The latter is a flawed and unsustainable approach (the more migrations
+# you'll amass, the slower it'll run and the greater likelihood for issues).
+#
+# It's strongly recommended that you check this file into your version control system.
+
+ActiveRecord::Schema.define(version: 20161019045258) do
+
+ create_table "products", force: :cascade do |t|
+ t.string "name"
+ t.string "description"
+ t.integer "stock"
+ t.integer "price"
+ t.string "photo_url"
+ t.integer "merchant_id"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+end
diff --git a/test/fixtures/products.yml b/test/fixtures/products.yml
new file mode 100644
index 0000000000..3940fc3bc6
--- /dev/null
+++ b/test/fixtures/products.yml
@@ -0,0 +1,12 @@
+cat_suit:
+ name: cat suit
+ price: 1234
+ description: your cat will look dapper in this one-piece suit!
+ stock: 4
+ photo_url: http://placekitten.com/200/300
+hamster_monocle:
+ name: hamster monocle
+ price: 500
+ description: a classy way to correct your hamster's vision.
+ stock: 20
+ photo_url: http://placekitten.com/200/300
diff --git a/test/models/product_test.rb b/test/models/product_test.rb
new file mode 100644
index 0000000000..39e84bf73a
--- /dev/null
+++ b/test/models/product_test.rb
@@ -0,0 +1,72 @@
+require 'test_helper'
+
+class ProductTest < ActiveSupport::TestCase
+ test "Cannot create a product without a name" do
+ product = Product.new
+ assert_not product.valid?
+ assert_includes product.errors, :name
+ end
+
+ test "Cannot create a product without a price" do
+ product = Product.new(name: "kitten tux")
+ assert_not product.valid?
+ assert_includes product.errors, :price
+ end
+
+ test "Cannot create a product with a duplicate name" do
+ product = Product.new(name: "cat suit", price: 224)
+ assert_not product.valid?
+ assert_not product.save
+ assert_includes product.errors, :name
+ end
+
+ test "Price must be an integer" do
+ product = Product.new(name: "dog sunglasses", price: "foo")
+ assert_not product.valid?
+ assert_not product.save
+ assert_equal ["is not a number"], product.errors.messages[:price]
+ end
+
+ test "Price can't be 0" do
+ product = Product.new(name: "foobar", price: 0)
+ assert_not product.valid?
+ assert_not product.save
+ assert_equal ["must be greater than 0"], product.errors.messages[:price]
+ end
+
+ test "Price can't be less than 0" do
+ product = Product.new(name: "foobar", price: -1)
+ assert_not product.valid?
+ assert_not product.save
+ assert_equal ["must be greater than 0"], product.errors.messages[:price]
+ end
+
+ test "create Product with valid data" do
+ product = Product.new(name: "penguin bowtie", price: 2150)
+ assert product.valid?
+ assert_not_nil product.name
+ assert_not_nil product.price
+ end
+
+ test "create products with different names" do
+ products.each do |product|
+ assert product.valid?
+ assert product.save
+ end
+ end
+
+ # test "Product can be assigned a merchant id" do
+ # product = Product.create!(name: "mouse hat", price: 1240)
+ # merchant = Merchant.create!(username: "testing", email: "test@test.com")
+ #
+ # product.merchant = merchant
+ # assert product.save
+ #
+ # assert_equal product.merchant_id, merchant.id
+ # assert_includes merchant.products, product
+ # end
+
+ # test "Product can have many categories" do
+ # # INSERT TEST HERE
+ # end
+end
From 88082bfda806d0c3831d59ca641b089f5c461c97 Mon Sep 17 00:00:00 2001
From: Shari Meggs
Date: Wed, 19 Oct 2016 11:49:03 -0700
Subject: [PATCH 015/144] Created the merchant model and made sure the Git
login information was in the env file
---
app/models/merchant.rb | 12 +++++++++
config/initializers/omniauth.rb | 3 +++
db/migrate/20161019005406_create_merchants.rb | 11 ++++++++
db/schema.rb | 25 +++++++++++++++++++
test/fixtures/merchants.yml | 11 ++++++++
test/models/merchant_test.rb | 12 +++++++++
6 files changed, 74 insertions(+)
create mode 100644 app/models/merchant.rb
create mode 100644 config/initializers/omniauth.rb
create mode 100644 db/migrate/20161019005406_create_merchants.rb
create mode 100644 db/schema.rb
create mode 100644 test/fixtures/merchants.yml
create mode 100644 test/models/merchant_test.rb
diff --git a/app/models/merchant.rb b/app/models/merchant.rb
new file mode 100644
index 0000000000..101c02c85a
--- /dev/null
+++ b/app/models/merchant.rb
@@ -0,0 +1,12 @@
+class Merchant < ActiveRecord::Base
+ validates :email, :user_name, :uid, :provider, presence: true
+
+ def self.build_from_github(auth_hash)
+ merchant = Merchant.new
+ merchant.uid = auth_hash[:uid]
+ merchant.provider = 'github'
+ merchant.user_name = auth_hash['info']['nickname']
+ merchant.email = auth_hash['info']['email']
+ return merchant
+ end
+end
diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb
new file mode 100644
index 0000000000..fd4416122a
--- /dev/null
+++ b/config/initializers/omniauth.rb
@@ -0,0 +1,3 @@
+Rails.application.config.middleware.use OmniAuth::Builder do
+ provider :github, ENV["GITHUB_CLIENT_ID"], ENV["GITHUB_CLIENT_SECRET"], scope: "user:email"
+end
diff --git a/db/migrate/20161019005406_create_merchants.rb b/db/migrate/20161019005406_create_merchants.rb
new file mode 100644
index 0000000000..758eccaa6f
--- /dev/null
+++ b/db/migrate/20161019005406_create_merchants.rb
@@ -0,0 +1,11 @@
+class CreateMerchants < ActiveRecord::Migration
+ def change
+ create_table :merchants do |t|
+ t.string :user_name
+ t.string :email
+ t.integer :uid, null: false
+ t.string :provider, null: false
+ t.timestamps null: false
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
new file mode 100644
index 0000000000..dad67ec5e1
--- /dev/null
+++ b/db/schema.rb
@@ -0,0 +1,25 @@
+# encoding: UTF-8
+# This file is auto-generated from the current state of the database. Instead
+# of editing this file, please use the migrations feature of Active Record to
+# incrementally modify your database, and then regenerate this schema definition.
+#
+# Note that this schema.rb definition is the authoritative source for your
+# database schema. If you need to create the application database on another
+# system, you should be using db:schema:load, not running all the migrations
+# from scratch. The latter is a flawed and unsustainable approach (the more migrations
+# you'll amass, the slower it'll run and the greater likelihood for issues).
+#
+# It's strongly recommended that you check this file into your version control system.
+
+ActiveRecord::Schema.define(version: 20161019005406) do
+
+ create_table "merchants", force: :cascade do |t|
+ t.string "user_name"
+ t.string "email"
+ t.integer "uid", null: false
+ t.string "provider", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+end
diff --git a/test/fixtures/merchants.yml b/test/fixtures/merchants.yml
new file mode 100644
index 0000000000..937a0c002e
--- /dev/null
+++ b/test/fixtures/merchants.yml
@@ -0,0 +1,11 @@
+# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+# This model initially had no columns defined. If you add columns to the
+# model remove the '{}' from the fixture names and add the columns immediately
+# below each fixture, per the syntax in the comments below
+#
+one: {}
+# column: value
+#
+two: {}
+# column: value
diff --git a/test/models/merchant_test.rb b/test/models/merchant_test.rb
new file mode 100644
index 0000000000..9ab2cc202f
--- /dev/null
+++ b/test/models/merchant_test.rb
@@ -0,0 +1,12 @@
+require 'test_helper'
+
+class MerchantTest < ActiveSupport::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+ test "create a merchant with no email or uid" do
+ merchant = Merchant.new
+ assert_not merchant.valid?
+ end
+
+end
From 7502d5eefa5e7532b7965174016c3ad5372a02e6 Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Wed, 19 Oct 2016 11:49:39 -0700
Subject: [PATCH 016/144] fixed order_item model validation to work with a
boolean value so that all tests now pass. Updated order_item yml file to test
for a true and false shipped?
---
app/models/order_item.rb | 2 +-
test/fixtures/order_items.yml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/models/order_item.rb b/app/models/order_item.rb
index 3192d4dab1..6aaa4fbdb4 100644
--- a/app/models/order_item.rb
+++ b/app/models/order_item.rb
@@ -5,7 +5,7 @@ class OrderItem < ActiveRecord::Base
# Also, need to confirm what the quantity/stock variable is in the Product model and update the last word of this commented out code in line 3.
validates :product_id, presence: true
validates :order_id, presence: true
- validates :shipped?, presence: true
+ validates :shipped?, inclusion: { in: [true, false] }
belongs_to :product
belongs_to :order
diff --git a/test/fixtures/order_items.yml b/test/fixtures/order_items.yml
index ea52eb6faf..3d983a3d27 100644
--- a/test/fixtures/order_items.yml
+++ b/test/fixtures/order_items.yml
@@ -7,4 +7,4 @@ four_unit:
quantity: 4
product_id: 123
order_id: 456
- shipped?: false
+ shipped?: true
From 594f97c584a97d062353701b2f9983f8343e865e Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Wed, 19 Oct 2016 11:50:32 -0700
Subject: [PATCH 017/144] modified test data for merchant validation
---
test/models/product_test.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/models/product_test.rb b/test/models/product_test.rb
index 39e84bf73a..869ce5c1db 100644
--- a/test/models/product_test.rb
+++ b/test/models/product_test.rb
@@ -57,7 +57,7 @@ class ProductTest < ActiveSupport::TestCase
# test "Product can be assigned a merchant id" do
# product = Product.create!(name: "mouse hat", price: 1240)
- # merchant = Merchant.create!(username: "testing", email: "test@test.com")
+ # merchant = Merchant.create!(user_name: "testing", email: "test@test.com")
#
# product.merchant = merchant
# assert product.save
From b4a4007c32a0f2e107772c0cf5b665b8ce78dc2c Mon Sep 17 00:00:00 2001
From: Shari Meggs
Date: Wed, 19 Oct 2016 12:20:30 -0700
Subject: [PATCH 018/144] Erased data in the yml file, added some session set
up in test_helper and put in empty merchant test
---
test/fixtures/merchants.yml | 5 -----
test/models/merchant_test.rb | 2 +-
test/test_helper.rb | 9 ++++++++-
3 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/test/fixtures/merchants.yml b/test/fixtures/merchants.yml
index 937a0c002e..be145aeb97 100644
--- a/test/fixtures/merchants.yml
+++ b/test/fixtures/merchants.yml
@@ -4,8 +4,3 @@
# model remove the '{}' from the fixture names and add the columns immediately
# below each fixture, per the syntax in the comments below
#
-one: {}
-# column: value
-#
-two: {}
-# column: value
diff --git a/test/models/merchant_test.rb b/test/models/merchant_test.rb
index 9ab2cc202f..b9694f0c87 100644
--- a/test/models/merchant_test.rb
+++ b/test/models/merchant_test.rb
@@ -4,7 +4,7 @@ class MerchantTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
- test "create a merchant with no email or uid" do
+ test "create a merchant with no email, name or uid" do
merchant = Merchant.new
assert_not merchant.valid?
end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 2c5373e528..8d650ce276 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -8,6 +8,13 @@ class ActiveSupport::TestCase
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
Minitest::Reporters.use!
+ def setup
- # Add more helper methods to be used by all tests here...
+ OmniAuth.config.test_mode = true
+
+ OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new({
+ provider: 'github', uid: '123545', info: { email: "a@b.com", name: "Ada" }
+ })
+ end
+ # Add more helper methods to be used by all tests here...
end
From c41976c367bf453e0a9393b99995a6fc01164c41 Mon Sep 17 00:00:00 2001
From: Shari Meggs
Date: Wed, 19 Oct 2016 13:52:33 -0700
Subject: [PATCH 019/144] Other test added but commented out for market model
---
test/models/merchant_test.rb | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/test/models/merchant_test.rb b/test/models/merchant_test.rb
index b9694f0c87..877bea082b 100644
--- a/test/models/merchant_test.rb
+++ b/test/models/merchant_test.rb
@@ -9,4 +9,11 @@ class MerchantTest < ActiveSupport::TestCase
assert_not merchant.valid?
end
+ # test "the provider must be from github" do
+ # git_hash = {user_name: "kitty", email: "abc@aol.com", uid: 15, provider: "twitter"}
+ # merchant = Merchant.build_from_github(git_hash)
+ # assert_not merchant.valid?
+ #
+ # end
+
end
From 5698a3dd1a86f09857d96c3f0f5bd1f005e1dd09 Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Wed, 19 Oct 2016 13:52:43 -0700
Subject: [PATCH 020/144] index: true added to belongs_to :merchant
---
app/models/product.rb | 1 +
db/migrate/20161019045258_create_products.rb | 2 +-
db/schema.rb | 2 ++
3 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/app/models/product.rb b/app/models/product.rb
index 6887aee930..f21abd6d59 100644
--- a/app/models/product.rb
+++ b/app/models/product.rb
@@ -1,5 +1,6 @@
class Product < ActiveRecord::Base
has_and_belongs_to_many :categories
+ has_many :orderitems
belongs_to :merchant
validates :name, presence: true, uniqueness: true
validates :price, presence: true, numericality: {only_integer: true, greater_than: 0}
diff --git a/db/migrate/20161019045258_create_products.rb b/db/migrate/20161019045258_create_products.rb
index 4929487895..d3eab0c07e 100644
--- a/db/migrate/20161019045258_create_products.rb
+++ b/db/migrate/20161019045258_create_products.rb
@@ -6,7 +6,7 @@ def change
t.integer :stock
t.integer :price
t.string :photo_url
- t.belongs_to :merchant
+ t.belongs_to :merchant, index: true
t.timestamps null: false
end
diff --git a/db/schema.rb b/db/schema.rb
index 023c2295a4..e67a5fd00a 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -24,4 +24,6 @@
t.datetime "updated_at", null: false
end
+ add_index "products", ["merchant_id"], name: "index_products_on_merchant_id"
+
end
From 723c4b5842de4dcc40dafa7b01c7287936a093da Mon Sep 17 00:00:00 2001
From: Yeni
Date: Wed, 19 Oct 2016 13:59:00 -0700
Subject: [PATCH 021/144] Created order and category models and created the
tests for order. Added validations to Order.
---
app/models/category.rb | 3 ++
app/models/order.rb | 15 ++++++++
.../20161019080932_create_categories.rb | 8 +++++
db/migrate/20161019094620_create_orders.rb | 19 ++++++++++
.../20161019205504_remove_total_from_order.rb | 5 +++
db/schema.rb | 36 +++++++++++++++++++
test/fixtures/categories.yml | 11 ++++++
test/fixtures/orders.yml | 11 ++++++
test/models/category_test.rb | 7 ++++
test/models/order_test.rb | 24 +++++++++++++
10 files changed, 139 insertions(+)
create mode 100644 app/models/category.rb
create mode 100644 app/models/order.rb
create mode 100644 db/migrate/20161019080932_create_categories.rb
create mode 100644 db/migrate/20161019094620_create_orders.rb
create mode 100644 db/migrate/20161019205504_remove_total_from_order.rb
create mode 100644 db/schema.rb
create mode 100644 test/fixtures/categories.yml
create mode 100644 test/fixtures/orders.yml
create mode 100644 test/models/category_test.rb
create mode 100644 test/models/order_test.rb
diff --git a/app/models/category.rb b/app/models/category.rb
new file mode 100644
index 0000000000..7f018cd725
--- /dev/null
+++ b/app/models/category.rb
@@ -0,0 +1,3 @@
+class Category < ActiveRecord::Base
+ has_and_belongs_to_many :products
+end
diff --git a/app/models/order.rb b/app/models/order.rb
new file mode 100644
index 0000000000..9adfb3ab8e
--- /dev/null
+++ b/app/models/order.rb
@@ -0,0 +1,15 @@
+class Order < ActiveRecord::Base
+ has_many :order_items
+ validates :cc_number, presence: true, numericality: {only_integer: true}, length: { is: 16 }
+ validates :cc_exp_year, presence: true, length: {is: 4}
+ validates :cc_exp_month, presence: true, numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 12}
+ validate :valid_exp
+
+ def valid_exp
+ if cc_exp_year < Time.now.year
+ errors.add(:cc_exp_year, "Card year is expired")
+ elsif cc_exp_year == Time.now.year && cc_exp_month < Time.now.month
+ errors.add(:cc_exp_month, "Card month is expired")
+ end
+ end
+end
diff --git a/db/migrate/20161019080932_create_categories.rb b/db/migrate/20161019080932_create_categories.rb
new file mode 100644
index 0000000000..4b0ff05055
--- /dev/null
+++ b/db/migrate/20161019080932_create_categories.rb
@@ -0,0 +1,8 @@
+class CreateCategories < ActiveRecord::Migration
+ def change
+ create_table :categories do |t|
+ t.string :name
+ t.timestamps null: false
+ end
+ end
+end
diff --git a/db/migrate/20161019094620_create_orders.rb b/db/migrate/20161019094620_create_orders.rb
new file mode 100644
index 0000000000..569c40288e
--- /dev/null
+++ b/db/migrate/20161019094620_create_orders.rb
@@ -0,0 +1,19 @@
+class CreateOrders < ActiveRecord::Migration
+ def change
+ create_table :orders do |t|
+
+ t.string :status
+ t.datetime :date_purchased
+ t.string :email
+ t.string :address
+ t.string :cc_name
+ t.integer :cc_number
+ t.integer :cc_exp_year
+ t.integer :cc_exp_month
+ t.integer :billing_zip
+ t.integer :total
+
+ t.timestamps null: false
+ end
+ end
+end
diff --git a/db/migrate/20161019205504_remove_total_from_order.rb b/db/migrate/20161019205504_remove_total_from_order.rb
new file mode 100644
index 0000000000..3f023ddf11
--- /dev/null
+++ b/db/migrate/20161019205504_remove_total_from_order.rb
@@ -0,0 +1,5 @@
+class RemoveTotalFromOrder < ActiveRecord::Migration
+ def change
+ remove_column(:orders, :total)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
new file mode 100644
index 0000000000..25c9333501
--- /dev/null
+++ b/db/schema.rb
@@ -0,0 +1,36 @@
+# encoding: UTF-8
+# This file is auto-generated from the current state of the database. Instead
+# of editing this file, please use the migrations feature of Active Record to
+# incrementally modify your database, and then regenerate this schema definition.
+#
+# Note that this schema.rb definition is the authoritative source for your
+# database schema. If you need to create the application database on another
+# system, you should be using db:schema:load, not running all the migrations
+# from scratch. The latter is a flawed and unsustainable approach (the more migrations
+# you'll amass, the slower it'll run and the greater likelihood for issues).
+#
+# It's strongly recommended that you check this file into your version control system.
+
+ActiveRecord::Schema.define(version: 20161019205504) do
+
+ create_table "categories", force: :cascade do |t|
+ t.string "name"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+ create_table "orders", force: :cascade do |t|
+ t.string "status"
+ t.datetime "date_purchased"
+ t.string "email"
+ t.string "address"
+ t.string "cc_name"
+ t.integer "cc_number"
+ t.integer "cc_exp_year"
+ t.integer "cc_exp_month"
+ t.integer "billing_zip"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+end
diff --git a/test/fixtures/categories.yml b/test/fixtures/categories.yml
new file mode 100644
index 0000000000..937a0c002e
--- /dev/null
+++ b/test/fixtures/categories.yml
@@ -0,0 +1,11 @@
+# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+# This model initially had no columns defined. If you add columns to the
+# model remove the '{}' from the fixture names and add the columns immediately
+# below each fixture, per the syntax in the comments below
+#
+one: {}
+# column: value
+#
+two: {}
+# column: value
diff --git a/test/fixtures/orders.yml b/test/fixtures/orders.yml
new file mode 100644
index 0000000000..937a0c002e
--- /dev/null
+++ b/test/fixtures/orders.yml
@@ -0,0 +1,11 @@
+# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+# This model initially had no columns defined. If you add columns to the
+# model remove the '{}' from the fixture names and add the columns immediately
+# below each fixture, per the syntax in the comments below
+#
+one: {}
+# column: value
+#
+two: {}
+# column: value
diff --git a/test/models/category_test.rb b/test/models/category_test.rb
new file mode 100644
index 0000000000..4733541516
--- /dev/null
+++ b/test/models/category_test.rb
@@ -0,0 +1,7 @@
+require 'test_helper'
+
+class CategoryTest < ActiveSupport::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end
diff --git a/test/models/order_test.rb b/test/models/order_test.rb
new file mode 100644
index 0000000000..53a53e3a38
--- /dev/null
+++ b/test/models/order_test.rb
@@ -0,0 +1,24 @@
+require 'test_helper'
+
+class OrderTest < ActiveSupport::TestCase
+ test "credit card must present and be 16 characters long" do
+ order = Order.new(cc_number: 2345123454326789, cc_exp_year: 2016, cc_exp_month: 10)
+ order2 = Order.new(cc_number: 1234, cc_exp_year: 1990, cc_exp_month: 7)
+ order3 = Order.new(cc_number: 2345123454326789234, cc_exp_year: 1990, cc_exp_month: 7)
+
+ assert order.valid?
+ assert_not order2.valid?
+ assert_not order3.valid?
+ end
+
+ test "credit card should not have an expired date" do
+ order = Order.new(cc_exp_year: 2014, cc_exp_month: 12)
+ order2 = Order.new(cc_exp_month: 10, cc_exp_year: 2013)
+ order3 = Order.new(cc_exp_year: 2016, cc_exp_month: 9, cc_number: 2345123454326789)
+
+ assert_not order.valid?
+ assert_not order2.valid?
+ #this assertion should not pass since month is in the past, month is not yet linked to year, working on it
+ assert_not order3.valid?
+ end
+end
From 195a2345dd3a8b2e2888222f05093befd432acb7 Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Wed, 19 Oct 2016 14:12:43 -0700
Subject: [PATCH 022/144] schema changes
---
db/schema.rb | 58 +++++++++++++++++++++++++++-------------------------
1 file changed, 30 insertions(+), 28 deletions(-)
diff --git a/db/schema.rb b/db/schema.rb
index b1e76cda0d..9ca4ea1782 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,37 +11,12 @@
#
# It's strongly recommended that you check this file into your version control system.
-
-ActiveRecord::Schema.define(version: 20161019043527) do
-
- create_table "order_items", force: :cascade do |t|
- t.integer "quantity", default: 1
- t.integer "product_id"
- t.integer "order_id"
- t.boolean "shipped?", default: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- end
-
- create_table "reviews", force: :cascade do |t|
- t.integer "rating"
- t.string "description"
- t.integer "product_id"
- end
-
- create_table "products", force: :cascade do |t|
- t.string "name"
- t.string "description"
- t.integer "stock"
- t.integer "price"
- t.string "photo_url"
- t.integer "merchant_id"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- end
+ActiveRecord::Schema.define(version: 20161019205504) do
create_table "categories", force: :cascade do |t|
t.string "name"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
end
create_table "merchants", force: :cascade do |t|
@@ -53,6 +28,14 @@
t.datetime "updated_at", null: false
end
+ create_table "order_items", force: :cascade do |t|
+ t.integer "quantity", default: 1
+ t.integer "product_id"
+ t.integer "order_id"
+ t.boolean "shipped?", default: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
create_table "orders", force: :cascade do |t|
t.string "status"
@@ -68,6 +51,25 @@
t.datetime "updated_at", null: false
end
+ create_table "products", force: :cascade do |t|
+ t.string "name"
+ t.string "description"
+ t.integer "stock"
+ t.integer "price"
+ t.string "photo_url"
+ t.integer "merchant_id"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
add_index "products", ["merchant_id"], name: "index_products_on_merchant_id"
+ create_table "reviews", force: :cascade do |t|
+ t.integer "rating"
+ t.string "description"
+ t.integer "product_id"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
end
From 6eb420b7e3b97ac0de392de10c459ffdc7072cfd Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Wed, 19 Oct 2016 14:34:30 -0700
Subject: [PATCH 023/144] added 2 controllers: home and sessions
---
app/assets/javascripts/home.coffee | 3 +++
app/assets/javascripts/sessions.coffee | 3 +++
app/assets/stylesheets/home.scss | 3 +++
app/assets/stylesheets/sessions.scss | 3 +++
app/controllers/home_controller.rb | 3 +++
app/controllers/sessions_controller.rb | 2 ++
app/helpers/home_helper.rb | 2 ++
app/helpers/sessions_helper.rb | 2 ++
config/routes.rb | 4 ++--
test/controllers/home_controller_test.rb | 7 +++++++
test/controllers/sessions_controller_test.rb | 7 +++++++
11 files changed, 37 insertions(+), 2 deletions(-)
create mode 100644 app/assets/javascripts/home.coffee
create mode 100644 app/assets/javascripts/sessions.coffee
create mode 100644 app/assets/stylesheets/home.scss
create mode 100644 app/assets/stylesheets/sessions.scss
create mode 100644 app/controllers/home_controller.rb
create mode 100644 app/controllers/sessions_controller.rb
create mode 100644 app/helpers/home_helper.rb
create mode 100644 app/helpers/sessions_helper.rb
create mode 100644 test/controllers/home_controller_test.rb
create mode 100644 test/controllers/sessions_controller_test.rb
diff --git a/app/assets/javascripts/home.coffee b/app/assets/javascripts/home.coffee
new file mode 100644
index 0000000000..24f83d18bb
--- /dev/null
+++ b/app/assets/javascripts/home.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/javascripts/sessions.coffee b/app/assets/javascripts/sessions.coffee
new file mode 100644
index 0000000000..24f83d18bb
--- /dev/null
+++ b/app/assets/javascripts/sessions.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/stylesheets/home.scss b/app/assets/stylesheets/home.scss
new file mode 100644
index 0000000000..f0ddc6846a
--- /dev/null
+++ b/app/assets/stylesheets/home.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the home controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/sessions.scss b/app/assets/stylesheets/sessions.scss
new file mode 100644
index 0000000000..7bef9cf826
--- /dev/null
+++ b/app/assets/stylesheets/sessions.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the sessions controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
new file mode 100644
index 0000000000..3930f023d2
--- /dev/null
+++ b/app/controllers/home_controller.rb
@@ -0,0 +1,3 @@
+class HomeController < ApplicationController
+ def index; end
+end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
new file mode 100644
index 0000000000..16d11b5710
--- /dev/null
+++ b/app/controllers/sessions_controller.rb
@@ -0,0 +1,2 @@
+class SessionsController < ApplicationController
+end
diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb
new file mode 100644
index 0000000000..23de56ac60
--- /dev/null
+++ b/app/helpers/home_helper.rb
@@ -0,0 +1,2 @@
+module HomeHelper
+end
diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb
new file mode 100644
index 0000000000..309f8b2eb3
--- /dev/null
+++ b/app/helpers/sessions_helper.rb
@@ -0,0 +1,2 @@
+module SessionsHelper
+end
diff --git a/config/routes.rb b/config/routes.rb
index 2d0b71e807..4fe790456b 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,6 +1,6 @@
Rails.application.routes.draw do
- root 'ffc#index'
+ root 'home#index'
resources :merchants, only: [:show] do
resources :products
@@ -24,7 +24,7 @@
# Sessions routes - can be further flushed out...
get '/auth/:provider/callback' => 'sessions#create'
- # get "/sessions/login_failure", to: "sessions#login_failure", as: "login_failure"
+ get "/sessions/login_failure", to: "sessions#login_failure", as: "login_failure"
get '/sessions', to: 'sessions#index', as: 'sessions'
diff --git a/test/controllers/home_controller_test.rb b/test/controllers/home_controller_test.rb
new file mode 100644
index 0000000000..730478d380
--- /dev/null
+++ b/test/controllers/home_controller_test.rb
@@ -0,0 +1,7 @@
+require 'test_helper'
+
+class HomeControllerTest < ActionController::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end
diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb
new file mode 100644
index 0000000000..d30ebc380a
--- /dev/null
+++ b/test/controllers/sessions_controller_test.rb
@@ -0,0 +1,7 @@
+require 'test_helper'
+
+class SessionsControllerTest < ActionController::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end
From baae425f4e4098bbaed73023920816f63b85e868 Mon Sep 17 00:00:00 2001
From: Shari Meggs
Date: Wed, 19 Oct 2016 14:43:15 -0700
Subject: [PATCH 024/144] Created Products controller and added two tests for
index and show
---
app/assets/javascripts/products.coffee | 3 +++
app/assets/stylesheets/products.scss | 3 +++
app/controllers/products_controller.rb | 11 +++++++++++
app/helpers/products_helper.rb | 2 ++
test/controllers/products_controller_test.rb | 20 ++++++++++++++++++++
test/fixtures/products.yml | 1 +
6 files changed, 40 insertions(+)
create mode 100644 app/assets/javascripts/products.coffee
create mode 100644 app/assets/stylesheets/products.scss
create mode 100644 app/controllers/products_controller.rb
create mode 100644 app/helpers/products_helper.rb
create mode 100644 test/controllers/products_controller_test.rb
diff --git a/app/assets/javascripts/products.coffee b/app/assets/javascripts/products.coffee
new file mode 100644
index 0000000000..24f83d18bb
--- /dev/null
+++ b/app/assets/javascripts/products.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/stylesheets/products.scss b/app/assets/stylesheets/products.scss
new file mode 100644
index 0000000000..89e2e8db07
--- /dev/null
+++ b/app/assets/stylesheets/products.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the products controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb
new file mode 100644
index 0000000000..8260adda46
--- /dev/null
+++ b/app/controllers/products_controller.rb
@@ -0,0 +1,11 @@
+class ProductsController < ApplicationController
+
+ def index
+ @product = Product.all
+ end
+
+ def show
+ @product = Product.find()
+ end
+
+end
diff --git a/app/helpers/products_helper.rb b/app/helpers/products_helper.rb
new file mode 100644
index 0000000000..ab5c42b325
--- /dev/null
+++ b/app/helpers/products_helper.rb
@@ -0,0 +1,2 @@
+module ProductsHelper
+end
diff --git a/test/controllers/products_controller_test.rb b/test/controllers/products_controller_test.rb
new file mode 100644
index 0000000000..e24dfd03dd
--- /dev/null
+++ b/test/controllers/products_controller_test.rb
@@ -0,0 +1,20 @@
+require 'test_helper'
+
+class ProductsControllerTest < ActionController::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+ test "should show the index page" do
+ get :index
+ assert_template :index
+ assert_response :success
+
+ end
+
+ test "show should a specific product" do
+ get :show, {id: products(:cat_suit).id}
+ assert_response :success
+ assert_template :show
+ assert_equal assigns(:product), products(:cat_suit)
+ end
+end
diff --git a/test/fixtures/products.yml b/test/fixtures/products.yml
index 3940fc3bc6..88dccbd403 100644
--- a/test/fixtures/products.yml
+++ b/test/fixtures/products.yml
@@ -4,6 +4,7 @@ cat_suit:
description: your cat will look dapper in this one-piece suit!
stock: 4
photo_url: http://placekitten.com/200/300
+
hamster_monocle:
name: hamster monocle
price: 500
From 6b692cb212768f0f7b5ee23ee141fc7962195b2a Mon Sep 17 00:00:00 2001
From: Shari Meggs
Date: Wed, 19 Oct 2016 14:47:36 -0700
Subject: [PATCH 025/144] Created views for product and passed tests
---
app/controllers/products_controller.rb | 7 ++++++-
app/views/products/index.html.erb | 1 +
app/views/products/show.html.erb | 1 +
test/controllers/products_controller_test.rb | 1 -
4 files changed, 8 insertions(+), 2 deletions(-)
create mode 100644 app/views/products/index.html.erb
create mode 100644 app/views/products/show.html.erb
diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb
index 8260adda46..93fa2cf387 100644
--- a/app/controllers/products_controller.rb
+++ b/app/controllers/products_controller.rb
@@ -5,7 +5,12 @@ def index
end
def show
- @product = Product.find()
+ @product = Product.find(params[:id])
+ end
+ private
+
+ def product_params
+ params.require(:product).permit(:name, :description, :stock, :price,:photo_url, :merchant_id)
end
end
diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb
new file mode 100644
index 0000000000..9a2e16e17c
--- /dev/null
+++ b/app/views/products/index.html.erb
@@ -0,0 +1 @@
+All Products Here
diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb
new file mode 100644
index 0000000000..6dffa8bf1a
--- /dev/null
+++ b/app/views/products/show.html.erb
@@ -0,0 +1 @@
+Show single product here
diff --git a/test/controllers/products_controller_test.rb b/test/controllers/products_controller_test.rb
index e24dfd03dd..46de3ddb10 100644
--- a/test/controllers/products_controller_test.rb
+++ b/test/controllers/products_controller_test.rb
@@ -8,7 +8,6 @@ class ProductsControllerTest < ActionController::TestCase
get :index
assert_template :index
assert_response :success
-
end
test "show should a specific product" do
From a4e3975c4f6bd086994ffae6416d449fdf1bea3d Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Wed, 19 Oct 2016 15:14:22 -0700
Subject: [PATCH 026/144] created SESSIONS controller--and 2 views: index and
login_failure. Created the home_index view. Added login and logout buttons to
views.
---
app/controllers/sessions_controller.rb | 38 +++++++++++++++++++++++
app/views/home/index.html.erb | 5 +++
app/views/sessions/index.html.erb | 5 +++
app/views/sessions/login_failure.html.erb | 3 ++
4 files changed, 51 insertions(+)
create mode 100644 app/views/home/index.html.erb
create mode 100644 app/views/sessions/index.html.erb
create mode 100644 app/views/sessions/login_failure.html.erb
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 16d11b5710..96a8907a04 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -1,2 +1,40 @@
class SessionsController < ApplicationController
+# If we want to implement this later, will need to customize authorization specifics...
+ # skip_before_action :require_login, only: [:login, :create]
+
+ def login_failure; end
+
+ def login; end
+
+ def create
+ auth_hash = request.env['omniauth.auth']
+ redirect_to login_failure_path unless auth_hash['uid']
+
+ @merchant = Merchant.find_by(uid: auth_hash[:uid], provider: 'github')
+ if @merchant.nil?
+ # Merchant doesn't match anything in the DB.
+ # Attempt to create a new merchant.
+ @merchant = Merchant.build_from_github(auth_hash)
+ render :login_failure unless @merchant.save
+ end
+
+ # Save the merchant ID in the session
+ session[:merchant_id] = @merchant.id
+
+ redirect_to sessions_path
+ end
+
+ def index
+ if session[:merchant_id].nil?
+ redirect_to login_failure_path
+ else
+ @merchant = Merchant.find(session[:merchant_id]) # < recalls the value set in a previous request
+ end
+ end
+
+ def destroy
+ session.delete(:merchant_id)
+ redirect_to login_failure_path
+ end
+
end
diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb
new file mode 100644
index 0000000000..2f5646d339
--- /dev/null
+++ b/app/views/home/index.html.erb
@@ -0,0 +1,5 @@
+<% if flash[:error] %>
+ <%= flash[:error] %>
+<% end %>
+
+Please <%= link_to "log in", "/auth/github" %>
diff --git a/app/views/sessions/index.html.erb b/app/views/sessions/index.html.erb
new file mode 100644
index 0000000000..44df1ee082
--- /dev/null
+++ b/app/views/sessions/index.html.erb
@@ -0,0 +1,5 @@
+Merchant Portal...
+Login successful.
+Welcome <%= @merchant.email %>!
+
+<%= button_to "Log out", sessions_path, method: :delete %>
diff --git a/app/views/sessions/login_failure.html.erb b/app/views/sessions/login_failure.html.erb
new file mode 100644
index 0000000000..c2c225e6c2
--- /dev/null
+++ b/app/views/sessions/login_failure.html.erb
@@ -0,0 +1,3 @@
+You failed to login.
+
+Please <%= link_to "log in", "/auth/github" %>
From 21177544a5b1276b0e187de069d1354a429de957 Mon Sep 17 00:00:00 2001
From: Shari Meggs
Date: Wed, 19 Oct 2016 15:26:22 -0700
Subject: [PATCH 027/144] Added Seed data to product and played around with the
index page
---
app/views/products/index.html.erb | 8 +++++
db/seeds.rb | 49 +++++++++++++++++++++++++++++++
2 files changed, 57 insertions(+)
diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb
index 9a2e16e17c..5af24b4aa4 100644
--- a/app/views/products/index.html.erb
+++ b/app/views/products/index.html.erb
@@ -1 +1,9 @@
All Products Here
+
+<%@product.each do |product|%>
+
+ <%= image_tag "#{product.url}"%>
+ <%= product.name %>
+ <%= product.description %>
+
+<%end%>
diff --git a/db/seeds.rb b/db/seeds.rb
index 4edb1e857e..8df550054a 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -5,3 +5,52 @@
#
# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
# Mayor.create(name: 'Emanuel', city: cities.first)
+
+product = [
+ {
+ name: "cat pants",
+ description: "a nice pair of slacks with an elastic waist",
+ stock: 15,
+ price: 850,
+ photo_url: "https://s-media-cache-ak0.pinimg.com/236x/72/77/86/7277863e594b27677d2228101ef7e293.jpg/200/300"
+ },
+ {
+ name: "llama scarf",
+ description: "long and warm and not made with other llamas",
+ stock: 20,
+ price: 1000,
+ photo_url: "http://www.yesandyes.org/wp-content/uploads/2011/01/scarf5.jpg/200/300"
+ },
+ {
+ name: "penguin hawaiin shirt",
+ description: "For the penguin who wants to get away",
+ stock: 4,
+ price: 2000,
+ photo_url: "https://s-media-cache-ak0.pinimg.com/originals/bf/8a/c8/bf8ac87e3ae2ff56ec7db987edd60d7d.jpg/200/300"
+ },
+ {
+ name: "top hat for dogs",
+ description: "For the dog who likes nice things",
+ stock: 30,
+ price: 500,
+ photo_url: "http://2damnfunny.com/wp-content/uploads/2013/12/Classy-Dog-With-a-Fancy-Top-Hat-a-Rich-Mahogany-Smoke-Pipe.png/200/300"
+ },
+ {
+ name: "pigglet dance outfit",
+ description: "For the lil piggies who just wanna dance!",
+ stock: 12,
+ price: 3050,
+ photo_url: "http://www.fashionworld.co.uk/blog/wp-content/uploads/2016/01/Cute-Animals-Wearing-Clothes-Pigs-in-Tutus-Playing-Pets-25.jpg/200/300"
+ },
+ {
+ name: "Baby Bunny outfit",
+ description: "Are you a new mommy? Dress you little hopper in style!",
+ stock: 4,
+ price: 1900,
+ photo_url: "http://4.bp.blogspot.com/-hZqBuA0W9W8/Uda9I5QVTiI/AAAAAAAABes/Ijk17zrUo-w/s500/tumblr_lsxp1kA2Ks1r4zdm8o1_500.jpg/200/300"
+ }
+]
+
+product.each do |item|
+ Product.create(item)
+end
From 3215e9472008e44d842026f99bd4460370f05875 Mon Sep 17 00:00:00 2001
From: Shari Meggs
Date: Wed, 19 Oct 2016 15:48:44 -0700
Subject: [PATCH 028/144] Changed the index page for products
---
app/views/products/index.html.erb | 2 +-
db/seeds.rb | 12 ++++++------
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb
index 5af24b4aa4..97217e5c32 100644
--- a/app/views/products/index.html.erb
+++ b/app/views/products/index.html.erb
@@ -2,8 +2,8 @@
<%@product.each do |product|%>
- <%= image_tag "#{product.url}"%>
<%= product.name %>
+ <%= image_tag "#{product.photo_url}"%>
<%= product.description %>
<%end%>
diff --git a/db/seeds.rb b/db/seeds.rb
index 8df550054a..23f0682148 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -12,42 +12,42 @@
description: "a nice pair of slacks with an elastic waist",
stock: 15,
price: 850,
- photo_url: "https://s-media-cache-ak0.pinimg.com/236x/72/77/86/7277863e594b27677d2228101ef7e293.jpg/200/300"
+ photo_url: "https://s-media-cache-ak0.pinimg.com/236x/72/77/86/7277863e594b27677d2228101ef7e293.jpg"
},
{
name: "llama scarf",
description: "long and warm and not made with other llamas",
stock: 20,
price: 1000,
- photo_url: "http://www.yesandyes.org/wp-content/uploads/2011/01/scarf5.jpg/200/300"
+ photo_url: "http://www.yesandyes.org/wp-content/uploads/2011/01/scarf5.jpg"
},
{
name: "penguin hawaiin shirt",
description: "For the penguin who wants to get away",
stock: 4,
price: 2000,
- photo_url: "https://s-media-cache-ak0.pinimg.com/originals/bf/8a/c8/bf8ac87e3ae2ff56ec7db987edd60d7d.jpg/200/300"
+ photo_url: "https://s-media-cache-ak0.pinimg.com/originals/bf/8a/c8/bf8ac87e3ae2ff56ec7db987edd60d7d.jpg"
},
{
name: "top hat for dogs",
description: "For the dog who likes nice things",
stock: 30,
price: 500,
- photo_url: "http://2damnfunny.com/wp-content/uploads/2013/12/Classy-Dog-With-a-Fancy-Top-Hat-a-Rich-Mahogany-Smoke-Pipe.png/200/300"
+ photo_url: "http://2damnfunny.com/wp-content/uploads/2013/12/Classy-Dog-With-a-Fancy-Top-Hat-a-Rich-Mahogany-Smoke-Pipe.png"
},
{
name: "pigglet dance outfit",
description: "For the lil piggies who just wanna dance!",
stock: 12,
price: 3050,
- photo_url: "http://www.fashionworld.co.uk/blog/wp-content/uploads/2016/01/Cute-Animals-Wearing-Clothes-Pigs-in-Tutus-Playing-Pets-25.jpg/200/300"
+ photo_url: "http://www.fashionworld.co.uk/blog/wp-content/uploads/2016/01/Cute-Animals-Wearing-Clothes-Pigs-in-Tutus-Playing-Pets-25.jpg"
},
{
name: "Baby Bunny outfit",
description: "Are you a new mommy? Dress you little hopper in style!",
stock: 4,
price: 1900,
- photo_url: "http://4.bp.blogspot.com/-hZqBuA0W9W8/Uda9I5QVTiI/AAAAAAAABes/Ijk17zrUo-w/s500/tumblr_lsxp1kA2Ks1r4zdm8o1_500.jpg/200/300"
+ photo_url: "http://4.bp.blogspot.com/-hZqBuA0W9W8/Uda9I5QVTiI/AAAAAAAABes/Ijk17zrUo-w/s500/tumblr_lsxp1kA2Ks1r4zdm8o1_500.jpg"
}
]
From 72625b6d86fe65c8a6b1156fbcc44c34a38cebd4 Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Wed, 19 Oct 2016 19:40:28 -0700
Subject: [PATCH 029/144] implemented join table for Products and Categories
---
db/migrate/20161020020026_create_join_table.rb | 8 ++++++++
db/schema.rb | 10 +++++++++-
2 files changed, 17 insertions(+), 1 deletion(-)
create mode 100644 db/migrate/20161020020026_create_join_table.rb
diff --git a/db/migrate/20161020020026_create_join_table.rb b/db/migrate/20161020020026_create_join_table.rb
new file mode 100644
index 0000000000..5923f7079e
--- /dev/null
+++ b/db/migrate/20161020020026_create_join_table.rb
@@ -0,0 +1,8 @@
+class CreateJoinTable < ActiveRecord::Migration
+ def change
+ create_join_table :products, :categories do |t|
+ t.index [:product_id, :category_id]
+ t.index [:category_id, :product_id]
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 9ca4ea1782..60adab146f 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20161019205504) do
+ActiveRecord::Schema.define(version: 20161020020026) do
create_table "categories", force: :cascade do |t|
t.string "name"
@@ -19,6 +19,14 @@
t.datetime "updated_at", null: false
end
+ create_table "categories_products", id: false, force: :cascade do |t|
+ t.integer "product_id", null: false
+ t.integer "category_id", null: false
+ end
+
+ add_index "categories_products", ["category_id", "product_id"], name: "index_categories_products_on_category_id_and_product_id"
+ add_index "categories_products", ["product_id", "category_id"], name: "index_categories_products_on_product_id_and_category_id"
+
create_table "merchants", force: :cascade do |t|
t.string "user_name"
t.string "email"
From e3f4c851597955ee747fc481822f13eeadc22c65 Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Wed, 19 Oct 2016 20:24:45 -0700
Subject: [PATCH 030/144] modified join table migration to remove primary key
---
db/migrate/20161020020026_create_join_table.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/db/migrate/20161020020026_create_join_table.rb b/db/migrate/20161020020026_create_join_table.rb
index 5923f7079e..f0eb23754e 100644
--- a/db/migrate/20161020020026_create_join_table.rb
+++ b/db/migrate/20161020020026_create_join_table.rb
@@ -1,6 +1,6 @@
class CreateJoinTable < ActiveRecord::Migration
def change
- create_join_table :products, :categories do |t|
+ create_join_table :products, :categories, id:false do |t|
t.index [:product_id, :category_id]
t.index [:category_id, :product_id]
end
From aadb76a19b713427cd9b632e1a7dcb0123d2017d Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Thu, 20 Oct 2016 14:13:59 -0700
Subject: [PATCH 031/144] added category seeds and generated categories
controller
---
app/assets/javascripts/categories.coffee | 3 ++
app/assets/stylesheets/categories.scss | 3 ++
app/controllers/categories_controller.rb | 2 ++
app/helpers/categories_helper.rb | 2 ++
app/views/products/index.html.erb | 24 ++++++++++++---
db/seeds.rb | 29 +++++++++++++++++--
.../controllers/categories_controller_test.rb | 7 +++++
7 files changed, 63 insertions(+), 7 deletions(-)
create mode 100644 app/assets/javascripts/categories.coffee
create mode 100644 app/assets/stylesheets/categories.scss
create mode 100644 app/controllers/categories_controller.rb
create mode 100644 app/helpers/categories_helper.rb
create mode 100644 test/controllers/categories_controller_test.rb
diff --git a/app/assets/javascripts/categories.coffee b/app/assets/javascripts/categories.coffee
new file mode 100644
index 0000000000..24f83d18bb
--- /dev/null
+++ b/app/assets/javascripts/categories.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/stylesheets/categories.scss b/app/assets/stylesheets/categories.scss
new file mode 100644
index 0000000000..ef1657f8c9
--- /dev/null
+++ b/app/assets/stylesheets/categories.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the categories controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb
new file mode 100644
index 0000000000..a14959525a
--- /dev/null
+++ b/app/controllers/categories_controller.rb
@@ -0,0 +1,2 @@
+class CategoriesController < ApplicationController
+end
diff --git a/app/helpers/categories_helper.rb b/app/helpers/categories_helper.rb
new file mode 100644
index 0000000000..e06f31554c
--- /dev/null
+++ b/app/helpers/categories_helper.rb
@@ -0,0 +1,2 @@
+module CategoriesHelper
+end
diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb
index 97217e5c32..e5daef4b33 100644
--- a/app/views/products/index.html.erb
+++ b/app/views/products/index.html.erb
@@ -1,9 +1,25 @@
All Products Here
<%@product.each do |product|%>
-
- <%= product.name %>
- <%= image_tag "#{product.photo_url}"%>
- <%= product.description %>
+
+
+ <%= product.name %>
+
+
+ <%= image_tag "#{product.photo_url}"%>
+
+
+ <%= product.description %>
+
+
+
+ Categories
+
+
+
+ <% product.categories.each do |category| %>
+ <%= category.name %>
+ <% end %>
+
<%end%>
diff --git a/db/seeds.rb b/db/seeds.rb
index 23f0682148..1c999d6d50 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -22,7 +22,7 @@
photo_url: "http://www.yesandyes.org/wp-content/uploads/2011/01/scarf5.jpg"
},
{
- name: "penguin hawaiin shirt",
+ name: "penguin hawaiian shirt",
description: "For the penguin who wants to get away",
stock: 4,
price: 2000,
@@ -36,7 +36,7 @@
photo_url: "http://2damnfunny.com/wp-content/uploads/2013/12/Classy-Dog-With-a-Fancy-Top-Hat-a-Rich-Mahogany-Smoke-Pipe.png"
},
{
- name: "pigglet dance outfit",
+ name: "piglet dance outfit",
description: "For the lil piggies who just wanna dance!",
stock: 12,
price: 3050,
@@ -44,7 +44,7 @@
},
{
name: "Baby Bunny outfit",
- description: "Are you a new mommy? Dress you little hopper in style!",
+ description: "Are you a new mommy? Dress your little hopper in style!",
stock: 4,
price: 1900,
photo_url: "http://4.bp.blogspot.com/-hZqBuA0W9W8/Uda9I5QVTiI/AAAAAAAABes/Ijk17zrUo-w/s500/tumblr_lsxp1kA2Ks1r4zdm8o1_500.jpg"
@@ -54,3 +54,26 @@
product.each do |item|
Product.create(item)
end
+
+categories = ["cat", "pants", "llama", "scarves", "penguin", "shirts", "hats", "dogs", "pig", "tutu", "baby", "bunny"]
+
+categories.each do |category|
+ Category.create(name: category)
+end
+
+all_categories = Category.all
+all_products = Product.all
+
+
+# NOTE: The code below only works for the seed! My seed data has 2 categories per product in the seed. If this seed
+# data changes, then this code WILL NOT WORK.
+i = 0
+j = 0
+until i == all_products.size
+ product = all_products[i]
+ all_products[i].categories << all_categories[j]
+ j += 1
+ all_products[i].categories << all_categories[j]
+ i+=1
+ j += 1
+end
diff --git a/test/controllers/categories_controller_test.rb b/test/controllers/categories_controller_test.rb
new file mode 100644
index 0000000000..12ca7c1c06
--- /dev/null
+++ b/test/controllers/categories_controller_test.rb
@@ -0,0 +1,7 @@
+require 'test_helper'
+
+class CategoriesControllerTest < ActionController::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end
From 26ade11ee01ee37e068d733f832db7e59f2b4329 Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Thu, 20 Oct 2016 14:27:22 -0700
Subject: [PATCH 032/144] WIP Product / Category tests
---
app/models/category.rb | 1 +
app/models/merchant.rb | 1 +
test/fixtures/categories.yml | 17 ++++++-----------
test/models/category_test.rb | 4 +---
test/models/product_test.rb | 20 ++++++++++----------
5 files changed, 19 insertions(+), 24 deletions(-)
diff --git a/app/models/category.rb b/app/models/category.rb
index 7f018cd725..ea38f95005 100644
--- a/app/models/category.rb
+++ b/app/models/category.rb
@@ -1,3 +1,4 @@
class Category < ActiveRecord::Base
has_and_belongs_to_many :products
+
end
diff --git a/app/models/merchant.rb b/app/models/merchant.rb
index 101c02c85a..0c4b55bc54 100644
--- a/app/models/merchant.rb
+++ b/app/models/merchant.rb
@@ -1,5 +1,6 @@
class Merchant < ActiveRecord::Base
validates :email, :user_name, :uid, :provider, presence: true
+ has_many :products
def self.build_from_github(auth_hash)
merchant = Merchant.new
diff --git a/test/fixtures/categories.yml b/test/fixtures/categories.yml
index 937a0c002e..662436aebe 100644
--- a/test/fixtures/categories.yml
+++ b/test/fixtures/categories.yml
@@ -1,11 +1,6 @@
-# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
-
-# This model initially had no columns defined. If you add columns to the
-# model remove the '{}' from the fixture names and add the columns immediately
-# below each fixture, per the syntax in the comments below
-#
-one: {}
-# column: value
-#
-two: {}
-# column: value
+shoes:
+ name: shoes
+ducks:
+ name: ducks
+hats:
+ name: hats
diff --git a/test/models/category_test.rb b/test/models/category_test.rb
index 4733541516..3b9edf75cc 100644
--- a/test/models/category_test.rb
+++ b/test/models/category_test.rb
@@ -1,7 +1,5 @@
require 'test_helper'
class CategoryTest < ActiveSupport::TestCase
- # test "the truth" do
- # assert true
- # end
+
end
diff --git a/test/models/product_test.rb b/test/models/product_test.rb
index 869ce5c1db..90fbe23328 100644
--- a/test/models/product_test.rb
+++ b/test/models/product_test.rb
@@ -55,16 +55,16 @@ class ProductTest < ActiveSupport::TestCase
end
end
- # test "Product can be assigned a merchant id" do
- # product = Product.create!(name: "mouse hat", price: 1240)
- # merchant = Merchant.create!(user_name: "testing", email: "test@test.com")
- #
- # product.merchant = merchant
- # assert product.save
- #
- # assert_equal product.merchant_id, merchant.id
- # assert_includes merchant.products, product
- # end
+ test "Product can be assigned a merchant id" do
+ product = Product.create!(name: "mouse hat", price: 1240)
+ merchant = Merchant.create!(user_name: "testing", email: "test@test.com", uid: 124, provider: "github")
+
+ product.merchant = merchant
+ assert product.save
+
+ assert_equal product.merchant_id, merchant.id
+ assert_includes merchant.products, product
+ end
# test "Product can have many categories" do
# # INSERT TEST HERE
From 8530855a6b220aa0b8add557525b318a9e6f9d32 Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Thu, 20 Oct 2016 14:34:09 -0700
Subject: [PATCH 033/144] created (and ran) migration, adding an active? column
[boolean datatype] into the products table so newly created products are
active by default, and we have a space in the database that will hold this
active status, so that merchants can 'retire' their products if they choose.
The retire code will come later.
---
...20161020212452_add_active_column_into_products_table.rb | 5 +++++
db/schema.rb | 7 ++++---
2 files changed, 9 insertions(+), 3 deletions(-)
create mode 100644 db/migrate/20161020212452_add_active_column_into_products_table.rb
diff --git a/db/migrate/20161020212452_add_active_column_into_products_table.rb b/db/migrate/20161020212452_add_active_column_into_products_table.rb
new file mode 100644
index 0000000000..45ac8c9bfa
--- /dev/null
+++ b/db/migrate/20161020212452_add_active_column_into_products_table.rb
@@ -0,0 +1,5 @@
+class AddActiveColumnIntoProductsTable < ActiveRecord::Migration
+ def change
+ add_column(:products, :active?, :boolean, :default => true)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 9ca4ea1782..463d9eb7df 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20161019205504) do
+ActiveRecord::Schema.define(version: 20161020212452) do
create_table "categories", force: :cascade do |t|
t.string "name"
@@ -58,8 +58,9 @@
t.integer "price"
t.string "photo_url"
t.integer "merchant_id"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.boolean "active?", default: true
end
add_index "products", ["merchant_id"], name: "index_products_on_merchant_id"
From d0af4a09f2fe4645cc7fff0a64ce719b8df92077 Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Thu, 20 Oct 2016 15:26:22 -0700
Subject: [PATCH 034/144] created (and ran) migration adding total column back
into the order table. Yeni was correct! This is needed for cart
functionality.
---
...oops_adding_total_back_into_order_table_yeni_is_wise.rb | 5 +++++
db/schema.rb | 7 ++++---
2 files changed, 9 insertions(+), 3 deletions(-)
create mode 100644 db/migrate/20161020222236_oops_adding_total_back_into_order_table_yeni_is_wise.rb
diff --git a/db/migrate/20161020222236_oops_adding_total_back_into_order_table_yeni_is_wise.rb b/db/migrate/20161020222236_oops_adding_total_back_into_order_table_yeni_is_wise.rb
new file mode 100644
index 0000000000..017e951f4f
--- /dev/null
+++ b/db/migrate/20161020222236_oops_adding_total_back_into_order_table_yeni_is_wise.rb
@@ -0,0 +1,5 @@
+class OopsAddingTotalBackIntoOrderTableYeniIsWise < ActiveRecord::Migration
+ def change
+ add_column(:orders, :total, :integer, :default => 0)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 463d9eb7df..9e858bfbe1 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20161020212452) do
+ActiveRecord::Schema.define(version: 20161020222236) do
create_table "categories", force: :cascade do |t|
t.string "name"
@@ -47,8 +47,9 @@
t.integer "cc_exp_year"
t.integer "cc_exp_month"
t.integer "billing_zip"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.integer "total", default: 0
end
create_table "products", force: :cascade do |t|
From 0d4fee257c4684b6aaa62f8bd17c91c860051b0a Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Thu, 20 Oct 2016 15:29:12 -0700
Subject: [PATCH 035/144] CART set-up: updated order model
---
app/models/order.rb | 51 +++++++++++++++++++++++++++++++++------------
1 file changed, 38 insertions(+), 13 deletions(-)
diff --git a/app/models/order.rb b/app/models/order.rb
index 9adfb3ab8e..0ea89d0aa0 100644
--- a/app/models/order.rb
+++ b/app/models/order.rb
@@ -1,15 +1,40 @@
class Order < ActiveRecord::Base
- has_many :order_items
- validates :cc_number, presence: true, numericality: {only_integer: true}, length: { is: 16 }
- validates :cc_exp_year, presence: true, length: {is: 4}
- validates :cc_exp_month, presence: true, numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 12}
- validate :valid_exp
-
- def valid_exp
- if cc_exp_year < Time.now.year
- errors.add(:cc_exp_year, "Card year is expired")
- elsif cc_exp_year == Time.now.year && cc_exp_month < Time.now.month
- errors.add(:cc_exp_month, "Card month is expired")
- end
- end
+ has_many :order_items
+ validates :cc_number, presence: true, numericality: {only_integer: true}, length: { is: 16 }
+ validates :cc_exp_year, presence: true, length: {is: 4}
+ validates :cc_exp_month, presence: true, numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 12}
+ validates :total, presence: true, numericality: { greater_than_or_equal_to: 0}
+ validate :valid_exp
+ validates_associated :order_items
+ validate :acceptable_status
+ before_create :set_order_status
+ before_save :update_total
+
+ def valid_exp
+ if cc_exp_year < Time.now.year
+ errors.add(:cc_exp_year, "Card year is expired")
+ elsif cc_exp_year == Time.now.year && cc_exp_month < Time.now.month
+ errors.add(:cc_exp_month, "Card month is expired")
+ end
+ end
+
+ def acceptable_status
+ if status != "PENDING" && status != "PAID" && status != "COMPLETE" && status != "CANCELLED"
+ errors.add(:status, "Must be PENDING, PAID, COMPLETE or CANCELLED")
+ end
+ end
+
+ def total
+ order_items.collect { |oi| oi.valid? ? (oi.quantity * Product.find(oi.product_id).price) : 0 }.sum
+ end
+
+
+ private
+ def set_order_status
+ self.status = "PENDING"
+ end
+
+ def update_total
+ self[:total] = total
+ end
end
From cfc33fe03ae19a76cb8f18d7616d9b7246352bd5 Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Thu, 20 Oct 2016 18:53:10 -0700
Subject: [PATCH 036/144] finished tests for Product model
---
test/fixtures/categories.yml | 14 ++++++++------
test/fixtures/products.yml | 2 ++
test/models/product_test.rb | 14 ++++++++++----
3 files changed, 20 insertions(+), 10 deletions(-)
diff --git a/test/fixtures/categories.yml b/test/fixtures/categories.yml
index 662436aebe..3a7f156d98 100644
--- a/test/fixtures/categories.yml
+++ b/test/fixtures/categories.yml
@@ -1,6 +1,8 @@
-shoes:
- name: shoes
-ducks:
- name: ducks
-hats:
- name: hats
+cat:
+ name: cat
+formal_wear:
+ name: formal wear
+hamster:
+ name: hamster
+eyewear:
+ name: eyewear
diff --git a/test/fixtures/products.yml b/test/fixtures/products.yml
index 88dccbd403..6f1f89c783 100644
--- a/test/fixtures/products.yml
+++ b/test/fixtures/products.yml
@@ -4,6 +4,7 @@ cat_suit:
description: your cat will look dapper in this one-piece suit!
stock: 4
photo_url: http://placekitten.com/200/300
+ categories: cat, formal_wear
hamster_monocle:
name: hamster monocle
@@ -11,3 +12,4 @@ hamster_monocle:
description: a classy way to correct your hamster's vision.
stock: 20
photo_url: http://placekitten.com/200/300
+ categories: hamster, eyewear
diff --git a/test/models/product_test.rb b/test/models/product_test.rb
index 90fbe23328..fa4a09991a 100644
--- a/test/models/product_test.rb
+++ b/test/models/product_test.rb
@@ -42,7 +42,8 @@ class ProductTest < ActiveSupport::TestCase
end
test "create Product with valid data" do
- product = Product.new(name: "penguin bowtie", price: 2150)
+ product = products(:cat_suit)
+
assert product.valid?
assert_not_nil product.name
assert_not_nil product.price
@@ -66,7 +67,12 @@ class ProductTest < ActiveSupport::TestCase
assert_includes merchant.products, product
end
- # test "Product can have many categories" do
- # # INSERT TEST HERE
- # end
+ test "Product can have many categories" do
+ product = products(:hamster_monocle)
+ category_one = categories(:hamster)
+ category_two = categories(:eyewear)
+ assert_equal 2, product.categories.length
+ assert_includes product.category_ids, category_one.id
+ assert_includes product.category_ids, category_two.id
+ end
end
From 283274d39769f3259be0889c8bc4fad04cfdd28c Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Thu, 20 Oct 2016 19:06:57 -0700
Subject: [PATCH 037/144] wrote tests for and implemented Category model
---
app/models/category.rb | 2 +-
test/models/category_test.rb | 22 +++++++++++++++++++++-
test/models/product_test.rb | 1 +
3 files changed, 23 insertions(+), 2 deletions(-)
diff --git a/app/models/category.rb b/app/models/category.rb
index ea38f95005..77f7f99af7 100644
--- a/app/models/category.rb
+++ b/app/models/category.rb
@@ -1,4 +1,4 @@
class Category < ActiveRecord::Base
has_and_belongs_to_many :products
-
+ validates :name, presence: true, uniqueness: true
end
diff --git a/test/models/category_test.rb b/test/models/category_test.rb
index 3b9edf75cc..a068b01dda 100644
--- a/test/models/category_test.rb
+++ b/test/models/category_test.rb
@@ -1,5 +1,25 @@
require 'test_helper'
class CategoryTest < ActiveSupport::TestCase
-
+ test "category can't have an empty name" do
+ category = Category.new
+ assert_not category.valid?
+ assert_not category.save
+ assert_includes category.errors, :name
+ end
+
+ test "category is valid if it has a name" do
+ categories.each do |category|
+ category = Category.create!(name: category.name)
+ assert category.valid?
+ assert_not_nil category.name
+ end
+ end
+
+ test "category name must be unique" do
+ category = Category.new(name: "formal wear")
+ assert_not category.valid?
+ assert_not category.save
+ assert_equal ["has already been taken"], category.errors.messages[:name]
+ end
end
diff --git a/test/models/product_test.rb b/test/models/product_test.rb
index fa4a09991a..3f87ceeaed 100644
--- a/test/models/product_test.rb
+++ b/test/models/product_test.rb
@@ -18,6 +18,7 @@ class ProductTest < ActiveSupport::TestCase
assert_not product.valid?
assert_not product.save
assert_includes product.errors, :name
+ assert_equal ["has already been taken"], product.errors.messages[:name]
end
test "Price must be an integer" do
From ea1158cb4390a721adc3e9999ec5d7d0249aa0a7 Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Thu, 20 Oct 2016 20:27:05 -0700
Subject: [PATCH 038/144] created (and ran) migrations to add two columns to
the order items table, unit price and total price. This is needed so that the
price when a customer adds something to their cart will not change if the
merchant decides to edit that product's price prior to the customer making
their final purchase.
---
.../20161020231651_add_unit_price_to_order_items_table.rb | 6 ++++++
...161021032202_rename_total_column_in_order_items_table.rb | 5 +++++
db/schema.rb | 4 +++-
3 files changed, 14 insertions(+), 1 deletion(-)
create mode 100644 db/migrate/20161020231651_add_unit_price_to_order_items_table.rb
create mode 100644 db/migrate/20161021032202_rename_total_column_in_order_items_table.rb
diff --git a/db/migrate/20161020231651_add_unit_price_to_order_items_table.rb b/db/migrate/20161020231651_add_unit_price_to_order_items_table.rb
new file mode 100644
index 0000000000..d4cb319224
--- /dev/null
+++ b/db/migrate/20161020231651_add_unit_price_to_order_items_table.rb
@@ -0,0 +1,6 @@
+class AddUnitPriceToOrderItemsTable < ActiveRecord::Migration
+ def change
+ add_column(:order_items, :unit_price, :integer)
+ add_column(:order_items, :total, :integer)
+ end
+end
diff --git a/db/migrate/20161021032202_rename_total_column_in_order_items_table.rb b/db/migrate/20161021032202_rename_total_column_in_order_items_table.rb
new file mode 100644
index 0000000000..ed5397c41d
--- /dev/null
+++ b/db/migrate/20161021032202_rename_total_column_in_order_items_table.rb
@@ -0,0 +1,5 @@
+class RenameTotalColumnInOrderItemsTable < ActiveRecord::Migration
+ def change
+ rename_column(:order_items, :total, :total_price)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 9e858bfbe1..4eeb4e2991 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20161020222236) do
+ActiveRecord::Schema.define(version: 20161020231651) do
create_table "categories", force: :cascade do |t|
t.string "name"
@@ -35,6 +35,8 @@
t.boolean "shipped?", default: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
+ t.integer "unit_price"
+ t.integer "total"
end
create_table "orders", force: :cascade do |t|
From e1849d0cfdc216fd509eb0e0f283a02913788d1c Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Thu, 20 Oct 2016 20:27:30 -0700
Subject: [PATCH 039/144] forgot to add schema to the previous commit
---
db/schema.rb | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/db/schema.rb b/db/schema.rb
index 4eeb4e2991..b0346accdb 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20161020231651) do
+ActiveRecord::Schema.define(version: 20161021032202) do
create_table "categories", force: :cascade do |t|
t.string "name"
@@ -29,14 +29,14 @@
end
create_table "order_items", force: :cascade do |t|
- t.integer "quantity", default: 1
+ t.integer "quantity", default: 1
t.integer "product_id"
t.integer "order_id"
- t.boolean "shipped?", default: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.boolean "shipped?", default: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
t.integer "unit_price"
- t.integer "total"
+ t.integer "total_price"
end
create_table "orders", force: :cascade do |t|
From 7e78a496e7e492edc963ce17e46cc517a7505399 Mon Sep 17 00:00:00 2001
From: Shari Meggs
Date: Thu, 20 Oct 2016 21:23:35 -0700
Subject: [PATCH 040/144] added test to merchant
---
app/models/merchant.rb | 5 +++--
test/models/merchant_test.rb | 12 ++++++------
2 files changed, 9 insertions(+), 8 deletions(-)
diff --git a/app/models/merchant.rb b/app/models/merchant.rb
index 101c02c85a..35484eb289 100644
--- a/app/models/merchant.rb
+++ b/app/models/merchant.rb
@@ -2,11 +2,12 @@ class Merchant < ActiveRecord::Base
validates :email, :user_name, :uid, :provider, presence: true
def self.build_from_github(auth_hash)
+
merchant = Merchant.new
merchant.uid = auth_hash[:uid]
merchant.provider = 'github'
- merchant.user_name = auth_hash['info']['nickname']
- merchant.email = auth_hash['info']['email']
+ merchant.user_name = auth_hash[:info]['nickname']
+ merchant.email = auth_hash[:info]['email']
return merchant
end
end
diff --git a/test/models/merchant_test.rb b/test/models/merchant_test.rb
index 877bea082b..0af2333c8f 100644
--- a/test/models/merchant_test.rb
+++ b/test/models/merchant_test.rb
@@ -9,11 +9,11 @@ class MerchantTest < ActiveSupport::TestCase
assert_not merchant.valid?
end
- # test "the provider must be from github" do
- # git_hash = {user_name: "kitty", email: "abc@aol.com", uid: 15, provider: "twitter"}
- # merchant = Merchant.build_from_github(git_hash)
- # assert_not merchant.valid?
- #
- # end
+ test "the Merchant will not be valid without an email" do
+ git_hash = {uid: 15, provider: "twitter",
+ info: {nickname: 'kitty'}}
+ merchant = Merchant.build_from_github(git_hash)
+ assert_not merchant.valid?
+ end
end
From 2bd7f597d586ac41c7e177001d5f8fbd6d6ae941 Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Fri, 21 Oct 2016 08:35:05 -0700
Subject: [PATCH 041/144] modified views for Products, modified seeds to test
Category show pages
---
app/controllers/categories_controller.rb | 8 +++++++
app/views/categories/show.html.erb | 11 +++++++++
app/views/products/index.html.erb | 16 +++----------
app/views/products/show.html.erb | 20 +++++++++++++++-
db/seeds.rb | 13 ++++++++++-
.../controllers/categories_controller_test.rb | 23 ++++++++++++++++---
6 files changed, 73 insertions(+), 18 deletions(-)
create mode 100644 app/views/categories/show.html.erb
diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb
index a14959525a..5ee0e80115 100644
--- a/app/controllers/categories_controller.rb
+++ b/app/controllers/categories_controller.rb
@@ -1,2 +1,10 @@
class CategoriesController < ApplicationController
+ def show
+ begin
+ @category = Category.find(params[:id])
+ @products = @category.products
+ rescue ActiveRecord::RecordNotFound => err
+ render file: "#{Rails.root}/public/404.html", layout: false, status: :not_found
+ end
+ end
end
diff --git a/app/views/categories/show.html.erb b/app/views/categories/show.html.erb
new file mode 100644
index 0000000000..054e874b66
--- /dev/null
+++ b/app/views/categories/show.html.erb
@@ -0,0 +1,11 @@
+<%= @category.name.capitalize %>
+<% if @products.empty? %>
+
+ No products in this category!
+
+<% end %>
+
+<% @products.each do |product| %>
+ <%= product.name.capitalize %>
+ <%= link_to image_tag("#{product.photo_url}"), product_path(product) %>
+<% end %>
diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb
index e5daef4b33..110577de92 100644
--- a/app/views/products/index.html.erb
+++ b/app/views/products/index.html.erb
@@ -1,9 +1,9 @@
-All Products Here
+All Products
<%@product.each do |product|%>
- <%= product.name %>
+ <%= link_to product.name, product_path(product) %>
<%= image_tag "#{product.photo_url}"%>
@@ -11,15 +11,5 @@
<%= product.description %>
-
-
- Categories
-
-
-
- <% product.categories.each do |category| %>
- <%= category.name %>
- <% end %>
-
-
+
<%end%>
diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb
index 6dffa8bf1a..235fc1d109 100644
--- a/app/views/products/show.html.erb
+++ b/app/views/products/show.html.erb
@@ -1 +1,19 @@
-Show single product here
+<%= @product.name.capitalize %>
+
+
+ <%= image_tag "#{@product.photo_url}" %>
+
+
+
+ Description : <%= @product.description %>
+
+
+<% unless @product.categories.empty? %>
+ Categories
+
+ <% @product.categories.each do |category| %>
+ <%= link_to "#{category.name}", category_path(category) %>
+ <% end %>
+
+<% end %>
+
diff --git a/db/seeds.rb b/db/seeds.rb
index 1c999d6d50..3e70288c89 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -55,7 +55,7 @@
Product.create(item)
end
-categories = ["cat", "pants", "llama", "scarves", "penguin", "shirts", "hats", "dogs", "pig", "tutu", "baby", "bunny"]
+categories = ["cat", "pants", "llama", "scarves", "penguin", "shirts", "hats", "dogs", "pig", "tutu", "baby", "bunny", "mammals", "birds"]
categories.each do |category|
Category.create(name: category)
@@ -77,3 +77,14 @@
i+=1
j += 1
end
+
+birds = Category.find_by(name: "birds")
+mammals = Category.find_by(name: "mammals")
+
+all_products.each do |item|
+ if item.name == "penguin hawaiian shirt"
+ item.categories << birds
+ else
+ item.categories << mammals
+ end
+end
diff --git a/test/controllers/categories_controller_test.rb b/test/controllers/categories_controller_test.rb
index 12ca7c1c06..159fac6006 100644
--- a/test/controllers/categories_controller_test.rb
+++ b/test/controllers/categories_controller_test.rb
@@ -1,7 +1,24 @@
require 'test_helper'
class CategoriesControllerTest < ActionController::TestCase
- # test "the truth" do
- # assert true
- # end
+ test "show page should show the proper category" do
+ cat_id = categories(:cat).id
+ get :show, {id: cat_id}
+ assert_response :success
+ assert_template :show
+
+ category = assigns(:category)
+ assert_not_nil category
+ assert_equal category.id, cat_id
+ end
+
+ test "can't show a category that doesn't exist" do
+ cat_id = 12345
+ assert_raises ActiveRecord::RecordNotFound do
+ Category.find(cat_id)
+ end
+
+ get :show, {id: cat_id}
+ assert_response :not_found
+ end
end
From 90f2278c085fcb19e431392acd296a4fd345c2db Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Fri, 21 Oct 2016 13:09:53 -0700
Subject: [PATCH 042/144] tried to implement all of the guidance website's
code, customizing pieces to our structure. This didn't seem to work, so will
begin refactoring wiht Shari
---
app/assets/javascripts/carts.coffee | 3 ++
app/assets/javascripts/order_items.coffee | 3 ++
app/assets/stylesheets/carts.scss | 3 ++
app/assets/stylesheets/order_items.scss | 3 ++
app/controllers/application_controller.rb | 15 +++++++--
app/controllers/carts_controller.rb | 5 +++
app/controllers/order_items_controller.rb | 26 ++++++++++++++++
app/controllers/products_controller.rb | 3 +-
app/helpers/carts_helper.rb | 2 ++
app/helpers/order_items_helper.rb | 2 ++
app/models/order.rb | 5 +--
app/models/order_item.rb | 19 +++++++++---
app/models/product.rb | 13 +++++---
app/views/carts/_cart_row.html.erb | 28 +++++++++++++++++
app/views/carts/_shopping_cart.html.erb | 18 +++++++++++
app/views/carts/show.html.erb | 3 ++
app/views/layouts/_cart_text.html.erb | 1 +
app/views/layouts/application.html.erb | 31 ++++++++++++++-----
app/views/order_items/create.js.erb | 5 +++
app/views/order_items/destroy.js.erb | 2 ++
app/views/order_items/update.js.erb | 2 ++
app/views/products/_product_row.html.erb | 22 +++++++++++++
app/views/products/index.html.erb | 26 +++++++++++-----
config/routes.rb | 2 ++
test/controllers/carts_controller_test.rb | 7 +++++
.../order_items_controller_test.rb | 7 +++++
test/fixtures/order_items.yml | 2 +-
test/models/order_item_test.rb | 4 +--
28 files changed, 228 insertions(+), 34 deletions(-)
create mode 100644 app/assets/javascripts/carts.coffee
create mode 100644 app/assets/javascripts/order_items.coffee
create mode 100644 app/assets/stylesheets/carts.scss
create mode 100644 app/assets/stylesheets/order_items.scss
create mode 100644 app/controllers/carts_controller.rb
create mode 100644 app/controllers/order_items_controller.rb
create mode 100644 app/helpers/carts_helper.rb
create mode 100644 app/helpers/order_items_helper.rb
create mode 100644 app/views/carts/_cart_row.html.erb
create mode 100644 app/views/carts/_shopping_cart.html.erb
create mode 100644 app/views/carts/show.html.erb
create mode 100644 app/views/layouts/_cart_text.html.erb
create mode 100644 app/views/order_items/create.js.erb
create mode 100644 app/views/order_items/destroy.js.erb
create mode 100644 app/views/order_items/update.js.erb
create mode 100644 app/views/products/_product_row.html.erb
create mode 100644 test/controllers/carts_controller_test.rb
create mode 100644 test/controllers/order_items_controller_test.rb
diff --git a/app/assets/javascripts/carts.coffee b/app/assets/javascripts/carts.coffee
new file mode 100644
index 0000000000..24f83d18bb
--- /dev/null
+++ b/app/assets/javascripts/carts.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/javascripts/order_items.coffee b/app/assets/javascripts/order_items.coffee
new file mode 100644
index 0000000000..24f83d18bb
--- /dev/null
+++ b/app/assets/javascripts/order_items.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/stylesheets/carts.scss b/app/assets/stylesheets/carts.scss
new file mode 100644
index 0000000000..62647c9dde
--- /dev/null
+++ b/app/assets/stylesheets/carts.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the carts controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/order_items.scss b/app/assets/stylesheets/order_items.scss
new file mode 100644
index 0000000000..584862de9b
--- /dev/null
+++ b/app/assets/stylesheets/order_items.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the OrderItems controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index d83690e1b9..c4d9355895 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,5 +1,14 @@
class ApplicationController < ActionController::Base
- # Prevent CSRF attacks by raising an exception.
- # For APIs, you may want to use :null_session instead.
- protect_from_forgery with: :exception
+ # Prevent CSRF attacks by raising an exception.
+ # For APIs, you may want to use :null_session instead.
+ protect_from_forgery with: :exception
+ helper_method :current_order
+
+ def current_order
+ if !session[:order_id].nil?
+ Order.find(session[:order_id])
+ else
+ Order.new
+ end
+ end
end
diff --git a/app/controllers/carts_controller.rb b/app/controllers/carts_controller.rb
new file mode 100644
index 0000000000..82507d4df7
--- /dev/null
+++ b/app/controllers/carts_controller.rb
@@ -0,0 +1,5 @@
+class CartsController < ApplicationController
+ def show
+ @order_items = current_order.order_items
+ end
+end
diff --git a/app/controllers/order_items_controller.rb b/app/controllers/order_items_controller.rb
new file mode 100644
index 0000000000..f3f00ba416
--- /dev/null
+++ b/app/controllers/order_items_controller.rb
@@ -0,0 +1,26 @@
+class OrderItemsController < ApplicationController
+ def create
+ @order = current_order
+ @order_item = @order.order_items.new(order_item_params)
+ @order.save
+ session[:order_id] = @order.id
+ end
+
+ def update
+ @order = current_order
+ @order_item = @order.order_items.find(params[:id])
+ @order_item.update_attributes(order_item_params)
+ @order_items = @order.order_items
+ end
+
+ def destroy
+ @order = current_order
+ @order_item = @order.order_items.find(params[:id])
+ @order_item.destroy
+ @order_items = @order.order_items
+ end
+ private
+ def order_item_params
+ params.require(:order_item).permit(:quantity, :product_id)
+ end
+end
diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb
index 93fa2cf387..1b554c24ca 100644
--- a/app/controllers/products_controller.rb
+++ b/app/controllers/products_controller.rb
@@ -1,7 +1,8 @@
class ProductsController < ApplicationController
def index
- @product = Product.all
+ @products = Product.all
+ @order_item = current_order.order_items.new
end
def show
diff --git a/app/helpers/carts_helper.rb b/app/helpers/carts_helper.rb
new file mode 100644
index 0000000000..d99c380cb5
--- /dev/null
+++ b/app/helpers/carts_helper.rb
@@ -0,0 +1,2 @@
+module CartsHelper
+end
diff --git a/app/helpers/order_items_helper.rb b/app/helpers/order_items_helper.rb
new file mode 100644
index 0000000000..e197528ae1
--- /dev/null
+++ b/app/helpers/order_items_helper.rb
@@ -0,0 +1,2 @@
+module OrderItemsHelper
+end
diff --git a/app/models/order.rb b/app/models/order.rb
index 0ea89d0aa0..13175c661c 100644
--- a/app/models/order.rb
+++ b/app/models/order.rb
@@ -1,5 +1,6 @@
class Order < ActiveRecord::Base
has_many :order_items
+
validates :cc_number, presence: true, numericality: {only_integer: true}, length: { is: 16 }
validates :cc_exp_year, presence: true, length: {is: 4}
validates :cc_exp_month, presence: true, numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 12}
@@ -7,6 +8,7 @@ class Order < ActiveRecord::Base
validate :valid_exp
validates_associated :order_items
validate :acceptable_status
+
before_create :set_order_status
before_save :update_total
@@ -25,10 +27,9 @@ def acceptable_status
end
def total
- order_items.collect { |oi| oi.valid? ? (oi.quantity * Product.find(oi.product_id).price) : 0 }.sum
+ order_items.collect { |oi| oi.valid? ? (oi.quantity * oi.product.price) : 0 }.sum
end
-
private
def set_order_status
self.status = "PENDING"
diff --git a/app/models/order_item.rb b/app/models/order_item.rb
index 6aaa4fbdb4..665c4955ae 100644
--- a/app/models/order_item.rb
+++ b/app/models/order_item.rb
@@ -1,12 +1,21 @@
class OrderItem < ActiveRecord::Base
+ belongs_to :product
+ belongs_to :order
+
validates :quantity, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 1 }
- # less_than_or_equal_to: Product.find(self.product_id).quantity }
- # I'd like this last line of code to work in the numericality hash--it's not working right now. Maybe it will work once the Product model has been created in this version of our app?
- # Also, need to confirm what the quantity/stock variable is in the Product model and update the last word of this commented out code in line 3.
validates :product_id, presence: true
validates :order_id, presence: true
validates :shipped?, inclusion: { in: [true, false] }
+ validate :valid_quantity
- belongs_to :product
- belongs_to :order
+ def total_price
+ self.product.price * quantity
+ end
+
+ private
+ def valid_quantity
+ if quantity > product.stock
+ errors.add(:quantity, "there is not enough stock of this product to fulfill your request, please try again")
+ end
+ end
end
diff --git a/app/models/product.rb b/app/models/product.rb
index f21abd6d59..d3b607cce2 100644
--- a/app/models/product.rb
+++ b/app/models/product.rb
@@ -1,7 +1,10 @@
class Product < ActiveRecord::Base
- has_and_belongs_to_many :categories
- has_many :orderitems
- belongs_to :merchant
- validates :name, presence: true, uniqueness: true
- validates :price, presence: true, numericality: {only_integer: true, greater_than: 0}
+ has_and_belongs_to_many :categories
+ has_many :orderitems
+ belongs_to :merchant
+
+ validates :name, presence: true, uniqueness: true
+ validates :price, presence: true, numericality: {only_integer: true, greater_than: 0}
+
+ default_scope { where(active?: true) }
end
diff --git a/app/views/carts/_cart_row.html.erb b/app/views/carts/_cart_row.html.erb
new file mode 100644
index 0000000000..deccf8ed76
--- /dev/null
+++ b/app/views/carts/_cart_row.html.erb
@@ -0,0 +1,28 @@
+
+
+
+
+
<%= product.name %>
+
+
+
+ <%= form_for order_item, remote: true do |f| %>
+
Unit Price: <%= number_to_currency order_item.unit_price %>
+
+
+ <%= f.number_field :quantity, value: order_item.quantity.to_i, class: "form-control", min: 1 %>
+ <%= f.hidden_field :product_id, value: product.id %>
+
+
+
+ <%= f.submit "Update Quantity", class: "btn btn-primary" %>
+ <%= link_to "Delete", order_item, { data: { confirm: "Are you sure you wish to delete the product '#{ order_item.product.name }' from your cart?" }, method: :delete, remote: true, class: "btn btn-danger" } %>
+
+
+
+
Total Price: <%= number_to_currency order_item.total_price %>
+ <% end %>
+
+
+
+
diff --git a/app/views/carts/_shopping_cart.html.erb b/app/views/carts/_shopping_cart.html.erb
new file mode 100644
index 0000000000..cadfc553cf
--- /dev/null
+++ b/app/views/carts/_shopping_cart.html.erb
@@ -0,0 +1,18 @@
+<% if !@order_item.nil? && @order_item.errors.any? %>
+
+
+ <% @order_item.errors.full_messages.each do |msg| %>
+ <%= msg %>
+ <% end %>
+
+
+<% end %>
+<% if @order_items.size == 0 %>
+
+ There are no items in your shopping cart. Please <%= link_to "go back", root_path %> and add some items to your cart.
+
+<% else %>
+ <% @order_items.each do |order_item| %>
+ <%= render 'carts/cart_row', product: order_item.product, order_item: order_item, show_total: true %>
+ <% end %>
+<% end %>
diff --git a/app/views/carts/show.html.erb b/app/views/carts/show.html.erb
new file mode 100644
index 0000000000..82c62a8d08
--- /dev/null
+++ b/app/views/carts/show.html.erb
@@ -0,0 +1,3 @@
+
+ <%= render "shopping_cart" %>
+
diff --git a/app/views/layouts/_cart_text.html.erb b/app/views/layouts/_cart_text.html.erb
new file mode 100644
index 0000000000..89d05ac0f6
--- /dev/null
+++ b/app/views/layouts/_cart_text.html.erb
@@ -0,0 +1 @@
+<%= link_to "#{current_order.order_items.size} Items in Cart ( #{number_to_currency current_order.total} )", cart_path, class: "btn btn-link" %>
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 9b70e01c2e..a50849697a 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -1,14 +1,31 @@
- Betsy
- <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
- <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
- <%= csrf_meta_tags %>
+ Betsy
+ <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
+ <% if request.ssl? %>
+ <%= stylesheet_link_tag 'https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css' %>
+ <%= javascript_include_tag 'https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js' %>
+ <% else %>
+ <%= stylesheet_link_tag 'http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css' %>
+ <%= javascript_include_tag 'http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js' %>
+ <% end %>
+ <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
+
+ <%= csrf_meta_tags %>
-
-<%= yield %>
-
+
+
+
+
<%= link_to "My Store", root_path %>
+
+
+
<%= render 'layouts/cart_text' %>
+
+
+
+ <%= yield %>
+
diff --git a/app/views/order_items/create.js.erb b/app/views/order_items/create.js.erb
new file mode 100644
index 0000000000..dbc113b962
--- /dev/null
+++ b/app/views/order_items/create.js.erb
@@ -0,0 +1,5 @@
+<% if @order.errors.any? || @order_item.errors.any? %>
+alert("not valid.")
+<% else %>
+$(".cart-text").html("<%= escape_javascript(render 'layouts/cart_text') %>")
+<% end %>
diff --git a/app/views/order_items/destroy.js.erb b/app/views/order_items/destroy.js.erb
new file mode 100644
index 0000000000..d5038cd48e
--- /dev/null
+++ b/app/views/order_items/destroy.js.erb
@@ -0,0 +1,2 @@
+$(".cart-text").html("<%= escape_javascript(render 'layouts/cart_text') %>")
+$(".shopping-cart").html("<%= escape_javascript(render 'carts/shopping_cart') %>")
diff --git a/app/views/order_items/update.js.erb b/app/views/order_items/update.js.erb
new file mode 100644
index 0000000000..d5038cd48e
--- /dev/null
+++ b/app/views/order_items/update.js.erb
@@ -0,0 +1,2 @@
+$(".cart-text").html("<%= escape_javascript(render 'layouts/cart_text') %>")
+$(".shopping-cart").html("<%= escape_javascript(render 'carts/shopping_cart') %>")
diff --git a/app/views/products/_product_row.html.erb b/app/views/products/_product_row.html.erb
new file mode 100644
index 0000000000..dd73d4f7b2
--- /dev/null
+++ b/app/views/products/_product_row.html.erb
@@ -0,0 +1,22 @@
+
+
+
+
+
<%= product.name %>
+
+
+
+ <%= form_for @order_item, remote: true do |f| %>
+
Unit Price: <%= number_to_currency product.price %>
+
+ <% end %>
+
+
+
+
diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb
index 97217e5c32..7096af2f87 100644
--- a/app/views/products/index.html.erb
+++ b/app/views/products/index.html.erb
@@ -1,9 +1,19 @@
-All Products Here
+
-<%@product.each do |product|%>
-
- <%= product.name %>
- <%= image_tag "#{product.photo_url}"%>
- <%= product.description %>
-
-<%end%>
+<%#@products.each do |product|%>
+
+ <%# product.name %>
+ <%# image_tag "#{product.photo_url}"%>
+ <%# product.description %>
+
+<%#end%>
+
+WebTutorial -- Products for Sale
+
+
+
+ <% @products.each do |product| %>
+ <%= render "product_row", product: product, order_item: @order_item %>
+ <% end %>
+
+
diff --git a/config/routes.rb b/config/routes.rb
index 4fe790456b..aff59f062c 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -16,6 +16,8 @@
resources :categories, only: [:new, :create, :show]
+ resource :cart, only: [:show]
+
# We're going to talk about this more if any of us needs to edit this. :)
resources :orders, only: [:new, :create, :show] do
resources :order_items, except: [:index, :show]
diff --git a/test/controllers/carts_controller_test.rb b/test/controllers/carts_controller_test.rb
new file mode 100644
index 0000000000..1e748b83e3
--- /dev/null
+++ b/test/controllers/carts_controller_test.rb
@@ -0,0 +1,7 @@
+require 'test_helper'
+
+class CartsControllerTest < ActionController::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end
diff --git a/test/controllers/order_items_controller_test.rb b/test/controllers/order_items_controller_test.rb
new file mode 100644
index 0000000000..2969f1a158
--- /dev/null
+++ b/test/controllers/order_items_controller_test.rb
@@ -0,0 +1,7 @@
+require 'test_helper'
+
+class OrderItemsControllerTest < ActionController::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end
diff --git a/test/fixtures/order_items.yml b/test/fixtures/order_items.yml
index 3d983a3d27..3060530e6f 100644
--- a/test/fixtures/order_items.yml
+++ b/test/fixtures/order_items.yml
@@ -2,7 +2,7 @@ one_unit:
quantity: 1
product_id: 123
order_id: 456
- shipped?: false
+ shipped?: true
four_unit:
quantity: 4
product_id: 123
diff --git a/test/models/order_item_test.rb b/test/models/order_item_test.rb
index ac7973fe02..1b6fc10c2c 100644
--- a/test/models/order_item_test.rb
+++ b/test/models/order_item_test.rb
@@ -31,7 +31,7 @@ class OrderItemTest < ActiveSupport::TestCase
assert_not no_product_id.valid?
assert_includes(no_product_id.errors, :product_id)
- assert order_items(:one_unit).valid?
+ assert_equal(123, order_items(:one_unit))
assert order_items(:four_unit).valid?
end
@@ -44,7 +44,7 @@ class OrderItemTest < ActiveSupport::TestCase
assert order_items(:four_unit).valid?
end
- test "Creating an order item instantiate :shipped? as false" do
+ test "Creating an order item instantiates :shipped? as false" do
o = OrderItem.new
assert_equal(false, o.shipped?)
end
From 7d845ddec095529d84b1c0a70eefafd0a5672430 Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Fri, 21 Oct 2016 13:51:17 -0700
Subject: [PATCH 043/144] stubbed out necessary product controllers
---
app/controllers/products_controller.rb | 29 ++++++++++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb
index 93fa2cf387..0eab65a23e 100644
--- a/app/controllers/products_controller.rb
+++ b/app/controllers/products_controller.rb
@@ -1,5 +1,16 @@
class ProductsController < ApplicationController
+# products products#index
+# merchant_products products#index
+# " " products#create
+# new_merchant_product products#new
+# edit_merchant_product products#edit
+# product products#show
+# merchant_product products#show
+# " " products#update
+# " " products#update
+# " " products#destroy
+
def index
@product = Product.all
end
@@ -7,6 +18,24 @@ def index
def show
@product = Product.find(params[:id])
end
+
+# should be limited to merchants
+ # def new
+ # end
+ #
+ # def create
+ # end
+ #
+ #
+ # def edit
+ # end
+ #
+ # def update
+ # end
+ #
+ # def destroy
+ # end
+
private
def product_params
From 30402bc5e27c6b92b7553b5541ecab0fb9827688 Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Fri, 21 Oct 2016 15:01:44 -0700
Subject: [PATCH 044/144] cleaned up views and product tests
---
app/models/product.rb | 2 +-
app/views/categories/show.html.erb | 8 ++++----
app/views/products/show.html.erb | 7 ++++++-
test/models/product_test.rb | 7 +++++++
4 files changed, 18 insertions(+), 6 deletions(-)
diff --git a/app/models/product.rb b/app/models/product.rb
index f21abd6d59..70ea3cc125 100644
--- a/app/models/product.rb
+++ b/app/models/product.rb
@@ -1,6 +1,6 @@
class Product < ActiveRecord::Base
has_and_belongs_to_many :categories
- has_many :orderitems
+ has_many :order_items
belongs_to :merchant
validates :name, presence: true, uniqueness: true
validates :price, presence: true, numericality: {only_integer: true, greater_than: 0}
diff --git a/app/views/categories/show.html.erb b/app/views/categories/show.html.erb
index 054e874b66..277fadf86e 100644
--- a/app/views/categories/show.html.erb
+++ b/app/views/categories/show.html.erb
@@ -1,11 +1,11 @@
<%= @category.name.capitalize %>
<% if @products.empty? %>
-
- No products in this category!
-
+
+ No products in this category!
+
<% end %>
<% @products.each do |product| %>
<%= product.name.capitalize %>
- <%= link_to image_tag("#{product.photo_url}"), product_path(product) %>
+ <%= link_to image_tag("#{product.photo_url}"), product_path(product) %>
<% end %>
diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb
index 235fc1d109..33131fef37 100644
--- a/app/views/products/show.html.erb
+++ b/app/views/products/show.html.erb
@@ -8,10 +8,15 @@
Description : <%= @product.description %>
+
+
+ Price: $<%= @product.price / 100.00 %>
+
+
<% unless @product.categories.empty? %>
Categories
- <% @product.categories.each do |category| %>
+ <% @product.categories.each do |category| %>
<%= link_to "#{category.name}", category_path(category) %>
<% end %>
diff --git a/test/models/product_test.rb b/test/models/product_test.rb
index 3f87ceeaed..b73caabd80 100644
--- a/test/models/product_test.rb
+++ b/test/models/product_test.rb
@@ -76,4 +76,11 @@ class ProductTest < ActiveSupport::TestCase
assert_includes product.category_ids, category_one.id
assert_includes product.category_ids, category_two.id
end
+
+ # test "Products can have many order_items" do
+ # product = Product.create!(name: "mouse hat", price: 1240)
+ # order_item = OrderItem.create!(quantity: 1, product_id: product.id, order_id: 1294, shipped?: false)
+ #
+ # assert_equal order_item.product_id, product.id
+ # end
end
From a992011f02bfb199fbd385bb2abde53bf3f8f23c Mon Sep 17 00:00:00 2001
From: Shari Meggs
Date: Fri, 21 Oct 2016 15:06:38 -0700
Subject: [PATCH 045/144] started adding tests for order
---
test/models/order_test.rb | 1 +
1 file changed, 1 insertion(+)
diff --git a/test/models/order_test.rb b/test/models/order_test.rb
index 53a53e3a38..cab804e7a1 100644
--- a/test/models/order_test.rb
+++ b/test/models/order_test.rb
@@ -21,4 +21,5 @@ class OrderTest < ActiveSupport::TestCase
#this assertion should not pass since month is in the past, month is not yet linked to year, working on it
assert_not order3.valid?
end
+
end
From dd00a914ec46502f9f3b17eca06c9ec1519c1638 Mon Sep 17 00:00:00 2001
From: Yeni
Date: Fri, 21 Oct 2016 15:09:44 -0700
Subject: [PATCH 046/144] merged files from branch
---
app/assets/stylesheets/application.css | 40 ++++++++++++--------
app/controllers/sessions_controller.rb | 23 ++++++++---
app/views/home/index.html.erb | 3 +-
app/views/layouts/application.html.erb | 13 ++++++-
app/views/sessions/index.html.erb | 4 +-
app/views/sessions/login_failure.html.erb | 2 +-
test/controllers/sessions_controller_test.rb | 27 +++++++++++--
test/test_helper.rb | 2 +-
8 files changed, 82 insertions(+), 32 deletions(-)
diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css
index f9cd5b3483..19d085d894 100644
--- a/app/assets/stylesheets/application.css
+++ b/app/assets/stylesheets/application.css
@@ -1,15 +1,25 @@
-/*
- * This is a manifest file that'll be compiled into application.css, which will include all the files
- * listed below.
- *
- * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
- * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
- *
- * You're free to add application-wide styles to this file and they'll appear at the bottom of the
- * compiled file so the styles you add here take precedence over styles defined in any styles
- * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
- * file per style scope.
- *
- *= require_tree .
- *= require_self
- */
+.nav-bar li {
+ display: inline;
+ font-family: 'Julius Sans One', sans-serif;
+ border-bottom: 2pt solid #F5B19A;
+ color: #666699;
+ /*previous color #39324b*/
+ margin-right: 8%;
+ margin-left: 8%;
+ padding: 5px 25px;
+ font-size: 9pt;
+}
+
+nav a {
+ color: grey;
+}
+
+a:hover {
+ color: #666699;
+}
+
+a {
+ color: #F5B19A;
+ font-family: 'Quicksand';
+ text-decoration: none;
+}
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 96a8907a04..1e6c83f172 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -12,16 +12,27 @@ def create
@merchant = Merchant.find_by(uid: auth_hash[:uid], provider: 'github')
if @merchant.nil?
- # Merchant doesn't match anything in the DB.
- # Attempt to create a new merchant.
- @merchant = Merchant.build_from_github(auth_hash)
- render :login_failure unless @merchant.save
+ @merchant = Merchant.build_from_github(auth_hash)
+ render :login_failure unless @merchant.save
end
- # Save the merchant ID in the session
session[:merchant_id] = @merchant.id
-
redirect_to sessions_path
+ # if @merchant.nil?
+ # # Merchant doesn't match anything in the DB.
+ # # Attempt to create a new merchant.
+ # @merchant = Merchant.build_from_github(auth_hash)
+ # if @merchant.save
+ # redirect_to sessions_path
+ # else
+ # redirect_to login_failure_path
+ # end
+ # else
+ # # Save the merchant ID in the session
+ # session[:merchant_id] = @merchant.id
+ #
+ # redirect_to sessions_path
+ # end
end
def index
diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb
index 2f5646d339..2e53dc63fc 100644
--- a/app/views/home/index.html.erb
+++ b/app/views/home/index.html.erb
@@ -1,5 +1,4 @@
+
<% if flash[:error] %>
<%= flash[:error] %>
<% end %>
-
-Please <%= link_to "log in", "/auth/github" %>
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 9b70e01c2e..f890f0d524 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -5,9 +5,20 @@
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
<%= csrf_meta_tags %>
+
+
-
+
+ <%= @page_title %>
+
+
+ <%= link_to "HOME", root_path %>
+ <%= link_to "LOGIN", '/auth/github' %>
+ <%= link_to "LOGOUT", sessions_path, method: :delete %>
+
+
+
<%= yield %>
diff --git a/app/views/sessions/index.html.erb b/app/views/sessions/index.html.erb
index 44df1ee082..2d0694d203 100644
--- a/app/views/sessions/index.html.erb
+++ b/app/views/sessions/index.html.erb
@@ -1,5 +1,3 @@
-Merchant Portal...
+<% @page_title= "Merchant Portal..."%>
Login successful.
Welcome <%= @merchant.email %>!
-
-<%= button_to "Log out", sessions_path, method: :delete %>
diff --git a/app/views/sessions/login_failure.html.erb b/app/views/sessions/login_failure.html.erb
index c2c225e6c2..fbcf1eec0a 100644
--- a/app/views/sessions/login_failure.html.erb
+++ b/app/views/sessions/login_failure.html.erb
@@ -1,3 +1,3 @@
You failed to login.
-Please <%= link_to "log in", "/auth/github" %>
+Please log into your account
diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb
index d30ebc380a..49799a552a 100644
--- a/test/controllers/sessions_controller_test.rb
+++ b/test/controllers/sessions_controller_test.rb
@@ -1,7 +1,28 @@
require 'test_helper'
class SessionsControllerTest < ActionController::TestCase
- # test "the truth" do
- # assert true
- # end
+ def login_a_user
+ request.env['omniauth.auth'] = OmniAuth.config.mock_auth[:github]
+ get :create, {provider: "github"}
+ end
+
+ test "Can Create a user" do
+ assert_difference('Merchant.count', 1) do
+ login_a_user
+ assert_response :redirect
+ assert_redirected_to sessions_path
+ end
+ end
+
+ test "If a user logs in twice it doesn't create a 2nd user" do
+ assert_difference('Merchant.count', 1) do
+ login_a_user
+ end
+ assert_no_difference('Merchant.count') do
+ login_a_user
+ assert_response :redirect
+ assert_redirected_to sessions_path
+ assert_not_nil session[:merchant_id]
+ end
+ end
end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 8d650ce276..f782ad5465 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -13,7 +13,7 @@ def setup
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new({
- provider: 'github', uid: '123545', info: { email: "a@b.com", name: "Ada" }
+ provider: 'github', uid: '123545', info: { email: "a@b.com", nickname: "Ada" }
})
end
# Add more helper methods to be used by all tests here...
From f57160bc332b60b2bd1a2cbeb540ddac8e8013ff Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Fri, 21 Oct 2016 15:43:12 -0700
Subject: [PATCH 047/144] friday refactoring with Shari
---
app/controllers/order_items_controller.rb | 23 ++++++++++++++-----
app/models/product.rb | 2 +-
app/views/order_items/_form.html.erb | 5 ++++
app/views/order_items/create.js.erb | 5 ----
app/views/order_items/destroy.js.erb | 2 --
app/views/order_items/edit.html.erb | 1 +
app/views/order_items/update.js.erb | 2 --
.../20161021201059_refactoring_with_shari.rb | 6 +++++
db/schema.rb | 12 ++++------
9 files changed, 35 insertions(+), 23 deletions(-)
create mode 100644 app/views/order_items/_form.html.erb
delete mode 100644 app/views/order_items/create.js.erb
delete mode 100644 app/views/order_items/destroy.js.erb
create mode 100644 app/views/order_items/edit.html.erb
delete mode 100644 app/views/order_items/update.js.erb
create mode 100644 db/migrate/20161021201059_refactoring_with_shari.rb
diff --git a/app/controllers/order_items_controller.rb b/app/controllers/order_items_controller.rb
index f3f00ba416..8b9119674c 100644
--- a/app/controllers/order_items_controller.rb
+++ b/app/controllers/order_items_controller.rb
@@ -4,23 +4,34 @@ def create
@order_item = @order.order_items.new(order_item_params)
@order.save
session[:order_id] = @order.id
+ redirect_to products_path
end
def update
@order = current_order
@order_item = @order.order_items.find(params[:id])
- @order_item.update_attributes(order_item_params)
- @order_items = @order.order_items
+ if @order_item.update(order_item_params)
+ @order_item.save
+ redirect_to order_path(params[:order_id])
+ else
+ render :edit
+ end
end
- def destroy
+ def edit
@order = current_order
@order_item = @order.order_items.find(params[:id])
- @order_item.destroy
- @order_items = @order.order_items
end
+
+ def destroy
+ @order = current_order
+ @order_item = @order.order_items.find(params[:id]).destroy
+ redirect_to order_path(params[:order_id])
+ end
+
+
private
def order_item_params
- params.require(:order_item).permit(:quantity, :product_id)
+ params.require(:order_item).permit(:quantity, :product_id, :order_id, :shipped?)
end
end
diff --git a/app/models/product.rb b/app/models/product.rb
index d3b607cce2..9a8e453996 100644
--- a/app/models/product.rb
+++ b/app/models/product.rb
@@ -1,6 +1,6 @@
class Product < ActiveRecord::Base
has_and_belongs_to_many :categories
- has_many :orderitems
+ has_many :order_items
belongs_to :merchant
validates :name, presence: true, uniqueness: true
diff --git a/app/views/order_items/_form.html.erb b/app/views/order_items/_form.html.erb
new file mode 100644
index 0000000000..41eaa1a8df
--- /dev/null
+++ b/app/views/order_items/_form.html.erb
@@ -0,0 +1,5 @@
+<%= form_for @order_item do |f| %>
+<%= f.label :quantity %>
+<%= f.text_field :quantity %>
+<%= f.submit %>
+<% end %>
diff --git a/app/views/order_items/create.js.erb b/app/views/order_items/create.js.erb
deleted file mode 100644
index dbc113b962..0000000000
--- a/app/views/order_items/create.js.erb
+++ /dev/null
@@ -1,5 +0,0 @@
-<% if @order.errors.any? || @order_item.errors.any? %>
-alert("not valid.")
-<% else %>
-$(".cart-text").html("<%= escape_javascript(render 'layouts/cart_text') %>")
-<% end %>
diff --git a/app/views/order_items/destroy.js.erb b/app/views/order_items/destroy.js.erb
deleted file mode 100644
index d5038cd48e..0000000000
--- a/app/views/order_items/destroy.js.erb
+++ /dev/null
@@ -1,2 +0,0 @@
-$(".cart-text").html("<%= escape_javascript(render 'layouts/cart_text') %>")
-$(".shopping-cart").html("<%= escape_javascript(render 'carts/shopping_cart') %>")
diff --git a/app/views/order_items/edit.html.erb b/app/views/order_items/edit.html.erb
new file mode 100644
index 0000000000..d44c608b6a
--- /dev/null
+++ b/app/views/order_items/edit.html.erb
@@ -0,0 +1 @@
+<%= render partial: "form" %>
diff --git a/app/views/order_items/update.js.erb b/app/views/order_items/update.js.erb
deleted file mode 100644
index d5038cd48e..0000000000
--- a/app/views/order_items/update.js.erb
+++ /dev/null
@@ -1,2 +0,0 @@
-$(".cart-text").html("<%= escape_javascript(render 'layouts/cart_text') %>")
-$(".shopping-cart").html("<%= escape_javascript(render 'carts/shopping_cart') %>")
diff --git a/db/migrate/20161021201059_refactoring_with_shari.rb b/db/migrate/20161021201059_refactoring_with_shari.rb
new file mode 100644
index 0000000000..2acd10f98e
--- /dev/null
+++ b/db/migrate/20161021201059_refactoring_with_shari.rb
@@ -0,0 +1,6 @@
+class RefactoringWithShari < ActiveRecord::Migration
+ def change
+ remove_column(:order_items, :unit_price)
+ remove_column(:order_items, :total_price)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index b0346accdb..1d7c4b534b 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20161021032202) do
+ActiveRecord::Schema.define(version: 20161021201059) do
create_table "categories", force: :cascade do |t|
t.string "name"
@@ -29,14 +29,12 @@
end
create_table "order_items", force: :cascade do |t|
- t.integer "quantity", default: 1
+ t.integer "quantity", default: 1
t.integer "product_id"
t.integer "order_id"
- t.boolean "shipped?", default: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
- t.integer "unit_price"
- t.integer "total_price"
+ t.boolean "shipped?", default: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
end
create_table "orders", force: :cascade do |t|
From 8a55835c0a2ac7d6221b9bf8edf37acc9e97e85b Mon Sep 17 00:00:00 2001
From: Shari Meggs
Date: Sat, 22 Oct 2016 18:04:02 -0700
Subject: [PATCH 048/144] Created cart routes and put in functionality for cart
in controller. Added some basic html but no tests
---
app/controllers/carts_controller.rb | 31 +++++++++++++++++++++++--
app/views/carts/_cart_row.html.erb | 28 ----------------------
app/views/carts/_shopping_cart.html.erb | 18 --------------
app/views/carts/index.html.erb | 27 +++++++++++++++++++++
app/views/carts/show.html.erb | 3 ---
app/views/products/index.html.erb | 5 +++-
config/routes.rb | 7 +++++-
db/schema.rb | 4 +---
8 files changed, 67 insertions(+), 56 deletions(-)
delete mode 100644 app/views/carts/_cart_row.html.erb
delete mode 100644 app/views/carts/_shopping_cart.html.erb
create mode 100644 app/views/carts/index.html.erb
delete mode 100644 app/views/carts/show.html.erb
diff --git a/app/controllers/carts_controller.rb b/app/controllers/carts_controller.rb
index 82507d4df7..d82514aed1 100644
--- a/app/controllers/carts_controller.rb
+++ b/app/controllers/carts_controller.rb
@@ -1,5 +1,32 @@
class CartsController < ApplicationController
- def show
- @order_items = current_order.order_items
+ def index
+ if session[:cart]
+ @cart= session[:cart]
+ else
+ @cart ={}
+ end
+ end
+
+ def add_to_cart
+ id = params[:id]
+
+ if session[:cart]
+ cart = session[:cart]
+ else
+ session[:cart] = {}
+ cart = session[:cart]
+ end
+
+ if cart[id]
+ cart[id] = cart[id] + 1
+ else
+ cart[id] = 1
+ end
+ redirect_to carts_path
+ end
+
+ def empty_cart
+ session[:cart] = nil
+ redirect_to carts_path
end
end
diff --git a/app/views/carts/_cart_row.html.erb b/app/views/carts/_cart_row.html.erb
deleted file mode 100644
index deccf8ed76..0000000000
--- a/app/views/carts/_cart_row.html.erb
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
<%= product.name %>
-
-
-
- <%= form_for order_item, remote: true do |f| %>
-
Unit Price: <%= number_to_currency order_item.unit_price %>
-
-
- <%= f.number_field :quantity, value: order_item.quantity.to_i, class: "form-control", min: 1 %>
- <%= f.hidden_field :product_id, value: product.id %>
-
-
-
- <%= f.submit "Update Quantity", class: "btn btn-primary" %>
- <%= link_to "Delete", order_item, { data: { confirm: "Are you sure you wish to delete the product '#{ order_item.product.name }' from your cart?" }, method: :delete, remote: true, class: "btn btn-danger" } %>
-
-
-
-
Total Price: <%= number_to_currency order_item.total_price %>
- <% end %>
-
-
-
-
diff --git a/app/views/carts/_shopping_cart.html.erb b/app/views/carts/_shopping_cart.html.erb
deleted file mode 100644
index cadfc553cf..0000000000
--- a/app/views/carts/_shopping_cart.html.erb
+++ /dev/null
@@ -1,18 +0,0 @@
-<% if !@order_item.nil? && @order_item.errors.any? %>
-
-
- <% @order_item.errors.full_messages.each do |msg| %>
- <%= msg %>
- <% end %>
-
-
-<% end %>
-<% if @order_items.size == 0 %>
-
- There are no items in your shopping cart. Please <%= link_to "go back", root_path %> and add some items to your cart.
-
-<% else %>
- <% @order_items.each do |order_item| %>
- <%= render 'carts/cart_row', product: order_item.product, order_item: order_item, show_total: true %>
- <% end %>
-<% end %>
diff --git a/app/views/carts/index.html.erb b/app/views/carts/index.html.erb
new file mode 100644
index 0000000000..12ec11df04
--- /dev/null
+++ b/app/views/carts/index.html.erb
@@ -0,0 +1,27 @@
+
+Your Cart
+
+
+<% total = 0 %>
+
+
+
+ <% @cart.each do |id, quantity| %>
+ <% product = Product.find(id) %>
+
+ <%=product.name%>
+ <%=link_to "Learn More", product_path(id)%>
+ <%=number_to_currency(product.price, :unit => '$')%>
+ Quantity: <%=quantity%> <%=link_to "Add More!", add_cart_path(product.id), method: :get%>
+
+ <% total += quantity * product.price %>
+ <%end%>
+ <%if total != 0%>
+ Total: <%=number_to_currency(total, :unit => '$')%>
+ Continue shopping <%=link_to "Here", products_path %>
+
+ <%= link_to "Empty your cart", carts_empty_cart_path%>
+
+<%else%>
+ Your cart is currently empty. Continue shopping <%=link_to "Here", products_path %>
+<%end%>
diff --git a/app/views/carts/show.html.erb b/app/views/carts/show.html.erb
deleted file mode 100644
index 82c62a8d08..0000000000
--- a/app/views/carts/show.html.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-
- <%= render "shopping_cart" %>
-
diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb
index 110577de92..47b1115da6 100644
--- a/app/views/products/index.html.erb
+++ b/app/views/products/index.html.erb
@@ -1,6 +1,6 @@
All Products
-<%@product.each do |product|%>
+<%@products.each do |product|%>
<%= link_to product.name, product_path(product) %>
@@ -11,5 +11,8 @@
<%= product.description %>
+
+ Add to Cart
+
<%end%>
diff --git a/config/routes.rb b/config/routes.rb
index aff59f062c..075eb8ce09 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -16,7 +16,7 @@
resources :categories, only: [:new, :create, :show]
- resource :cart, only: [:show]
+
# We're going to talk about this more if any of us needs to edit this. :)
resources :orders, only: [:new, :create, :show] do
@@ -32,4 +32,9 @@
delete '/sessions', to: 'sessions#destroy'
+ #specific routes for the cart!
+ get '/carts' => 'carts#index'
+ get 'carts/empty_cart' =>'carts#empty_cart'
+ get '/carts/:id', to: 'carts#add_to_cart', as: "add_cart"
+
end
diff --git a/db/schema.rb b/db/schema.rb
index d949fdf785..9999476e2d 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,9 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-
-ActiveRecord::Schema.define(version: 20161020020026) do
-
+ActiveRecord::Schema.define(version: 20161021201059) do
create_table "categories", force: :cascade do |t|
t.string "name"
From 9fdff79a1996c829625253a35966bf18cedf85c5 Mon Sep 17 00:00:00 2001
From: Shari Meggs
Date: Sat, 22 Oct 2016 18:08:39 -0700
Subject: [PATCH 049/144] Fixed indentation
---
app/controllers/carts_controller.rb | 48 ++++++++++++++---------------
1 file changed, 24 insertions(+), 24 deletions(-)
diff --git a/app/controllers/carts_controller.rb b/app/controllers/carts_controller.rb
index d82514aed1..e2caac6aa2 100644
--- a/app/controllers/carts_controller.rb
+++ b/app/controllers/carts_controller.rb
@@ -1,32 +1,32 @@
class CartsController < ApplicationController
- def index
- if session[:cart]
- @cart= session[:cart]
- else
- @cart ={}
- end
+ def index
+ if session[:cart]
+ @cart= session[:cart]
+ else
+ @cart ={}
end
+ end
- def add_to_cart
- id = params[:id]
+ def add_to_cart
+ id = params[:id]
- if session[:cart]
- cart = session[:cart]
- else
- session[:cart] = {}
- cart = session[:cart]
- end
-
- if cart[id]
- cart[id] = cart[id] + 1
- else
- cart[id] = 1
- end
- redirect_to carts_path
+ if session[:cart]
+ cart = session[:cart]
+ else
+ session[:cart] = {}
+ cart = session[:cart]
end
- def empty_cart
- session[:cart] = nil
- redirect_to carts_path
+ if cart[id]
+ cart[id] = cart[id] + 1
+ else
+ cart[id] = 1
end
+ redirect_to carts_path
+ end
+
+ def empty_cart
+ session[:cart] = nil
+ redirect_to carts_path
+ end
end
From 958a26ec2bbf876abcd7691f20393f828937f39b Mon Sep 17 00:00:00 2001
From: Shari Meggs
Date: Sat, 22 Oct 2016 23:10:37 -0700
Subject: [PATCH 050/144] In Progress: Having items in cart get reduce or
deleted
---
app/controllers/carts_controller.rb | 16 ++++++++++++++--
app/views/carts/index.html.erb | 7 ++++++-
config/routes.rb | 2 ++
test/controllers/carts_controller_test.rb | 2 ++
4 files changed, 24 insertions(+), 3 deletions(-)
diff --git a/app/controllers/carts_controller.rb b/app/controllers/carts_controller.rb
index e2caac6aa2..5bfee82616 100644
--- a/app/controllers/carts_controller.rb
+++ b/app/controllers/carts_controller.rb
@@ -1,7 +1,7 @@
class CartsController < ApplicationController
def index
if session[:cart]
- @cart= session[:cart]
+ @cart = session[:cart]
else
@cart ={}
end
@@ -18,13 +18,25 @@ def add_to_cart
end
if cart[id]
- cart[id] = cart[id] + 1
+ cart[id] = cart[id] - 1
else
cart[id] = 1
end
redirect_to carts_path
end
+ def sub_cart
+ cart = session[:cart]
+ cart[params[:id]] = cart[params[:id]] - 1
+ redirect_to carts_path
+ end
+
+ def destroy
+ cart = session[:cart]
+ cart.destroy
+ redirect_to carts_path
+ end
+
def empty_cart
session[:cart] = nil
redirect_to carts_path
diff --git a/app/views/carts/index.html.erb b/app/views/carts/index.html.erb
index 12ec11df04..70173246b1 100644
--- a/app/views/carts/index.html.erb
+++ b/app/views/carts/index.html.erb
@@ -12,7 +12,12 @@
<%=product.name%>
<%=link_to "Learn More", product_path(id)%>
<%=number_to_currency(product.price, :unit => '$')%>
- Quantity: <%=quantity%> <%=link_to "Add More!", add_cart_path(product.id), method: :get%>
+ Quantity: <%=quantity%> <%=link_to "Add More!", add_cart_path(product.id), method: :get%>
+ <%=link_to "Delete Item", sub_cart_path(product.id), method: :delete%>
+
+
+ <%=link_to "Reduce!", sub_cart_path(product.id)%>
+
<% total += quantity * product.price %>
<%end%>
diff --git a/config/routes.rb b/config/routes.rb
index 075eb8ce09..6fe1c855a0 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -36,5 +36,7 @@
get '/carts' => 'carts#index'
get 'carts/empty_cart' =>'carts#empty_cart'
get '/carts/:id', to: 'carts#add_to_cart', as: "add_cart"
+ get '/carts/:id', to: 'carts#sub_cart', as: "sub_cart"
+ delete '/carts/:id', to: 'carts#destroy'
end
diff --git a/test/controllers/carts_controller_test.rb b/test/controllers/carts_controller_test.rb
index 1e748b83e3..f3bb6a94a8 100644
--- a/test/controllers/carts_controller_test.rb
+++ b/test/controllers/carts_controller_test.rb
@@ -4,4 +4,6 @@ class CartsControllerTest < ActionController::TestCase
# test "the truth" do
# assert true
# end
+
+
end
From 4e7c7275c37f0319da4167750a234627e093d006 Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Sun, 23 Oct 2016 13:24:16 -0700
Subject: [PATCH 051/144] edited tests for reviews, products, and categories;
wrote helper methods for displaying prices and capitalization, created
reviews controller and new view, created review form partial
---
app/assets/javascripts/reviews.coffee | 3 +
app/assets/stylesheets/reviews.scss | 3 +
app/controllers/reviews_controller.rb | 13 +++
app/helpers/application_helper.rb | 8 ++
app/helpers/reviews_helper.rb | 2 +
app/models/product.rb | 1 +
app/views/products/index.html.erb | 2 +-
app/views/products/show.html.erb | 5 +-
app/views/reviews/_form.html.erb | 34 +++++++
app/views/reviews/new.html.erb | 9 ++
test/controllers/reviews_controller_test.rb | 22 +++++
test/fixtures/categories.yml | 2 +
test/fixtures/products.yml | 14 ++-
test/fixtures/reviews.yml | 24 ++---
test/models/category_test.rb | 17 +++-
test/models/product_test.rb | 23 +++--
test/models/review_test.rb | 104 ++++++++++----------
17 files changed, 206 insertions(+), 80 deletions(-)
create mode 100644 app/assets/javascripts/reviews.coffee
create mode 100644 app/assets/stylesheets/reviews.scss
create mode 100644 app/controllers/reviews_controller.rb
create mode 100644 app/helpers/reviews_helper.rb
create mode 100644 app/views/reviews/_form.html.erb
create mode 100644 app/views/reviews/new.html.erb
create mode 100644 test/controllers/reviews_controller_test.rb
diff --git a/app/assets/javascripts/reviews.coffee b/app/assets/javascripts/reviews.coffee
new file mode 100644
index 0000000000..24f83d18bb
--- /dev/null
+++ b/app/assets/javascripts/reviews.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/stylesheets/reviews.scss b/app/assets/stylesheets/reviews.scss
new file mode 100644
index 0000000000..6ea2454d26
--- /dev/null
+++ b/app/assets/stylesheets/reviews.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the reviews controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/controllers/reviews_controller.rb b/app/controllers/reviews_controller.rb
new file mode 100644
index 0000000000..f9aaf76d30
--- /dev/null
+++ b/app/controllers/reviews_controller.rb
@@ -0,0 +1,13 @@
+class ReviewsController < ApplicationController
+ def new
+ @product = Product.find(params[:product_id])
+ @review = Review.new
+ end
+
+ private
+
+ def review_params
+ params.require(:review).permit(:rating, :description, :product_id)
+ end
+
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index de6be7945c..10f0e02796 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,2 +1,10 @@
module ApplicationHelper
+ def capitalize_each_word(phrase)
+ phrase.split.map(&:capitalize).join(" ")
+ end
+
+ def show_dollars(price)
+ price = price / 100.0
+ number_to_currency(price)
+ end
end
diff --git a/app/helpers/reviews_helper.rb b/app/helpers/reviews_helper.rb
new file mode 100644
index 0000000000..682b7b1abc
--- /dev/null
+++ b/app/helpers/reviews_helper.rb
@@ -0,0 +1,2 @@
+module ReviewsHelper
+end
diff --git a/app/models/product.rb b/app/models/product.rb
index 70ea3cc125..15cb079758 100644
--- a/app/models/product.rb
+++ b/app/models/product.rb
@@ -1,6 +1,7 @@
class Product < ActiveRecord::Base
has_and_belongs_to_many :categories
has_many :order_items
+ has_many :reviews
belongs_to :merchant
validates :name, presence: true, uniqueness: true
validates :price, presence: true, numericality: {only_integer: true, greater_than: 0}
diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb
index 110577de92..ec98476bec 100644
--- a/app/views/products/index.html.erb
+++ b/app/views/products/index.html.erb
@@ -3,7 +3,7 @@
<%@product.each do |product|%>
- <%= link_to product.name, product_path(product) %>
+ <%= link_to capitalize_each_word(product.name), product_path(product) %>
<%= image_tag "#{product.photo_url}"%>
diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb
index 33131fef37..7c318857e5 100644
--- a/app/views/products/show.html.erb
+++ b/app/views/products/show.html.erb
@@ -1,4 +1,4 @@
-
<%= @product.name.capitalize %>
+<%= capitalize_each_word(@product.name) %>
<%= image_tag "#{@product.photo_url}" %>
@@ -9,8 +9,7 @@
-
- Price: $<%= @product.price / 100.00 %>
+ Price: <%= show_dollars(@product.price) %>
<% unless @product.categories.empty? %>
diff --git a/app/views/reviews/_form.html.erb b/app/views/reviews/_form.html.erb
new file mode 100644
index 0000000000..d0cb62ab2c
--- /dev/null
+++ b/app/views/reviews/_form.html.erb
@@ -0,0 +1,34 @@
+
+ <%= form_for @review, method: http_method, url: local_path do |f| %>
+ <% if @review.errors.any? %>
+
+
+ <% @review.errors.each do |column, message| %>
+
+ <%= column.capitalize %>
+ <%= message %>
+
+ <% end %>
+
+
+ <% end %>
+
+
+
+ <%= f.label :rating, "Rating (1 (worst) to 5 (best)): " %>
+
+
+ <%= f.number_field :rating, in: 1..5, size: "30x1" %>
+
+
+
+ <%= f.label :description, "Description (max 400 characters):" %>
+
+
+ <%= f.text_area :description, size: "30x10" %>
+
+
+ <%= f.submit "Submit", class: "success button" %>
+ <% end %>
+
+
diff --git a/app/views/reviews/new.html.erb b/app/views/reviews/new.html.erb
new file mode 100644
index 0000000000..5927a092a9
--- /dev/null
+++ b/app/views/reviews/new.html.erb
@@ -0,0 +1,9 @@
+Review <%= capitalize_each_word(@product.name) %>
+
+<%= render partial: "form",
+ locals: {
+ http_method: :post,
+ local_path: product_reviews_path
+ } %>
+
+<%= link_to "Back to Product", product_path(@product) %>
diff --git a/test/controllers/reviews_controller_test.rb b/test/controllers/reviews_controller_test.rb
new file mode 100644
index 0000000000..dc867be63c
--- /dev/null
+++ b/test/controllers/reviews_controller_test.rb
@@ -0,0 +1,22 @@
+require 'test_helper'
+
+class ReviewsControllerTest < ActionController::TestCase
+ test "should be able to view the form to add a new review" do
+ product = products(:cat_suit)
+ ex_review = reviews(:one_star)
+ all_reviews = product.reviews
+
+ get :new, {product_id: product.id}
+ assert_response :success
+ assert_template :new
+ assert_template partial: "_form"
+ end
+
+ # test "should be able to save a review to the database" do
+ # product = products(:cat_suit)
+ # end
+ #
+ # test "should not be able to save a review with empty values" do
+ #
+ # end
+end
diff --git a/test/fixtures/categories.yml b/test/fixtures/categories.yml
index 3a7f156d98..b08e755443 100644
--- a/test/fixtures/categories.yml
+++ b/test/fixtures/categories.yml
@@ -6,3 +6,5 @@ hamster:
name: hamster
eyewear:
name: eyewear
+mammals:
+ name: mammals
diff --git a/test/fixtures/products.yml b/test/fixtures/products.yml
index 6f1f89c783..069ea7d029 100644
--- a/test/fixtures/products.yml
+++ b/test/fixtures/products.yml
@@ -4,7 +4,8 @@ cat_suit:
description: your cat will look dapper in this one-piece suit!
stock: 4
photo_url: http://placekitten.com/200/300
- categories: cat, formal_wear
+ categories: cat, formal_wear, mammals
+ merchant: 1234
hamster_monocle:
name: hamster monocle
@@ -12,4 +13,13 @@ hamster_monocle:
description: a classy way to correct your hamster's vision.
stock: 20
photo_url: http://placekitten.com/200/300
- categories: hamster, eyewear
+ categories: hamster, eyewear, mammals
+ merchant: 1234
+
+dog_bunny_ears:
+ name: Dog bunny ears
+ price: 3453
+ description: make your dog look like a bunny with these adorable bunny ears!
+ stock: 1
+ photo_url: http://placekitten.com/200/300
+ merchant: 2341
diff --git a/test/fixtures/reviews.yml b/test/fixtures/reviews.yml
index 71935fdf51..ba9fb29869 100644
--- a/test/fixtures/reviews.yml
+++ b/test/fixtures/reviews.yml
@@ -1,16 +1,16 @@
one_star:
- rating: 1
- description: This product is awesome.
- product_id: 123
+ rating: 1
+ description: This product is terrible!
+ product: cat_suit
four_star:
- rating: 4
- description: Highly recommend purchasing this item.
- product_id: 123
+ rating: 4
+ description: Highly recommend purchasing this item.
+ product: hamster_monocle
five_star:
- rating: 5
- description: Another description...
- product_id: 123
+ rating: 5
+ description: Another description...
+ product: dog_bunny_ears
character_400:
- rating: 4
- description: Here is a review of a book for sale. It is a great book, worth reading and possibly adding to your personal library. You will not be disappointed. Check this book out from your local library so you can form your own opinion and upvote it here. klsfjshrjdf lkajsf jej faiosejf esj oiasejf lsdjf ;oasjef ojsdfkl jasefj sodf lsdfj oisejf SDJf lKSDJFo iejw lKDSFJ l;SEF SIefj l;KSZDf sd eddjjrhdm fydowkf
- product_id: 123
+ rating: 4
+ description: Here is a review of a book for sale. It is a great book, worth reading and possibly adding to your personal library. You will not be disappointed. Check this book out from your local library so you can form your own opinion and upvote it here. klsfjshrjdf lkajsf jej faiosejf esj oiasejf lsdjf ;oasjef ojsdfkl jasefj sodf lsdfj oisejf SDJf lKSDJFo iejw lKDSFJ l;SEF SIefj l;KSZDf sd eddjjrhdm fydowkf
+ product: cat_suit
diff --git a/test/models/category_test.rb b/test/models/category_test.rb
index a068b01dda..cc4feecad5 100644
--- a/test/models/category_test.rb
+++ b/test/models/category_test.rb
@@ -1,14 +1,14 @@
require 'test_helper'
class CategoryTest < ActiveSupport::TestCase
- test "category can't have an empty name" do
+ test "Category can't have an empty name" do
category = Category.new
assert_not category.valid?
assert_not category.save
assert_includes category.errors, :name
end
- test "category is valid if it has a name" do
+ test "Category is valid if it has a name" do
categories.each do |category|
category = Category.create!(name: category.name)
assert category.valid?
@@ -16,10 +16,21 @@ class CategoryTest < ActiveSupport::TestCase
end
end
- test "category name must be unique" do
+ test "Category name must be unique" do
category = Category.new(name: "formal wear")
assert_not category.valid?
assert_not category.save
assert_equal ["has already been taken"], category.errors.messages[:name]
end
+
+ test "Category can have many products" do
+ category = categories(:mammals)
+ product_one = products(:cat_suit)
+ product_two = products(:hamster_monocle)
+
+ assert_equal 2, category.products.length
+ assert_includes category.product_ids, product_one.id
+ assert_includes category.product_ids, product_two.id
+ assert_respond_to category, :products
+ end
end
diff --git a/test/models/product_test.rb b/test/models/product_test.rb
index b73caabd80..afe47c7246 100644
--- a/test/models/product_test.rb
+++ b/test/models/product_test.rb
@@ -57,7 +57,7 @@ class ProductTest < ActiveSupport::TestCase
end
end
- test "Product can be assigned a merchant id" do
+ test "Product belongs to a merchant" do
product = Product.create!(name: "mouse hat", price: 1240)
merchant = Merchant.create!(user_name: "testing", email: "test@test.com", uid: 124, provider: "github")
@@ -66,21 +66,28 @@ class ProductTest < ActiveSupport::TestCase
assert_equal product.merchant_id, merchant.id
assert_includes merchant.products, product
+ assert_respond_to product, :merchant
end
test "Product can have many categories" do
product = products(:hamster_monocle)
category_one = categories(:hamster)
category_two = categories(:eyewear)
- assert_equal 2, product.categories.length
+ category_three = categories(:mammals)
+ assert_equal 3, product.categories.length
assert_includes product.category_ids, category_one.id
assert_includes product.category_ids, category_two.id
+ assert_includes product.category_ids, category_three.id
+ assert_respond_to product, :categories
end
- # test "Products can have many order_items" do
- # product = Product.create!(name: "mouse hat", price: 1240)
- # order_item = OrderItem.create!(quantity: 1, product_id: product.id, order_id: 1294, shipped?: false)
- #
- # assert_equal order_item.product_id, product.id
- # end
+ test "Products can have many order items" do
+ product = products(:hamster_monocle)
+ assert_respond_to product, :order_items
+ end
+
+ test "Products can have many reviews" do
+ product = products(:cat_suit)
+ assert_respond_to product, :reviews
+ end
end
diff --git a/test/models/review_test.rb b/test/models/review_test.rb
index 27a82295bd..25077ad0b9 100644
--- a/test/models/review_test.rb
+++ b/test/models/review_test.rb
@@ -1,68 +1,70 @@
require 'test_helper'
class ReviewTest < ActiveSupport::TestCase
- test "Creating a new review will instantiate rating as nil" do
- r = Review.new
- assert_equal(nil, r.rating)
- end
+ test "Creating a new review will instantiate rating as nil" do
+ r = Review.new
+ assert_equal(nil, r.rating)
+ end
- test "Cannot save a nil rating to the database" do
- no_rating = Review.new(description: "whatever", product_id: 123)
- assert_not no_rating.valid?
- assert_includes(no_rating.errors, :rating)
- end
+ test "Cannot save a nil rating to the database" do
+ no_rating = Review.new(description: "whatever", product_id: 123)
+ assert_not no_rating.valid?
+ assert_includes(no_rating.errors, :rating)
+ end
- test "Can create reviews with ratings between 1 and 5" do
- assert reviews(:one_star).valid?
- assert reviews(:four_star).valid?
- assert reviews(:five_star).valid?
- end
+ test "Can create reviews with ratings between 1 and 5" do
+ assert reviews(:one_star).valid?
+ assert reviews(:four_star).valid?
+ assert reviews(:five_star).valid?
+ end
- test "Can only create reviews with integer ratings" do
- float_rating = Review.new(rating: 3.2, description: "whatever", product_id: 123)
- assert_not float_rating.valid?
- assert_includes(float_rating.errors, :rating)
+ test "Can only create reviews with integer ratings" do
+ float_rating = Review.new(rating: 3.2, description: "whatever", product_id: 123)
+ assert_not float_rating.valid?
+ assert_includes(float_rating.errors, :rating)
- string_rating = Review.new(rating: "excellent", description: "whatever", product_id: 123)
- assert_not string_rating.valid?
- assert_includes(string_rating.errors, :rating)
- end
+ string_rating = Review.new(rating: "excellent", description: "whatever", product_id: 123)
+ assert_not string_rating.valid?
+ assert_includes(string_rating.errors, :rating)
+ end
- test "Cannot create reviews with ratings less than 1 or greater than 5" do
- too_low_rating = Review.new(rating: 0, description: "whatever", product_id: 123)
- assert_not too_low_rating.valid?
- assert_includes(too_low_rating.errors, :rating)
+ test "Cannot create reviews with ratings less than 1 or greater than 5" do
+ too_low_rating = Review.new(rating: 0, description: "whatever", product_id: 123)
+ assert_not too_low_rating.valid?
+ assert_includes(too_low_rating.errors, :rating)
- too_high_rating = Review.new(rating: 6, description: "whatever", product_id: 123)
- assert_not too_high_rating.valid?
- assert_includes(too_high_rating.errors, :rating)
- end
+ too_high_rating = Review.new(rating: 6, description: "whatever", product_id: 123)
+ assert_not too_high_rating.valid?
+ assert_includes(too_high_rating.errors, :rating)
+ end
- test "Review creation requires a description [string]" do
- assert reviews(:one_star).valid?
+ test "Review creation requires a description [string]" do
+ assert reviews(:one_star).valid?
- no_description = Review.new(rating: 0, product_id: 123)
- assert_not no_description.valid?
- assert_includes(no_description.errors, :description)
- end
+ no_description = Review.new(rating: 0, product_id: 123)
+ assert_not no_description.valid?
+ assert_includes(no_description.errors, :description)
+ end
- test "Can only create reviews with descriptions 400 characters or less" do
- assert reviews(:character_400).valid?
+ test "Can only create reviews with descriptions 400 characters or less" do
+ assert reviews(:character_400).valid?
- character_401 = Review.new(rating: 0, product_id: 123, description: "Here is a review of a book for sale. It is a great book, worth reading and possibly adding to your personal library. You will not be disappointed. Check this book out from your local library so you can form your own opinion and upvote it here. klsfjshrjdf lkajsf jej faiosejf esj oiasejf lsdjf ;oasjef ojsdfkl jasefj sodf lsdfj oisejf SDJf lKSDJFo iejw lKDSFJ l;SEF SIefj l;KSZDf sd eddjjrhdm fydowkf!")
- assert_not character_401.valid?
- assert_includes(character_401.errors, :description)
- end
+ character_401 = Review.new(rating: 0, product_id: 123, description: "Here is a review of a book for sale. It is a great book, worth reading and possibly adding to your personal library. You will not be disappointed. Check this book out from your local library so you can form your own opinion and upvote it here. klsfjshrjdf lkajsf jej faiosejf esj oiasejf lsdjf ;oasjef ojsdfkl jasefj sodf lsdfj oisejf SDJf lKSDJFo iejw lKDSFJ l;SEF SIefj l;KSZDf sd eddjjrhdm fydowkf!")
+ assert_not character_401.valid?
+ assert_includes(character_401.errors, :description)
+ end
- test "Review creation requires a product_id [integer]" do
- assert reviews(:one_star).valid?
+ test "Review creation requires a product_id [integer]" do
+ assert reviews(:one_star).valid?
- no_product_id = Review.new(rating: 0, description: "hello")
- assert_not no_product_id.valid?
- assert_includes(no_product_id.errors, :product_id)
- end
-end
+ no_product_id = Review.new(rating: 0, description: "hello")
+ assert_not no_product_id.valid?
+ assert_includes(no_product_id.errors, :product_id)
+ end
-# note: have not written any tests on the belongs_to relationship, as we haven't set-up the product_id in the controller using params yet. I think this relationship can be tested in the controller instead of the model tests?
+ test "Review belongs to a product" do
+ review = reviews(:one_star)
-# fyi from earlier project: on rails console, object.new goes straight to the model, bypassing the controller completely.
+ assert_respond_to review, :product
+ end
+end
From d89ad9bc65f6ce503d622b11ac41ed1d5f047747 Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Sun, 23 Oct 2016 15:18:01 -0700
Subject: [PATCH 052/144] implemented review post function
---
app/controllers/reviews_controller.rb | 11 ++++++++
app/views/categories/show.html.erb | 6 ++---
app/views/products/index.html.erb | 2 +-
app/views/products/show.html.erb | 25 +++++++++++++++++-
app/views/reviews/_form.html.erb | 6 ++---
app/views/reviews/new.html.erb | 5 +++-
test/controllers/reviews_controller_test.rb | 29 ++++++++++++++++-----
7 files changed, 67 insertions(+), 17 deletions(-)
diff --git a/app/controllers/reviews_controller.rb b/app/controllers/reviews_controller.rb
index f9aaf76d30..4a1eb591d8 100644
--- a/app/controllers/reviews_controller.rb
+++ b/app/controllers/reviews_controller.rb
@@ -4,6 +4,17 @@ def new
@review = Review.new
end
+ def create
+ @review = Review.new(review_params)
+ @product = Product.find(params[:product_id])
+ @review.product_id = @product.id
+ if @review.save
+ redirect_to product_path(@product.id)
+ else
+ render :new
+ end
+ end
+
private
def review_params
diff --git a/app/views/categories/show.html.erb b/app/views/categories/show.html.erb
index 277fadf86e..aab8acdf43 100644
--- a/app/views/categories/show.html.erb
+++ b/app/views/categories/show.html.erb
@@ -1,4 +1,4 @@
-<%= @category.name.capitalize %>
+<%= capitalize_each_word(@category.name) %>
<% if @products.empty? %>
No products in this category!
@@ -6,6 +6,6 @@
<% end %>
<% @products.each do |product| %>
-
<%= product.name.capitalize %>
- <%= link_to image_tag("#{product.photo_url}"), product_path(product) %>
+ <%= capitalize_each_word(product.name) %>
+ <%= link_to image_tag("#{product.photo_url}", alt: "#{product.name}"), product_path(product) %>
<% end %>
diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb
index ec98476bec..35e5d98f4f 100644
--- a/app/views/products/index.html.erb
+++ b/app/views/products/index.html.erb
@@ -6,7 +6,7 @@
<%= link_to capitalize_each_word(product.name), product_path(product) %>
- <%= image_tag "#{product.photo_url}"%>
+ <%= image_tag "#{product.photo_url}", alt: "#{product.name}"%>
<%= product.description %>
diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb
index 7c318857e5..ad4228047b 100644
--- a/app/views/products/show.html.erb
+++ b/app/views/products/show.html.erb
@@ -1,7 +1,7 @@
<%= capitalize_each_word(@product.name) %>
- <%= image_tag "#{@product.photo_url}" %>
+ <%= image_tag "#{@product.photo_url}", alt: "#{@product.name}" %>
@@ -20,4 +20,27 @@
<% end %>
<% end %>
+
+
+
Reviews
+<% if @product.reviews.empty? %>
+
+ No reviews! <%= link_to "Be the first.", new_product_review_path(@product.id) %>
+
+<% else %>
+<% @product.reviews.each do |review| %>
+
+
+ <% review.created_at %>
+
+
+
+ Rating: <%= review.rating %>
+
+
+
+ Description: <%= review.description %>
+
+<% end %>
+<% end %>
diff --git a/app/views/reviews/_form.html.erb b/app/views/reviews/_form.html.erb
index d0cb62ab2c..0e0ea482e7 100644
--- a/app/views/reviews/_form.html.erb
+++ b/app/views/reviews/_form.html.erb
@@ -16,16 +16,14 @@
<%= f.label :rating, "Rating (1 (worst) to 5 (best)): " %>
-
-
<%= f.number_field :rating, in: 1..5, size: "30x1" %>
<%= f.label :description, "Description (max 400 characters):" %>
-
- <%= f.text_area :description, size: "30x10" %>
+
+ <%= f.text_area :description, size: "35x10" %>
<%= f.submit "Submit", class: "success button" %>
diff --git a/app/views/reviews/new.html.erb b/app/views/reviews/new.html.erb
index 5927a092a9..3a237816c5 100644
--- a/app/views/reviews/new.html.erb
+++ b/app/views/reviews/new.html.erb
@@ -1,4 +1,5 @@
Review <%= capitalize_each_word(@product.name) %>
+<%#= image_tag "#{@product.photo_url}", alt: "#{@product.name}" %>
<%= render partial: "form",
locals: {
@@ -6,4 +7,6 @@
local_path: product_reviews_path
} %>
-<%= link_to "Back to Product", product_path(@product) %>
+
+ <%= link_to "Back to Product", product_path(@product) %>
+
diff --git a/test/controllers/reviews_controller_test.rb b/test/controllers/reviews_controller_test.rb
index dc867be63c..f0d82fd179 100644
--- a/test/controllers/reviews_controller_test.rb
+++ b/test/controllers/reviews_controller_test.rb
@@ -12,11 +12,26 @@ class ReviewsControllerTest < ActionController::TestCase
assert_template partial: "_form"
end
- # test "should be able to save a review to the database" do
- # product = products(:cat_suit)
- # end
- #
- # test "should not be able to save a review with empty values" do
- #
- # end
+ test "should be able to save a review to the database" do
+ product = products(:cat_suit)
+ review = { review: {rating: 1, description: "ugh"}, product_id: product.id }
+
+ assert_difference("Review.count") do
+ post :create, review
+ end
+
+ assert_redirected_to product_path(product.id)
+
+ end
+
+ test "should not save an invalid review to the database" do
+ product = products(:cat_suit)
+ review = { review: {rating: 1}, product_id: product.id }
+
+ assert_no_difference("Review.count") do
+ post :create, review
+ end
+
+ assert_template :new
+ end
end
From 88e22286f34d3fb958f4a92299fb13a716b4cfda Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Sun, 23 Oct 2016 15:32:02 -0700
Subject: [PATCH 053/144] modified products show views to show reviews/links to
reviews
---
app/helpers/application_helper.rb | 6 ++-
app/views/products/show.html.erb | 62 +++++++++++++++++--------------
2 files changed, 40 insertions(+), 28 deletions(-)
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 10f0e02796..3dfdd67f4a 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -5,6 +5,10 @@ def capitalize_each_word(phrase)
def show_dollars(price)
price = price / 100.0
- number_to_currency(price)
+ return number_to_currency(price)
+ end
+
+ def date_format(time)
+ return time.strftime("%B %-d, %Y at %l:%M:%S %p")
end
end
diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb
index ad4228047b..bb480379af 100644
--- a/app/views/products/show.html.erb
+++ b/app/views/products/show.html.erb
@@ -11,36 +11,44 @@
Price: <%= show_dollars(@product.price) %>
+
-<% unless @product.categories.empty? %>
- Categories
-
- <% @product.categories.each do |category| %>
- <%= link_to "#{category.name}", category_path(category) %>
- <% end %>
-
-<% end %>
-
+
+ <% unless @product.categories.empty? %>
+ Categories
+
+ <% @product.categories.each do |category| %>
+ <%= link_to "#{category.name}", category_path(category) %>
+ <% end %>
+
+ <% end %>
+
-Reviews
-<% if @product.reviews.empty? %>
-
- No reviews! <%= link_to "Be the first.", new_product_review_path(@product.id) %>
-
-<% else %>
-<% @product.reviews.each do |review| %>
+
+ Reviews
+ <% if @product.reviews.empty? %>
+
+ No reviews! <%= link_to "Be the first.", new_product_review_path(@product.id) %>
+
+ <% else %>
+ <% @product.reviews.each do |review| %>
-
- <% review.created_at %>
-
+
+
+ <%= date_format(review.created_at) %>
+
-
- Rating: <%= review.rating %>
-
+
+ Rating: <%= review.rating %>
+
-
- Description: <%= review.description %>
-
-<% end %>
-<% end %>
+
+ Description: <%= review.description %>
+
+ <% end %>
+
+
+ <%= link_to "Review this product!", new_product_review_path(@product.id) %>
+
+ <% end %>
From db07b1105d9a4651f6237e68c19c8ec353b11016 Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Sun, 23 Oct 2016 15:47:44 -0700
Subject: [PATCH 054/144] modified products show page
---
app/views/products/show.html.erb | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb
index bb480379af..316ecc6128 100644
--- a/app/views/products/show.html.erb
+++ b/app/views/products/show.html.erb
@@ -3,7 +3,9 @@
<%= image_tag "#{@product.photo_url}", alt: "#{@product.name}" %>
+
+
Description : <%= @product.description %>
@@ -11,6 +13,10 @@
Price: <%= show_dollars(@product.price) %>
+
+
+ Quantity: <%= @product.stock %>
+
From aebdc5fca09570868fe37b7f14506b1a992a443d Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Sun, 23 Oct 2016 16:47:25 -0700
Subject: [PATCH 055/144] modified views
---
app/views/categories/show.html.erb | 3 +--
app/views/products/show.html.erb | 4 +++-
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/app/views/categories/show.html.erb b/app/views/categories/show.html.erb
index aab8acdf43..713e2c9ab6 100644
--- a/app/views/categories/show.html.erb
+++ b/app/views/categories/show.html.erb
@@ -6,6 +6,5 @@
<% end %>
<% @products.each do |product| %>
- <%= capitalize_each_word(product.name) %>
- <%= link_to image_tag("#{product.photo_url}", alt: "#{product.name}"), product_path(product) %>
+ <%= link_to capitalize_each_word(product.name), product_path(product) %>
<% end %>
diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb
index 316ecc6128..4a0298c1b6 100644
--- a/app/views/products/show.html.erb
+++ b/app/views/products/show.html.erb
@@ -24,7 +24,9 @@
Categories
<% @product.categories.each do |category| %>
- <%= link_to "#{category.name}", category_path(category) %>
+
+ <%= link_to "#{category.name}", category_path(category) %>
+
<% end %>
<% end %>
From ef8a823533d5130b10fde6aeecfcfc0962ab5f32 Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Sun, 23 Oct 2016 20:32:15 -0700
Subject: [PATCH 056/144] added and refined some tests
---
app/models/order.rb | 4 ++--
app/models/order_item.rb | 2 +-
db/schema.rb | 4 +---
test/fixtures/order_items.yml | 6 +++---
test/fixtures/orders.yml | 16 +++++-----------
test/fixtures/products.yml | 1 +
test/models/order_item_test.rb | 2 +-
test/models/order_test.rb | 30 ++++++++++++++++++------------
8 files changed, 32 insertions(+), 33 deletions(-)
diff --git a/app/models/order.rb b/app/models/order.rb
index 13175c661c..a6549da21b 100644
--- a/app/models/order.rb
+++ b/app/models/order.rb
@@ -1,7 +1,7 @@
class Order < ActiveRecord::Base
has_many :order_items
- validates :cc_number, presence: true, numericality: {only_integer: true}, length: { is: 16 }
+ validates :cc_number, presence: true, numericality: {only_integer: true}, length: { is: 4 }
validates :cc_exp_year, presence: true, length: {is: 4}
validates :cc_exp_month, presence: true, numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 12}
validates :total, presence: true, numericality: { greater_than_or_equal_to: 0}
@@ -32,7 +32,7 @@ def total
private
def set_order_status
- self.status = "PENDING"
+ self[:status] = "PENDING"
end
def update_total
diff --git a/app/models/order_item.rb b/app/models/order_item.rb
index 665c4955ae..da66d7da8f 100644
--- a/app/models/order_item.rb
+++ b/app/models/order_item.rb
@@ -14,7 +14,7 @@ def total_price
private
def valid_quantity
- if quantity > product.stock
+ if quantity > self.product.stock
errors.add(:quantity, "there is not enough stock of this product to fulfill your request, please try again")
end
end
diff --git a/db/schema.rb b/db/schema.rb
index d949fdf785..9999476e2d 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,9 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-
-ActiveRecord::Schema.define(version: 20161020020026) do
-
+ActiveRecord::Schema.define(version: 20161021201059) do
create_table "categories", force: :cascade do |t|
t.string "name"
diff --git a/test/fixtures/order_items.yml b/test/fixtures/order_items.yml
index 3060530e6f..65ef7e714e 100644
--- a/test/fixtures/order_items.yml
+++ b/test/fixtures/order_items.yml
@@ -1,10 +1,10 @@
one_unit:
quantity: 1
- product_id: 123
+ product_id: 1234
order_id: 456
shipped?: true
four_unit:
quantity: 4
- product_id: 123
- order_id: 456
+ product_id: 1234
+ order_id: 458
shipped?: true
diff --git a/test/fixtures/orders.yml b/test/fixtures/orders.yml
index 937a0c002e..08633dea3b 100644
--- a/test/fixtures/orders.yml
+++ b/test/fixtures/orders.yml
@@ -1,11 +1,5 @@
-# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
-
-# This model initially had no columns defined. If you add columns to the
-# model remove the '{}' from the fixture names and add the columns immediately
-# below each fixture, per the syntax in the comments below
-#
-one: {}
-# column: value
-#
-two: {}
-# column: value
+valid_order:
+ cc_number: 6789
+ cc_exp_year: 2016
+ cc_exp_month: 12
+ status: PENDING
diff --git a/test/fixtures/products.yml b/test/fixtures/products.yml
index 6f1f89c783..afd682091d 100644
--- a/test/fixtures/products.yml
+++ b/test/fixtures/products.yml
@@ -1,4 +1,5 @@
cat_suit:
+ id: 1234
name: cat suit
price: 1234
description: your cat will look dapper in this one-piece suit!
diff --git a/test/models/order_item_test.rb b/test/models/order_item_test.rb
index 1b6fc10c2c..bf0e54135a 100644
--- a/test/models/order_item_test.rb
+++ b/test/models/order_item_test.rb
@@ -31,7 +31,7 @@ class OrderItemTest < ActiveSupport::TestCase
assert_not no_product_id.valid?
assert_includes(no_product_id.errors, :product_id)
- assert_equal(123, order_items(:one_unit))
+ assert_equal(1234, order_items(:one_unit))
assert order_items(:four_unit).valid?
end
diff --git a/test/models/order_test.rb b/test/models/order_test.rb
index cab804e7a1..5f18c45cd2 100644
--- a/test/models/order_test.rb
+++ b/test/models/order_test.rb
@@ -1,25 +1,31 @@
require 'test_helper'
class OrderTest < ActiveSupport::TestCase
- test "credit card must present and be 16 characters long" do
- order = Order.new(cc_number: 2345123454326789, cc_exp_year: 2016, cc_exp_month: 10)
- order2 = Order.new(cc_number: 1234, cc_exp_year: 1990, cc_exp_month: 7)
- order3 = Order.new(cc_number: 2345123454326789234, cc_exp_year: 1990, cc_exp_month: 7)
+ test "credit card number must be present and be 4 characters long" do
+ order2 = Order.new(cc_number: 34, cc_exp_year: 1990, cc_exp_month: 7, status: "PENDING")
+ order3 = Order.new(cc_number: 6789234, cc_exp_year: 1990, cc_exp_month: 7, status: "PENDING")
- assert order.valid?
+ assert orders(:valid_order).valid?
assert_not order2.valid?
assert_not order3.valid?
end
test "credit card should not have an expired date" do
- order = Order.new(cc_exp_year: 2014, cc_exp_month: 12)
- order2 = Order.new(cc_exp_month: 10, cc_exp_year: 2013)
- order3 = Order.new(cc_exp_year: 2016, cc_exp_month: 9, cc_number: 2345123454326789)
+ expired_year = Order.new(cc_number: 1234, cc_exp_year: (Time.now.year - 2), cc_exp_month: 12, status: "PENDING")
+ expired_month_in_current_year = Order.new(cc_number: 1234, cc_exp_year: Time.now.year, cc_exp_month: (Time.now.month - 2), status: "PENDING")
- assert_not order.valid?
- assert_not order2.valid?
- #this assertion should not pass since month is in the past, month is not yet linked to year, working on it
- assert_not order3.valid?
+ assert_not expired_year.valid?
+ assert_not expired_month_in_current_year.valid?
end
+
+ test "an order model object's status is nil upon initiation, then set to PENDING due to the set_order_status private method in the model" do
+ order = Order.new(cc_number: 1231, cc_exp_year: 2016, cc_exp_month: 12)
+ assert_equal(nil, order.status)
+
+ order.save
+ assert_equal("PENDING", order.status)
+ end
+
+
end
From 54c967e419945b393f845732b981f92a4c3d5f1e Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Mon, 24 Oct 2016 10:46:28 -0700
Subject: [PATCH 057/144] edited review
---
app/models/review.rb | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/app/models/review.rb b/app/models/review.rb
index e094e15fec..2df58a6e9b 100644
--- a/app/models/review.rb
+++ b/app/models/review.rb
@@ -1,7 +1,7 @@
class Review < ActiveRecord::Base
- validates :rating, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: 5 }
- validates :description, presence: true, length: { maximum: 400 }
- validates :product_id, presence: true
+ validates :rating, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: 5 }
+ validates :description, presence: true, length: { maximum: 400 }
+ validates :product_id, presence: true
- belongs_to :product
+ belongs_to :product
end
From 0b377652b2baba54753c5b952906b316251196de Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Mon, 24 Oct 2016 11:52:00 -0700
Subject: [PATCH 058/144] created (and ran) migration that set the order
model's default status field to PENDING so that we can delete a private
method that's unnecessary.
---
.../20161024184627_orders_table_add_default_to_status.rb | 5 +++++
db/schema.rb | 8 ++++----
2 files changed, 9 insertions(+), 4 deletions(-)
create mode 100644 db/migrate/20161024184627_orders_table_add_default_to_status.rb
diff --git a/db/migrate/20161024184627_orders_table_add_default_to_status.rb b/db/migrate/20161024184627_orders_table_add_default_to_status.rb
new file mode 100644
index 0000000000..18fd76f605
--- /dev/null
+++ b/db/migrate/20161024184627_orders_table_add_default_to_status.rb
@@ -0,0 +1,5 @@
+class OrdersTableAddDefaultToStatus < ActiveRecord::Migration
+ def change
+ change_column(:orders, :status, :string, :default => "PENDING")
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 9999476e2d..5def01b7bc 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20161021201059) do
+ActiveRecord::Schema.define(version: 20161024184627) do
create_table "categories", force: :cascade do |t|
t.string "name"
@@ -46,7 +46,7 @@
end
create_table "orders", force: :cascade do |t|
- t.string "status"
+ t.string "status", default: "PENDING"
t.datetime "date_purchased"
t.string "email"
t.string "address"
@@ -55,8 +55,8 @@
t.integer "cc_exp_year"
t.integer "cc_exp_month"
t.integer "billing_zip"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
t.integer "total", default: 0
end
From e046ad6f6eb16e1b9c008e34802b5dacf561f739 Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Mon, 24 Oct 2016 12:25:55 -0700
Subject: [PATCH 059/144] debugging so that all of the following model tests
now pass: orders and order_items
---
app/models/order.rb | 13 ++++++++-----
app/models/order_item.rb | 2 ++
test/fixtures/order_items.yml | 4 ++--
test/fixtures/orders.yml | 1 -
test/fixtures/products.yml | 1 -
test/models/order_item_test.rb | 16 +++++++---------
test/models/order_test.rb | 22 +++++++++++-----------
7 files changed, 30 insertions(+), 29 deletions(-)
diff --git a/app/models/order.rb b/app/models/order.rb
index a6549da21b..ebf8d3c3fe 100644
--- a/app/models/order.rb
+++ b/app/models/order.rb
@@ -9,7 +9,6 @@ class Order < ActiveRecord::Base
validates_associated :order_items
validate :acceptable_status
- before_create :set_order_status
before_save :update_total
def valid_exp
@@ -31,11 +30,15 @@ def total
end
private
- def set_order_status
- self[:status] = "PENDING"
- end
-
def update_total
self[:total] = total
+ puts ">>>>>>>> UPDATING TOTAL!"
end
end
+
+# test
+# create order model object
+# add order items to it (fixtures)
+#
+# assert_difference
+# end
diff --git a/app/models/order_item.rb b/app/models/order_item.rb
index da66d7da8f..aaa04b2d96 100644
--- a/app/models/order_item.rb
+++ b/app/models/order_item.rb
@@ -14,6 +14,8 @@ def total_price
private
def valid_quantity
+ return false if quantity.nil?
+ return false if self.product.nil?
if quantity > self.product.stock
errors.add(:quantity, "there is not enough stock of this product to fulfill your request, please try again")
end
diff --git a/test/fixtures/order_items.yml b/test/fixtures/order_items.yml
index 65ef7e714e..5a06f882a4 100644
--- a/test/fixtures/order_items.yml
+++ b/test/fixtures/order_items.yml
@@ -1,10 +1,10 @@
one_unit:
quantity: 1
- product_id: 1234
+ product: cat_suit
order_id: 456
shipped?: true
four_unit:
quantity: 4
- product_id: 1234
+ product: cat_suit
order_id: 458
shipped?: true
diff --git a/test/fixtures/orders.yml b/test/fixtures/orders.yml
index 08633dea3b..1be061624c 100644
--- a/test/fixtures/orders.yml
+++ b/test/fixtures/orders.yml
@@ -2,4 +2,3 @@ valid_order:
cc_number: 6789
cc_exp_year: 2016
cc_exp_month: 12
- status: PENDING
diff --git a/test/fixtures/products.yml b/test/fixtures/products.yml
index afd682091d..6f1f89c783 100644
--- a/test/fixtures/products.yml
+++ b/test/fixtures/products.yml
@@ -1,5 +1,4 @@
cat_suit:
- id: 1234
name: cat suit
price: 1234
description: your cat will look dapper in this one-piece suit!
diff --git a/test/models/order_item_test.rb b/test/models/order_item_test.rb
index bf0e54135a..155d44c124 100644
--- a/test/models/order_item_test.rb
+++ b/test/models/order_item_test.rb
@@ -2,26 +2,26 @@
class OrderItemTest < ActiveSupport::TestCase
test "Cannot create an order item with nil quantity" do
- nil_quantity = OrderItem.new(quantity: nil, product_id: 123, order_id: 456)
+ nil_quantity = OrderItem.new(quantity: nil, product: products(:cat_suit), order_id: 456)
assert_equal(nil, nil_quantity.quantity)
assert_not nil_quantity.valid?
assert_includes(nil_quantity.errors, :quantity)
end
test "Order item quantity is always instantiated at 1" do
- default_quantity = OrderItem.new(product_id: 123, order_id: 456)
+ default_quantity = OrderItem.new(product: products(:cat_suit), order_id: 456)
assert_equal(1, default_quantity.quantity)
end
test "Order item quantity must be a positive (non-zero) integer (LESS THAN OR EQUAL TO THE PRODUCT'S QUANTITY/STOCK, WOULD lIKE TO BUILD IN THIS PART OF THE TEST)" do
- assert order_items(:four_unit).valid?
+ assert order_items(:four_unit).valid?, "Validation failed: #{order_items(:four_unit).errors.messages}"
assert order_items(:one_unit).valid?
- zero_quantity = OrderItem.new(quantity: 0, product_id: 123, order_id: 456)
+ zero_quantity = OrderItem.new(quantity: 0, product: products(:cat_suit), order_id: 456)
assert_not zero_quantity.valid?
assert_includes(zero_quantity.errors, :quantity)
- negative_quantity = OrderItem.new(quantity: -6, product_id: 123, order_id: 456)
+ negative_quantity = OrderItem.new(quantity: -6, product: products(:cat_suit), order_id: 456)
assert_not negative_quantity.valid?
assert_includes(negative_quantity.errors, :quantity)
end
@@ -31,12 +31,12 @@ class OrderItemTest < ActiveSupport::TestCase
assert_not no_product_id.valid?
assert_includes(no_product_id.errors, :product_id)
- assert_equal(1234, order_items(:one_unit))
+ assert_equal(products(:cat_suit).id, order_items(:one_unit).product_id)
assert order_items(:four_unit).valid?
end
test "Order item creation requires an order_id [integer]" do
- no_order_id = OrderItem.new(quantity: 2, product_id: 456)
+ no_order_id = OrderItem.new(quantity: 2, product: products(:cat_suit))
assert_not no_order_id.valid?
assert_includes(no_order_id.errors, :order_id)
@@ -50,6 +50,4 @@ class OrderItemTest < ActiveSupport::TestCase
end
end
- # note: have not written any tests on the belongs_to relationships, as we haven't set-up the product_id or order_id in the controller using params yet. I think these relationships can be tested in the controller instead of the model tests?
-
# fyi from earlier project: on rails console, object.new goes straight to the model, bypassing the controller completely.
diff --git a/test/models/order_test.rb b/test/models/order_test.rb
index 5f18c45cd2..7d98f2c9ae 100644
--- a/test/models/order_test.rb
+++ b/test/models/order_test.rb
@@ -2,8 +2,8 @@
class OrderTest < ActiveSupport::TestCase
test "credit card number must be present and be 4 characters long" do
- order2 = Order.new(cc_number: 34, cc_exp_year: 1990, cc_exp_month: 7, status: "PENDING")
- order3 = Order.new(cc_number: 6789234, cc_exp_year: 1990, cc_exp_month: 7, status: "PENDING")
+ order2 = Order.new(cc_number: 34, cc_exp_year: 1990, cc_exp_month: 7)
+ order3 = Order.new(cc_number: 6789234, cc_exp_year: 1990, cc_exp_month: 7)
assert orders(:valid_order).valid?
assert_not order2.valid?
@@ -11,21 +11,21 @@ class OrderTest < ActiveSupport::TestCase
end
test "credit card should not have an expired date" do
- expired_year = Order.new(cc_number: 1234, cc_exp_year: (Time.now.year - 2), cc_exp_month: 12, status: "PENDING")
- expired_month_in_current_year = Order.new(cc_number: 1234, cc_exp_year: Time.now.year, cc_exp_month: (Time.now.month - 2), status: "PENDING")
+ expired_year = Order.new(cc_number: 1234, cc_exp_year: (Time.now.year - 2), cc_exp_month: 12)
+ expired_month_in_current_year = Order.new(cc_number: 1234, cc_exp_year: Time.now.year, cc_exp_month: (Time.now.month - 2))
assert_not expired_year.valid?
assert_not expired_month_in_current_year.valid?
end
- test "an order model object's status is nil upon initiation, then set to PENDING due to the set_order_status private method in the model" do
- order = Order.new(cc_number: 1231, cc_exp_year: 2016, cc_exp_month: 12)
- assert_equal(nil, order.status)
-
- order.save
- assert_equal("PENDING", order.status)
+ test "an order model object's status defaults to PENDING upon being saved to the database" do
+ o = orders(:valid_order)
+ o.save
+ assert_equal("PENDING", o.status)
end
-
+
+
+
end
From a2d54c561bb3337aa9113d5fa6676c2a2d120be7 Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Mon, 24 Oct 2016 12:52:29 -0700
Subject: [PATCH 060/144] added a test to confirm the private update_total
method is running, given that these relationships are interconnected and
required fields
---
app/models/order.rb | 8 --------
test/fixtures/order_items.yml | 2 +-
test/models/order_test.rb | 17 ++++++++++++++++-
3 files changed, 17 insertions(+), 10 deletions(-)
diff --git a/app/models/order.rb b/app/models/order.rb
index ebf8d3c3fe..d3257a5674 100644
--- a/app/models/order.rb
+++ b/app/models/order.rb
@@ -32,13 +32,5 @@ def total
private
def update_total
self[:total] = total
- puts ">>>>>>>> UPDATING TOTAL!"
end
end
-
-# test
-# create order model object
-# add order items to it (fixtures)
-#
-# assert_difference
-# end
diff --git a/test/fixtures/order_items.yml b/test/fixtures/order_items.yml
index 5a06f882a4..95b93accf6 100644
--- a/test/fixtures/order_items.yml
+++ b/test/fixtures/order_items.yml
@@ -6,5 +6,5 @@ one_unit:
four_unit:
quantity: 4
product: cat_suit
- order_id: 458
+ order: valid_order
shipped?: true
diff --git a/test/models/order_test.rb b/test/models/order_test.rb
index 7d98f2c9ae..ba8121065c 100644
--- a/test/models/order_test.rb
+++ b/test/models/order_test.rb
@@ -24,8 +24,23 @@ class OrderTest < ActiveSupport::TestCase
assert_equal("PENDING", o.status)
end
+ test "confirming the private method update_total occurs on an order object" do
+ p = Order.new
+ assert_equal(0, p.total)
+ o = orders(:valid_order)
+ assert_equal(4936, o.total)
-
+ # assert_difference("o.order_items = order_items(:four_unit)", 0) do
+ # o.save
+ # end
+ end
end
+
+# test
+# create order model object
+# add order items to it (fixtures)
+#
+# assert_difference
+# end
From f871a2f5335a7860eb8c3a93bd36301160c2e309 Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Mon, 24 Oct 2016 14:25:35 -0700
Subject: [PATCH 061/144] implemented Merchant show page; edited routes to
allow all categories (index) view
---
app/assets/javascripts/merchants.coffee | 3 +
app/assets/stylesheets/merchants.scss | 3 +
app/controllers/merchants_controller.rb | 20 +++
app/helpers/merchants_helper.rb | 2 +
app/views/merchants/show.html.erb | 9 ++
config/routes.rb | 2 +-
test/controllers/merchants_controller_test.rb | 133 ++++++++++++++++++
test/fixtures/merchants.yml | 21 ++-
8 files changed, 186 insertions(+), 7 deletions(-)
create mode 100644 app/assets/javascripts/merchants.coffee
create mode 100644 app/assets/stylesheets/merchants.scss
create mode 100644 app/controllers/merchants_controller.rb
create mode 100644 app/helpers/merchants_helper.rb
create mode 100644 app/views/merchants/show.html.erb
create mode 100644 test/controllers/merchants_controller_test.rb
diff --git a/app/assets/javascripts/merchants.coffee b/app/assets/javascripts/merchants.coffee
new file mode 100644
index 0000000000..24f83d18bb
--- /dev/null
+++ b/app/assets/javascripts/merchants.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/stylesheets/merchants.scss b/app/assets/stylesheets/merchants.scss
new file mode 100644
index 0000000000..2327c00afe
--- /dev/null
+++ b/app/assets/stylesheets/merchants.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the merchants controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/controllers/merchants_controller.rb b/app/controllers/merchants_controller.rb
new file mode 100644
index 0000000000..9c5217d7a1
--- /dev/null
+++ b/app/controllers/merchants_controller.rb
@@ -0,0 +1,20 @@
+class MerchantsController < ApplicationController
+ before_action :find_merchant, only: [:show, :edit, :update]
+
+ def show; end
+
+
+ private
+
+ def find_merchant
+ begin
+ @merchant = Merchant.find(params[:id])
+ rescue ActiveRecord::RecordNotFound
+ render file: "#{Rails.root}/public/404.html", layout: false, status: :not_found
+ end
+ end
+
+ def merchant_params
+ params.require(:merchant).permit(:user_name, :email, :uid, :provider)
+ end
+end
diff --git a/app/helpers/merchants_helper.rb b/app/helpers/merchants_helper.rb
new file mode 100644
index 0000000000..5337747b0f
--- /dev/null
+++ b/app/helpers/merchants_helper.rb
@@ -0,0 +1,2 @@
+module MerchantsHelper
+end
diff --git a/app/views/merchants/show.html.erb b/app/views/merchants/show.html.erb
new file mode 100644
index 0000000000..b700c1d119
--- /dev/null
+++ b/app/views/merchants/show.html.erb
@@ -0,0 +1,9 @@
+<%= @merchant.user_name %>
+
+<% @merchant.products.each do |product| %>
+
+
+ <%= link_to capitalize_each_word(product.name), product_path(product) %>
+
+
+<% end %>
diff --git a/config/routes.rb b/config/routes.rb
index 4fe790456b..21e20ce8fa 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -14,7 +14,7 @@
resources :reviews, only: [:new, :create]
end
- resources :categories, only: [:new, :create, :show]
+ resources :categories, only: [:index, :new, :create, :show]
# We're going to talk about this more if any of us needs to edit this. :)
resources :orders, only: [:new, :create, :show] do
diff --git a/test/controllers/merchants_controller_test.rb b/test/controllers/merchants_controller_test.rb
new file mode 100644
index 0000000000..0ce73b3a9b
--- /dev/null
+++ b/test/controllers/merchants_controller_test.rb
@@ -0,0 +1,133 @@
+require 'test_helper'
+
+class MerchantsControllerTest < ActionController::TestCase
+ test "show the individual merchant page" do
+ merchant_id = merchants(:hilarious).id
+
+ get :show, {id: merchant_id}
+ assert_response :success
+ assert_template :show
+
+ merchant = assigns(:merchant)
+ assert_not_nil merchant
+ assert_equal merchant.id, merchant_id
+ end
+
+ test "show a merchant that doesn't exist" do
+ merchant_id = 334456777592
+ assert_raises ActiveRecord::RecordNotFound do
+ Merchant.find(merchant_id)
+ end
+
+ get :show, {id: merchant_id}
+ assert_response :not_found
+ end
+end
+
+
+
+# test "should get the new form" do
+# get :new
+# assert_template :new
+# assert_template partial: '_form'
+# assert_response :success
+# end
+#
+# test "add a new book to the database" do
+# post_params = {book: {title: "Anna Karenina", author: "Tolstoy?"} }
+# assert_difference("Book.count", 1) do
+# post :create, post_params
+# end
+#
+# assert_redirected_to books_path
+# end
+#
+# test "a book with no title can't change the database" do
+# post_params = { book: {author: "someone", description: "empty values"}}
+#
+# assert_no_difference("Book.count") do
+# post :create, post_params
+# end
+#
+# assert_template :new
+# end
+#
+# test "a book with no author can't change the database" do
+# post_params = { book: {title: "something", description: "empty values"}}
+#
+# assert_no_difference("Book.count") do
+# post :create, post_params
+# end
+#
+# assert_template :new
+# end
+#
+# test "should get the edit form" do
+# book_id = books(:lotr).id
+# get :edit, {id: book_id}
+# assert_template :edit
+# assert_template partial: '_form'
+# assert_response :success
+#
+# book = assigns(:book)
+# assert_not_nil book
+# assert_equal book.id, book_id
+# end
+#
+# test "update should change the book" do
+# book_id = books(:of_mice_and_men).id
+# patch :update, {id: book_id, book: {title: "Of Mice & Men"} }
+# assert_equal "Of Mice & Men", Book.find(book_id).title
+#
+# assert_redirected_to book_path
+# end
+#
+# test "update should not allow nil title" do
+# book_id = books(:lotr).id
+# patch :update, {id: book_id, book: {title: nil} }
+#
+# assert_equal "Lord of the Rings", Book.find(book_id).title
+#
+# assert_template :edit
+# end
+#
+# test "update should not allow nil author" do
+# book_id = books(:gone_with_the_wind).id
+# patch :update, {id: book_id, book: {author: nil} }
+#
+# assert_equal "Margaret Mitchell", Book.find(book_id).author
+# assert_template :edit
+# end
+#
+# test "destroy should delete the item" do
+# book_id = books(:lotr).id
+#
+# assert_difference("Book.count", -1) do
+# delete :destroy, {id: book_id}
+# end
+#
+# assert_raises ActiveRecord::RecordNotFound do
+# Book.find(book_id)
+# end
+#
+# assert_redirected_to books_path
+# end
+#
+# test "upvote should increment rank by one" do
+# book_id = books(:lotr).id
+#
+# assert_difference("Book.find(book_id).rank", 1) do
+# patch :upvote, {id: book_id}
+# end
+#
+# assert_redirected_to book_path(book_id)
+# end
+#
+# test "upvote should set nil ranks to one" do
+# book_id = books(:nil_rank).id
+#
+# assert_difference("Book.find(book_id).rank", 1) do
+# patch :upvote, {id: book_id}
+# end
+# end
+# end
diff --git a/test/fixtures/merchants.yml b/test/fixtures/merchants.yml
index be145aeb97..b1c3150bb4 100644
--- a/test/fixtures/merchants.yml
+++ b/test/fixtures/merchants.yml
@@ -1,6 +1,15 @@
-# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
-
-# This model initially had no columns defined. If you add columns to the
-# model remove the '{}' from the fixture names and add the columns immediately
-# below each fixture, per the syntax in the comments below
-#
+hilarious:
+ user_name: Hillary Oss
+ email: hillary@oss.com
+ uid: 125673
+ provider: github
+chanda:
+ user_name: Chanda Lear
+ email: chandelier@gmail.com
+ uid: 67890
+ provider: github
+doctor:
+ user_name: The Doctor
+ email: doctorwho@tardis.net
+ uid: 42
+ provider: github
From 20f8aa1301356ca67e701263ad18997c771517b3 Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Mon, 24 Oct 2016 14:37:24 -0700
Subject: [PATCH 062/144] cleanup merchant test
---
test/controllers/merchants_controller_test.rb | 108 ------------------
1 file changed, 108 deletions(-)
diff --git a/test/controllers/merchants_controller_test.rb b/test/controllers/merchants_controller_test.rb
index 0ce73b3a9b..19b0b41149 100644
--- a/test/controllers/merchants_controller_test.rb
+++ b/test/controllers/merchants_controller_test.rb
@@ -23,111 +23,3 @@ class MerchantsControllerTest < ActionController::TestCase
assert_response :not_found
end
end
-
-
-
-# test "should get the new form" do
-# get :new
-# assert_template :new
-# assert_template partial: '_form'
-# assert_response :success
-# end
-#
-# test "add a new book to the database" do
-# post_params = {book: {title: "Anna Karenina", author: "Tolstoy?"} }
-# assert_difference("Book.count", 1) do
-# post :create, post_params
-# end
-#
-# assert_redirected_to books_path
-# end
-#
-# test "a book with no title can't change the database" do
-# post_params = { book: {author: "someone", description: "empty values"}}
-#
-# assert_no_difference("Book.count") do
-# post :create, post_params
-# end
-#
-# assert_template :new
-# end
-#
-# test "a book with no author can't change the database" do
-# post_params = { book: {title: "something", description: "empty values"}}
-#
-# assert_no_difference("Book.count") do
-# post :create, post_params
-# end
-#
-# assert_template :new
-# end
-#
-# test "should get the edit form" do
-# book_id = books(:lotr).id
-# get :edit, {id: book_id}
-# assert_template :edit
-# assert_template partial: '_form'
-# assert_response :success
-#
-# book = assigns(:book)
-# assert_not_nil book
-# assert_equal book.id, book_id
-# end
-#
-# test "update should change the book" do
-# book_id = books(:of_mice_and_men).id
-# patch :update, {id: book_id, book: {title: "Of Mice & Men"} }
-# assert_equal "Of Mice & Men", Book.find(book_id).title
-#
-# assert_redirected_to book_path
-# end
-#
-# test "update should not allow nil title" do
-# book_id = books(:lotr).id
-# patch :update, {id: book_id, book: {title: nil} }
-#
-# assert_equal "Lord of the Rings", Book.find(book_id).title
-#
-# assert_template :edit
-# end
-#
-# test "update should not allow nil author" do
-# book_id = books(:gone_with_the_wind).id
-# patch :update, {id: book_id, book: {author: nil} }
-#
-# assert_equal "Margaret Mitchell", Book.find(book_id).author
-# assert_template :edit
-# end
-#
-# test "destroy should delete the item" do
-# book_id = books(:lotr).id
-#
-# assert_difference("Book.count", -1) do
-# delete :destroy, {id: book_id}
-# end
-#
-# assert_raises ActiveRecord::RecordNotFound do
-# Book.find(book_id)
-# end
-#
-# assert_redirected_to books_path
-# end
-#
-# test "upvote should increment rank by one" do
-# book_id = books(:lotr).id
-#
-# assert_difference("Book.find(book_id).rank", 1) do
-# patch :upvote, {id: book_id}
-# end
-#
-# assert_redirected_to book_path(book_id)
-# end
-#
-# test "upvote should set nil ranks to one" do
-# book_id = books(:nil_rank).id
-#
-# assert_difference("Book.find(book_id).rank", 1) do
-# patch :upvote, {id: book_id}
-# end
-# end
-# end
From e516a9ba37ccb8533cdd945a3cd47bc0d87d3ce2 Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Mon, 24 Oct 2016 15:31:53 -0700
Subject: [PATCH 063/144] added uniqueness to join table:
---
app/controllers/merchants_controller.rb | 11 ++++-------
.../20161024221053_add_uniqueness_to_cat_prod.rb | 8 ++++++++
db/schema.rb | 6 +++---
3 files changed, 15 insertions(+), 10 deletions(-)
create mode 100644 db/migrate/20161024221053_add_uniqueness_to_cat_prod.rb
diff --git a/app/controllers/merchants_controller.rb b/app/controllers/merchants_controller.rb
index 9c5217d7a1..030610c038 100644
--- a/app/controllers/merchants_controller.rb
+++ b/app/controllers/merchants_controller.rb
@@ -1,12 +1,6 @@
class MerchantsController < ApplicationController
- before_action :find_merchant, only: [:show, :edit, :update]
- def show; end
-
-
- private
-
- def find_merchant
+ def show
begin
@merchant = Merchant.find(params[:id])
rescue ActiveRecord::RecordNotFound
@@ -14,6 +8,9 @@ def find_merchant
end
end
+
+ private
+
def merchant_params
params.require(:merchant).permit(:user_name, :email, :uid, :provider)
end
diff --git a/db/migrate/20161024221053_add_uniqueness_to_cat_prod.rb b/db/migrate/20161024221053_add_uniqueness_to_cat_prod.rb
new file mode 100644
index 0000000000..99d47f6230
--- /dev/null
+++ b/db/migrate/20161024221053_add_uniqueness_to_cat_prod.rb
@@ -0,0 +1,8 @@
+class AddUniquenessToCatProd < ActiveRecord::Migration
+ def change
+ remove_index(:categories_products, [:product_id, :category_id])
+ remove_index(:categories_products, [:category_id, :product_id])
+ add_index(:categories_products, [:product_id, :category_id], :unique => true)
+ add_index(:categories_products, [:category_id, :product_id], :unique => true)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 60adab146f..beb24139dc 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20161020020026) do
+ActiveRecord::Schema.define(version: 20161024221053) do
create_table "categories", force: :cascade do |t|
t.string "name"
@@ -24,8 +24,8 @@
t.integer "category_id", null: false
end
- add_index "categories_products", ["category_id", "product_id"], name: "index_categories_products_on_category_id_and_product_id"
- add_index "categories_products", ["product_id", "category_id"], name: "index_categories_products_on_product_id_and_category_id"
+ add_index "categories_products", ["category_id", "product_id"], name: "index_categories_products_on_category_id_and_product_id", unique: true
+ add_index "categories_products", ["product_id", "category_id"], name: "index_categories_products_on_product_id_and_category_id", unique: true
create_table "merchants", force: :cascade do |t|
t.string "user_name"
From 0ec46103f4c35baeb99ac8b67b7d230e62c1301c Mon Sep 17 00:00:00 2001
From: Shari Meggs
Date: Mon, 24 Oct 2016 15:59:32 -0700
Subject: [PATCH 064/144] Was playing around with different ways on how to have
the cart add items. Reverted back to an old state
---
app/controllers/application_controller.rb | 9 +++++++-
app/controllers/carts_controller.rb | 24 +++++++++-----------
app/controllers/order_items_controller.rb | 2 +-
app/models/merchant.rb | 1 -
app/views/carts/index.html.erb | 2 +-
app/views/products/index.html.erb | 27 ++++++++++++-----------
config/routes.rb | 2 +-
7 files changed, 35 insertions(+), 32 deletions(-)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index c4d9355895..d87c0156be 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -2,7 +2,7 @@ class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
- helper_method :current_order
+ helper_method :current_order, :shopping_cart
def current_order
if !session[:order_id].nil?
@@ -11,4 +11,11 @@ def current_order
Order.new
end
end
+ def shopping_cart
+ if session[:cart]
+ @cart = session[:cart]
+ else
+ @cart ={}
+ end
+ end
end
diff --git a/app/controllers/carts_controller.rb b/app/controllers/carts_controller.rb
index 5bfee82616..29fa15b197 100644
--- a/app/controllers/carts_controller.rb
+++ b/app/controllers/carts_controller.rb
@@ -1,24 +1,17 @@
class CartsController < ApplicationController
+ before_action :shopping_cart, only: [:index]
def index
- if session[:cart]
- @cart = session[:cart]
- else
- @cart ={}
- end
+ return @cart
end
def add_to_cart
id = params[:id]
- if session[:cart]
- cart = session[:cart]
- else
- session[:cart] = {}
- cart = session[:cart]
- end
+ @product = Product.find(id)
+
if cart[id]
- cart[id] = cart[id] - 1
+ cart[id] = cart[id] + 1
else
cart[id] = 1
end
@@ -32,8 +25,11 @@ def sub_cart
end
def destroy
- cart = session[:cart]
- cart.destroy
+
+ @product = Product.find(params[:id])
+ session[:cart].delete(@product)
+ # @cart = shopping_cart
+ # @product = @cart.product.find(params[:id]).destroy
redirect_to carts_path
end
diff --git a/app/controllers/order_items_controller.rb b/app/controllers/order_items_controller.rb
index 8b9119674c..49180bbe96 100644
--- a/app/controllers/order_items_controller.rb
+++ b/app/controllers/order_items_controller.rb
@@ -26,7 +26,7 @@ def edit
def destroy
@order = current_order
@order_item = @order.order_items.find(params[:id]).destroy
- redirect_to order_path(params[:order_id])
+ redirect_to order_path(params[:order_id])
end
diff --git a/app/models/merchant.rb b/app/models/merchant.rb
index e540e6ae23..644e7a85c6 100644
--- a/app/models/merchant.rb
+++ b/app/models/merchant.rb
@@ -3,7 +3,6 @@ class Merchant < ActiveRecord::Base
has_many :products
def self.build_from_github(auth_hash)
-
merchant = Merchant.new
merchant.uid = auth_hash[:uid]
merchant.provider = 'github'
diff --git a/app/views/carts/index.html.erb b/app/views/carts/index.html.erb
index 70173246b1..d5b74574c5 100644
--- a/app/views/carts/index.html.erb
+++ b/app/views/carts/index.html.erb
@@ -13,7 +13,7 @@
<%=link_to "Learn More", product_path(id)%>
<%=number_to_currency(product.price, :unit => '$')%>
Quantity: <%=quantity%> <%=link_to "Add More!", add_cart_path(product.id), method: :get%>
- <%=link_to "Delete Item", sub_cart_path(product.id), method: :delete%>
+ <%=link_to "Delete Item", delete_cart_path(product.id), method: :delete%>
<%=link_to "Reduce!", sub_cart_path(product.id)%>
diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb
index 47b1115da6..267bb6818e 100644
--- a/app/views/products/index.html.erb
+++ b/app/views/products/index.html.erb
@@ -1,18 +1,19 @@
All Products
<%@products.each do |product|%>
-
-
- <%= link_to product.name, product_path(product) %>
-
-
- <%= image_tag "#{product.photo_url}"%>
-
-
- <%= product.description %>
-
-
- Add to Cart
+
+
+ <%= link_to product.name, product_path(product) %>
+
+
+ <%= image_tag "#{product.photo_url}"%>
+
+
+ <%= product.description %>
+
+
+
+ Add to Cart
-<%end%>
+ <%end%>
diff --git a/config/routes.rb b/config/routes.rb
index 6fe1c855a0..48805f1f7d 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -37,6 +37,6 @@
get 'carts/empty_cart' =>'carts#empty_cart'
get '/carts/:id', to: 'carts#add_to_cart', as: "add_cart"
get '/carts/:id', to: 'carts#sub_cart', as: "sub_cart"
- delete '/carts/:id', to: 'carts#destroy'
+ delete '/carts/products/:id', to: 'carts#destroy', as: 'delete_cart'
end
From f1b092389d778ecd9b9471221476cfca5d71a039 Mon Sep 17 00:00:00 2001
From: Shari Meggs
Date: Mon, 24 Oct 2016 16:29:22 -0700
Subject: [PATCH 065/144] I changed some of the cart controller
---
app/controllers/carts_controller.rb | 19 ++++++++++++++-----
app/views/carts/index.html.erb | 15 ++++++++-------
2 files changed, 22 insertions(+), 12 deletions(-)
diff --git a/app/controllers/carts_controller.rb b/app/controllers/carts_controller.rb
index 29fa15b197..ed050dc8fb 100644
--- a/app/controllers/carts_controller.rb
+++ b/app/controllers/carts_controller.rb
@@ -1,19 +1,28 @@
class CartsController < ApplicationController
- before_action :shopping_cart, only: [:index]
+ before_action :shopping_cart
def index
+ @cart.each do | k, v|
+ @id = k
+ end
+ @product = Product.find(@id)
+ @order_item = OrderItem.new
+ @product.order_items << @order_item
+ @product.save
return @cart
end
def add_to_cart
id = params[:id]
-
@product = Product.find(id)
+ @order_item = OrderItem.new
+ @product.order_items << @order_item
+ @product.save
- if cart[id]
- cart[id] = cart[id] + 1
+ if @cart[id]
+ @cart[id] = @cart[id] + 1
else
- cart[id] = 1
+ @cart[id] = 1
end
redirect_to carts_path
end
diff --git a/app/views/carts/index.html.erb b/app/views/carts/index.html.erb
index d5b74574c5..528aa196bd 100644
--- a/app/views/carts/index.html.erb
+++ b/app/views/carts/index.html.erb
@@ -7,19 +7,20 @@
<% @cart.each do |id, quantity| %>
- <% product = Product.find(id) %>
+
+
- <%=product.name%>
+ <%=@product.name%>
<%=link_to "Learn More", product_path(id)%>
- <%=number_to_currency(product.price, :unit => '$')%>
- Quantity: <%=quantity%> <%=link_to "Add More!", add_cart_path(product.id), method: :get%>
- <%=link_to "Delete Item", delete_cart_path(product.id), method: :delete%>
+
<%=number_to_currency(@product.price, :unit => '$')%>
+ Quantity: <%=quantity%> <%=link_to "Add More!", add_cart_path(@product.id), method: :get%>
+ <%=link_to "Delete Item", delete_cart_path(@product.id), method: :delete%>
- <%=link_to "Reduce!", sub_cart_path(product.id)%>
+ <%=link_to "Reduce!", sub_cart_path(@product.id)%>
- <% total += quantity * product.price %>
+ <% total += quantity * @product.price %>
<%end%>
<%if total != 0%>
Total: <%=number_to_currency(total, :unit => '$')%>
From 1ad4645c3cc9b4e23f258d78d13036f0df4179e6 Mon Sep 17 00:00:00 2001
From: Yeni
Date: Mon, 24 Oct 2016 16:31:18 -0700
Subject: [PATCH 066/144] updated session routes and the controller.
---
app/controllers/application_controller.rb | 18 ++++++++++++
app/controllers/sessions_controller.rb | 31 ++++++--------------
app/helpers/application_helper.rb | 35 +++++++++++++++++++++++
app/views/layouts/application.html.erb | 4 +--
app/views/sessions/login.html.erb | 1 +
config/routes.rb | 6 ++--
6 files changed, 68 insertions(+), 27 deletions(-)
create mode 100644 app/views/sessions/login.html.erb
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index d83690e1b9..b642aeb5d7 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -2,4 +2,22 @@ class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
+
+ before_action :require_login
+
+private
+def current_user
+ begin
+ @current_user ||= Merchant.find(session[:user_id]) if session[:user_id]
+ rescue ActiveRecord::RecordNotFound
+ nil
+ end
+end
+
+def require_login
+ if current_user.nil?
+ flash[:error] = "You must be logged in to view this section"
+ redirect_to login_path
+ end
+end
end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 1e6c83f172..213138b9d3 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -1,6 +1,6 @@
class SessionsController < ApplicationController
# If we want to implement this later, will need to customize authorization specifics...
- # skip_before_action :require_login, only: [:login, :create]
+ skip_before_action :require_login, only: [:login, :create]
def login_failure; end
@@ -16,36 +16,21 @@ def create
render :login_failure unless @merchant.save
end
- session[:merchant_id] = @merchant.id
+ session[:user_id] = @merchant.id
redirect_to sessions_path
- # if @merchant.nil?
- # # Merchant doesn't match anything in the DB.
- # # Attempt to create a new merchant.
- # @merchant = Merchant.build_from_github(auth_hash)
- # if @merchant.save
- # redirect_to sessions_path
- # else
- # redirect_to login_failure_path
- # end
- # else
- # # Save the merchant ID in the session
- # session[:merchant_id] = @merchant.id
- #
- # redirect_to sessions_path
- # end
end
def index
- if session[:merchant_id].nil?
- redirect_to login_failure_path
+ if session[:user_id].nil?
+ redirect_to login_path
else
- @merchant = Merchant.find(session[:merchant_id]) # < recalls the value set in a previous request
+ @merchant = Merchant.find(session[:user_id]) # < recalls the value set in a previous request
end
end
- def destroy
- session.delete(:merchant_id)
- redirect_to login_failure_path
+ def logout
+ session.delete(:user_id)
+ redirect_to root_path
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index de6be7945c..aacbbc9efe 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,2 +1,37 @@
module ApplicationHelper
+ def user_name
+ begin
+ merchant = Merchant.find(session[:user_id])
+ if merchant.user_name
+ return merchant.user_name
+ elsif merchant.email
+ return merchant.email
+ else
+ return "#{merchant.provider} merchant #{merchant.uid}"
+ end
+ rescue ActiveRecord::RecordNotFound
+ return "ERROR: merchant not in database"
+ end
+ end
+
+ def login_status
+ if session[:user_id]
+ "Logged in as #{user_name}"
+ else
+ "Not logged in"
+ end
+ end
+
+ def login_button(**kwargs)
+ if session[:user_id]
+ text = "Log Out"
+ path = '/sessions'
+ method = :delete
+ else
+ text = "Log In"
+ path = "/auth/github"
+ method = :get
+ end
+ link_to text, path, method: method, **kwargs
+ end
end
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index f890f0d524..ae43ba0a78 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -14,8 +14,8 @@
<%= link_to "HOME", root_path %>
- <%= link_to "LOGIN", '/auth/github' %>
- <%= link_to "LOGOUT", sessions_path, method: :delete %>
+ <%= login_status %>
+ <%= login_button %>
diff --git a/app/views/sessions/login.html.erb b/app/views/sessions/login.html.erb
new file mode 100644
index 0000000000..7882fcfdf9
--- /dev/null
+++ b/app/views/sessions/login.html.erb
@@ -0,0 +1 @@
+<%= link_to "Merchant Portal", sessions_path %>
diff --git a/config/routes.rb b/config/routes.rb
index 4fe790456b..006bfc72cb 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -26,8 +26,10 @@
get "/sessions/login_failure", to: "sessions#login_failure", as: "login_failure"
- get '/sessions', to: 'sessions#index', as: 'sessions'
+ get '/sessions', to: 'sessions#index', as: 'portal'
- delete '/sessions', to: 'sessions#destroy'
+ delete '/auth/logout', to: 'sessions#logout', as: "logout"
+
+ get '/auth/login', to: 'sessions#login', as: 'login'
end
From ca138d5bf92e97c0dd6697cc10817e57006d5c4d Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Mon, 24 Oct 2016 16:32:50 -0700
Subject: [PATCH 067/144] one model order test not passing yet
---
app/views/products/index.html.erb | 2 +-
test/models/order_test.rb | 12 ++++++++----
2 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb
index 110577de92..cf86b20e0e 100644
--- a/app/views/products/index.html.erb
+++ b/app/views/products/index.html.erb
@@ -1,6 +1,6 @@
All Products
-<%@product.each do |product|%>
+<%@products.each do |product|%>
<%= link_to product.name, product_path(product) %>
diff --git a/test/models/order_test.rb b/test/models/order_test.rb
index ba8121065c..035829f465 100644
--- a/test/models/order_test.rb
+++ b/test/models/order_test.rb
@@ -25,10 +25,14 @@ class OrderTest < ActiveSupport::TestCase
end
test "confirming the private method update_total occurs on an order object" do
- p = Order.new
- assert_equal(0, p.total)
-
- o = orders(:valid_order)
+ o = Order.new(cc_number: 6789, cc_exp_year: 2016, cc_exp_month: 12)
+ assert_equal(0, o.total)
+
+ p = OrderItem.new(quantity: 4, product: products(:cat_suit), shipped?: true, order_id: o)
+ puts "Here are o's attributes: #{o.attributes}."
+ # o[:order_item] = p
+ o.save
+ puts "Did o save? #{o.save}."
assert_equal(4936, o.total)
# assert_difference("o.order_items = order_items(:four_unit)", 0) do
From 67aaad5eae910238d265866ea10cbdedd2c2b67f Mon Sep 17 00:00:00 2001
From: Yeni
Date: Mon, 24 Oct 2016 16:32:50 -0700
Subject: [PATCH 068/144] adding foundation js and css files
---
app/assets/javascripts/foundation.js | 9069 +++++++++++++++++++++++++
app/assets/stylesheets/foundation.css | 4194 ++++++++++++
2 files changed, 13263 insertions(+)
create mode 100644 app/assets/javascripts/foundation.js
create mode 100644 app/assets/stylesheets/foundation.css
diff --git a/app/assets/javascripts/foundation.js b/app/assets/javascripts/foundation.js
new file mode 100644
index 0000000000..87e04589c2
--- /dev/null
+++ b/app/assets/javascripts/foundation.js
@@ -0,0 +1,9069 @@
+!function ($) {
+
+ "use strict";
+
+ var FOUNDATION_VERSION = '6.2.2';
+
+ // Global Foundation object
+ // This is attached to the window, or used as a module for AMD/Browserify
+ var Foundation = {
+ version: FOUNDATION_VERSION,
+
+ /**
+ * Stores initialized plugins.
+ */
+ _plugins: {},
+
+ /**
+ * Stores generated unique ids for plugin instances
+ */
+ _uuids: [],
+
+ /**
+ * Returns a boolean for RTL support
+ */
+ rtl: function () {
+ return $('html').attr('dir') === 'rtl';
+ },
+ /**
+ * Defines a Foundation plugin, adding it to the `Foundation` namespace and the list of plugins to initialize when reflowing.
+ * @param {Object} plugin - The constructor of the plugin.
+ */
+ plugin: function (plugin, name) {
+ // Object key to use when adding to global Foundation object
+ // Examples: Foundation.Reveal, Foundation.OffCanvas
+ var className = name || functionName(plugin);
+ // Object key to use when storing the plugin, also used to create the identifying data attribute for the plugin
+ // Examples: data-reveal, data-off-canvas
+ var attrName = hyphenate(className);
+
+ // Add to the Foundation object and the plugins list (for reflowing)
+ this._plugins[attrName] = this[className] = plugin;
+ },
+ /**
+ * @function
+ * Populates the _uuids array with pointers to each individual plugin instance.
+ * Adds the `zfPlugin` data-attribute to programmatically created plugins to allow use of $(selector).foundation(method) calls.
+ * Also fires the initialization event for each plugin, consolidating repetitive code.
+ * @param {Object} plugin - an instance of a plugin, usually `this` in context.
+ * @param {String} name - the name of the plugin, passed as a camelCased string.
+ * @fires Plugin#init
+ */
+ registerPlugin: function (plugin, name) {
+ var pluginName = name ? hyphenate(name) : functionName(plugin.constructor).toLowerCase();
+ plugin.uuid = this.GetYoDigits(6, pluginName);
+
+ if (!plugin.$element.attr('data-' + pluginName)) {
+ plugin.$element.attr('data-' + pluginName, plugin.uuid);
+ }
+ if (!plugin.$element.data('zfPlugin')) {
+ plugin.$element.data('zfPlugin', plugin);
+ }
+ /**
+ * Fires when the plugin has initialized.
+ * @event Plugin#init
+ */
+ plugin.$element.trigger('init.zf.' + pluginName);
+
+ this._uuids.push(plugin.uuid);
+
+ return;
+ },
+ /**
+ * @function
+ * Removes the plugins uuid from the _uuids array.
+ * Removes the zfPlugin data attribute, as well as the data-plugin-name attribute.
+ * Also fires the destroyed event for the plugin, consolidating repetitive code.
+ * @param {Object} plugin - an instance of a plugin, usually `this` in context.
+ * @fires Plugin#destroyed
+ */
+ unregisterPlugin: function (plugin) {
+ var pluginName = hyphenate(functionName(plugin.$element.data('zfPlugin').constructor));
+
+ this._uuids.splice(this._uuids.indexOf(plugin.uuid), 1);
+ plugin.$element.removeAttr('data-' + pluginName).removeData('zfPlugin')
+ /**
+ * Fires when the plugin has been destroyed.
+ * @event Plugin#destroyed
+ */
+ .trigger('destroyed.zf.' + pluginName);
+ for (var prop in plugin) {
+ plugin[prop] = null; //clean up script to prep for garbage collection.
+ }
+ return;
+ },
+
+ /**
+ * @function
+ * Causes one or more active plugins to re-initialize, resetting event listeners, recalculating positions, etc.
+ * @param {String} plugins - optional string of an individual plugin key, attained by calling `$(element).data('pluginName')`, or string of a plugin class i.e. `'dropdown'`
+ * @default If no argument is passed, reflow all currently active plugins.
+ */
+ reInit: function (plugins) {
+ var isJQ = plugins instanceof $;
+ try {
+ if (isJQ) {
+ plugins.each(function () {
+ $(this).data('zfPlugin')._init();
+ });
+ } else {
+ var type = typeof plugins,
+ _this = this,
+ fns = {
+ 'object': function (plgs) {
+ plgs.forEach(function (p) {
+ p = hyphenate(p);
+ $('[data-' + p + ']').foundation('_init');
+ });
+ },
+ 'string': function () {
+ plugins = hyphenate(plugins);
+ $('[data-' + plugins + ']').foundation('_init');
+ },
+ 'undefined': function () {
+ this['object'](Object.keys(_this._plugins));
+ }
+ };
+ fns[type](plugins);
+ }
+ } catch (err) {
+ console.error(err);
+ } finally {
+ return plugins;
+ }
+ },
+
+ /**
+ * returns a random base-36 uid with namespacing
+ * @function
+ * @param {Number} length - number of random base-36 digits desired. Increase for more random strings.
+ * @param {String} namespace - name of plugin to be incorporated in uid, optional.
+ * @default {String} '' - if no plugin name is provided, nothing is appended to the uid.
+ * @returns {String} - unique id
+ */
+ GetYoDigits: function (length, namespace) {
+ length = length || 6;
+ return Math.round(Math.pow(36, length + 1) - Math.random() * Math.pow(36, length)).toString(36).slice(1) + (namespace ? '-' + namespace : '');
+ },
+ /**
+ * Initialize plugins on any elements within `elem` (and `elem` itself) that aren't already initialized.
+ * @param {Object} elem - jQuery object containing the element to check inside. Also checks the element itself, unless it's the `document` object.
+ * @param {String|Array} plugins - A list of plugins to initialize. Leave this out to initialize everything.
+ */
+ reflow: function (elem, plugins) {
+
+ // If plugins is undefined, just grab everything
+ if (typeof plugins === 'undefined') {
+ plugins = Object.keys(this._plugins);
+ }
+ // If plugins is a string, convert it to an array with one item
+ else if (typeof plugins === 'string') {
+ plugins = [plugins];
+ }
+
+ var _this = this;
+
+ // Iterate through each plugin
+ $.each(plugins, function (i, name) {
+ // Get the current plugin
+ var plugin = _this._plugins[name];
+
+ // Localize the search to all elements inside elem, as well as elem itself, unless elem === document
+ var $elem = $(elem).find('[data-' + name + ']').addBack('[data-' + name + ']');
+
+ // For each plugin found, initialize it
+ $elem.each(function () {
+ var $el = $(this),
+ opts = {};
+ // Don't double-dip on plugins
+ if ($el.data('zfPlugin')) {
+ console.warn("Tried to initialize " + name + " on an element that already has a Foundation plugin.");
+ return;
+ }
+
+ if ($el.attr('data-options')) {
+ var thing = $el.attr('data-options').split(';').forEach(function (e, i) {
+ var opt = e.split(':').map(function (el) {
+ return el.trim();
+ });
+ if (opt[0]) opts[opt[0]] = parseValue(opt[1]);
+ });
+ }
+ try {
+ $el.data('zfPlugin', new plugin($(this), opts));
+ } catch (er) {
+ console.error(er);
+ } finally {
+ return;
+ }
+ });
+ });
+ },
+ getFnName: functionName,
+ transitionend: function ($elem) {
+ var transitions = {
+ 'transition': 'transitionend',
+ 'WebkitTransition': 'webkitTransitionEnd',
+ 'MozTransition': 'transitionend',
+ 'OTransition': 'otransitionend'
+ };
+ var elem = document.createElement('div'),
+ end;
+
+ for (var t in transitions) {
+ if (typeof elem.style[t] !== 'undefined') {
+ end = transitions[t];
+ }
+ }
+ if (end) {
+ return end;
+ } else {
+ end = setTimeout(function () {
+ $elem.triggerHandler('transitionend', [$elem]);
+ }, 1);
+ return 'transitionend';
+ }
+ }
+ };
+
+ Foundation.util = {
+ /**
+ * Function for applying a debounce effect to a function call.
+ * @function
+ * @param {Function} func - Function to be called at end of timeout.
+ * @param {Number} delay - Time in ms to delay the call of `func`.
+ * @returns function
+ */
+ throttle: function (func, delay) {
+ var timer = null;
+
+ return function () {
+ var context = this,
+ args = arguments;
+
+ if (timer === null) {
+ timer = setTimeout(function () {
+ func.apply(context, args);
+ timer = null;
+ }, delay);
+ }
+ };
+ }
+ };
+
+ // TODO: consider not making this a jQuery function
+ // TODO: need way to reflow vs. re-initialize
+ /**
+ * The Foundation jQuery method.
+ * @param {String|Array} method - An action to perform on the current jQuery object.
+ */
+ var foundation = function (method) {
+ var type = typeof method,
+ $meta = $('meta.foundation-mq'),
+ $noJS = $('.no-js');
+
+ if (!$meta.length) {
+ $(' ').appendTo(document.head);
+ }
+ if ($noJS.length) {
+ $noJS.removeClass('no-js');
+ }
+
+ if (type === 'undefined') {
+ //needs to initialize the Foundation object, or an individual plugin.
+ Foundation.MediaQuery._init();
+ Foundation.reflow(this);
+ } else if (type === 'string') {
+ //an individual method to invoke on a plugin or group of plugins
+ var args = Array.prototype.slice.call(arguments, 1); //collect all the arguments, if necessary
+ var plugClass = this.data('zfPlugin'); //determine the class of plugin
+
+ if (plugClass !== undefined && plugClass[method] !== undefined) {
+ //make sure both the class and method exist
+ if (this.length === 1) {
+ //if there's only one, call it directly.
+ plugClass[method].apply(plugClass, args);
+ } else {
+ this.each(function (i, el) {
+ //otherwise loop through the jQuery collection and invoke the method on each
+ plugClass[method].apply($(el).data('zfPlugin'), args);
+ });
+ }
+ } else {
+ //error for no class or no method
+ throw new ReferenceError("We're sorry, '" + method + "' is not an available method for " + (plugClass ? functionName(plugClass) : 'this element') + '.');
+ }
+ } else {
+ //error for invalid argument type
+ throw new TypeError('We\'re sorry, ' + type + ' is not a valid parameter. You must use a string representing the method you wish to invoke.');
+ }
+ return this;
+ };
+
+ window.Foundation = Foundation;
+ $.fn.foundation = foundation;
+
+ // Polyfill for requestAnimationFrame
+ (function () {
+ if (!Date.now || !window.Date.now) window.Date.now = Date.now = function () {
+ return new Date().getTime();
+ };
+
+ var vendors = ['webkit', 'moz'];
+ for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) {
+ var vp = vendors[i];
+ window.requestAnimationFrame = window[vp + 'RequestAnimationFrame'];
+ window.cancelAnimationFrame = window[vp + 'CancelAnimationFrame'] || window[vp + 'CancelRequestAnimationFrame'];
+ }
+ if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) || !window.requestAnimationFrame || !window.cancelAnimationFrame) {
+ var lastTime = 0;
+ window.requestAnimationFrame = function (callback) {
+ var now = Date.now();
+ var nextTime = Math.max(lastTime + 16, now);
+ return setTimeout(function () {
+ callback(lastTime = nextTime);
+ }, nextTime - now);
+ };
+ window.cancelAnimationFrame = clearTimeout;
+ }
+ /**
+ * Polyfill for performance.now, required by rAF
+ */
+ if (!window.performance || !window.performance.now) {
+ window.performance = {
+ start: Date.now(),
+ now: function () {
+ return Date.now() - this.start;
+ }
+ };
+ }
+ })();
+ if (!Function.prototype.bind) {
+ Function.prototype.bind = function (oThis) {
+ if (typeof this !== 'function') {
+ // closest thing possible to the ECMAScript 5
+ // internal IsCallable function
+ throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
+ }
+
+ var aArgs = Array.prototype.slice.call(arguments, 1),
+ fToBind = this,
+ fNOP = function () {},
+ fBound = function () {
+ return fToBind.apply(this instanceof fNOP ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments)));
+ };
+
+ if (this.prototype) {
+ // native functions don't have a prototype
+ fNOP.prototype = this.prototype;
+ }
+ fBound.prototype = new fNOP();
+
+ return fBound;
+ };
+ }
+ // Polyfill to get the name of a function in IE9
+ function functionName(fn) {
+ if (Function.prototype.name === undefined) {
+ var funcNameRegex = /function\s([^(]{1,})\(/;
+ var results = funcNameRegex.exec(fn.toString());
+ return results && results.length > 1 ? results[1].trim() : "";
+ } else if (fn.prototype === undefined) {
+ return fn.constructor.name;
+ } else {
+ return fn.prototype.constructor.name;
+ }
+ }
+ function parseValue(str) {
+ if (/true/.test(str)) return true;else if (/false/.test(str)) return false;else if (!isNaN(str * 1)) return parseFloat(str);
+ return str;
+ }
+ // Convert PascalCase to kebab-case
+ // Thank you: http://stackoverflow.com/a/8955580
+ function hyphenate(str) {
+ return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
+ }
+}(jQuery);
+'use strict';
+
+!function ($) {
+
+ // Default set of media queries
+ var defaultQueries = {
+ 'default': 'only screen',
+ landscape: 'only screen and (orientation: landscape)',
+ portrait: 'only screen and (orientation: portrait)',
+ retina: 'only screen and (-webkit-min-device-pixel-ratio: 2),' + 'only screen and (min--moz-device-pixel-ratio: 2),' + 'only screen and (-o-min-device-pixel-ratio: 2/1),' + 'only screen and (min-device-pixel-ratio: 2),' + 'only screen and (min-resolution: 192dpi),' + 'only screen and (min-resolution: 2dppx)'
+ };
+
+ var MediaQuery = {
+ queries: [],
+
+ current: '',
+
+ /**
+ * Initializes the media query helper, by extracting the breakpoint list from the CSS and activating the breakpoint watcher.
+ * @function
+ * @private
+ */
+ _init: function () {
+ var self = this;
+ var extractedStyles = $('.foundation-mq').css('font-family');
+ var namedQueries;
+
+ namedQueries = parseStyleToObject(extractedStyles);
+
+ for (var key in namedQueries) {
+ if (namedQueries.hasOwnProperty(key)) {
+ self.queries.push({
+ name: key,
+ value: 'only screen and (min-width: ' + namedQueries[key] + ')'
+ });
+ }
+ }
+
+ this.current = this._getCurrentSize();
+
+ this._watcher();
+ },
+
+
+ /**
+ * Checks if the screen is at least as wide as a breakpoint.
+ * @function
+ * @param {String} size - Name of the breakpoint to check.
+ * @returns {Boolean} `true` if the breakpoint matches, `false` if it's smaller.
+ */
+ atLeast: function (size) {
+ var query = this.get(size);
+
+ if (query) {
+ return window.matchMedia(query).matches;
+ }
+
+ return false;
+ },
+
+
+ /**
+ * Gets the media query of a breakpoint.
+ * @function
+ * @param {String} size - Name of the breakpoint to get.
+ * @returns {String|null} - The media query of the breakpoint, or `null` if the breakpoint doesn't exist.
+ */
+ get: function (size) {
+ for (var i in this.queries) {
+ if (this.queries.hasOwnProperty(i)) {
+ var query = this.queries[i];
+ if (size === query.name) return query.value;
+ }
+ }
+
+ return null;
+ },
+
+
+ /**
+ * Gets the current breakpoint name by testing every breakpoint and returning the last one to match (the biggest one).
+ * @function
+ * @private
+ * @returns {String} Name of the current breakpoint.
+ */
+ _getCurrentSize: function () {
+ var matched;
+
+ for (var i = 0; i < this.queries.length; i++) {
+ var query = this.queries[i];
+
+ if (window.matchMedia(query.value).matches) {
+ matched = query;
+ }
+ }
+
+ if (typeof matched === 'object') {
+ return matched.name;
+ } else {
+ return matched;
+ }
+ },
+
+
+ /**
+ * Activates the breakpoint watcher, which fires an event on the window whenever the breakpoint changes.
+ * @function
+ * @private
+ */
+ _watcher: function () {
+ var _this = this;
+
+ $(window).on('resize.zf.mediaquery', function () {
+ var newSize = _this._getCurrentSize(),
+ currentSize = _this.current;
+
+ if (newSize !== currentSize) {
+ // Change the current media query
+ _this.current = newSize;
+
+ // Broadcast the media query change on the window
+ $(window).trigger('changed.zf.mediaquery', [newSize, currentSize]);
+ }
+ });
+ }
+ };
+
+ Foundation.MediaQuery = MediaQuery;
+
+ // matchMedia() polyfill - Test a CSS media type/query in JS.
+ // Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas, David Knight. Dual MIT/BSD license
+ window.matchMedia || (window.matchMedia = function () {
+ 'use strict';
+
+ // For browsers that support matchMedium api such as IE 9 and webkit
+
+ var styleMedia = window.styleMedia || window.media;
+
+ // For those that don't support matchMedium
+ if (!styleMedia) {
+ var style = document.createElement('style'),
+ script = document.getElementsByTagName('script')[0],
+ info = null;
+
+ style.type = 'text/css';
+ style.id = 'matchmediajs-test';
+
+ script.parentNode.insertBefore(style, script);
+
+ // 'style.currentStyle' is used by IE <= 8 and 'window.getComputedStyle' for all other browsers
+ info = 'getComputedStyle' in window && window.getComputedStyle(style, null) || style.currentStyle;
+
+ styleMedia = {
+ matchMedium: function (media) {
+ var text = '@media ' + media + '{ #matchmediajs-test { width: 1px; } }';
+
+ // 'style.styleSheet' is used by IE <= 8 and 'style.textContent' for all other browsers
+ if (style.styleSheet) {
+ style.styleSheet.cssText = text;
+ } else {
+ style.textContent = text;
+ }
+
+ // Test if media query is true or false
+ return info.width === '1px';
+ }
+ };
+ }
+
+ return function (media) {
+ return {
+ matches: styleMedia.matchMedium(media || 'all'),
+ media: media || 'all'
+ };
+ };
+ }());
+
+ // Thank you: https://github.com/sindresorhus/query-string
+ function parseStyleToObject(str) {
+ var styleObject = {};
+
+ if (typeof str !== 'string') {
+ return styleObject;
+ }
+
+ str = str.trim().slice(1, -1); // browsers re-quote string style values
+
+ if (!str) {
+ return styleObject;
+ }
+
+ styleObject = str.split('&').reduce(function (ret, param) {
+ var parts = param.replace(/\+/g, ' ').split('=');
+ var key = parts[0];
+ var val = parts[1];
+ key = decodeURIComponent(key);
+
+ // missing `=` should be `null`:
+ // http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters
+ val = val === undefined ? null : decodeURIComponent(val);
+
+ if (!ret.hasOwnProperty(key)) {
+ ret[key] = val;
+ } else if (Array.isArray(ret[key])) {
+ ret[key].push(val);
+ } else {
+ ret[key] = [ret[key], val];
+ }
+ return ret;
+ }, {});
+
+ return styleObject;
+ }
+
+ Foundation.MediaQuery = MediaQuery;
+}(jQuery);
+/*******************************************
+ * *
+ * This util was created by Marius Olbertz *
+ * Please thank Marius on GitHub /owlbertz *
+ * or the web http://www.mariusolbertz.de/ *
+ * *
+ ******************************************/
+
+'use strict';
+
+!function ($) {
+
+ var keyCodes = {
+ 9: 'TAB',
+ 13: 'ENTER',
+ 27: 'ESCAPE',
+ 32: 'SPACE',
+ 37: 'ARROW_LEFT',
+ 38: 'ARROW_UP',
+ 39: 'ARROW_RIGHT',
+ 40: 'ARROW_DOWN'
+ };
+
+ var commands = {};
+
+ var Keyboard = {
+ keys: getKeyCodes(keyCodes),
+
+ /**
+ * Parses the (keyboard) event and returns a String that represents its key
+ * Can be used like Foundation.parseKey(event) === Foundation.keys.SPACE
+ * @param {Event} event - the event generated by the event handler
+ * @return String key - String that represents the key pressed
+ */
+ parseKey: function (event) {
+ var key = keyCodes[event.which || event.keyCode] || String.fromCharCode(event.which).toUpperCase();
+ if (event.shiftKey) key = 'SHIFT_' + key;
+ if (event.ctrlKey) key = 'CTRL_' + key;
+ if (event.altKey) key = 'ALT_' + key;
+ return key;
+ },
+
+
+ /**
+ * Handles the given (keyboard) event
+ * @param {Event} event - the event generated by the event handler
+ * @param {String} component - Foundation component's name, e.g. Slider or Reveal
+ * @param {Objects} functions - collection of functions that are to be executed
+ */
+ handleKey: function (event, component, functions) {
+ var commandList = commands[component],
+ keyCode = this.parseKey(event),
+ cmds,
+ command,
+ fn;
+
+ if (!commandList) return console.warn('Component not defined!');
+
+ if (typeof commandList.ltr === 'undefined') {
+ // this component does not differentiate between ltr and rtl
+ cmds = commandList; // use plain list
+ } else {
+ // merge ltr and rtl: if document is rtl, rtl overwrites ltr and vice versa
+ if (Foundation.rtl()) cmds = $.extend({}, commandList.ltr, commandList.rtl);else cmds = $.extend({}, commandList.rtl, commandList.ltr);
+ }
+ command = cmds[keyCode];
+
+ fn = functions[command];
+ if (fn && typeof fn === 'function') {
+ // execute function if exists
+ var returnValue = fn.apply();
+ if (functions.handled || typeof functions.handled === 'function') {
+ // execute function when event was handled
+ functions.handled(returnValue);
+ }
+ } else {
+ if (functions.unhandled || typeof functions.unhandled === 'function') {
+ // execute function when event was not handled
+ functions.unhandled();
+ }
+ }
+ },
+
+
+ /**
+ * Finds all focusable elements within the given `$element`
+ * @param {jQuery} $element - jQuery object to search within
+ * @return {jQuery} $focusable - all focusable elements within `$element`
+ */
+ findFocusable: function ($element) {
+ return $element.find('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]').filter(function () {
+ if (!$(this).is(':visible') || $(this).attr('tabindex') < 0) {
+ return false;
+ } //only have visible elements and those that have a tabindex greater or equal 0
+ return true;
+ });
+ },
+
+
+ /**
+ * Returns the component name name
+ * @param {Object} component - Foundation component, e.g. Slider or Reveal
+ * @return String componentName
+ */
+
+ register: function (componentName, cmds) {
+ commands[componentName] = cmds;
+ }
+ };
+
+ /*
+ * Constants for easier comparing.
+ * Can be used like Foundation.parseKey(event) === Foundation.keys.SPACE
+ */
+ function getKeyCodes(kcs) {
+ var k = {};
+ for (var kc in kcs) {
+ k[kcs[kc]] = kcs[kc];
+ }return k;
+ }
+
+ Foundation.Keyboard = Keyboard;
+}(jQuery);
+'use strict';
+
+!function ($) {
+
+ /**
+ * Motion module.
+ * @module foundation.motion
+ */
+
+ var initClasses = ['mui-enter', 'mui-leave'];
+ var activeClasses = ['mui-enter-active', 'mui-leave-active'];
+
+ var Motion = {
+ animateIn: function (element, animation, cb) {
+ animate(true, element, animation, cb);
+ },
+
+ animateOut: function (element, animation, cb) {
+ animate(false, element, animation, cb);
+ }
+ };
+
+ function Move(duration, elem, fn) {
+ var anim,
+ prog,
+ start = null;
+ // console.log('called');
+
+ function move(ts) {
+ if (!start) start = window.performance.now();
+ // console.log(start, ts);
+ prog = ts - start;
+ fn.apply(elem);
+
+ if (prog < duration) {
+ anim = window.requestAnimationFrame(move, elem);
+ } else {
+ window.cancelAnimationFrame(anim);
+ elem.trigger('finished.zf.animate', [elem]).triggerHandler('finished.zf.animate', [elem]);
+ }
+ }
+ anim = window.requestAnimationFrame(move);
+ }
+
+ /**
+ * Animates an element in or out using a CSS transition class.
+ * @function
+ * @private
+ * @param {Boolean} isIn - Defines if the animation is in or out.
+ * @param {Object} element - jQuery or HTML object to animate.
+ * @param {String} animation - CSS class to use.
+ * @param {Function} cb - Callback to run when animation is finished.
+ */
+ function animate(isIn, element, animation, cb) {
+ element = $(element).eq(0);
+
+ if (!element.length) return;
+
+ var initClass = isIn ? initClasses[0] : initClasses[1];
+ var activeClass = isIn ? activeClasses[0] : activeClasses[1];
+
+ // Set up the animation
+ reset();
+
+ element.addClass(animation).css('transition', 'none');
+
+ requestAnimationFrame(function () {
+ element.addClass(initClass);
+ if (isIn) element.show();
+ });
+
+ // Start the animation
+ requestAnimationFrame(function () {
+ element[0].offsetWidth;
+ element.css('transition', '').addClass(activeClass);
+ });
+
+ // Clean up the animation when it finishes
+ element.one(Foundation.transitionend(element), finish);
+
+ // Hides the element (for out animations), resets the element, and runs a callback
+ function finish() {
+ if (!isIn) element.hide();
+ reset();
+ if (cb) cb.apply(element);
+ }
+
+ // Resets transitions and removes motion-specific classes
+ function reset() {
+ element[0].style.transitionDuration = 0;
+ element.removeClass(initClass + ' ' + activeClass + ' ' + animation);
+ }
+ }
+
+ Foundation.Move = Move;
+ Foundation.Motion = Motion;
+}(jQuery);
+'use strict';
+
+!function ($) {
+
+ var Nest = {
+ Feather: function (menu) {
+ var type = arguments.length <= 1 || arguments[1] === undefined ? 'zf' : arguments[1];
+
+ menu.attr('role', 'menubar');
+
+ var items = menu.find('li').attr({ 'role': 'menuitem' }),
+ subMenuClass = 'is-' + type + '-submenu',
+ subItemClass = subMenuClass + '-item',
+ hasSubClass = 'is-' + type + '-submenu-parent';
+
+ menu.find('a:first').attr('tabindex', 0);
+
+ items.each(function () {
+ var $item = $(this),
+ $sub = $item.children('ul');
+
+ if ($sub.length) {
+ $item.addClass(hasSubClass).attr({
+ 'aria-haspopup': true,
+ 'aria-expanded': false,
+ 'aria-label': $item.children('a:first').text()
+ });
+
+ $sub.addClass('submenu ' + subMenuClass).attr({
+ 'data-submenu': '',
+ 'aria-hidden': true,
+ 'role': 'menu'
+ });
+ }
+
+ if ($item.parent('[data-submenu]').length) {
+ $item.addClass('is-submenu-item ' + subItemClass);
+ }
+ });
+
+ return;
+ },
+ Burn: function (menu, type) {
+ var items = menu.find('li').removeAttr('tabindex'),
+ subMenuClass = 'is-' + type + '-submenu',
+ subItemClass = subMenuClass + '-item',
+ hasSubClass = 'is-' + type + '-submenu-parent';
+
+ menu.find('*').removeClass(subMenuClass + ' ' + subItemClass + ' ' + hasSubClass + ' is-submenu-item submenu is-active').removeAttr('data-submenu').css('display', '');
+
+ // console.log( menu.find('.' + subMenuClass + ', .' + subItemClass + ', .has-submenu, .is-submenu-item, .submenu, [data-submenu]')
+ // .removeClass(subMenuClass + ' ' + subItemClass + ' has-submenu is-submenu-item submenu')
+ // .removeAttr('data-submenu'));
+ // items.each(function(){
+ // var $item = $(this),
+ // $sub = $item.children('ul');
+ // if($item.parent('[data-submenu]').length){
+ // $item.removeClass('is-submenu-item ' + subItemClass);
+ // }
+ // if($sub.length){
+ // $item.removeClass('has-submenu');
+ // $sub.removeClass('submenu ' + subMenuClass).removeAttr('data-submenu');
+ // }
+ // });
+ }
+ };
+
+ Foundation.Nest = Nest;
+}(jQuery);
+'use strict';
+
+!function ($) {
+
+ Foundation.Box = {
+ ImNotTouchingYou: ImNotTouchingYou,
+ GetDimensions: GetDimensions,
+ GetOffsets: GetOffsets
+ };
+
+ /**
+ * Compares the dimensions of an element to a container and determines collision events with container.
+ * @function
+ * @param {jQuery} element - jQuery object to test for collisions.
+ * @param {jQuery} parent - jQuery object to use as bounding container.
+ * @param {Boolean} lrOnly - set to true to check left and right values only.
+ * @param {Boolean} tbOnly - set to true to check top and bottom values only.
+ * @default if no parent object passed, detects collisions with `window`.
+ * @returns {Boolean} - true if collision free, false if a collision in any direction.
+ */
+ function ImNotTouchingYou(element, parent, lrOnly, tbOnly) {
+ var eleDims = GetDimensions(element),
+ top,
+ bottom,
+ left,
+ right;
+
+ if (parent) {
+ var parDims = GetDimensions(parent);
+
+ bottom = eleDims.offset.top + eleDims.height <= parDims.height + parDims.offset.top;
+ top = eleDims.offset.top >= parDims.offset.top;
+ left = eleDims.offset.left >= parDims.offset.left;
+ right = eleDims.offset.left + eleDims.width <= parDims.width + parDims.offset.left;
+ } else {
+ bottom = eleDims.offset.top + eleDims.height <= eleDims.windowDims.height + eleDims.windowDims.offset.top;
+ top = eleDims.offset.top >= eleDims.windowDims.offset.top;
+ left = eleDims.offset.left >= eleDims.windowDims.offset.left;
+ right = eleDims.offset.left + eleDims.width <= eleDims.windowDims.width;
+ }
+
+ var allDirs = [bottom, top, left, right];
+
+ if (lrOnly) {
+ return left === right === true;
+ }
+
+ if (tbOnly) {
+ return top === bottom === true;
+ }
+
+ return allDirs.indexOf(false) === -1;
+ };
+
+ /**
+ * Uses native methods to return an object of dimension values.
+ * @function
+ * @param {jQuery || HTML} element - jQuery object or DOM element for which to get the dimensions. Can be any element other that document or window.
+ * @returns {Object} - nested object of integer pixel values
+ * TODO - if element is window, return only those values.
+ */
+ function GetDimensions(elem, test) {
+ elem = elem.length ? elem[0] : elem;
+
+ if (elem === window || elem === document) {
+ throw new Error("I'm sorry, Dave. I'm afraid I can't do that.");
+ }
+
+ var rect = elem.getBoundingClientRect(),
+ parRect = elem.parentNode.getBoundingClientRect(),
+ winRect = document.body.getBoundingClientRect(),
+ winY = window.pageYOffset,
+ winX = window.pageXOffset;
+
+ return {
+ width: rect.width,
+ height: rect.height,
+ offset: {
+ top: rect.top + winY,
+ left: rect.left + winX
+ },
+ parentDims: {
+ width: parRect.width,
+ height: parRect.height,
+ offset: {
+ top: parRect.top + winY,
+ left: parRect.left + winX
+ }
+ },
+ windowDims: {
+ width: winRect.width,
+ height: winRect.height,
+ offset: {
+ top: winY,
+ left: winX
+ }
+ }
+ };
+ }
+
+ /**
+ * Returns an object of top and left integer pixel values for dynamically rendered elements,
+ * such as: Tooltip, Reveal, and Dropdown
+ * @function
+ * @param {jQuery} element - jQuery object for the element being positioned.
+ * @param {jQuery} anchor - jQuery object for the element's anchor point.
+ * @param {String} position - a string relating to the desired position of the element, relative to it's anchor
+ * @param {Number} vOffset - integer pixel value of desired vertical separation between anchor and element.
+ * @param {Number} hOffset - integer pixel value of desired horizontal separation between anchor and element.
+ * @param {Boolean} isOverflow - if a collision event is detected, sets to true to default the element to full width - any desired offset.
+ * TODO alter/rewrite to work with `em` values as well/instead of pixels
+ */
+ function GetOffsets(element, anchor, position, vOffset, hOffset, isOverflow) {
+ var $eleDims = GetDimensions(element),
+ $anchorDims = anchor ? GetDimensions(anchor) : null;
+
+ switch (position) {
+ case 'top':
+ return {
+ left: Foundation.rtl() ? $anchorDims.offset.left - $eleDims.width + $anchorDims.width : $anchorDims.offset.left,
+ top: $anchorDims.offset.top - ($eleDims.height + vOffset)
+ };
+ break;
+ case 'left':
+ return {
+ left: $anchorDims.offset.left - ($eleDims.width + hOffset),
+ top: $anchorDims.offset.top
+ };
+ break;
+ case 'right':
+ return {
+ left: $anchorDims.offset.left + $anchorDims.width + hOffset,
+ top: $anchorDims.offset.top
+ };
+ break;
+ case 'center top':
+ return {
+ left: $anchorDims.offset.left + $anchorDims.width / 2 - $eleDims.width / 2,
+ top: $anchorDims.offset.top - ($eleDims.height + vOffset)
+ };
+ break;
+ case 'center bottom':
+ return {
+ left: isOverflow ? hOffset : $anchorDims.offset.left + $anchorDims.width / 2 - $eleDims.width / 2,
+ top: $anchorDims.offset.top + $anchorDims.height + vOffset
+ };
+ break;
+ case 'center left':
+ return {
+ left: $anchorDims.offset.left - ($eleDims.width + hOffset),
+ top: $anchorDims.offset.top + $anchorDims.height / 2 - $eleDims.height / 2
+ };
+ break;
+ case 'center right':
+ return {
+ left: $anchorDims.offset.left + $anchorDims.width + hOffset + 1,
+ top: $anchorDims.offset.top + $anchorDims.height / 2 - $eleDims.height / 2
+ };
+ break;
+ case 'center':
+ return {
+ left: $eleDims.windowDims.offset.left + $eleDims.windowDims.width / 2 - $eleDims.width / 2,
+ top: $eleDims.windowDims.offset.top + $eleDims.windowDims.height / 2 - $eleDims.height / 2
+ };
+ break;
+ case 'reveal':
+ return {
+ left: ($eleDims.windowDims.width - $eleDims.width) / 2,
+ top: $eleDims.windowDims.offset.top + vOffset
+ };
+ case 'reveal full':
+ return {
+ left: $eleDims.windowDims.offset.left,
+ top: $eleDims.windowDims.offset.top
+ };
+ break;
+ case 'left bottom':
+ return {
+ left: $anchorDims.offset.left - ($eleDims.width + hOffset),
+ top: $anchorDims.offset.top + $anchorDims.height
+ };
+ break;
+ case 'right bottom':
+ return {
+ left: $anchorDims.offset.left + $anchorDims.width + hOffset - $eleDims.width,
+ top: $anchorDims.offset.top + $anchorDims.height
+ };
+ break;
+ default:
+ return {
+ left: Foundation.rtl() ? $anchorDims.offset.left - $eleDims.width + $anchorDims.width : $anchorDims.offset.left,
+ top: $anchorDims.offset.top + $anchorDims.height + vOffset
+ };
+ }
+ }
+}(jQuery);
+'use strict';
+
+!function ($) {
+
+ var MutationObserver = function () {
+ var prefixes = ['WebKit', 'Moz', 'O', 'Ms', ''];
+ for (var i = 0; i < prefixes.length; i++) {
+ if (prefixes[i] + 'MutationObserver' in window) {
+ return window[prefixes[i] + 'MutationObserver'];
+ }
+ }
+ return false;
+ }();
+
+ var triggers = function (el, type) {
+ el.data(type).split(' ').forEach(function (id) {
+ $('#' + id)[type === 'close' ? 'trigger' : 'triggerHandler'](type + '.zf.trigger', [el]);
+ });
+ };
+ // Elements with [data-open] will reveal a plugin that supports it when clicked.
+ $(document).on('click.zf.trigger', '[data-open]', function () {
+ triggers($(this), 'open');
+ });
+
+ // Elements with [data-close] will close a plugin that supports it when clicked.
+ // If used without a value on [data-close], the event will bubble, allowing it to close a parent component.
+ $(document).on('click.zf.trigger', '[data-close]', function () {
+ var id = $(this).data('close');
+ if (id) {
+ triggers($(this), 'close');
+ } else {
+ $(this).trigger('close.zf.trigger');
+ }
+ });
+
+ // Elements with [data-toggle] will toggle a plugin that supports it when clicked.
+ $(document).on('click.zf.trigger', '[data-toggle]', function () {
+ triggers($(this), 'toggle');
+ });
+
+ // Elements with [data-closable] will respond to close.zf.trigger events.
+ $(document).on('close.zf.trigger', '[data-closable]', function (e) {
+ e.stopPropagation();
+ var animation = $(this).data('closable');
+
+ if (animation !== '') {
+ Foundation.Motion.animateOut($(this), animation, function () {
+ $(this).trigger('closed.zf');
+ });
+ } else {
+ $(this).fadeOut().trigger('closed.zf');
+ }
+ });
+
+ $(document).on('focus.zf.trigger blur.zf.trigger', '[data-toggle-focus]', function () {
+ var id = $(this).data('toggle-focus');
+ $('#' + id).triggerHandler('toggle.zf.trigger', [$(this)]);
+ });
+
+ /**
+ * Fires once after all other scripts have loaded
+ * @function
+ * @private
+ */
+ $(window).load(function () {
+ checkListeners();
+ });
+
+ function checkListeners() {
+ eventsListener();
+ resizeListener();
+ scrollListener();
+ closemeListener();
+ }
+
+ //******** only fires this function once on load, if there's something to watch ********
+ function closemeListener(pluginName) {
+ var yetiBoxes = $('[data-yeti-box]'),
+ plugNames = ['dropdown', 'tooltip', 'reveal'];
+
+ if (pluginName) {
+ if (typeof pluginName === 'string') {
+ plugNames.push(pluginName);
+ } else if (typeof pluginName === 'object' && typeof pluginName[0] === 'string') {
+ plugNames.concat(pluginName);
+ } else {
+ console.error('Plugin names must be strings');
+ }
+ }
+ if (yetiBoxes.length) {
+ var listeners = plugNames.map(function (name) {
+ return 'closeme.zf.' + name;
+ }).join(' ');
+
+ $(window).off(listeners).on(listeners, function (e, pluginId) {
+ var plugin = e.namespace.split('.')[0];
+ var plugins = $('[data-' + plugin + ']').not('[data-yeti-box="' + pluginId + '"]');
+
+ plugins.each(function () {
+ var _this = $(this);
+
+ _this.triggerHandler('close.zf.trigger', [_this]);
+ });
+ });
+ }
+ }
+
+ function resizeListener(debounce) {
+ var timer = void 0,
+ $nodes = $('[data-resize]');
+ if ($nodes.length) {
+ $(window).off('resize.zf.trigger').on('resize.zf.trigger', function (e) {
+ if (timer) {
+ clearTimeout(timer);
+ }
+
+ timer = setTimeout(function () {
+
+ if (!MutationObserver) {
+ //fallback for IE 9
+ $nodes.each(function () {
+ $(this).triggerHandler('resizeme.zf.trigger');
+ });
+ }
+ //trigger all listening elements and signal a resize event
+ $nodes.attr('data-events', "resize");
+ }, debounce || 10); //default time to emit resize event
+ });
+ }
+ }
+
+ function scrollListener(debounce) {
+ var timer = void 0,
+ $nodes = $('[data-scroll]');
+ if ($nodes.length) {
+ $(window).off('scroll.zf.trigger').on('scroll.zf.trigger', function (e) {
+ if (timer) {
+ clearTimeout(timer);
+ }
+
+ timer = setTimeout(function () {
+
+ if (!MutationObserver) {
+ //fallback for IE 9
+ $nodes.each(function () {
+ $(this).triggerHandler('scrollme.zf.trigger');
+ });
+ }
+ //trigger all listening elements and signal a scroll event
+ $nodes.attr('data-events', "scroll");
+ }, debounce || 10); //default time to emit scroll event
+ });
+ }
+ }
+
+ function eventsListener() {
+ if (!MutationObserver) {
+ return false;
+ }
+ var nodes = document.querySelectorAll('[data-resize], [data-scroll], [data-mutate]');
+
+ //element callback
+ var listeningElementsMutation = function (mutationRecordsList) {
+ var $target = $(mutationRecordsList[0].target);
+ //trigger the event handler for the element depending on type
+ switch ($target.attr("data-events")) {
+
+ case "resize":
+ $target.triggerHandler('resizeme.zf.trigger', [$target]);
+ break;
+
+ case "scroll":
+ $target.triggerHandler('scrollme.zf.trigger', [$target, window.pageYOffset]);
+ break;
+
+ // case "mutate" :
+ // console.log('mutate', $target);
+ // $target.triggerHandler('mutate.zf.trigger');
+ //
+ // //make sure we don't get stuck in an infinite loop from sloppy codeing
+ // if ($target.index('[data-mutate]') == $("[data-mutate]").length-1) {
+ // domMutationObserver();
+ // }
+ // break;
+
+ default:
+ return false;
+ //nothing
+ }
+ };
+
+ if (nodes.length) {
+ //for each element that needs to listen for resizing, scrolling, (or coming soon mutation) add a single observer
+ for (var i = 0; i <= nodes.length - 1; i++) {
+ var elementObserver = new MutationObserver(listeningElementsMutation);
+ elementObserver.observe(nodes[i], { attributes: true, childList: false, characterData: false, subtree: false, attributeFilter: ["data-events"] });
+ }
+ }
+ }
+
+ // ------------------------------------
+
+ // [PH]
+ // Foundation.CheckWatchers = checkWatchers;
+ Foundation.IHearYou = checkListeners;
+ // Foundation.ISeeYou = scrollListener;
+ // Foundation.IFeelYou = closemeListener;
+}(jQuery);
+
+// function domMutationObserver(debounce) {
+// // !!! This is coming soon and needs more work; not active !!! //
+// var timer,
+// nodes = document.querySelectorAll('[data-mutate]');
+// //
+// if (nodes.length) {
+// // var MutationObserver = (function () {
+// // var prefixes = ['WebKit', 'Moz', 'O', 'Ms', ''];
+// // for (var i=0; i < prefixes.length; i++) {
+// // if (prefixes[i] + 'MutationObserver' in window) {
+// // return window[prefixes[i] + 'MutationObserver'];
+// // }
+// // }
+// // return false;
+// // }());
+//
+//
+// //for the body, we need to listen for all changes effecting the style and class attributes
+// var bodyObserver = new MutationObserver(bodyMutation);
+// bodyObserver.observe(document.body, { attributes: true, childList: true, characterData: false, subtree:true, attributeFilter:["style", "class"]});
+//
+//
+// //body callback
+// function bodyMutation(mutate) {
+// //trigger all listening elements and signal a mutation event
+// if (timer) { clearTimeout(timer); }
+//
+// timer = setTimeout(function() {
+// bodyObserver.disconnect();
+// $('[data-mutate]').attr('data-events',"mutate");
+// }, debounce || 150);
+// }
+// }
+// }
+'use strict';
+
+!function ($) {
+
+ function Timer(elem, options, cb) {
+ var _this = this,
+ duration = options.duration,
+ //options is an object for easily adding features later.
+ nameSpace = Object.keys(elem.data())[0] || 'timer',
+ remain = -1,
+ start,
+ timer;
+
+ this.isPaused = false;
+
+ this.restart = function () {
+ remain = -1;
+ clearTimeout(timer);
+ this.start();
+ };
+
+ this.start = function () {
+ this.isPaused = false;
+ // if(!elem.data('paused')){ return false; }//maybe implement this sanity check if used for other things.
+ clearTimeout(timer);
+ remain = remain <= 0 ? duration : remain;
+ elem.data('paused', false);
+ start = Date.now();
+ timer = setTimeout(function () {
+ if (options.infinite) {
+ _this.restart(); //rerun the timer.
+ }
+ cb();
+ }, remain);
+ elem.trigger('timerstart.zf.' + nameSpace);
+ };
+
+ this.pause = function () {
+ this.isPaused = true;
+ //if(elem.data('paused')){ return false; }//maybe implement this sanity check if used for other things.
+ clearTimeout(timer);
+ elem.data('paused', true);
+ var end = Date.now();
+ remain = remain - (end - start);
+ elem.trigger('timerpaused.zf.' + nameSpace);
+ };
+ }
+
+ /**
+ * Runs a callback function when images are fully loaded.
+ * @param {Object} images - Image(s) to check if loaded.
+ * @param {Func} callback - Function to execute when image is fully loaded.
+ */
+ function onImagesLoaded(images, callback) {
+ var self = this,
+ unloaded = images.length;
+
+ if (unloaded === 0) {
+ callback();
+ }
+
+ images.each(function () {
+ if (this.complete) {
+ singleImageLoaded();
+ } else if (typeof this.naturalWidth !== 'undefined' && this.naturalWidth > 0) {
+ singleImageLoaded();
+ } else {
+ $(this).one('load', function () {
+ singleImageLoaded();
+ });
+ }
+ });
+
+ function singleImageLoaded() {
+ unloaded--;
+ if (unloaded === 0) {
+ callback();
+ }
+ }
+ }
+
+ Foundation.Timer = Timer;
+ Foundation.onImagesLoaded = onImagesLoaded;
+}(jQuery);
+//**************************************************
+//**Work inspired by multiple jquery swipe plugins**
+//**Done by Yohai Ararat ***************************
+//**************************************************
+(function ($) {
+
+ $.spotSwipe = {
+ version: '1.0.0',
+ enabled: 'ontouchstart' in document.documentElement,
+ preventDefault: false,
+ moveThreshold: 75,
+ timeThreshold: 200
+ };
+
+ var startPosX,
+ startPosY,
+ startTime,
+ elapsedTime,
+ isMoving = false;
+
+ function onTouchEnd() {
+ // alert(this);
+ this.removeEventListener('touchmove', onTouchMove);
+ this.removeEventListener('touchend', onTouchEnd);
+ isMoving = false;
+ }
+
+ function onTouchMove(e) {
+ if ($.spotSwipe.preventDefault) {
+ e.preventDefault();
+ }
+ if (isMoving) {
+ var x = e.touches[0].pageX;
+ var y = e.touches[0].pageY;
+ var dx = startPosX - x;
+ var dy = startPosY - y;
+ var dir;
+ elapsedTime = new Date().getTime() - startTime;
+ if (Math.abs(dx) >= $.spotSwipe.moveThreshold && elapsedTime <= $.spotSwipe.timeThreshold) {
+ dir = dx > 0 ? 'left' : 'right';
+ }
+ // else if(Math.abs(dy) >= $.spotSwipe.moveThreshold && elapsedTime <= $.spotSwipe.timeThreshold) {
+ // dir = dy > 0 ? 'down' : 'up';
+ // }
+ if (dir) {
+ e.preventDefault();
+ onTouchEnd.call(this);
+ $(this).trigger('swipe', dir).trigger('swipe' + dir);
+ }
+ }
+ }
+
+ function onTouchStart(e) {
+ if (e.touches.length == 1) {
+ startPosX = e.touches[0].pageX;
+ startPosY = e.touches[0].pageY;
+ isMoving = true;
+ startTime = new Date().getTime();
+ this.addEventListener('touchmove', onTouchMove, false);
+ this.addEventListener('touchend', onTouchEnd, false);
+ }
+ }
+
+ function init() {
+ this.addEventListener && this.addEventListener('touchstart', onTouchStart, false);
+ }
+
+ function teardown() {
+ this.removeEventListener('touchstart', onTouchStart);
+ }
+
+ $.event.special.swipe = { setup: init };
+
+ $.each(['left', 'up', 'down', 'right'], function () {
+ $.event.special['swipe' + this] = { setup: function () {
+ $(this).on('swipe', $.noop);
+ } };
+ });
+})(jQuery);
+/****************************************************
+ * Method for adding psuedo drag events to elements *
+ ***************************************************/
+!function ($) {
+ $.fn.addTouch = function () {
+ this.each(function (i, el) {
+ $(el).bind('touchstart touchmove touchend touchcancel', function () {
+ //we pass the original event object because the jQuery event
+ //object is normalized to w3c specs and does not provide the TouchList
+ handleTouch(event);
+ });
+ });
+
+ var handleTouch = function (event) {
+ var touches = event.changedTouches,
+ first = touches[0],
+ eventTypes = {
+ touchstart: 'mousedown',
+ touchmove: 'mousemove',
+ touchend: 'mouseup'
+ },
+ type = eventTypes[event.type],
+ simulatedEvent;
+
+ if ('MouseEvent' in window && typeof window.MouseEvent === 'function') {
+ simulatedEvent = new window.MouseEvent(type, {
+ 'bubbles': true,
+ 'cancelable': true,
+ 'screenX': first.screenX,
+ 'screenY': first.screenY,
+ 'clientX': first.clientX,
+ 'clientY': first.clientY
+ });
+ } else {
+ simulatedEvent = document.createEvent('MouseEvent');
+ simulatedEvent.initMouseEvent(type, true, true, window, 1, first.screenX, first.screenY, first.clientX, first.clientY, false, false, false, false, 0 /*left*/, null);
+ }
+ first.target.dispatchEvent(simulatedEvent);
+ };
+ };
+}(jQuery);
+
+//**********************************
+//**From the jQuery Mobile Library**
+//**need to recreate functionality**
+//**and try to improve if possible**
+//**********************************
+
+/* Removing the jQuery function ****
+************************************
+
+(function( $, window, undefined ) {
+
+ var $document = $( document ),
+ // supportTouch = $.mobile.support.touch,
+ touchStartEvent = 'touchstart'//supportTouch ? "touchstart" : "mousedown",
+ touchStopEvent = 'touchend'//supportTouch ? "touchend" : "mouseup",
+ touchMoveEvent = 'touchmove'//supportTouch ? "touchmove" : "mousemove";
+
+ // setup new event shortcuts
+ $.each( ( "touchstart touchmove touchend " +
+ "swipe swipeleft swiperight" ).split( " " ), function( i, name ) {
+
+ $.fn[ name ] = function( fn ) {
+ return fn ? this.bind( name, fn ) : this.trigger( name );
+ };
+
+ // jQuery < 1.8
+ if ( $.attrFn ) {
+ $.attrFn[ name ] = true;
+ }
+ });
+
+ function triggerCustomEvent( obj, eventType, event, bubble ) {
+ var originalType = event.type;
+ event.type = eventType;
+ if ( bubble ) {
+ $.event.trigger( event, undefined, obj );
+ } else {
+ $.event.dispatch.call( obj, event );
+ }
+ event.type = originalType;
+ }
+
+ // also handles taphold
+
+ // Also handles swipeleft, swiperight
+ $.event.special.swipe = {
+
+ // More than this horizontal displacement, and we will suppress scrolling.
+ scrollSupressionThreshold: 30,
+
+ // More time than this, and it isn't a swipe.
+ durationThreshold: 1000,
+
+ // Swipe horizontal displacement must be more than this.
+ horizontalDistanceThreshold: window.devicePixelRatio >= 2 ? 15 : 30,
+
+ // Swipe vertical displacement must be less than this.
+ verticalDistanceThreshold: window.devicePixelRatio >= 2 ? 15 : 30,
+
+ getLocation: function ( event ) {
+ var winPageX = window.pageXOffset,
+ winPageY = window.pageYOffset,
+ x = event.clientX,
+ y = event.clientY;
+
+ if ( event.pageY === 0 && Math.floor( y ) > Math.floor( event.pageY ) ||
+ event.pageX === 0 && Math.floor( x ) > Math.floor( event.pageX ) ) {
+
+ // iOS4 clientX/clientY have the value that should have been
+ // in pageX/pageY. While pageX/page/ have the value 0
+ x = x - winPageX;
+ y = y - winPageY;
+ } else if ( y < ( event.pageY - winPageY) || x < ( event.pageX - winPageX ) ) {
+
+ // Some Android browsers have totally bogus values for clientX/Y
+ // when scrolling/zooming a page. Detectable since clientX/clientY
+ // should never be smaller than pageX/pageY minus page scroll
+ x = event.pageX - winPageX;
+ y = event.pageY - winPageY;
+ }
+
+ return {
+ x: x,
+ y: y
+ };
+ },
+
+ start: function( event ) {
+ var data = event.originalEvent.touches ?
+ event.originalEvent.touches[ 0 ] : event,
+ location = $.event.special.swipe.getLocation( data );
+ return {
+ time: ( new Date() ).getTime(),
+ coords: [ location.x, location.y ],
+ origin: $( event.target )
+ };
+ },
+
+ stop: function( event ) {
+ var data = event.originalEvent.touches ?
+ event.originalEvent.touches[ 0 ] : event,
+ location = $.event.special.swipe.getLocation( data );
+ return {
+ time: ( new Date() ).getTime(),
+ coords: [ location.x, location.y ]
+ };
+ },
+
+ handleSwipe: function( start, stop, thisObject, origTarget ) {
+ if ( stop.time - start.time < $.event.special.swipe.durationThreshold &&
+ Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.horizontalDistanceThreshold &&
+ Math.abs( start.coords[ 1 ] - stop.coords[ 1 ] ) < $.event.special.swipe.verticalDistanceThreshold ) {
+ var direction = start.coords[0] > stop.coords[ 0 ] ? "swipeleft" : "swiperight";
+
+ triggerCustomEvent( thisObject, "swipe", $.Event( "swipe", { target: origTarget, swipestart: start, swipestop: stop }), true );
+ triggerCustomEvent( thisObject, direction,$.Event( direction, { target: origTarget, swipestart: start, swipestop: stop } ), true );
+ return true;
+ }
+ return false;
+
+ },
+
+ // This serves as a flag to ensure that at most one swipe event event is
+ // in work at any given time
+ eventInProgress: false,
+
+ setup: function() {
+ var events,
+ thisObject = this,
+ $this = $( thisObject ),
+ context = {};
+
+ // Retrieve the events data for this element and add the swipe context
+ events = $.data( this, "mobile-events" );
+ if ( !events ) {
+ events = { length: 0 };
+ $.data( this, "mobile-events", events );
+ }
+ events.length++;
+ events.swipe = context;
+
+ context.start = function( event ) {
+
+ // Bail if we're already working on a swipe event
+ if ( $.event.special.swipe.eventInProgress ) {
+ return;
+ }
+ $.event.special.swipe.eventInProgress = true;
+
+ var stop,
+ start = $.event.special.swipe.start( event ),
+ origTarget = event.target,
+ emitted = false;
+
+ context.move = function( event ) {
+ if ( !start || event.isDefaultPrevented() ) {
+ return;
+ }
+
+ stop = $.event.special.swipe.stop( event );
+ if ( !emitted ) {
+ emitted = $.event.special.swipe.handleSwipe( start, stop, thisObject, origTarget );
+ if ( emitted ) {
+
+ // Reset the context to make way for the next swipe event
+ $.event.special.swipe.eventInProgress = false;
+ }
+ }
+ // prevent scrolling
+ if ( Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.scrollSupressionThreshold ) {
+ event.preventDefault();
+ }
+ };
+
+ context.stop = function() {
+ emitted = true;
+
+ // Reset the context to make way for the next swipe event
+ $.event.special.swipe.eventInProgress = false;
+ $document.off( touchMoveEvent, context.move );
+ context.move = null;
+ };
+
+ $document.on( touchMoveEvent, context.move )
+ .one( touchStopEvent, context.stop );
+ };
+ $this.on( touchStartEvent, context.start );
+ },
+
+ teardown: function() {
+ var events, context;
+
+ events = $.data( this, "mobile-events" );
+ if ( events ) {
+ context = events.swipe;
+ delete events.swipe;
+ events.length--;
+ if ( events.length === 0 ) {
+ $.removeData( this, "mobile-events" );
+ }
+ }
+
+ if ( context ) {
+ if ( context.start ) {
+ $( this ).off( touchStartEvent, context.start );
+ }
+ if ( context.move ) {
+ $document.off( touchMoveEvent, context.move );
+ }
+ if ( context.stop ) {
+ $document.off( touchStopEvent, context.stop );
+ }
+ }
+ }
+ };
+ $.each({
+ swipeleft: "swipe.left",
+ swiperight: "swipe.right"
+ }, function( event, sourceEvent ) {
+
+ $.event.special[ event ] = {
+ setup: function() {
+ $( this ).bind( sourceEvent, $.noop );
+ },
+ teardown: function() {
+ $( this ).unbind( sourceEvent );
+ }
+ };
+ });
+})( jQuery, this );
+*/
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+!function ($) {
+
+ /**
+ * Abide module.
+ * @module foundation.abide
+ */
+
+ var Abide = function () {
+ /**
+ * Creates a new instance of Abide.
+ * @class
+ * @fires Abide#init
+ * @param {Object} element - jQuery object to add the trigger to.
+ * @param {Object} options - Overrides to the default plugin settings.
+ */
+
+ function Abide(element) {
+ var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+ _classCallCheck(this, Abide);
+
+ this.$element = element;
+ this.options = $.extend({}, Abide.defaults, this.$element.data(), options);
+
+ this._init();
+
+ Foundation.registerPlugin(this, 'Abide');
+ }
+
+ /**
+ * Initializes the Abide plugin and calls functions to get Abide functioning on load.
+ * @private
+ */
+
+
+ _createClass(Abide, [{
+ key: '_init',
+ value: function _init() {
+ this.$inputs = this.$element.find('input, textarea, select');
+
+ this._events();
+ }
+
+ /**
+ * Initializes events for Abide.
+ * @private
+ */
+
+ }, {
+ key: '_events',
+ value: function _events() {
+ var _this2 = this;
+
+ this.$element.off('.abide').on('reset.zf.abide', function () {
+ _this2.resetForm();
+ }).on('submit.zf.abide', function () {
+ return _this2.validateForm();
+ });
+
+ if (this.options.validateOn === 'fieldChange') {
+ this.$inputs.off('change.zf.abide').on('change.zf.abide', function (e) {
+ _this2.validateInput($(e.target));
+ });
+ }
+
+ if (this.options.liveValidate) {
+ this.$inputs.off('input.zf.abide').on('input.zf.abide', function (e) {
+ _this2.validateInput($(e.target));
+ });
+ }
+ }
+
+ /**
+ * Calls necessary functions to update Abide upon DOM change
+ * @private
+ */
+
+ }, {
+ key: '_reflow',
+ value: function _reflow() {
+ this._init();
+ }
+
+ /**
+ * Checks whether or not a form element has the required attribute and if it's checked or not
+ * @param {Object} element - jQuery object to check for required attribute
+ * @returns {Boolean} Boolean value depends on whether or not attribute is checked or empty
+ */
+
+ }, {
+ key: 'requiredCheck',
+ value: function requiredCheck($el) {
+ if (!$el.attr('required')) return true;
+
+ var isGood = true;
+
+ switch ($el[0].type) {
+ case 'checkbox':
+ isGood = $el[0].checked;
+ break;
+
+ case 'select':
+ case 'select-one':
+ case 'select-multiple':
+ var opt = $el.find('option:selected');
+ if (!opt.length || !opt.val()) isGood = false;
+ break;
+
+ default:
+ if (!$el.val() || !$el.val().length) isGood = false;
+ }
+
+ return isGood;
+ }
+
+ /**
+ * Based on $el, get the first element with selector in this order:
+ * 1. The element's direct sibling('s).
+ * 3. The element's parent's children.
+ *
+ * This allows for multiple form errors per input, though if none are found, no form errors will be shown.
+ *
+ * @param {Object} $el - jQuery object to use as reference to find the form error selector.
+ * @returns {Object} jQuery object with the selector.
+ */
+
+ }, {
+ key: 'findFormError',
+ value: function findFormError($el) {
+ var $error = $el.siblings(this.options.formErrorSelector);
+
+ if (!$error.length) {
+ $error = $el.parent().find(this.options.formErrorSelector);
+ }
+
+ return $error;
+ }
+
+ /**
+ * Get the first element in this order:
+ * 2. The with the attribute `[for="someInputId"]`
+ * 3. The `.closest()`
+ *
+ * @param {Object} $el - jQuery object to check for required attribute
+ * @returns {Boolean} Boolean value depends on whether or not attribute is checked or empty
+ */
+
+ }, {
+ key: 'findLabel',
+ value: function findLabel($el) {
+ var id = $el[0].id;
+ var $label = this.$element.find('label[for="' + id + '"]');
+
+ if (!$label.length) {
+ return $el.closest('label');
+ }
+
+ return $label;
+ }
+
+ /**
+ * Get the set of labels associated with a set of radio els in this order
+ * 2. The with the attribute `[for="someInputId"]`
+ * 3. The `.closest()`
+ *
+ * @param {Object} $el - jQuery object to check for required attribute
+ * @returns {Boolean} Boolean value depends on whether or not attribute is checked or empty
+ */
+
+ }, {
+ key: 'findRadioLabels',
+ value: function findRadioLabels($els) {
+ var _this3 = this;
+
+ var labels = $els.map(function (i, el) {
+ var id = el.id;
+ var $label = _this3.$element.find('label[for="' + id + '"]');
+
+ if (!$label.length) {
+ $label = $(el).closest('label');
+ }
+ return $label[0];
+ });
+
+ return $(labels);
+ }
+
+ /**
+ * Adds the CSS error class as specified by the Abide settings to the label, input, and the form
+ * @param {Object} $el - jQuery object to add the class to
+ */
+
+ }, {
+ key: 'addErrorClasses',
+ value: function addErrorClasses($el) {
+ var $label = this.findLabel($el);
+ var $formError = this.findFormError($el);
+
+ if ($label.length) {
+ $label.addClass(this.options.labelErrorClass);
+ }
+
+ if ($formError.length) {
+ $formError.addClass(this.options.formErrorClass);
+ }
+
+ $el.addClass(this.options.inputErrorClass).attr('data-invalid', '');
+ }
+
+ /**
+ * Remove CSS error classes etc from an entire radio button group
+ * @param {String} groupName - A string that specifies the name of a radio button group
+ *
+ */
+
+ }, {
+ key: 'removeRadioErrorClasses',
+ value: function removeRadioErrorClasses(groupName) {
+ var $els = this.$element.find(':radio[name="' + groupName + '"]');
+ var $labels = this.findRadioLabels($els);
+ var $formErrors = this.findFormError($els);
+
+ if ($labels.length) {
+ $labels.removeClass(this.options.labelErrorClass);
+ }
+
+ if ($formErrors.length) {
+ $formErrors.removeClass(this.options.formErrorClass);
+ }
+
+ $els.removeClass(this.options.inputErrorClass).removeAttr('data-invalid');
+ }
+
+ /**
+ * Removes CSS error class as specified by the Abide settings from the label, input, and the form
+ * @param {Object} $el - jQuery object to remove the class from
+ */
+
+ }, {
+ key: 'removeErrorClasses',
+ value: function removeErrorClasses($el) {
+ // radios need to clear all of the els
+ if ($el[0].type == 'radio') {
+ return this.removeRadioErrorClasses($el.attr('name'));
+ }
+
+ var $label = this.findLabel($el);
+ var $formError = this.findFormError($el);
+
+ if ($label.length) {
+ $label.removeClass(this.options.labelErrorClass);
+ }
+
+ if ($formError.length) {
+ $formError.removeClass(this.options.formErrorClass);
+ }
+
+ $el.removeClass(this.options.inputErrorClass).removeAttr('data-invalid');
+ }
+
+ /**
+ * Goes through a form to find inputs and proceeds to validate them in ways specific to their type
+ * @fires Abide#invalid
+ * @fires Abide#valid
+ * @param {Object} element - jQuery object to validate, should be an HTML input
+ * @returns {Boolean} goodToGo - If the input is valid or not.
+ */
+
+ }, {
+ key: 'validateInput',
+ value: function validateInput($el) {
+ var clearRequire = this.requiredCheck($el),
+ validated = false,
+ customValidator = true,
+ validator = $el.attr('data-validator'),
+ equalTo = true;
+
+ // don't validate ignored inputs or hidden inputs
+ if ($el.is('[data-abide-ignore]') || $el.is('[type="hidden"]')) {
+ return true;
+ }
+
+ switch ($el[0].type) {
+ case 'radio':
+ validated = this.validateRadio($el.attr('name'));
+ break;
+
+ case 'checkbox':
+ validated = clearRequire;
+ break;
+
+ case 'select':
+ case 'select-one':
+ case 'select-multiple':
+ validated = clearRequire;
+ break;
+
+ default:
+ validated = this.validateText($el);
+ }
+
+ if (validator) {
+ customValidator = this.matchValidation($el, validator, $el.attr('required'));
+ }
+
+ if ($el.attr('data-equalto')) {
+ equalTo = this.options.validators.equalTo($el);
+ }
+
+ var goodToGo = [clearRequire, validated, customValidator, equalTo].indexOf(false) === -1;
+ var message = (goodToGo ? 'valid' : 'invalid') + '.zf.abide';
+
+ this[goodToGo ? 'removeErrorClasses' : 'addErrorClasses']($el);
+
+ /**
+ * Fires when the input is done checking for validation. Event trigger is either `valid.zf.abide` or `invalid.zf.abide`
+ * Trigger includes the DOM element of the input.
+ * @event Abide#valid
+ * @event Abide#invalid
+ */
+ $el.trigger(message, [$el]);
+
+ return goodToGo;
+ }
+
+ /**
+ * Goes through a form and if there are any invalid inputs, it will display the form error element
+ * @returns {Boolean} noError - true if no errors were detected...
+ * @fires Abide#formvalid
+ * @fires Abide#forminvalid
+ */
+
+ }, {
+ key: 'validateForm',
+ value: function validateForm() {
+ var acc = [];
+ var _this = this;
+
+ this.$inputs.each(function () {
+ acc.push(_this.validateInput($(this)));
+ });
+
+ var noError = acc.indexOf(false) === -1;
+
+ this.$element.find('[data-abide-error]').css('display', noError ? 'none' : 'block');
+
+ /**
+ * Fires when the form is finished validating. Event trigger is either `formvalid.zf.abide` or `forminvalid.zf.abide`.
+ * Trigger includes the element of the form.
+ * @event Abide#formvalid
+ * @event Abide#forminvalid
+ */
+ this.$element.trigger((noError ? 'formvalid' : 'forminvalid') + '.zf.abide', [this.$element]);
+
+ return noError;
+ }
+
+ /**
+ * Determines whether or a not a text input is valid based on the pattern specified in the attribute. If no matching pattern is found, returns true.
+ * @param {Object} $el - jQuery object to validate, should be a text input HTML element
+ * @param {String} pattern - string value of one of the RegEx patterns in Abide.options.patterns
+ * @returns {Boolean} Boolean value depends on whether or not the input value matches the pattern specified
+ */
+
+ }, {
+ key: 'validateText',
+ value: function validateText($el, pattern) {
+ // A pattern can be passed to this function, or it will be infered from the input's "pattern" attribute, or it's "type" attribute
+ pattern = pattern || $el.attr('pattern') || $el.attr('type');
+ var inputText = $el.val();
+ var valid = false;
+
+ if (inputText.length) {
+ // If the pattern attribute on the element is in Abide's list of patterns, then test that regexp
+ if (this.options.patterns.hasOwnProperty(pattern)) {
+ valid = this.options.patterns[pattern].test(inputText);
+ }
+ // If the pattern name isn't also the type attribute of the field, then test it as a regexp
+ else if (pattern !== $el.attr('type')) {
+ valid = new RegExp(pattern).test(inputText);
+ } else {
+ valid = true;
+ }
+ }
+ // An empty field is valid if it's not required
+ else if (!$el.prop('required')) {
+ valid = true;
+ }
+
+ return valid;
+ }
+
+ /**
+ * Determines whether or a not a radio input is valid based on whether or not it is required and selected. Although the function targets a single ` `, it validates by checking the `required` and `checked` properties of all radio buttons in its group.
+ * @param {String} groupName - A string that specifies the name of a radio button group
+ * @returns {Boolean} Boolean value depends on whether or not at least one radio input has been selected (if it's required)
+ */
+
+ }, {
+ key: 'validateRadio',
+ value: function validateRadio(groupName) {
+ // If at least one radio in the group has the `required` attribute, the group is considered required
+ // Per W3C spec, all radio buttons in a group should have `required`, but we're being nice
+ var $group = this.$element.find(':radio[name="' + groupName + '"]');
+ var valid = false,
+ required = false;
+
+ // For the group to be required, at least one radio needs to be required
+ $group.each(function (i, e) {
+ if ($(e).attr('required')) {
+ required = true;
+ }
+ });
+ if (!required) valid = true;
+
+ if (!valid) {
+ // For the group to be valid, at least one radio needs to be checked
+ $group.each(function (i, e) {
+ if ($(e).prop('checked')) {
+ valid = true;
+ }
+ });
+ };
+
+ return valid;
+ }
+
+ /**
+ * Determines if a selected input passes a custom validation function. Multiple validations can be used, if passed to the element with `data-validator="foo bar baz"` in a space separated listed.
+ * @param {Object} $el - jQuery input element.
+ * @param {String} validators - a string of function names matching functions in the Abide.options.validators object.
+ * @param {Boolean} required - self explanatory?
+ * @returns {Boolean} - true if validations passed.
+ */
+
+ }, {
+ key: 'matchValidation',
+ value: function matchValidation($el, validators, required) {
+ var _this4 = this;
+
+ required = required ? true : false;
+
+ var clear = validators.split(' ').map(function (v) {
+ return _this4.options.validators[v]($el, required, $el.parent());
+ });
+ return clear.indexOf(false) === -1;
+ }
+
+ /**
+ * Resets form inputs and styles
+ * @fires Abide#formreset
+ */
+
+ }, {
+ key: 'resetForm',
+ value: function resetForm() {
+ var $form = this.$element,
+ opts = this.options;
+
+ $('.' + opts.labelErrorClass, $form).not('small').removeClass(opts.labelErrorClass);
+ $('.' + opts.inputErrorClass, $form).not('small').removeClass(opts.inputErrorClass);
+ $(opts.formErrorSelector + '.' + opts.formErrorClass).removeClass(opts.formErrorClass);
+ $form.find('[data-abide-error]').css('display', 'none');
+ $(':input', $form).not(':button, :submit, :reset, :hidden, :radio, :checkbox, [data-abide-ignore]').val('').removeAttr('data-invalid');
+ $(':input:radio', $form).not('[data-abide-ignore]').prop('checked', false).removeAttr('data-invalid');
+ $(':input:checkbox', $form).not('[data-abide-ignore]').prop('checked', false).removeAttr('data-invalid');
+ /**
+ * Fires when the form has been reset.
+ * @event Abide#formreset
+ */
+ $form.trigger('formreset.zf.abide', [$form]);
+ }
+
+ /**
+ * Destroys an instance of Abide.
+ * Removes error styles and classes from elements, without resetting their values.
+ */
+
+ }, {
+ key: 'destroy',
+ value: function destroy() {
+ var _this = this;
+ this.$element.off('.abide').find('[data-abide-error]').css('display', 'none');
+
+ this.$inputs.off('.abide').each(function () {
+ _this.removeErrorClasses($(this));
+ });
+
+ Foundation.unregisterPlugin(this);
+ }
+ }]);
+
+ return Abide;
+ }();
+
+ /**
+ * Default settings for plugin
+ */
+
+
+ Abide.defaults = {
+ /**
+ * The default event to validate inputs. Checkboxes and radios validate immediately.
+ * Remove or change this value for manual validation.
+ * @option
+ * @example 'fieldChange'
+ */
+ validateOn: 'fieldChange',
+
+ /**
+ * Class to be applied to input labels on failed validation.
+ * @option
+ * @example 'is-invalid-label'
+ */
+ labelErrorClass: 'is-invalid-label',
+
+ /**
+ * Class to be applied to inputs on failed validation.
+ * @option
+ * @example 'is-invalid-input'
+ */
+ inputErrorClass: 'is-invalid-input',
+
+ /**
+ * Class selector to use to target Form Errors for show/hide.
+ * @option
+ * @example '.form-error'
+ */
+ formErrorSelector: '.form-error',
+
+ /**
+ * Class added to Form Errors on failed validation.
+ * @option
+ * @example 'is-visible'
+ */
+ formErrorClass: 'is-visible',
+
+ /**
+ * Set to true to validate text inputs on any value change.
+ * @option
+ * @example false
+ */
+ liveValidate: false,
+
+ patterns: {
+ alpha: /^[a-zA-Z]+$/,
+ alpha_numeric: /^[a-zA-Z0-9]+$/,
+ integer: /^[-+]?\d+$/,
+ number: /^[-+]?\d*(?:[\.\,]\d+)?$/,
+
+ // amex, visa, diners
+ card: /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$/,
+ cvv: /^([0-9]){3,4}$/,
+
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/states-of-the-type-attribute.html#valid-e-mail-address
+ email: /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/,
+
+ url: /^(https?|ftp|file|ssh):\/\/(((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/,
+ // abc.de
+ domain: /^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,8}$/,
+
+ datetime: /^([0-2][0-9]{3})\-([0-1][0-9])\-([0-3][0-9])T([0-5][0-9])\:([0-5][0-9])\:([0-5][0-9])(Z|([\-\+]([0-1][0-9])\:00))$/,
+ // YYYY-MM-DD
+ date: /(?:19|20)[0-9]{2}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-9])|(?:(?!02)(?:0[1-9]|1[0-2])-(?:30))|(?:(?:0[13578]|1[02])-31))$/,
+ // HH:MM:SS
+ time: /^(0[0-9]|1[0-9]|2[0-3])(:[0-5][0-9]){2}$/,
+ dateISO: /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/,
+ // MM/DD/YYYY
+ month_day_year: /^(0[1-9]|1[012])[- \/.](0[1-9]|[12][0-9]|3[01])[- \/.]\d{4}$/,
+ // DD/MM/YYYY
+ day_month_year: /^(0[1-9]|[12][0-9]|3[01])[- \/.](0[1-9]|1[012])[- \/.]\d{4}$/,
+
+ // #FFF or #FFFFFF
+ color: /^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/
+ },
+
+ /**
+ * Optional validation functions to be used. `equalTo` being the only default included function.
+ * Functions should return only a boolean if the input is valid or not. Functions are given the following arguments:
+ * el : The jQuery element to validate.
+ * required : Boolean value of the required attribute be present or not.
+ * parent : The direct parent of the input.
+ * @option
+ */
+ validators: {
+ equalTo: function (el, required, parent) {
+ return $('#' + el.attr('data-equalto')).val() === el.val();
+ }
+ }
+ };
+
+ // Window exports
+ Foundation.plugin(Abide, 'Abide');
+}(jQuery);
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+!function ($) {
+
+ /**
+ * Accordion module.
+ * @module foundation.accordion
+ * @requires foundation.util.keyboard
+ * @requires foundation.util.motion
+ */
+
+ var Accordion = function () {
+ /**
+ * Creates a new instance of an accordion.
+ * @class
+ * @fires Accordion#init
+ * @param {jQuery} element - jQuery object to make into an accordion.
+ * @param {Object} options - a plain object with settings to override the default options.
+ */
+
+ function Accordion(element, options) {
+ _classCallCheck(this, Accordion);
+
+ this.$element = element;
+ this.options = $.extend({}, Accordion.defaults, this.$element.data(), options);
+
+ this._init();
+
+ Foundation.registerPlugin(this, 'Accordion');
+ Foundation.Keyboard.register('Accordion', {
+ 'ENTER': 'toggle',
+ 'SPACE': 'toggle',
+ 'ARROW_DOWN': 'next',
+ 'ARROW_UP': 'previous'
+ });
+ }
+
+ /**
+ * Initializes the accordion by animating the preset active pane(s).
+ * @private
+ */
+
+
+ _createClass(Accordion, [{
+ key: '_init',
+ value: function _init() {
+ this.$element.attr('role', 'tablist');
+ this.$tabs = this.$element.children('li, [data-accordion-item]');
+
+ this.$tabs.each(function (idx, el) {
+ var $el = $(el),
+ $content = $el.children('[data-tab-content]'),
+ id = $content[0].id || Foundation.GetYoDigits(6, 'accordion'),
+ linkId = el.id || id + '-label';
+
+ $el.find('a:first').attr({
+ 'aria-controls': id,
+ 'role': 'tab',
+ 'id': linkId,
+ 'aria-expanded': false,
+ 'aria-selected': false
+ });
+
+ $content.attr({ 'role': 'tabpanel', 'aria-labelledby': linkId, 'aria-hidden': true, 'id': id });
+ });
+ var $initActive = this.$element.find('.is-active').children('[data-tab-content]');
+ if ($initActive.length) {
+ this.down($initActive, true);
+ }
+ this._events();
+ }
+
+ /**
+ * Adds event handlers for items within the accordion.
+ * @private
+ */
+
+ }, {
+ key: '_events',
+ value: function _events() {
+ var _this = this;
+
+ this.$tabs.each(function () {
+ var $elem = $(this);
+ var $tabContent = $elem.children('[data-tab-content]');
+ if ($tabContent.length) {
+ $elem.children('a').off('click.zf.accordion keydown.zf.accordion').on('click.zf.accordion', function (e) {
+ // $(this).children('a').on('click.zf.accordion', function(e) {
+ e.preventDefault();
+ if ($elem.hasClass('is-active')) {
+ if (_this.options.allowAllClosed || $elem.siblings().hasClass('is-active')) {
+ _this.up($tabContent);
+ }
+ } else {
+ _this.down($tabContent);
+ }
+ }).on('keydown.zf.accordion', function (e) {
+ Foundation.Keyboard.handleKey(e, 'Accordion', {
+ toggle: function () {
+ _this.toggle($tabContent);
+ },
+ next: function () {
+ var $a = $elem.next().find('a').focus();
+ if (!_this.options.multiExpand) {
+ $a.trigger('click.zf.accordion');
+ }
+ },
+ previous: function () {
+ var $a = $elem.prev().find('a').focus();
+ if (!_this.options.multiExpand) {
+ $a.trigger('click.zf.accordion');
+ }
+ },
+ handled: function () {
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ });
+ });
+ }
+ });
+ }
+
+ /**
+ * Toggles the selected content pane's open/close state.
+ * @param {jQuery} $target - jQuery object of the pane to toggle.
+ * @function
+ */
+
+ }, {
+ key: 'toggle',
+ value: function toggle($target) {
+ if ($target.parent().hasClass('is-active')) {
+ if (this.options.allowAllClosed || $target.parent().siblings().hasClass('is-active')) {
+ this.up($target);
+ } else {
+ return;
+ }
+ } else {
+ this.down($target);
+ }
+ }
+
+ /**
+ * Opens the accordion tab defined by `$target`.
+ * @param {jQuery} $target - Accordion pane to open.
+ * @param {Boolean} firstTime - flag to determine if reflow should happen.
+ * @fires Accordion#down
+ * @function
+ */
+
+ }, {
+ key: 'down',
+ value: function down($target, firstTime) {
+ var _this2 = this;
+
+ if (!this.options.multiExpand && !firstTime) {
+ var $currentActive = this.$element.children('.is-active').children('[data-tab-content]');
+ if ($currentActive.length) {
+ this.up($currentActive);
+ }
+ }
+
+ $target.attr('aria-hidden', false).parent('[data-tab-content]').addBack().parent().addClass('is-active');
+
+ $target.slideDown(this.options.slideSpeed, function () {
+ /**
+ * Fires when the tab is done opening.
+ * @event Accordion#down
+ */
+ _this2.$element.trigger('down.zf.accordion', [$target]);
+ });
+
+ $('#' + $target.attr('aria-labelledby')).attr({
+ 'aria-expanded': true,
+ 'aria-selected': true
+ });
+ }
+
+ /**
+ * Closes the tab defined by `$target`.
+ * @param {jQuery} $target - Accordion tab to close.
+ * @fires Accordion#up
+ * @function
+ */
+
+ }, {
+ key: 'up',
+ value: function up($target) {
+ var $aunts = $target.parent().siblings(),
+ _this = this;
+ var canClose = this.options.multiExpand ? $aunts.hasClass('is-active') : $target.parent().hasClass('is-active');
+
+ if (!this.options.allowAllClosed && !canClose) {
+ return;
+ }
+
+ // Foundation.Move(this.options.slideSpeed, $target, function(){
+ $target.slideUp(_this.options.slideSpeed, function () {
+ /**
+ * Fires when the tab is done collapsing up.
+ * @event Accordion#up
+ */
+ _this.$element.trigger('up.zf.accordion', [$target]);
+ });
+ // });
+
+ $target.attr('aria-hidden', true).parent().removeClass('is-active');
+
+ $('#' + $target.attr('aria-labelledby')).attr({
+ 'aria-expanded': false,
+ 'aria-selected': false
+ });
+ }
+
+ /**
+ * Destroys an instance of an accordion.
+ * @fires Accordion#destroyed
+ * @function
+ */
+
+ }, {
+ key: 'destroy',
+ value: function destroy() {
+ this.$element.find('[data-tab-content]').stop(true).slideUp(0).css('display', '');
+ this.$element.find('a').off('.zf.accordion');
+
+ Foundation.unregisterPlugin(this);
+ }
+ }]);
+
+ return Accordion;
+ }();
+
+ Accordion.defaults = {
+ /**
+ * Amount of time to animate the opening of an accordion pane.
+ * @option
+ * @example 250
+ */
+ slideSpeed: 250,
+ /**
+ * Allow the accordion to have multiple open panes.
+ * @option
+ * @example false
+ */
+ multiExpand: false,
+ /**
+ * Allow the accordion to close all panes.
+ * @option
+ * @example false
+ */
+ allowAllClosed: false
+ };
+
+ // Window exports
+ Foundation.plugin(Accordion, 'Accordion');
+}(jQuery);
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+!function ($) {
+
+ /**
+ * AccordionMenu module.
+ * @module foundation.accordionMenu
+ * @requires foundation.util.keyboard
+ * @requires foundation.util.motion
+ * @requires foundation.util.nest
+ */
+
+ var AccordionMenu = function () {
+ /**
+ * Creates a new instance of an accordion menu.
+ * @class
+ * @fires AccordionMenu#init
+ * @param {jQuery} element - jQuery object to make into an accordion menu.
+ * @param {Object} options - Overrides to the default plugin settings.
+ */
+
+ function AccordionMenu(element, options) {
+ _classCallCheck(this, AccordionMenu);
+
+ this.$element = element;
+ this.options = $.extend({}, AccordionMenu.defaults, this.$element.data(), options);
+
+ Foundation.Nest.Feather(this.$element, 'accordion');
+
+ this._init();
+
+ Foundation.registerPlugin(this, 'AccordionMenu');
+ Foundation.Keyboard.register('AccordionMenu', {
+ 'ENTER': 'toggle',
+ 'SPACE': 'toggle',
+ 'ARROW_RIGHT': 'open',
+ 'ARROW_UP': 'up',
+ 'ARROW_DOWN': 'down',
+ 'ARROW_LEFT': 'close',
+ 'ESCAPE': 'closeAll',
+ 'TAB': 'down',
+ 'SHIFT_TAB': 'up'
+ });
+ }
+
+ /**
+ * Initializes the accordion menu by hiding all nested menus.
+ * @private
+ */
+
+
+ _createClass(AccordionMenu, [{
+ key: '_init',
+ value: function _init() {
+ this.$element.find('[data-submenu]').not('.is-active').slideUp(0); //.find('a').css('padding-left', '1rem');
+ this.$element.attr({
+ 'role': 'tablist',
+ 'aria-multiselectable': this.options.multiOpen
+ });
+
+ this.$menuLinks = this.$element.find('.is-accordion-submenu-parent');
+ this.$menuLinks.each(function () {
+ var linkId = this.id || Foundation.GetYoDigits(6, 'acc-menu-link'),
+ $elem = $(this),
+ $sub = $elem.children('[data-submenu]'),
+ subId = $sub[0].id || Foundation.GetYoDigits(6, 'acc-menu'),
+ isActive = $sub.hasClass('is-active');
+ $elem.attr({
+ 'aria-controls': subId,
+ 'aria-expanded': isActive,
+ 'role': 'tab',
+ 'id': linkId
+ });
+ $sub.attr({
+ 'aria-labelledby': linkId,
+ 'aria-hidden': !isActive,
+ 'role': 'tabpanel',
+ 'id': subId
+ });
+ });
+ var initPanes = this.$element.find('.is-active');
+ if (initPanes.length) {
+ var _this = this;
+ initPanes.each(function () {
+ _this.down($(this));
+ });
+ }
+ this._events();
+ }
+
+ /**
+ * Adds event handlers for items within the menu.
+ * @private
+ */
+
+ }, {
+ key: '_events',
+ value: function _events() {
+ var _this = this;
+
+ this.$element.find('li').each(function () {
+ var $submenu = $(this).children('[data-submenu]');
+
+ if ($submenu.length) {
+ $(this).children('a').off('click.zf.accordionMenu').on('click.zf.accordionMenu', function (e) {
+ e.preventDefault();
+
+ _this.toggle($submenu);
+ });
+ }
+ }).on('keydown.zf.accordionmenu', function (e) {
+ var $element = $(this),
+ $elements = $element.parent('ul').children('li'),
+ $prevElement,
+ $nextElement,
+ $target = $element.children('[data-submenu]');
+
+ $elements.each(function (i) {
+ if ($(this).is($element)) {
+ $prevElement = $elements.eq(Math.max(0, i - 1)).find('a').first();
+ $nextElement = $elements.eq(Math.min(i + 1, $elements.length - 1)).find('a').first();
+
+ if ($(this).children('[data-submenu]:visible').length) {
+ // has open sub menu
+ $nextElement = $element.find('li:first-child').find('a').first();
+ }
+ if ($(this).is(':first-child')) {
+ // is first element of sub menu
+ $prevElement = $element.parents('li').first().find('a').first();
+ } else if ($prevElement.children('[data-submenu]:visible').length) {
+ // if previous element has open sub menu
+ $prevElement = $prevElement.find('li:last-child').find('a').first();
+ }
+ if ($(this).is(':last-child')) {
+ // is last element of sub menu
+ $nextElement = $element.parents('li').first().next('li').find('a').first();
+ }
+
+ return;
+ }
+ });
+ Foundation.Keyboard.handleKey(e, 'AccordionMenu', {
+ open: function () {
+ if ($target.is(':hidden')) {
+ _this.down($target);
+ $target.find('li').first().find('a').first().focus();
+ }
+ },
+ close: function () {
+ if ($target.length && !$target.is(':hidden')) {
+ // close active sub of this item
+ _this.up($target);
+ } else if ($element.parent('[data-submenu]').length) {
+ // close currently open sub
+ _this.up($element.parent('[data-submenu]'));
+ $element.parents('li').first().find('a').first().focus();
+ }
+ },
+ up: function () {
+ $prevElement.attr('tabindex', -1).focus();
+ return true;
+ },
+ down: function () {
+ $nextElement.attr('tabindex', -1).focus();
+ return true;
+ },
+ toggle: function () {
+ if ($element.children('[data-submenu]').length) {
+ _this.toggle($element.children('[data-submenu]'));
+ }
+ },
+ closeAll: function () {
+ _this.hideAll();
+ },
+ handled: function (preventDefault) {
+ if (preventDefault) {
+ e.preventDefault();
+ }
+ e.stopImmediatePropagation();
+ }
+ });
+ }); //.attr('tabindex', 0);
+ }
+
+ /**
+ * Closes all panes of the menu.
+ * @function
+ */
+
+ }, {
+ key: 'hideAll',
+ value: function hideAll() {
+ this.$element.find('[data-submenu]').slideUp(this.options.slideSpeed);
+ }
+
+ /**
+ * Toggles the open/close state of a submenu.
+ * @function
+ * @param {jQuery} $target - the submenu to toggle
+ */
+
+ }, {
+ key: 'toggle',
+ value: function toggle($target) {
+ if (!$target.is(':animated')) {
+ if (!$target.is(':hidden')) {
+ this.up($target);
+ } else {
+ this.down($target);
+ }
+ }
+ }
+
+ /**
+ * Opens the sub-menu defined by `$target`.
+ * @param {jQuery} $target - Sub-menu to open.
+ * @fires AccordionMenu#down
+ */
+
+ }, {
+ key: 'down',
+ value: function down($target) {
+ var _this = this;
+
+ if (!this.options.multiOpen) {
+ this.up(this.$element.find('.is-active').not($target.parentsUntil(this.$element).add($target)));
+ }
+
+ $target.addClass('is-active').attr({ 'aria-hidden': false }).parent('.is-accordion-submenu-parent').attr({ 'aria-expanded': true });
+
+ //Foundation.Move(this.options.slideSpeed, $target, function() {
+ $target.slideDown(_this.options.slideSpeed, function () {
+ /**
+ * Fires when the menu is done opening.
+ * @event AccordionMenu#down
+ */
+ _this.$element.trigger('down.zf.accordionMenu', [$target]);
+ });
+ //});
+ }
+
+ /**
+ * Closes the sub-menu defined by `$target`. All sub-menus inside the target will be closed as well.
+ * @param {jQuery} $target - Sub-menu to close.
+ * @fires AccordionMenu#up
+ */
+
+ }, {
+ key: 'up',
+ value: function up($target) {
+ var _this = this;
+ //Foundation.Move(this.options.slideSpeed, $target, function(){
+ $target.slideUp(_this.options.slideSpeed, function () {
+ /**
+ * Fires when the menu is done collapsing up.
+ * @event AccordionMenu#up
+ */
+ _this.$element.trigger('up.zf.accordionMenu', [$target]);
+ });
+ //});
+
+ var $menus = $target.find('[data-submenu]').slideUp(0).addBack().attr('aria-hidden', true);
+
+ $menus.parent('.is-accordion-submenu-parent').attr('aria-expanded', false);
+ }
+
+ /**
+ * Destroys an instance of accordion menu.
+ * @fires AccordionMenu#destroyed
+ */
+
+ }, {
+ key: 'destroy',
+ value: function destroy() {
+ this.$element.find('[data-submenu]').slideDown(0).css('display', '');
+ this.$element.find('a').off('click.zf.accordionMenu');
+
+ Foundation.Nest.Burn(this.$element, 'accordion');
+ Foundation.unregisterPlugin(this);
+ }
+ }]);
+
+ return AccordionMenu;
+ }();
+
+ AccordionMenu.defaults = {
+ /**
+ * Amount of time to animate the opening of a submenu in ms.
+ * @option
+ * @example 250
+ */
+ slideSpeed: 250,
+ /**
+ * Allow the menu to have multiple open panes.
+ * @option
+ * @example true
+ */
+ multiOpen: true
+ };
+
+ // Window exports
+ Foundation.plugin(AccordionMenu, 'AccordionMenu');
+}(jQuery);
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+!function ($) {
+
+ /**
+ * Drilldown module.
+ * @module foundation.drilldown
+ * @requires foundation.util.keyboard
+ * @requires foundation.util.motion
+ * @requires foundation.util.nest
+ */
+
+ var Drilldown = function () {
+ /**
+ * Creates a new instance of a drilldown menu.
+ * @class
+ * @param {jQuery} element - jQuery object to make into an accordion menu.
+ * @param {Object} options - Overrides to the default plugin settings.
+ */
+
+ function Drilldown(element, options) {
+ _classCallCheck(this, Drilldown);
+
+ this.$element = element;
+ this.options = $.extend({}, Drilldown.defaults, this.$element.data(), options);
+
+ Foundation.Nest.Feather(this.$element, 'drilldown');
+
+ this._init();
+
+ Foundation.registerPlugin(this, 'Drilldown');
+ Foundation.Keyboard.register('Drilldown', {
+ 'ENTER': 'open',
+ 'SPACE': 'open',
+ 'ARROW_RIGHT': 'next',
+ 'ARROW_UP': 'up',
+ 'ARROW_DOWN': 'down',
+ 'ARROW_LEFT': 'previous',
+ 'ESCAPE': 'close',
+ 'TAB': 'down',
+ 'SHIFT_TAB': 'up'
+ });
+ }
+
+ /**
+ * Initializes the drilldown by creating jQuery collections of elements
+ * @private
+ */
+
+
+ _createClass(Drilldown, [{
+ key: '_init',
+ value: function _init() {
+ this.$submenuAnchors = this.$element.find('li.is-drilldown-submenu-parent').children('a');
+ this.$submenus = this.$submenuAnchors.parent('li').children('[data-submenu]');
+ this.$menuItems = this.$element.find('li').not('.js-drilldown-back').attr('role', 'menuitem').find('a');
+
+ this._prepareMenu();
+
+ this._keyboardEvents();
+ }
+
+ /**
+ * prepares drilldown menu by setting attributes to links and elements
+ * sets a min height to prevent content jumping
+ * wraps the element if not already wrapped
+ * @private
+ * @function
+ */
+
+ }, {
+ key: '_prepareMenu',
+ value: function _prepareMenu() {
+ var _this = this;
+ // if(!this.options.holdOpen){
+ // this._menuLinkEvents();
+ // }
+ this.$submenuAnchors.each(function () {
+ var $link = $(this);
+ var $sub = $link.parent();
+ if (_this.options.parentLink) {
+ $link.clone().prependTo($sub.children('[data-submenu]')).wrap('');
+ }
+ $link.data('savedHref', $link.attr('href')).removeAttr('href');
+ $link.children('[data-submenu]').attr({
+ 'aria-hidden': true,
+ 'tabindex': 0,
+ 'role': 'menu'
+ });
+ _this._events($link);
+ });
+ this.$submenus.each(function () {
+ var $menu = $(this),
+ $back = $menu.find('.js-drilldown-back');
+ if (!$back.length) {
+ $menu.prepend(_this.options.backButton);
+ }
+ _this._back($menu);
+ });
+ if (!this.$element.parent().hasClass('is-drilldown')) {
+ this.$wrapper = $(this.options.wrapper).addClass('is-drilldown');
+ this.$wrapper = this.$element.wrap(this.$wrapper).parent().css(this._getMaxDims());
+ }
+ }
+
+ /**
+ * Adds event handlers to elements in the menu.
+ * @function
+ * @private
+ * @param {jQuery} $elem - the current menu item to add handlers to.
+ */
+
+ }, {
+ key: '_events',
+ value: function _events($elem) {
+ var _this = this;
+
+ $elem.off('click.zf.drilldown').on('click.zf.drilldown', function (e) {
+ if ($(e.target).parentsUntil('ul', 'li').hasClass('is-drilldown-submenu-parent')) {
+ e.stopImmediatePropagation();
+ e.preventDefault();
+ }
+
+ // if(e.target !== e.currentTarget.firstElementChild){
+ // return false;
+ // }
+ _this._show($elem.parent('li'));
+
+ if (_this.options.closeOnClick) {
+ var $body = $('body');
+ $body.off('.zf.drilldown').on('click.zf.drilldown', function (e) {
+ if (e.target === _this.$element[0] || $.contains(_this.$element[0], e.target)) {
+ return;
+ }
+ e.preventDefault();
+ _this._hideAll();
+ $body.off('.zf.drilldown');
+ });
+ }
+ });
+ }
+
+ /**
+ * Adds keydown event listener to `li`'s in the menu.
+ * @private
+ */
+
+ }, {
+ key: '_keyboardEvents',
+ value: function _keyboardEvents() {
+ var _this = this;
+
+ this.$menuItems.add(this.$element.find('.js-drilldown-back > a')).on('keydown.zf.drilldown', function (e) {
+
+ var $element = $(this),
+ $elements = $element.parent('li').parent('ul').children('li').children('a'),
+ $prevElement,
+ $nextElement;
+
+ $elements.each(function (i) {
+ if ($(this).is($element)) {
+ $prevElement = $elements.eq(Math.max(0, i - 1));
+ $nextElement = $elements.eq(Math.min(i + 1, $elements.length - 1));
+ return;
+ }
+ });
+
+ Foundation.Keyboard.handleKey(e, 'Drilldown', {
+ next: function () {
+ if ($element.is(_this.$submenuAnchors)) {
+ _this._show($element.parent('li'));
+ $element.parent('li').one(Foundation.transitionend($element), function () {
+ $element.parent('li').find('ul li a').filter(_this.$menuItems).first().focus();
+ });
+ return true;
+ }
+ },
+ previous: function () {
+ _this._hide($element.parent('li').parent('ul'));
+ $element.parent('li').parent('ul').one(Foundation.transitionend($element), function () {
+ setTimeout(function () {
+ $element.parent('li').parent('ul').parent('li').children('a').first().focus();
+ }, 1);
+ });
+ return true;
+ },
+ up: function () {
+ $prevElement.focus();
+ return true;
+ },
+ down: function () {
+ $nextElement.focus();
+ return true;
+ },
+ close: function () {
+ _this._back();
+ //_this.$menuItems.first().focus(); // focus to first element
+ },
+ open: function () {
+ if (!$element.is(_this.$menuItems)) {
+ // not menu item means back button
+ _this._hide($element.parent('li').parent('ul'));
+ $element.parent('li').parent('ul').one(Foundation.transitionend($element), function () {
+ setTimeout(function () {
+ $element.parent('li').parent('ul').parent('li').children('a').first().focus();
+ }, 1);
+ });
+ } else if ($element.is(_this.$submenuAnchors)) {
+ _this._show($element.parent('li'));
+ $element.parent('li').one(Foundation.transitionend($element), function () {
+ $element.parent('li').find('ul li a').filter(_this.$menuItems).first().focus();
+ });
+ }
+ return true;
+ },
+ handled: function (preventDefault) {
+ if (preventDefault) {
+ e.preventDefault();
+ }
+ e.stopImmediatePropagation();
+ }
+ });
+ }); // end keyboardAccess
+ }
+
+ /**
+ * Closes all open elements, and returns to root menu.
+ * @function
+ * @fires Drilldown#closed
+ */
+
+ }, {
+ key: '_hideAll',
+ value: function _hideAll() {
+ var $elem = this.$element.find('.is-drilldown-submenu.is-active').addClass('is-closing');
+ $elem.one(Foundation.transitionend($elem), function (e) {
+ $elem.removeClass('is-active is-closing');
+ });
+ /**
+ * Fires when the menu is fully closed.
+ * @event Drilldown#closed
+ */
+ this.$element.trigger('closed.zf.drilldown');
+ }
+
+ /**
+ * Adds event listener for each `back` button, and closes open menus.
+ * @function
+ * @fires Drilldown#back
+ * @param {jQuery} $elem - the current sub-menu to add `back` event.
+ */
+
+ }, {
+ key: '_back',
+ value: function _back($elem) {
+ var _this = this;
+ $elem.off('click.zf.drilldown');
+ $elem.children('.js-drilldown-back').on('click.zf.drilldown', function (e) {
+ e.stopImmediatePropagation();
+ // console.log('mouseup on back');
+ _this._hide($elem);
+ });
+ }
+
+ /**
+ * Adds event listener to menu items w/o submenus to close open menus on click.
+ * @function
+ * @private
+ */
+
+ }, {
+ key: '_menuLinkEvents',
+ value: function _menuLinkEvents() {
+ var _this = this;
+ this.$menuItems.not('.is-drilldown-submenu-parent').off('click.zf.drilldown').on('click.zf.drilldown', function (e) {
+ // e.stopImmediatePropagation();
+ setTimeout(function () {
+ _this._hideAll();
+ }, 0);
+ });
+ }
+
+ /**
+ * Opens a submenu.
+ * @function
+ * @fires Drilldown#open
+ * @param {jQuery} $elem - the current element with a submenu to open, i.e. the `li` tag.
+ */
+
+ }, {
+ key: '_show',
+ value: function _show($elem) {
+ $elem.children('[data-submenu]').addClass('is-active');
+ /**
+ * Fires when the submenu has opened.
+ * @event Drilldown#open
+ */
+ this.$element.trigger('open.zf.drilldown', [$elem]);
+ }
+ }, {
+ key: '_hide',
+
+
+ /**
+ * Hides a submenu
+ * @function
+ * @fires Drilldown#hide
+ * @param {jQuery} $elem - the current sub-menu to hide, i.e. the `ul` tag.
+ */
+ value: function _hide($elem) {
+ var _this = this;
+ $elem.addClass('is-closing').one(Foundation.transitionend($elem), function () {
+ $elem.removeClass('is-active is-closing');
+ $elem.blur();
+ });
+ /**
+ * Fires when the submenu has closed.
+ * @event Drilldown#hide
+ */
+ $elem.trigger('hide.zf.drilldown', [$elem]);
+ }
+
+ /**
+ * Iterates through the nested menus to calculate the min-height, and max-width for the menu.
+ * Prevents content jumping.
+ * @function
+ * @private
+ */
+
+ }, {
+ key: '_getMaxDims',
+ value: function _getMaxDims() {
+ var max = 0,
+ result = {};
+ this.$submenus.add(this.$element).each(function () {
+ var numOfElems = $(this).children('li').length;
+ max = numOfElems > max ? numOfElems : max;
+ });
+
+ result['min-height'] = max * this.$menuItems[0].getBoundingClientRect().height + 'px';
+ result['max-width'] = this.$element[0].getBoundingClientRect().width + 'px';
+
+ return result;
+ }
+
+ /**
+ * Destroys the Drilldown Menu
+ * @function
+ */
+
+ }, {
+ key: 'destroy',
+ value: function destroy() {
+ this._hideAll();
+ Foundation.Nest.Burn(this.$element, 'drilldown');
+ this.$element.unwrap().find('.js-drilldown-back, .is-submenu-parent-item').remove().end().find('.is-active, .is-closing, .is-drilldown-submenu').removeClass('is-active is-closing is-drilldown-submenu').end().find('[data-submenu]').removeAttr('aria-hidden tabindex role');
+ this.$submenuAnchors.each(function () {
+ $(this).off('.zf.drilldown');
+ });
+ this.$element.find('a').each(function () {
+ var $link = $(this);
+ if ($link.data('savedHref')) {
+ $link.attr('href', $link.data('savedHref')).removeData('savedHref');
+ } else {
+ return;
+ }
+ });
+ Foundation.unregisterPlugin(this);
+ }
+ }]);
+
+ return Drilldown;
+ }();
+
+ Drilldown.defaults = {
+ /**
+ * Markup used for JS generated back button. Prepended to submenu lists and deleted on `destroy` method, 'js-drilldown-back' class required. Remove the backslash (`\`) if copy and pasting.
+ * @option
+ * @example '<\li><\a>Back<\/a><\/li>'
+ */
+ backButton: 'Back ',
+ /**
+ * Markup used to wrap drilldown menu. Use a class name for independent styling; the JS applied class: `is-drilldown` is required. Remove the backslash (`\`) if copy and pasting.
+ * @option
+ * @example '<\div class="is-drilldown"><\/div>'
+ */
+ wrapper: '
',
+ /**
+ * Adds the parent link to the submenu.
+ * @option
+ * @example false
+ */
+ parentLink: false,
+ /**
+ * Allow the menu to return to root list on body click.
+ * @option
+ * @example false
+ */
+ closeOnClick: false
+ // holdOpen: false
+ };
+
+ // Window exports
+ Foundation.plugin(Drilldown, 'Drilldown');
+}(jQuery);
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+!function ($) {
+
+ /**
+ * Dropdown module.
+ * @module foundation.dropdown
+ * @requires foundation.util.keyboard
+ * @requires foundation.util.box
+ * @requires foundation.util.triggers
+ */
+
+ var Dropdown = function () {
+ /**
+ * Creates a new instance of a dropdown.
+ * @class
+ * @param {jQuery} element - jQuery object to make into a dropdown.
+ * Object should be of the dropdown panel, rather than its anchor.
+ * @param {Object} options - Overrides to the default plugin settings.
+ */
+
+ function Dropdown(element, options) {
+ _classCallCheck(this, Dropdown);
+
+ this.$element = element;
+ this.options = $.extend({}, Dropdown.defaults, this.$element.data(), options);
+ this._init();
+
+ Foundation.registerPlugin(this, 'Dropdown');
+ Foundation.Keyboard.register('Dropdown', {
+ 'ENTER': 'open',
+ 'SPACE': 'open',
+ 'ESCAPE': 'close',
+ 'TAB': 'tab_forward',
+ 'SHIFT_TAB': 'tab_backward'
+ });
+ }
+
+ /**
+ * Initializes the plugin by setting/checking options and attributes, adding helper variables, and saving the anchor.
+ * @function
+ * @private
+ */
+
+
+ _createClass(Dropdown, [{
+ key: '_init',
+ value: function _init() {
+ var $id = this.$element.attr('id');
+
+ this.$anchor = $('[data-toggle="' + $id + '"]') || $('[data-open="' + $id + '"]');
+ this.$anchor.attr({
+ 'aria-controls': $id,
+ 'data-is-focus': false,
+ 'data-yeti-box': $id,
+ 'aria-haspopup': true,
+ 'aria-expanded': false
+
+ });
+
+ this.options.positionClass = this.getPositionClass();
+ this.counter = 4;
+ this.usedPositions = [];
+ this.$element.attr({
+ 'aria-hidden': 'true',
+ 'data-yeti-box': $id,
+ 'data-resize': $id,
+ 'aria-labelledby': this.$anchor[0].id || Foundation.GetYoDigits(6, 'dd-anchor')
+ });
+ this._events();
+ }
+
+ /**
+ * Helper function to determine current orientation of dropdown pane.
+ * @function
+ * @returns {String} position - string value of a position class.
+ */
+
+ }, {
+ key: 'getPositionClass',
+ value: function getPositionClass() {
+ var verticalPosition = this.$element[0].className.match(/(top|left|right|bottom)/g);
+ verticalPosition = verticalPosition ? verticalPosition[0] : '';
+ var horizontalPosition = /float-(\S+)\s/.exec(this.$anchor[0].className);
+ horizontalPosition = horizontalPosition ? horizontalPosition[1] : '';
+ var position = horizontalPosition ? horizontalPosition + ' ' + verticalPosition : verticalPosition;
+ return position;
+ }
+
+ /**
+ * Adjusts the dropdown panes orientation by adding/removing positioning classes.
+ * @function
+ * @private
+ * @param {String} position - position class to remove.
+ */
+
+ }, {
+ key: '_reposition',
+ value: function _reposition(position) {
+ this.usedPositions.push(position ? position : 'bottom');
+ //default, try switching to opposite side
+ if (!position && this.usedPositions.indexOf('top') < 0) {
+ this.$element.addClass('top');
+ } else if (position === 'top' && this.usedPositions.indexOf('bottom') < 0) {
+ this.$element.removeClass(position);
+ } else if (position === 'left' && this.usedPositions.indexOf('right') < 0) {
+ this.$element.removeClass(position).addClass('right');
+ } else if (position === 'right' && this.usedPositions.indexOf('left') < 0) {
+ this.$element.removeClass(position).addClass('left');
+ }
+
+ //if default change didn't work, try bottom or left first
+ else if (!position && this.usedPositions.indexOf('top') > -1 && this.usedPositions.indexOf('left') < 0) {
+ this.$element.addClass('left');
+ } else if (position === 'top' && this.usedPositions.indexOf('bottom') > -1 && this.usedPositions.indexOf('left') < 0) {
+ this.$element.removeClass(position).addClass('left');
+ } else if (position === 'left' && this.usedPositions.indexOf('right') > -1 && this.usedPositions.indexOf('bottom') < 0) {
+ this.$element.removeClass(position);
+ } else if (position === 'right' && this.usedPositions.indexOf('left') > -1 && this.usedPositions.indexOf('bottom') < 0) {
+ this.$element.removeClass(position);
+ }
+ //if nothing cleared, set to bottom
+ else {
+ this.$element.removeClass(position);
+ }
+ this.classChanged = true;
+ this.counter--;
+ }
+
+ /**
+ * Sets the position and orientation of the dropdown pane, checks for collisions.
+ * Recursively calls itself if a collision is detected, with a new position class.
+ * @function
+ * @private
+ */
+
+ }, {
+ key: '_setPosition',
+ value: function _setPosition() {
+ if (this.$anchor.attr('aria-expanded') === 'false') {
+ return false;
+ }
+ var position = this.getPositionClass(),
+ $eleDims = Foundation.Box.GetDimensions(this.$element),
+ $anchorDims = Foundation.Box.GetDimensions(this.$anchor),
+ _this = this,
+ direction = position === 'left' ? 'left' : position === 'right' ? 'left' : 'top',
+ param = direction === 'top' ? 'height' : 'width',
+ offset = param === 'height' ? this.options.vOffset : this.options.hOffset;
+
+ if ($eleDims.width >= $eleDims.windowDims.width || !this.counter && !Foundation.Box.ImNotTouchingYou(this.$element)) {
+ this.$element.offset(Foundation.Box.GetOffsets(this.$element, this.$anchor, 'center bottom', this.options.vOffset, this.options.hOffset, true)).css({
+ 'width': $eleDims.windowDims.width - this.options.hOffset * 2,
+ 'height': 'auto'
+ });
+ this.classChanged = true;
+ return false;
+ }
+
+ this.$element.offset(Foundation.Box.GetOffsets(this.$element, this.$anchor, position, this.options.vOffset, this.options.hOffset));
+
+ while (!Foundation.Box.ImNotTouchingYou(this.$element, false, true) && this.counter) {
+ this._reposition(position);
+ this._setPosition();
+ }
+ }
+
+ /**
+ * Adds event listeners to the element utilizing the triggers utility library.
+ * @function
+ * @private
+ */
+
+ }, {
+ key: '_events',
+ value: function _events() {
+ var _this = this;
+ this.$element.on({
+ 'open.zf.trigger': this.open.bind(this),
+ 'close.zf.trigger': this.close.bind(this),
+ 'toggle.zf.trigger': this.toggle.bind(this),
+ 'resizeme.zf.trigger': this._setPosition.bind(this)
+ });
+
+ if (this.options.hover) {
+ this.$anchor.off('mouseenter.zf.dropdown mouseleave.zf.dropdown').on('mouseenter.zf.dropdown', function () {
+ clearTimeout(_this.timeout);
+ _this.timeout = setTimeout(function () {
+ _this.open();
+ _this.$anchor.data('hover', true);
+ }, _this.options.hoverDelay);
+ }).on('mouseleave.zf.dropdown', function () {
+ clearTimeout(_this.timeout);
+ _this.timeout = setTimeout(function () {
+ _this.close();
+ _this.$anchor.data('hover', false);
+ }, _this.options.hoverDelay);
+ });
+ if (this.options.hoverPane) {
+ this.$element.off('mouseenter.zf.dropdown mouseleave.zf.dropdown').on('mouseenter.zf.dropdown', function () {
+ clearTimeout(_this.timeout);
+ }).on('mouseleave.zf.dropdown', function () {
+ clearTimeout(_this.timeout);
+ _this.timeout = setTimeout(function () {
+ _this.close();
+ _this.$anchor.data('hover', false);
+ }, _this.options.hoverDelay);
+ });
+ }
+ }
+ this.$anchor.add(this.$element).on('keydown.zf.dropdown', function (e) {
+
+ var $target = $(this),
+ visibleFocusableElements = Foundation.Keyboard.findFocusable(_this.$element);
+
+ Foundation.Keyboard.handleKey(e, 'Dropdown', {
+ tab_forward: function () {
+ if (_this.$element.find(':focus').is(visibleFocusableElements.eq(-1))) {
+ // left modal downwards, setting focus to first element
+ if (_this.options.trapFocus) {
+ // if focus shall be trapped
+ visibleFocusableElements.eq(0).focus();
+ e.preventDefault();
+ } else {
+ // if focus is not trapped, close dropdown on focus out
+ _this.close();
+ }
+ }
+ },
+ tab_backward: function () {
+ if (_this.$element.find(':focus').is(visibleFocusableElements.eq(0)) || _this.$element.is(':focus')) {
+ // left modal upwards, setting focus to last element
+ if (_this.options.trapFocus) {
+ // if focus shall be trapped
+ visibleFocusableElements.eq(-1).focus();
+ e.preventDefault();
+ } else {
+ // if focus is not trapped, close dropdown on focus out
+ _this.close();
+ }
+ }
+ },
+ open: function () {
+ if ($target.is(_this.$anchor)) {
+ _this.open();
+ _this.$element.attr('tabindex', -1).focus();
+ e.preventDefault();
+ }
+ },
+ close: function () {
+ _this.close();
+ _this.$anchor.focus();
+ }
+ });
+ });
+ }
+
+ /**
+ * Adds an event handler to the body to close any dropdowns on a click.
+ * @function
+ * @private
+ */
+
+ }, {
+ key: '_addBodyHandler',
+ value: function _addBodyHandler() {
+ var $body = $(document.body).not(this.$element),
+ _this = this;
+ $body.off('click.zf.dropdown').on('click.zf.dropdown', function (e) {
+ if (_this.$anchor.is(e.target) || _this.$anchor.find(e.target).length) {
+ return;
+ }
+ if (_this.$element.find(e.target).length) {
+ return;
+ }
+ _this.close();
+ $body.off('click.zf.dropdown');
+ });
+ }
+
+ /**
+ * Opens the dropdown pane, and fires a bubbling event to close other dropdowns.
+ * @function
+ * @fires Dropdown#closeme
+ * @fires Dropdown#show
+ */
+
+ }, {
+ key: 'open',
+ value: function open() {
+ // var _this = this;
+ /**
+ * Fires to close other open dropdowns
+ * @event Dropdown#closeme
+ */
+ this.$element.trigger('closeme.zf.dropdown', this.$element.attr('id'));
+ this.$anchor.addClass('hover').attr({ 'aria-expanded': true });
+ // this.$element/*.show()*/;
+ this._setPosition();
+ this.$element.addClass('is-open').attr({ 'aria-hidden': false });
+
+ if (this.options.autoFocus) {
+ var $focusable = Foundation.Keyboard.findFocusable(this.$element);
+ if ($focusable.length) {
+ $focusable.eq(0).focus();
+ }
+ }
+
+ if (this.options.closeOnClick) {
+ this._addBodyHandler();
+ }
+
+ /**
+ * Fires once the dropdown is visible.
+ * @event Dropdown#show
+ */
+ this.$element.trigger('show.zf.dropdown', [this.$element]);
+ }
+
+ /**
+ * Closes the open dropdown pane.
+ * @function
+ * @fires Dropdown#hide
+ */
+
+ }, {
+ key: 'close',
+ value: function close() {
+ if (!this.$element.hasClass('is-open')) {
+ return false;
+ }
+ this.$element.removeClass('is-open').attr({ 'aria-hidden': true });
+
+ this.$anchor.removeClass('hover').attr('aria-expanded', false);
+
+ if (this.classChanged) {
+ var curPositionClass = this.getPositionClass();
+ if (curPositionClass) {
+ this.$element.removeClass(curPositionClass);
+ }
+ this.$element.addClass(this.options.positionClass)
+ /*.hide()*/.css({ height: '', width: '' });
+ this.classChanged = false;
+ this.counter = 4;
+ this.usedPositions.length = 0;
+ }
+ this.$element.trigger('hide.zf.dropdown', [this.$element]);
+ }
+
+ /**
+ * Toggles the dropdown pane's visibility.
+ * @function
+ */
+
+ }, {
+ key: 'toggle',
+ value: function toggle() {
+ if (this.$element.hasClass('is-open')) {
+ if (this.$anchor.data('hover')) return;
+ this.close();
+ } else {
+ this.open();
+ }
+ }
+
+ /**
+ * Destroys the dropdown.
+ * @function
+ */
+
+ }, {
+ key: 'destroy',
+ value: function destroy() {
+ this.$element.off('.zf.trigger').hide();
+ this.$anchor.off('.zf.dropdown');
+
+ Foundation.unregisterPlugin(this);
+ }
+ }]);
+
+ return Dropdown;
+ }();
+
+ Dropdown.defaults = {
+ /**
+ * Amount of time to delay opening a submenu on hover event.
+ * @option
+ * @example 250
+ */
+ hoverDelay: 250,
+ /**
+ * Allow submenus to open on hover events
+ * @option
+ * @example false
+ */
+ hover: false,
+ /**
+ * Don't close dropdown when hovering over dropdown pane
+ * @option
+ * @example true
+ */
+ hoverPane: false,
+ /**
+ * Number of pixels between the dropdown pane and the triggering element on open.
+ * @option
+ * @example 1
+ */
+ vOffset: 1,
+ /**
+ * Number of pixels between the dropdown pane and the triggering element on open.
+ * @option
+ * @example 1
+ */
+ hOffset: 1,
+ /**
+ * Class applied to adjust open position. JS will test and fill this in.
+ * @option
+ * @example 'top'
+ */
+ positionClass: '',
+ /**
+ * Allow the plugin to trap focus to the dropdown pane if opened with keyboard commands.
+ * @option
+ * @example false
+ */
+ trapFocus: false,
+ /**
+ * Allow the plugin to set focus to the first focusable element within the pane, regardless of method of opening.
+ * @option
+ * @example true
+ */
+ autoFocus: false,
+ /**
+ * Allows a click on the body to close the dropdown.
+ * @option
+ * @example false
+ */
+ closeOnClick: false
+ };
+
+ // Window exports
+ Foundation.plugin(Dropdown, 'Dropdown');
+}(jQuery);
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+!function ($) {
+
+ /**
+ * DropdownMenu module.
+ * @module foundation.dropdown-menu
+ * @requires foundation.util.keyboard
+ * @requires foundation.util.box
+ * @requires foundation.util.nest
+ */
+
+ var DropdownMenu = function () {
+ /**
+ * Creates a new instance of DropdownMenu.
+ * @class
+ * @fires DropdownMenu#init
+ * @param {jQuery} element - jQuery object to make into a dropdown menu.
+ * @param {Object} options - Overrides to the default plugin settings.
+ */
+
+ function DropdownMenu(element, options) {
+ _classCallCheck(this, DropdownMenu);
+
+ this.$element = element;
+ this.options = $.extend({}, DropdownMenu.defaults, this.$element.data(), options);
+
+ Foundation.Nest.Feather(this.$element, 'dropdown');
+ this._init();
+
+ Foundation.registerPlugin(this, 'DropdownMenu');
+ Foundation.Keyboard.register('DropdownMenu', {
+ 'ENTER': 'open',
+ 'SPACE': 'open',
+ 'ARROW_RIGHT': 'next',
+ 'ARROW_UP': 'up',
+ 'ARROW_DOWN': 'down',
+ 'ARROW_LEFT': 'previous',
+ 'ESCAPE': 'close'
+ });
+ }
+
+ /**
+ * Initializes the plugin, and calls _prepareMenu
+ * @private
+ * @function
+ */
+
+
+ _createClass(DropdownMenu, [{
+ key: '_init',
+ value: function _init() {
+ var subs = this.$element.find('li.is-dropdown-submenu-parent');
+ this.$element.children('.is-dropdown-submenu-parent').children('.is-dropdown-submenu').addClass('first-sub');
+
+ this.$menuItems = this.$element.find('[role="menuitem"]');
+ this.$tabs = this.$element.children('[role="menuitem"]');
+ this.$tabs.find('ul.is-dropdown-submenu').addClass(this.options.verticalClass);
+
+ if (this.$element.hasClass(this.options.rightClass) || this.options.alignment === 'right' || Foundation.rtl() || this.$element.parents('.top-bar-right').is('*')) {
+ this.options.alignment = 'right';
+ subs.addClass('opens-left');
+ } else {
+ subs.addClass('opens-right');
+ }
+ this.changed = false;
+ this._events();
+ }
+ }, {
+ key: '_events',
+
+ /**
+ * Adds event listeners to elements within the menu
+ * @private
+ * @function
+ */
+ value: function _events() {
+ var _this = this,
+ hasTouch = 'ontouchstart' in window || typeof window.ontouchstart !== 'undefined',
+ parClass = 'is-dropdown-submenu-parent';
+
+ // used for onClick and in the keyboard handlers
+ var handleClickFn = function (e) {
+ var $elem = $(e.target).parentsUntil('ul', '.' + parClass),
+ hasSub = $elem.hasClass(parClass),
+ hasClicked = $elem.attr('data-is-click') === 'true',
+ $sub = $elem.children('.is-dropdown-submenu');
+
+ if (hasSub) {
+ if (hasClicked) {
+ if (!_this.options.closeOnClick || !_this.options.clickOpen && !hasTouch || _this.options.forceFollow && hasTouch) {
+ return;
+ } else {
+ e.stopImmediatePropagation();
+ e.preventDefault();
+ _this._hide($elem);
+ }
+ } else {
+ e.preventDefault();
+ e.stopImmediatePropagation();
+ _this._show($elem.children('.is-dropdown-submenu'));
+ $elem.add($elem.parentsUntil(_this.$element, '.' + parClass)).attr('data-is-click', true);
+ }
+ } else {
+ return;
+ }
+ };
+
+ if (this.options.clickOpen || hasTouch) {
+ this.$menuItems.on('click.zf.dropdownmenu touchstart.zf.dropdownmenu', handleClickFn);
+ }
+
+ if (!this.options.disableHover) {
+ this.$menuItems.on('mouseenter.zf.dropdownmenu', function (e) {
+ var $elem = $(this),
+ hasSub = $elem.hasClass(parClass);
+
+ if (hasSub) {
+ clearTimeout(_this.delay);
+ _this.delay = setTimeout(function () {
+ _this._show($elem.children('.is-dropdown-submenu'));
+ }, _this.options.hoverDelay);
+ }
+ }).on('mouseleave.zf.dropdownmenu', function (e) {
+ var $elem = $(this),
+ hasSub = $elem.hasClass(parClass);
+ if (hasSub && _this.options.autoclose) {
+ if ($elem.attr('data-is-click') === 'true' && _this.options.clickOpen) {
+ return false;
+ }
+
+ clearTimeout(_this.delay);
+ _this.delay = setTimeout(function () {
+ _this._hide($elem);
+ }, _this.options.closingTime);
+ }
+ });
+ }
+ this.$menuItems.on('keydown.zf.dropdownmenu', function (e) {
+ var $element = $(e.target).parentsUntil('ul', '[role="menuitem"]'),
+ isTab = _this.$tabs.index($element) > -1,
+ $elements = isTab ? _this.$tabs : $element.siblings('li').add($element),
+ $prevElement,
+ $nextElement;
+
+ $elements.each(function (i) {
+ if ($(this).is($element)) {
+ $prevElement = $elements.eq(i - 1);
+ $nextElement = $elements.eq(i + 1);
+ return;
+ }
+ });
+
+ var nextSibling = function () {
+ if (!$element.is(':last-child')) {
+ $nextElement.children('a:first').focus();
+ e.preventDefault();
+ }
+ },
+ prevSibling = function () {
+ $prevElement.children('a:first').focus();
+ e.preventDefault();
+ },
+ openSub = function () {
+ var $sub = $element.children('ul.is-dropdown-submenu');
+ if ($sub.length) {
+ _this._show($sub);
+ $element.find('li > a:first').focus();
+ e.preventDefault();
+ } else {
+ return;
+ }
+ },
+ closeSub = function () {
+ //if ($element.is(':first-child')) {
+ var close = $element.parent('ul').parent('li');
+ close.children('a:first').focus();
+ _this._hide(close);
+ e.preventDefault();
+ //}
+ };
+ var functions = {
+ open: openSub,
+ close: function () {
+ _this._hide(_this.$element);
+ _this.$menuItems.find('a:first').focus(); // focus to first element
+ e.preventDefault();
+ },
+ handled: function () {
+ e.stopImmediatePropagation();
+ }
+ };
+
+ if (isTab) {
+ if (_this.$element.hasClass(_this.options.verticalClass)) {
+ // vertical menu
+ if (_this.options.alignment === 'left') {
+ // left aligned
+ $.extend(functions, {
+ down: nextSibling,
+ up: prevSibling,
+ next: openSub,
+ previous: closeSub
+ });
+ } else {
+ // right aligned
+ $.extend(functions, {
+ down: nextSibling,
+ up: prevSibling,
+ next: closeSub,
+ previous: openSub
+ });
+ }
+ } else {
+ // horizontal menu
+ $.extend(functions, {
+ next: nextSibling,
+ previous: prevSibling,
+ down: openSub,
+ up: closeSub
+ });
+ }
+ } else {
+ // not tabs -> one sub
+ if (_this.options.alignment === 'left') {
+ // left aligned
+ $.extend(functions, {
+ next: openSub,
+ previous: closeSub,
+ down: nextSibling,
+ up: prevSibling
+ });
+ } else {
+ // right aligned
+ $.extend(functions, {
+ next: closeSub,
+ previous: openSub,
+ down: nextSibling,
+ up: prevSibling
+ });
+ }
+ }
+ Foundation.Keyboard.handleKey(e, 'DropdownMenu', functions);
+ });
+ }
+
+ /**
+ * Adds an event handler to the body to close any dropdowns on a click.
+ * @function
+ * @private
+ */
+
+ }, {
+ key: '_addBodyHandler',
+ value: function _addBodyHandler() {
+ var $body = $(document.body),
+ _this = this;
+ $body.off('mouseup.zf.dropdownmenu touchend.zf.dropdownmenu').on('mouseup.zf.dropdownmenu touchend.zf.dropdownmenu', function (e) {
+ var $link = _this.$element.find(e.target);
+ if ($link.length) {
+ return;
+ }
+
+ _this._hide();
+ $body.off('mouseup.zf.dropdownmenu touchend.zf.dropdownmenu');
+ });
+ }
+
+ /**
+ * Opens a dropdown pane, and checks for collisions first.
+ * @param {jQuery} $sub - ul element that is a submenu to show
+ * @function
+ * @private
+ * @fires DropdownMenu#show
+ */
+
+ }, {
+ key: '_show',
+ value: function _show($sub) {
+ var idx = this.$tabs.index(this.$tabs.filter(function (i, el) {
+ return $(el).find($sub).length > 0;
+ }));
+ var $sibs = $sub.parent('li.is-dropdown-submenu-parent').siblings('li.is-dropdown-submenu-parent');
+ this._hide($sibs, idx);
+ $sub.css('visibility', 'hidden').addClass('js-dropdown-active').attr({ 'aria-hidden': false }).parent('li.is-dropdown-submenu-parent').addClass('is-active').attr({ 'aria-expanded': true });
+ var clear = Foundation.Box.ImNotTouchingYou($sub, null, true);
+ if (!clear) {
+ var oldClass = this.options.alignment === 'left' ? '-right' : '-left',
+ $parentLi = $sub.parent('.is-dropdown-submenu-parent');
+ $parentLi.removeClass('opens' + oldClass).addClass('opens-' + this.options.alignment);
+ clear = Foundation.Box.ImNotTouchingYou($sub, null, true);
+ if (!clear) {
+ $parentLi.removeClass('opens-' + this.options.alignment).addClass('opens-inner');
+ }
+ this.changed = true;
+ }
+ $sub.css('visibility', '');
+ if (this.options.closeOnClick) {
+ this._addBodyHandler();
+ }
+ /**
+ * Fires when the new dropdown pane is visible.
+ * @event DropdownMenu#show
+ */
+ this.$element.trigger('show.zf.dropdownmenu', [$sub]);
+ }
+
+ /**
+ * Hides a single, currently open dropdown pane, if passed a parameter, otherwise, hides everything.
+ * @function
+ * @param {jQuery} $elem - element with a submenu to hide
+ * @param {Number} idx - index of the $tabs collection to hide
+ * @private
+ */
+
+ }, {
+ key: '_hide',
+ value: function _hide($elem, idx) {
+ var $toClose;
+ if ($elem && $elem.length) {
+ $toClose = $elem;
+ } else if (idx !== undefined) {
+ $toClose = this.$tabs.not(function (i, el) {
+ return i === idx;
+ });
+ } else {
+ $toClose = this.$element;
+ }
+ var somethingToClose = $toClose.hasClass('is-active') || $toClose.find('.is-active').length > 0;
+
+ if (somethingToClose) {
+ $toClose.find('li.is-active').add($toClose).attr({
+ 'aria-expanded': false,
+ 'data-is-click': false
+ }).removeClass('is-active');
+
+ $toClose.find('ul.js-dropdown-active').attr({
+ 'aria-hidden': true
+ }).removeClass('js-dropdown-active');
+
+ if (this.changed || $toClose.find('opens-inner').length) {
+ var oldClass = this.options.alignment === 'left' ? 'right' : 'left';
+ $toClose.find('li.is-dropdown-submenu-parent').add($toClose).removeClass('opens-inner opens-' + this.options.alignment).addClass('opens-' + oldClass);
+ this.changed = false;
+ }
+ /**
+ * Fires when the open menus are closed.
+ * @event DropdownMenu#hide
+ */
+ this.$element.trigger('hide.zf.dropdownmenu', [$toClose]);
+ }
+ }
+
+ /**
+ * Destroys the plugin.
+ * @function
+ */
+
+ }, {
+ key: 'destroy',
+ value: function destroy() {
+ this.$menuItems.off('.zf.dropdownmenu').removeAttr('data-is-click').removeClass('is-right-arrow is-left-arrow is-down-arrow opens-right opens-left opens-inner');
+ $(document.body).off('.zf.dropdownmenu');
+ Foundation.Nest.Burn(this.$element, 'dropdown');
+ Foundation.unregisterPlugin(this);
+ }
+ }]);
+
+ return DropdownMenu;
+ }();
+
+ /**
+ * Default settings for plugin
+ */
+
+
+ DropdownMenu.defaults = {
+ /**
+ * Disallows hover events from opening submenus
+ * @option
+ * @example false
+ */
+ disableHover: false,
+ /**
+ * Allow a submenu to automatically close on a mouseleave event, if not clicked open.
+ * @option
+ * @example true
+ */
+ autoclose: true,
+ /**
+ * Amount of time to delay opening a submenu on hover event.
+ * @option
+ * @example 50
+ */
+ hoverDelay: 50,
+ /**
+ * Allow a submenu to open/remain open on parent click event. Allows cursor to move away from menu.
+ * @option
+ * @example true
+ */
+ clickOpen: false,
+ /**
+ * Amount of time to delay closing a submenu on a mouseleave event.
+ * @option
+ * @example 500
+ */
+
+ closingTime: 500,
+ /**
+ * Position of the menu relative to what direction the submenus should open. Handled by JS.
+ * @option
+ * @example 'left'
+ */
+ alignment: 'left',
+ /**
+ * Allow clicks on the body to close any open submenus.
+ * @option
+ * @example true
+ */
+ closeOnClick: true,
+ /**
+ * Class applied to vertical oriented menus, Foundation default is `vertical`. Update this if using your own class.
+ * @option
+ * @example 'vertical'
+ */
+ verticalClass: 'vertical',
+ /**
+ * Class applied to right-side oriented menus, Foundation default is `align-right`. Update this if using your own class.
+ * @option
+ * @example 'align-right'
+ */
+ rightClass: 'align-right',
+ /**
+ * Boolean to force overide the clicking of links to perform default action, on second touch event for mobile.
+ * @option
+ * @example false
+ */
+ forceFollow: true
+ };
+
+ // Window exports
+ Foundation.plugin(DropdownMenu, 'DropdownMenu');
+}(jQuery);
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+!function ($) {
+
+ /**
+ * Equalizer module.
+ * @module foundation.equalizer
+ */
+
+ var Equalizer = function () {
+ /**
+ * Creates a new instance of Equalizer.
+ * @class
+ * @fires Equalizer#init
+ * @param {Object} element - jQuery object to add the trigger to.
+ * @param {Object} options - Overrides to the default plugin settings.
+ */
+
+ function Equalizer(element, options) {
+ _classCallCheck(this, Equalizer);
+
+ this.$element = element;
+ this.options = $.extend({}, Equalizer.defaults, this.$element.data(), options);
+
+ this._init();
+
+ Foundation.registerPlugin(this, 'Equalizer');
+ }
+
+ /**
+ * Initializes the Equalizer plugin and calls functions to get equalizer functioning on load.
+ * @private
+ */
+
+
+ _createClass(Equalizer, [{
+ key: '_init',
+ value: function _init() {
+ var eqId = this.$element.attr('data-equalizer') || '';
+ var $watched = this.$element.find('[data-equalizer-watch="' + eqId + '"]');
+
+ this.$watched = $watched.length ? $watched : this.$element.find('[data-equalizer-watch]');
+ this.$element.attr('data-resize', eqId || Foundation.GetYoDigits(6, 'eq'));
+
+ this.hasNested = this.$element.find('[data-equalizer]').length > 0;
+ this.isNested = this.$element.parentsUntil(document.body, '[data-equalizer]').length > 0;
+ this.isOn = false;
+ this._bindHandler = {
+ onResizeMeBound: this._onResizeMe.bind(this),
+ onPostEqualizedBound: this._onPostEqualized.bind(this)
+ };
+
+ var imgs = this.$element.find('img');
+ var tooSmall;
+ if (this.options.equalizeOn) {
+ tooSmall = this._checkMQ();
+ $(window).on('changed.zf.mediaquery', this._checkMQ.bind(this));
+ } else {
+ this._events();
+ }
+ if (tooSmall !== undefined && tooSmall === false || tooSmall === undefined) {
+ if (imgs.length) {
+ Foundation.onImagesLoaded(imgs, this._reflow.bind(this));
+ } else {
+ this._reflow();
+ }
+ }
+ }
+
+ /**
+ * Removes event listeners if the breakpoint is too small.
+ * @private
+ */
+
+ }, {
+ key: '_pauseEvents',
+ value: function _pauseEvents() {
+ this.isOn = false;
+ this.$element.off({
+ '.zf.equalizer': this._bindHandler.onPostEqualizedBound,
+ 'resizeme.zf.trigger': this._bindHandler.onResizeMeBound
+ });
+ }
+
+ /**
+ * function to handle $elements resizeme.zf.trigger, with bound this on _bindHandler.onResizeMeBound
+ * @private
+ */
+
+ }, {
+ key: '_onResizeMe',
+ value: function _onResizeMe(e) {
+ this._reflow();
+ }
+
+ /**
+ * function to handle $elements postequalized.zf.equalizer, with bound this on _bindHandler.onPostEqualizedBound
+ * @private
+ */
+
+ }, {
+ key: '_onPostEqualized',
+ value: function _onPostEqualized(e) {
+ if (e.target !== this.$element[0]) {
+ this._reflow();
+ }
+ }
+
+ /**
+ * Initializes events for Equalizer.
+ * @private
+ */
+
+ }, {
+ key: '_events',
+ value: function _events() {
+ var _this = this;
+ this._pauseEvents();
+ if (this.hasNested) {
+ this.$element.on('postequalized.zf.equalizer', this._bindHandler.onPostEqualizedBound);
+ } else {
+ this.$element.on('resizeme.zf.trigger', this._bindHandler.onResizeMeBound);
+ }
+ this.isOn = true;
+ }
+
+ /**
+ * Checks the current breakpoint to the minimum required size.
+ * @private
+ */
+
+ }, {
+ key: '_checkMQ',
+ value: function _checkMQ() {
+ var tooSmall = !Foundation.MediaQuery.atLeast(this.options.equalizeOn);
+ if (tooSmall) {
+ if (this.isOn) {
+ this._pauseEvents();
+ this.$watched.css('height', 'auto');
+ }
+ } else {
+ if (!this.isOn) {
+ this._events();
+ }
+ }
+ return tooSmall;
+ }
+
+ /**
+ * A noop version for the plugin
+ * @private
+ */
+
+ }, {
+ key: '_killswitch',
+ value: function _killswitch() {
+ return;
+ }
+
+ /**
+ * Calls necessary functions to update Equalizer upon DOM change
+ * @private
+ */
+
+ }, {
+ key: '_reflow',
+ value: function _reflow() {
+ if (!this.options.equalizeOnStack) {
+ if (this._isStacked()) {
+ this.$watched.css('height', 'auto');
+ return false;
+ }
+ }
+ if (this.options.equalizeByRow) {
+ this.getHeightsByRow(this.applyHeightByRow.bind(this));
+ } else {
+ this.getHeights(this.applyHeight.bind(this));
+ }
+ }
+
+ /**
+ * Manually determines if the first 2 elements are *NOT* stacked.
+ * @private
+ */
+
+ }, {
+ key: '_isStacked',
+ value: function _isStacked() {
+ return this.$watched[0].getBoundingClientRect().top !== this.$watched[1].getBoundingClientRect().top;
+ }
+
+ /**
+ * Finds the outer heights of children contained within an Equalizer parent and returns them in an array
+ * @param {Function} cb - A non-optional callback to return the heights array to.
+ * @returns {Array} heights - An array of heights of children within Equalizer container
+ */
+
+ }, {
+ key: 'getHeights',
+ value: function getHeights(cb) {
+ var heights = [];
+ for (var i = 0, len = this.$watched.length; i < len; i++) {
+ this.$watched[i].style.height = 'auto';
+ heights.push(this.$watched[i].offsetHeight);
+ }
+ cb(heights);
+ }
+
+ /**
+ * Finds the outer heights of children contained within an Equalizer parent and returns them in an array
+ * @param {Function} cb - A non-optional callback to return the heights array to.
+ * @returns {Array} groups - An array of heights of children within Equalizer container grouped by row with element,height and max as last child
+ */
+
+ }, {
+ key: 'getHeightsByRow',
+ value: function getHeightsByRow(cb) {
+ var lastElTopOffset = this.$watched.length ? this.$watched.first().offset().top : 0,
+ groups = [],
+ group = 0;
+ //group by Row
+ groups[group] = [];
+ for (var i = 0, len = this.$watched.length; i < len; i++) {
+ this.$watched[i].style.height = 'auto';
+ //maybe could use this.$watched[i].offsetTop
+ var elOffsetTop = $(this.$watched[i]).offset().top;
+ if (elOffsetTop != lastElTopOffset) {
+ group++;
+ groups[group] = [];
+ lastElTopOffset = elOffsetTop;
+ }
+ groups[group].push([this.$watched[i], this.$watched[i].offsetHeight]);
+ }
+
+ for (var j = 0, ln = groups.length; j < ln; j++) {
+ var heights = $(groups[j]).map(function () {
+ return this[1];
+ }).get();
+ var max = Math.max.apply(null, heights);
+ groups[j].push(max);
+ }
+ cb(groups);
+ }
+
+ /**
+ * Changes the CSS height property of each child in an Equalizer parent to match the tallest
+ * @param {array} heights - An array of heights of children within Equalizer container
+ * @fires Equalizer#preequalized
+ * @fires Equalizer#postequalized
+ */
+
+ }, {
+ key: 'applyHeight',
+ value: function applyHeight(heights) {
+ var max = Math.max.apply(null, heights);
+ /**
+ * Fires before the heights are applied
+ * @event Equalizer#preequalized
+ */
+ this.$element.trigger('preequalized.zf.equalizer');
+
+ this.$watched.css('height', max);
+
+ /**
+ * Fires when the heights have been applied
+ * @event Equalizer#postequalized
+ */
+ this.$element.trigger('postequalized.zf.equalizer');
+ }
+
+ /**
+ * Changes the CSS height property of each child in an Equalizer parent to match the tallest by row
+ * @param {array} groups - An array of heights of children within Equalizer container grouped by row with element,height and max as last child
+ * @fires Equalizer#preequalized
+ * @fires Equalizer#preequalizedRow
+ * @fires Equalizer#postequalizedRow
+ * @fires Equalizer#postequalized
+ */
+
+ }, {
+ key: 'applyHeightByRow',
+ value: function applyHeightByRow(groups) {
+ /**
+ * Fires before the heights are applied
+ */
+ this.$element.trigger('preequalized.zf.equalizer');
+ for (var i = 0, len = groups.length; i < len; i++) {
+ var groupsILength = groups[i].length,
+ max = groups[i][groupsILength - 1];
+ if (groupsILength <= 2) {
+ $(groups[i][0][0]).css({ 'height': 'auto' });
+ continue;
+ }
+ /**
+ * Fires before the heights per row are applied
+ * @event Equalizer#preequalizedRow
+ */
+ this.$element.trigger('preequalizedrow.zf.equalizer');
+ for (var j = 0, lenJ = groupsILength - 1; j < lenJ; j++) {
+ $(groups[i][j][0]).css({ 'height': max });
+ }
+ /**
+ * Fires when the heights per row have been applied
+ * @event Equalizer#postequalizedRow
+ */
+ this.$element.trigger('postequalizedrow.zf.equalizer');
+ }
+ /**
+ * Fires when the heights have been applied
+ */
+ this.$element.trigger('postequalized.zf.equalizer');
+ }
+
+ /**
+ * Destroys an instance of Equalizer.
+ * @function
+ */
+
+ }, {
+ key: 'destroy',
+ value: function destroy() {
+ this._pauseEvents();
+ this.$watched.css('height', 'auto');
+
+ Foundation.unregisterPlugin(this);
+ }
+ }]);
+
+ return Equalizer;
+ }();
+
+ /**
+ * Default settings for plugin
+ */
+
+
+ Equalizer.defaults = {
+ /**
+ * Enable height equalization when stacked on smaller screens.
+ * @option
+ * @example true
+ */
+ equalizeOnStack: true,
+ /**
+ * Enable height equalization row by row.
+ * @option
+ * @example false
+ */
+ equalizeByRow: false,
+ /**
+ * String representing the minimum breakpoint size the plugin should equalize heights on.
+ * @option
+ * @example 'medium'
+ */
+ equalizeOn: ''
+ };
+
+ // Window exports
+ Foundation.plugin(Equalizer, 'Equalizer');
+}(jQuery);
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+!function ($) {
+
+ /**
+ * Interchange module.
+ * @module foundation.interchange
+ * @requires foundation.util.mediaQuery
+ * @requires foundation.util.timerAndImageLoader
+ */
+
+ var Interchange = function () {
+ /**
+ * Creates a new instance of Interchange.
+ * @class
+ * @fires Interchange#init
+ * @param {Object} element - jQuery object to add the trigger to.
+ * @param {Object} options - Overrides to the default plugin settings.
+ */
+
+ function Interchange(element, options) {
+ _classCallCheck(this, Interchange);
+
+ this.$element = element;
+ this.options = $.extend({}, Interchange.defaults, options);
+ this.rules = [];
+ this.currentPath = '';
+
+ this._init();
+ this._events();
+
+ Foundation.registerPlugin(this, 'Interchange');
+ }
+
+ /**
+ * Initializes the Interchange plugin and calls functions to get interchange functioning on load.
+ * @function
+ * @private
+ */
+
+
+ _createClass(Interchange, [{
+ key: '_init',
+ value: function _init() {
+ this._addBreakpoints();
+ this._generateRules();
+ this._reflow();
+ }
+
+ /**
+ * Initializes events for Interchange.
+ * @function
+ * @private
+ */
+
+ }, {
+ key: '_events',
+ value: function _events() {
+ $(window).on('resize.zf.interchange', Foundation.util.throttle(this._reflow.bind(this), 50));
+ }
+
+ /**
+ * Calls necessary functions to update Interchange upon DOM change
+ * @function
+ * @private
+ */
+
+ }, {
+ key: '_reflow',
+ value: function _reflow() {
+ var match;
+
+ // Iterate through each rule, but only save the last match
+ for (var i in this.rules) {
+ if (this.rules.hasOwnProperty(i)) {
+ var rule = this.rules[i];
+
+ if (window.matchMedia(rule.query).matches) {
+ match = rule;
+ }
+ }
+ }
+
+ if (match) {
+ this.replace(match.path);
+ }
+ }
+
+ /**
+ * Gets the Foundation breakpoints and adds them to the Interchange.SPECIAL_QUERIES object.
+ * @function
+ * @private
+ */
+
+ }, {
+ key: '_addBreakpoints',
+ value: function _addBreakpoints() {
+ for (var i in Foundation.MediaQuery.queries) {
+ if (Foundation.MediaQuery.queries.hasOwnProperty(i)) {
+ var query = Foundation.MediaQuery.queries[i];
+ Interchange.SPECIAL_QUERIES[query.name] = query.value;
+ }
+ }
+ }
+
+ /**
+ * Checks the Interchange element for the provided media query + content pairings
+ * @function
+ * @private
+ * @param {Object} element - jQuery object that is an Interchange instance
+ * @returns {Array} scenarios - Array of objects that have 'mq' and 'path' keys with corresponding keys
+ */
+
+ }, {
+ key: '_generateRules',
+ value: function _generateRules(element) {
+ var rulesList = [];
+ var rules;
+
+ if (this.options.rules) {
+ rules = this.options.rules;
+ } else {
+ rules = this.$element.data('interchange').match(/\[.*?\]/g);
+ }
+
+ for (var i in rules) {
+ if (rules.hasOwnProperty(i)) {
+ var rule = rules[i].slice(1, -1).split(', ');
+ var path = rule.slice(0, -1).join('');
+ var query = rule[rule.length - 1];
+
+ if (Interchange.SPECIAL_QUERIES[query]) {
+ query = Interchange.SPECIAL_QUERIES[query];
+ }
+
+ rulesList.push({
+ path: path,
+ query: query
+ });
+ }
+ }
+
+ this.rules = rulesList;
+ }
+
+ /**
+ * Update the `src` property of an image, or change the HTML of a container, to the specified path.
+ * @function
+ * @param {String} path - Path to the image or HTML partial.
+ * @fires Interchange#replaced
+ */
+
+ }, {
+ key: 'replace',
+ value: function replace(path) {
+ if (this.currentPath === path) return;
+
+ var _this = this,
+ trigger = 'replaced.zf.interchange';
+
+ // Replacing images
+ if (this.$element[0].nodeName === 'IMG') {
+ this.$element.attr('src', path).load(function () {
+ _this.currentPath = path;
+ }).trigger(trigger);
+ }
+ // Replacing background images
+ else if (path.match(/\.(gif|jpg|jpeg|png|svg|tiff)([?#].*)?/i)) {
+ this.$element.css({ 'background-image': 'url(' + path + ')' }).trigger(trigger);
+ }
+ // Replacing HTML
+ else {
+ $.get(path, function (response) {
+ _this.$element.html(response).trigger(trigger);
+ $(response).foundation();
+ _this.currentPath = path;
+ });
+ }
+
+ /**
+ * Fires when content in an Interchange element is done being loaded.
+ * @event Interchange#replaced
+ */
+ // this.$element.trigger('replaced.zf.interchange');
+ }
+
+ /**
+ * Destroys an instance of interchange.
+ * @function
+ */
+
+ }, {
+ key: 'destroy',
+ value: function destroy() {
+ //TODO this.
+ }
+ }]);
+
+ return Interchange;
+ }();
+
+ /**
+ * Default settings for plugin
+ */
+
+
+ Interchange.defaults = {
+ /**
+ * Rules to be applied to Interchange elements. Set with the `data-interchange` array notation.
+ * @option
+ */
+ rules: null
+ };
+
+ Interchange.SPECIAL_QUERIES = {
+ 'landscape': 'screen and (orientation: landscape)',
+ 'portrait': 'screen and (orientation: portrait)',
+ 'retina': 'only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx)'
+ };
+
+ // Window exports
+ Foundation.plugin(Interchange, 'Interchange');
+}(jQuery);
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+!function ($) {
+
+ /**
+ * Magellan module.
+ * @module foundation.magellan
+ */
+
+ var Magellan = function () {
+ /**
+ * Creates a new instance of Magellan.
+ * @class
+ * @fires Magellan#init
+ * @param {Object} element - jQuery object to add the trigger to.
+ * @param {Object} options - Overrides to the default plugin settings.
+ */
+
+ function Magellan(element, options) {
+ _classCallCheck(this, Magellan);
+
+ this.$element = element;
+ this.options = $.extend({}, Magellan.defaults, this.$element.data(), options);
+
+ this._init();
+
+ Foundation.registerPlugin(this, 'Magellan');
+ }
+
+ /**
+ * Initializes the Magellan plugin and calls functions to get equalizer functioning on load.
+ * @private
+ */
+
+
+ _createClass(Magellan, [{
+ key: '_init',
+ value: function _init() {
+ var id = this.$element[0].id || Foundation.GetYoDigits(6, 'magellan');
+ var _this = this;
+ this.$targets = $('[data-magellan-target]');
+ this.$links = this.$element.find('a');
+ this.$element.attr({
+ 'data-resize': id,
+ 'data-scroll': id,
+ 'id': id
+ });
+ this.$active = $();
+ this.scrollPos = parseInt(window.pageYOffset, 10);
+
+ this._events();
+ }
+
+ /**
+ * Calculates an array of pixel values that are the demarcation lines between locations on the page.
+ * Can be invoked if new elements are added or the size of a location changes.
+ * @function
+ */
+
+ }, {
+ key: 'calcPoints',
+ value: function calcPoints() {
+ var _this = this,
+ body = document.body,
+ html = document.documentElement;
+
+ this.points = [];
+ this.winHeight = Math.round(Math.max(window.innerHeight, html.clientHeight));
+ this.docHeight = Math.round(Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight));
+
+ this.$targets.each(function () {
+ var $tar = $(this),
+ pt = Math.round($tar.offset().top - _this.options.threshold);
+ $tar.targetPoint = pt;
+ _this.points.push(pt);
+ });
+ }
+
+ /**
+ * Initializes events for Magellan.
+ * @private
+ */
+
+ }, {
+ key: '_events',
+ value: function _events() {
+ var _this = this,
+ $body = $('html, body'),
+ opts = {
+ duration: _this.options.animationDuration,
+ easing: _this.options.animationEasing
+ };
+ $(window).one('load', function () {
+ if (_this.options.deepLinking) {
+ if (location.hash) {
+ _this.scrollToLoc(location.hash);
+ }
+ }
+ _this.calcPoints();
+ _this._updateActive();
+ });
+
+ this.$element.on({
+ 'resizeme.zf.trigger': this.reflow.bind(this),
+ 'scrollme.zf.trigger': this._updateActive.bind(this)
+ }).on('click.zf.magellan', 'a[href^="#"]', function (e) {
+ e.preventDefault();
+ var arrival = this.getAttribute('href');
+ _this.scrollToLoc(arrival);
+ });
+ }
+
+ /**
+ * Function to scroll to a given location on the page.
+ * @param {String} loc - a properly formatted jQuery id selector. Example: '#foo'
+ * @function
+ */
+
+ }, {
+ key: 'scrollToLoc',
+ value: function scrollToLoc(loc) {
+ var scrollPos = Math.round($(loc).offset().top - this.options.threshold / 2 - this.options.barOffset);
+
+ $('html, body').stop(true).animate({ scrollTop: scrollPos }, this.options.animationDuration, this.options.animationEasing);
+ }
+
+ /**
+ * Calls necessary functions to update Magellan upon DOM change
+ * @function
+ */
+
+ }, {
+ key: 'reflow',
+ value: function reflow() {
+ this.calcPoints();
+ this._updateActive();
+ }
+
+ /**
+ * Updates the visibility of an active location link, and updates the url hash for the page, if deepLinking enabled.
+ * @private
+ * @function
+ * @fires Magellan#update
+ */
+
+ }, {
+ key: '_updateActive',
+ value: function _updateActive() /*evt, elem, scrollPos*/{
+ var winPos = /*scrollPos ||*/parseInt(window.pageYOffset, 10),
+ curIdx;
+
+ if (winPos + this.winHeight === this.docHeight) {
+ curIdx = this.points.length - 1;
+ } else if (winPos < this.points[0]) {
+ curIdx = 0;
+ } else {
+ var isDown = this.scrollPos < winPos,
+ _this = this,
+ curVisible = this.points.filter(function (p, i) {
+ return isDown ? p - _this.options.barOffset <= winPos : p - _this.options.barOffset - _this.options.threshold <= winPos;
+ });
+ curIdx = curVisible.length ? curVisible.length - 1 : 0;
+ }
+
+ this.$active.removeClass(this.options.activeClass);
+ this.$active = this.$links.eq(curIdx).addClass(this.options.activeClass);
+
+ if (this.options.deepLinking) {
+ var hash = this.$active[0].getAttribute('href');
+ if (window.history.pushState) {
+ window.history.pushState(null, null, hash);
+ } else {
+ window.location.hash = hash;
+ }
+ }
+
+ this.scrollPos = winPos;
+ /**
+ * Fires when magellan is finished updating to the new active element.
+ * @event Magellan#update
+ */
+ this.$element.trigger('update.zf.magellan', [this.$active]);
+ }
+
+ /**
+ * Destroys an instance of Magellan and resets the url of the window.
+ * @function
+ */
+
+ }, {
+ key: 'destroy',
+ value: function destroy() {
+ this.$element.off('.zf.trigger .zf.magellan').find('.' + this.options.activeClass).removeClass(this.options.activeClass);
+
+ if (this.options.deepLinking) {
+ var hash = this.$active[0].getAttribute('href');
+ window.location.hash.replace(hash, '');
+ }
+
+ Foundation.unregisterPlugin(this);
+ }
+ }]);
+
+ return Magellan;
+ }();
+
+ /**
+ * Default settings for plugin
+ */
+
+
+ Magellan.defaults = {
+ /**
+ * Amount of time, in ms, the animated scrolling should take between locations.
+ * @option
+ * @example 500
+ */
+ animationDuration: 500,
+ /**
+ * Animation style to use when scrolling between locations.
+ * @option
+ * @example 'ease-in-out'
+ */
+ animationEasing: 'linear',
+ /**
+ * Number of pixels to use as a marker for location changes.
+ * @option
+ * @example 50
+ */
+ threshold: 50,
+ /**
+ * Class applied to the active locations link on the magellan container.
+ * @option
+ * @example 'active'
+ */
+ activeClass: 'active',
+ /**
+ * Allows the script to manipulate the url of the current page, and if supported, alter the history.
+ * @option
+ * @example true
+ */
+ deepLinking: false,
+ /**
+ * Number of pixels to offset the scroll of the page on item click if using a sticky nav bar.
+ * @option
+ * @example 25
+ */
+ barOffset: 0
+ };
+
+ // Window exports
+ Foundation.plugin(Magellan, 'Magellan');
+}(jQuery);
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+!function ($) {
+
+ /**
+ * OffCanvas module.
+ * @module foundation.offcanvas
+ * @requires foundation.util.mediaQuery
+ * @requires foundation.util.triggers
+ * @requires foundation.util.motion
+ */
+
+ var OffCanvas = function () {
+ /**
+ * Creates a new instance of an off-canvas wrapper.
+ * @class
+ * @fires OffCanvas#init
+ * @param {Object} element - jQuery object to initialize.
+ * @param {Object} options - Overrides to the default plugin settings.
+ */
+
+ function OffCanvas(element, options) {
+ _classCallCheck(this, OffCanvas);
+
+ this.$element = element;
+ this.options = $.extend({}, OffCanvas.defaults, this.$element.data(), options);
+ this.$lastTrigger = $();
+ this.$triggers = $();
+
+ this._init();
+ this._events();
+
+ Foundation.registerPlugin(this, 'OffCanvas');
+ }
+
+ /**
+ * Initializes the off-canvas wrapper by adding the exit overlay (if needed).
+ * @function
+ * @private
+ */
+
+
+ _createClass(OffCanvas, [{
+ key: '_init',
+ value: function _init() {
+ var id = this.$element.attr('id');
+
+ this.$element.attr('aria-hidden', 'true');
+
+ // Find triggers that affect this element and add aria-expanded to them
+ this.$triggers = $(document).find('[data-open="' + id + '"], [data-close="' + id + '"], [data-toggle="' + id + '"]').attr('aria-expanded', 'false').attr('aria-controls', id);
+
+ // Add a close trigger over the body if necessary
+ if (this.options.closeOnClick) {
+ if ($('.js-off-canvas-exit').length) {
+ this.$exiter = $('.js-off-canvas-exit');
+ } else {
+ var exiter = document.createElement('div');
+ exiter.setAttribute('class', 'js-off-canvas-exit');
+ $('[data-off-canvas-content]').append(exiter);
+
+ this.$exiter = $(exiter);
+ }
+ }
+
+ this.options.isRevealed = this.options.isRevealed || new RegExp(this.options.revealClass, 'g').test(this.$element[0].className);
+
+ if (this.options.isRevealed) {
+ this.options.revealOn = this.options.revealOn || this.$element[0].className.match(/(reveal-for-medium|reveal-for-large)/g)[0].split('-')[2];
+ this._setMQChecker();
+ }
+ if (!this.options.transitionTime) {
+ this.options.transitionTime = parseFloat(window.getComputedStyle($('[data-off-canvas-wrapper]')[0]).transitionDuration) * 1000;
+ }
+ }
+
+ /**
+ * Adds event handlers to the off-canvas wrapper and the exit overlay.
+ * @function
+ * @private
+ */
+
+ }, {
+ key: '_events',
+ value: function _events() {
+ this.$element.off('.zf.trigger .zf.offcanvas').on({
+ 'open.zf.trigger': this.open.bind(this),
+ 'close.zf.trigger': this.close.bind(this),
+ 'toggle.zf.trigger': this.toggle.bind(this),
+ 'keydown.zf.offcanvas': this._handleKeyboard.bind(this)
+ });
+
+ if (this.options.closeOnClick && this.$exiter.length) {
+ this.$exiter.on({ 'click.zf.offcanvas': this.close.bind(this) });
+ }
+ }
+
+ /**
+ * Applies event listener for elements that will reveal at certain breakpoints.
+ * @private
+ */
+
+ }, {
+ key: '_setMQChecker',
+ value: function _setMQChecker() {
+ var _this = this;
+
+ $(window).on('changed.zf.mediaquery', function () {
+ if (Foundation.MediaQuery.atLeast(_this.options.revealOn)) {
+ _this.reveal(true);
+ } else {
+ _this.reveal(false);
+ }
+ }).one('load.zf.offcanvas', function () {
+ if (Foundation.MediaQuery.atLeast(_this.options.revealOn)) {
+ _this.reveal(true);
+ }
+ });
+ }
+
+ /**
+ * Handles the revealing/hiding the off-canvas at breakpoints, not the same as open.
+ * @param {Boolean} isRevealed - true if element should be revealed.
+ * @function
+ */
+
+ }, {
+ key: 'reveal',
+ value: function reveal(isRevealed) {
+ var $closer = this.$element.find('[data-close]');
+ if (isRevealed) {
+ this.close();
+ this.isRevealed = true;
+ // if (!this.options.forceTop) {
+ // var scrollPos = parseInt(window.pageYOffset);
+ // this.$element[0].style.transform = 'translate(0,' + scrollPos + 'px)';
+ // }
+ // if (this.options.isSticky) { this._stick(); }
+ this.$element.off('open.zf.trigger toggle.zf.trigger');
+ if ($closer.length) {
+ $closer.hide();
+ }
+ } else {
+ this.isRevealed = false;
+ // if (this.options.isSticky || !this.options.forceTop) {
+ // this.$element[0].style.transform = '';
+ // $(window).off('scroll.zf.offcanvas');
+ // }
+ this.$element.on({
+ 'open.zf.trigger': this.open.bind(this),
+ 'toggle.zf.trigger': this.toggle.bind(this)
+ });
+ if ($closer.length) {
+ $closer.show();
+ }
+ }
+ }
+
+ /**
+ * Opens the off-canvas menu.
+ * @function
+ * @param {Object} event - Event object passed from listener.
+ * @param {jQuery} trigger - element that triggered the off-canvas to open.
+ * @fires OffCanvas#opened
+ */
+
+ }, {
+ key: 'open',
+ value: function open(event, trigger) {
+ if (this.$element.hasClass('is-open') || this.isRevealed) {
+ return;
+ }
+ var _this = this,
+ $body = $(document.body);
+
+ if (this.options.forceTop) {
+ $('body').scrollTop(0);
+ }
+ // window.pageYOffset = 0;
+
+ // if (!this.options.forceTop) {
+ // var scrollPos = parseInt(window.pageYOffset);
+ // this.$element[0].style.transform = 'translate(0,' + scrollPos + 'px)';
+ // if (this.$exiter.length) {
+ // this.$exiter[0].style.transform = 'translate(0,' + scrollPos + 'px)';
+ // }
+ // }
+ /**
+ * Fires when the off-canvas menu opens.
+ * @event OffCanvas#opened
+ */
+ Foundation.Move(this.options.transitionTime, this.$element, function () {
+ $('[data-off-canvas-wrapper]').addClass('is-off-canvas-open is-open-' + _this.options.position);
+
+ _this.$element.addClass('is-open');
+
+ // if (_this.options.isSticky) {
+ // _this._stick();
+ // }
+ });
+
+ this.$triggers.attr('aria-expanded', 'true');
+ this.$element.attr('aria-hidden', 'false').trigger('opened.zf.offcanvas');
+
+ if (this.options.closeOnClick) {
+ this.$exiter.addClass('is-visible');
+ }
+
+ if (trigger) {
+ this.$lastTrigger = trigger;
+ }
+
+ if (this.options.autoFocus) {
+ this.$element.one(Foundation.transitionend(this.$element), function () {
+ _this.$element.find('a, button').eq(0).focus();
+ });
+ }
+
+ if (this.options.trapFocus) {
+ $('[data-off-canvas-content]').attr('tabindex', '-1');
+ this._trapFocus();
+ }
+ }
+
+ /**
+ * Traps focus within the offcanvas on open.
+ * @private
+ */
+
+ }, {
+ key: '_trapFocus',
+ value: function _trapFocus() {
+ var focusable = Foundation.Keyboard.findFocusable(this.$element),
+ first = focusable.eq(0),
+ last = focusable.eq(-1);
+
+ focusable.off('.zf.offcanvas').on('keydown.zf.offcanvas', function (e) {
+ if (e.which === 9 || e.keycode === 9) {
+ if (e.target === last[0] && !e.shiftKey) {
+ e.preventDefault();
+ first.focus();
+ }
+ if (e.target === first[0] && e.shiftKey) {
+ e.preventDefault();
+ last.focus();
+ }
+ }
+ });
+ }
+
+ /**
+ * Allows the offcanvas to appear sticky utilizing translate properties.
+ * @private
+ */
+ // OffCanvas.prototype._stick = function() {
+ // var elStyle = this.$element[0].style;
+ //
+ // if (this.options.closeOnClick) {
+ // var exitStyle = this.$exiter[0].style;
+ // }
+ //
+ // $(window).on('scroll.zf.offcanvas', function(e) {
+ // console.log(e);
+ // var pageY = window.pageYOffset;
+ // elStyle.transform = 'translate(0,' + pageY + 'px)';
+ // if (exitStyle !== undefined) { exitStyle.transform = 'translate(0,' + pageY + 'px)'; }
+ // });
+ // // this.$element.trigger('stuck.zf.offcanvas');
+ // };
+ /**
+ * Closes the off-canvas menu.
+ * @function
+ * @param {Function} cb - optional cb to fire after closure.
+ * @fires OffCanvas#closed
+ */
+
+ }, {
+ key: 'close',
+ value: function close(cb) {
+ if (!this.$element.hasClass('is-open') || this.isRevealed) {
+ return;
+ }
+
+ var _this = this;
+
+ // Foundation.Move(this.options.transitionTime, this.$element, function() {
+ $('[data-off-canvas-wrapper]').removeClass('is-off-canvas-open is-open-' + _this.options.position);
+ _this.$element.removeClass('is-open');
+ // Foundation._reflow();
+ // });
+ this.$element.attr('aria-hidden', 'true')
+ /**
+ * Fires when the off-canvas menu opens.
+ * @event OffCanvas#closed
+ */
+ .trigger('closed.zf.offcanvas');
+ // if (_this.options.isSticky || !_this.options.forceTop) {
+ // setTimeout(function() {
+ // _this.$element[0].style.transform = '';
+ // $(window).off('scroll.zf.offcanvas');
+ // }, this.options.transitionTime);
+ // }
+ if (this.options.closeOnClick) {
+ this.$exiter.removeClass('is-visible');
+ }
+
+ this.$triggers.attr('aria-expanded', 'false');
+ if (this.options.trapFocus) {
+ $('[data-off-canvas-content]').removeAttr('tabindex');
+ }
+ }
+
+ /**
+ * Toggles the off-canvas menu open or closed.
+ * @function
+ * @param {Object} event - Event object passed from listener.
+ * @param {jQuery} trigger - element that triggered the off-canvas to open.
+ */
+
+ }, {
+ key: 'toggle',
+ value: function toggle(event, trigger) {
+ if (this.$element.hasClass('is-open')) {
+ this.close(event, trigger);
+ } else {
+ this.open(event, trigger);
+ }
+ }
+
+ /**
+ * Handles keyboard input when detected. When the escape key is pressed, the off-canvas menu closes, and focus is restored to the element that opened the menu.
+ * @function
+ * @private
+ */
+
+ }, {
+ key: '_handleKeyboard',
+ value: function _handleKeyboard(event) {
+ if (event.which !== 27) return;
+
+ event.stopPropagation();
+ event.preventDefault();
+ this.close();
+ this.$lastTrigger.focus();
+ }
+
+ /**
+ * Destroys the offcanvas plugin.
+ * @function
+ */
+
+ }, {
+ key: 'destroy',
+ value: function destroy() {
+ this.close();
+ this.$element.off('.zf.trigger .zf.offcanvas');
+ this.$exiter.off('.zf.offcanvas');
+
+ Foundation.unregisterPlugin(this);
+ }
+ }]);
+
+ return OffCanvas;
+ }();
+
+ OffCanvas.defaults = {
+ /**
+ * Allow the user to click outside of the menu to close it.
+ * @option
+ * @example true
+ */
+ closeOnClick: true,
+
+ /**
+ * Amount of time in ms the open and close transition requires. If none selected, pulls from body style.
+ * @option
+ * @example 500
+ */
+ transitionTime: 0,
+
+ /**
+ * Direction the offcanvas opens from. Determines class applied to body.
+ * @option
+ * @example left
+ */
+ position: 'left',
+
+ /**
+ * Force the page to scroll to top on open.
+ * @option
+ * @example true
+ */
+ forceTop: true,
+
+ /**
+ * Allow the offcanvas to remain open for certain breakpoints.
+ * @option
+ * @example false
+ */
+ isRevealed: false,
+
+ /**
+ * Breakpoint at which to reveal. JS will use a RegExp to target standard classes, if changing classnames, pass your class with the `revealClass` option.
+ * @option
+ * @example reveal-for-large
+ */
+ revealOn: null,
+
+ /**
+ * Force focus to the offcanvas on open. If true, will focus the opening trigger on close.
+ * @option
+ * @example true
+ */
+ autoFocus: true,
+
+ /**
+ * Class used to force an offcanvas to remain open. Foundation defaults for this are `reveal-for-large` & `reveal-for-medium`.
+ * @option
+ * TODO improve the regex testing for this.
+ * @example reveal-for-large
+ */
+ revealClass: 'reveal-for-',
+
+ /**
+ * Triggers optional focus trapping when opening an offcanvas. Sets tabindex of [data-off-canvas-content] to -1 for accessibility purposes.
+ * @option
+ * @example true
+ */
+ trapFocus: false
+ };
+
+ // Window exports
+ Foundation.plugin(OffCanvas, 'OffCanvas');
+}(jQuery);
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+!function ($) {
+
+ /**
+ * Orbit module.
+ * @module foundation.orbit
+ * @requires foundation.util.keyboard
+ * @requires foundation.util.motion
+ * @requires foundation.util.timerAndImageLoader
+ * @requires foundation.util.touch
+ */
+
+ var Orbit = function () {
+ /**
+ * Creates a new instance of an orbit carousel.
+ * @class
+ * @param {jQuery} element - jQuery object to make into an Orbit Carousel.
+ * @param {Object} options - Overrides to the default plugin settings.
+ */
+
+ function Orbit(element, options) {
+ _classCallCheck(this, Orbit);
+
+ this.$element = element;
+ this.options = $.extend({}, Orbit.defaults, this.$element.data(), options);
+
+ this._init();
+
+ Foundation.registerPlugin(this, 'Orbit');
+ Foundation.Keyboard.register('Orbit', {
+ 'ltr': {
+ 'ARROW_RIGHT': 'next',
+ 'ARROW_LEFT': 'previous'
+ },
+ 'rtl': {
+ 'ARROW_LEFT': 'next',
+ 'ARROW_RIGHT': 'previous'
+ }
+ });
+ }
+
+ /**
+ * Initializes the plugin by creating jQuery collections, setting attributes, and starting the animation.
+ * @function
+ * @private
+ */
+
+
+ _createClass(Orbit, [{
+ key: '_init',
+ value: function _init() {
+ this.$wrapper = this.$element.find('.' + this.options.containerClass);
+ this.$slides = this.$element.find('.' + this.options.slideClass);
+ var $images = this.$element.find('img'),
+ initActive = this.$slides.filter('.is-active');
+
+ if (!initActive.length) {
+ this.$slides.eq(0).addClass('is-active');
+ }
+
+ if (!this.options.useMUI) {
+ this.$slides.addClass('no-motionui');
+ }
+
+ if ($images.length) {
+ Foundation.onImagesLoaded($images, this._prepareForOrbit.bind(this));
+ } else {
+ this._prepareForOrbit(); //hehe
+ }
+
+ if (this.options.bullets) {
+ this._loadBullets();
+ }
+
+ this._events();
+
+ if (this.options.autoPlay && this.$slides.length > 1) {
+ this.geoSync();
+ }
+
+ if (this.options.accessible) {
+ // allow wrapper to be focusable to enable arrow navigation
+ this.$wrapper.attr('tabindex', 0);
+ }
+ }
+
+ /**
+ * Creates a jQuery collection of bullets, if they are being used.
+ * @function
+ * @private
+ */
+
+ }, {
+ key: '_loadBullets',
+ value: function _loadBullets() {
+ this.$bullets = this.$element.find('.' + this.options.boxOfBullets).find('button');
+ }
+
+ /**
+ * Sets a `timer` object on the orbit, and starts the counter for the next slide.
+ * @function
+ */
+
+ }, {
+ key: 'geoSync',
+ value: function geoSync() {
+ var _this = this;
+ this.timer = new Foundation.Timer(this.$element, {
+ duration: this.options.timerDelay,
+ infinite: false
+ }, function () {
+ _this.changeSlide(true);
+ });
+ this.timer.start();
+ }
+
+ /**
+ * Sets wrapper and slide heights for the orbit.
+ * @function
+ * @private
+ */
+
+ }, {
+ key: '_prepareForOrbit',
+ value: function _prepareForOrbit() {
+ var _this = this;
+ this._setWrapperHeight(function (max) {
+ _this._setSlideHeight(max);
+ });
+ }
+
+ /**
+ * Calulates the height of each slide in the collection, and uses the tallest one for the wrapper height.
+ * @function
+ * @private
+ * @param {Function} cb - a callback function to fire when complete.
+ */
+
+ }, {
+ key: '_setWrapperHeight',
+ value: function _setWrapperHeight(cb) {
+ //rewrite this to `for` loop
+ var max = 0,
+ temp,
+ counter = 0;
+
+ this.$slides.each(function () {
+ temp = this.getBoundingClientRect().height;
+ $(this).attr('data-slide', counter);
+
+ if (counter) {
+ //if not the first slide, set css position and display property
+ $(this).css({ 'position': 'relative', 'display': 'none' });
+ }
+ max = temp > max ? temp : max;
+ counter++;
+ });
+
+ if (counter === this.$slides.length) {
+ this.$wrapper.css({ 'height': max }); //only change the wrapper height property once.
+ cb(max); //fire callback with max height dimension.
+ }
+ }
+
+ /**
+ * Sets the max-height of each slide.
+ * @function
+ * @private
+ */
+
+ }, {
+ key: '_setSlideHeight',
+ value: function _setSlideHeight(height) {
+ this.$slides.each(function () {
+ $(this).css('max-height', height);
+ });
+ }
+
+ /**
+ * Adds event listeners to basically everything within the element.
+ * @function
+ * @private
+ */
+
+ }, {
+ key: '_events',
+ value: function _events() {
+ var _this = this;
+
+ //***************************************
+ //**Now using custom event - thanks to:**
+ //** Yohai Ararat of Toronto **
+ //***************************************
+ if (this.$slides.length > 1) {
+
+ if (this.options.swipe) {
+ this.$slides.off('swipeleft.zf.orbit swiperight.zf.orbit').on('swipeleft.zf.orbit', function (e) {
+ e.preventDefault();
+ _this.changeSlide(true);
+ }).on('swiperight.zf.orbit', function (e) {
+ e.preventDefault();
+ _this.changeSlide(false);
+ });
+ }
+ //***************************************
+
+ if (this.options.autoPlay) {
+ this.$slides.on('click.zf.orbit', function () {
+ _this.$element.data('clickedOn', _this.$element.data('clickedOn') ? false : true);
+ _this.timer[_this.$element.data('clickedOn') ? 'pause' : 'start']();
+ });
+
+ if (this.options.pauseOnHover) {
+ this.$element.on('mouseenter.zf.orbit', function () {
+ _this.timer.pause();
+ }).on('mouseleave.zf.orbit', function () {
+ if (!_this.$element.data('clickedOn')) {
+ _this.timer.start();
+ }
+ });
+ }
+ }
+
+ if (this.options.navButtons) {
+ var $controls = this.$element.find('.' + this.options.nextClass + ', .' + this.options.prevClass);
+ $controls.attr('tabindex', 0)
+ //also need to handle enter/return and spacebar key presses
+ .on('click.zf.orbit touchend.zf.orbit', function (e) {
+ e.preventDefault();
+ _this.changeSlide($(this).hasClass(_this.options.nextClass));
+ });
+ }
+
+ if (this.options.bullets) {
+ this.$bullets.on('click.zf.orbit touchend.zf.orbit', function () {
+ if (/is-active/g.test(this.className)) {
+ return false;
+ } //if this is active, kick out of function.
+ var idx = $(this).data('slide'),
+ ltr = idx > _this.$slides.filter('.is-active').data('slide'),
+ $slide = _this.$slides.eq(idx);
+
+ _this.changeSlide(ltr, $slide, idx);
+ });
+ }
+
+ this.$wrapper.add(this.$bullets).on('keydown.zf.orbit', function (e) {
+ // handle keyboard event with keyboard util
+ Foundation.Keyboard.handleKey(e, 'Orbit', {
+ next: function () {
+ _this.changeSlide(true);
+ },
+ previous: function () {
+ _this.changeSlide(false);
+ },
+ handled: function () {
+ // if bullet is focused, make sure focus moves
+ if ($(e.target).is(_this.$bullets)) {
+ _this.$bullets.filter('.is-active').focus();
+ }
+ }
+ });
+ });
+ }
+ }
+
+ /**
+ * Changes the current slide to a new one.
+ * @function
+ * @param {Boolean} isLTR - flag if the slide should move left to right.
+ * @param {jQuery} chosenSlide - the jQuery element of the slide to show next, if one is selected.
+ * @param {Number} idx - the index of the new slide in its collection, if one chosen.
+ * @fires Orbit#slidechange
+ */
+
+ }, {
+ key: 'changeSlide',
+ value: function changeSlide(isLTR, chosenSlide, idx) {
+ var $curSlide = this.$slides.filter('.is-active').eq(0);
+
+ if (/mui/g.test($curSlide[0].className)) {
+ return false;
+ } //if the slide is currently animating, kick out of the function
+
+ var $firstSlide = this.$slides.first(),
+ $lastSlide = this.$slides.last(),
+ dirIn = isLTR ? 'Right' : 'Left',
+ dirOut = isLTR ? 'Left' : 'Right',
+ _this = this,
+ $newSlide;
+
+ if (!chosenSlide) {
+ //most of the time, this will be auto played or clicked from the navButtons.
+ $newSlide = isLTR ? //if wrapping enabled, check to see if there is a `next` or `prev` sibling, if not, select the first or last slide to fill in. if wrapping not enabled, attempt to select `next` or `prev`, if there's nothing there, the function will kick out on next step. CRAZY NESTED TERNARIES!!!!!
+ this.options.infiniteWrap ? $curSlide.next('.' + this.options.slideClass).length ? $curSlide.next('.' + this.options.slideClass) : $firstSlide : $curSlide.next('.' + this.options.slideClass) : //pick next slide if moving left to right
+ this.options.infiniteWrap ? $curSlide.prev('.' + this.options.slideClass).length ? $curSlide.prev('.' + this.options.slideClass) : $lastSlide : $curSlide.prev('.' + this.options.slideClass); //pick prev slide if moving right to left
+ } else {
+ $newSlide = chosenSlide;
+ }
+
+ if ($newSlide.length) {
+ if (this.options.bullets) {
+ idx = idx || this.$slides.index($newSlide); //grab index to update bullets
+ this._updateBullets(idx);
+ }
+
+ if (this.options.useMUI) {
+ Foundation.Motion.animateIn($newSlide.addClass('is-active').css({ 'position': 'absolute', 'top': 0 }), this.options['animInFrom' + dirIn], function () {
+ $newSlide.css({ 'position': 'relative', 'display': 'block' }).attr('aria-live', 'polite');
+ });
+
+ Foundation.Motion.animateOut($curSlide.removeClass('is-active'), this.options['animOutTo' + dirOut], function () {
+ $curSlide.removeAttr('aria-live');
+ if (_this.options.autoPlay && !_this.timer.isPaused) {
+ _this.timer.restart();
+ }
+ //do stuff?
+ });
+ } else {
+ $curSlide.removeClass('is-active is-in').removeAttr('aria-live').hide();
+ $newSlide.addClass('is-active is-in').attr('aria-live', 'polite').show();
+ if (this.options.autoPlay && !this.timer.isPaused) {
+ this.timer.restart();
+ }
+ }
+ /**
+ * Triggers when the slide has finished animating in.
+ * @event Orbit#slidechange
+ */
+ this.$element.trigger('slidechange.zf.orbit', [$newSlide]);
+ }
+ }
+
+ /**
+ * Updates the active state of the bullets, if displayed.
+ * @function
+ * @private
+ * @param {Number} idx - the index of the current slide.
+ */
+
+ }, {
+ key: '_updateBullets',
+ value: function _updateBullets(idx) {
+ var $oldBullet = this.$element.find('.' + this.options.boxOfBullets).find('.is-active').removeClass('is-active').blur(),
+ span = $oldBullet.find('span:last').detach(),
+ $newBullet = this.$bullets.eq(idx).addClass('is-active').append(span);
+ }
+
+ /**
+ * Destroys the carousel and hides the element.
+ * @function
+ */
+
+ }, {
+ key: 'destroy',
+ value: function destroy() {
+ this.$element.off('.zf.orbit').find('*').off('.zf.orbit').end().hide();
+ Foundation.unregisterPlugin(this);
+ }
+ }]);
+
+ return Orbit;
+ }();
+
+ Orbit.defaults = {
+ /**
+ * Tells the JS to look for and loadBullets.
+ * @option
+ * @example true
+ */
+ bullets: true,
+ /**
+ * Tells the JS to apply event listeners to nav buttons
+ * @option
+ * @example true
+ */
+ navButtons: true,
+ /**
+ * motion-ui animation class to apply
+ * @option
+ * @example 'slide-in-right'
+ */
+ animInFromRight: 'slide-in-right',
+ /**
+ * motion-ui animation class to apply
+ * @option
+ * @example 'slide-out-right'
+ */
+ animOutToRight: 'slide-out-right',
+ /**
+ * motion-ui animation class to apply
+ * @option
+ * @example 'slide-in-left'
+ *
+ */
+ animInFromLeft: 'slide-in-left',
+ /**
+ * motion-ui animation class to apply
+ * @option
+ * @example 'slide-out-left'
+ */
+ animOutToLeft: 'slide-out-left',
+ /**
+ * Allows Orbit to automatically animate on page load.
+ * @option
+ * @example true
+ */
+ autoPlay: true,
+ /**
+ * Amount of time, in ms, between slide transitions
+ * @option
+ * @example 5000
+ */
+ timerDelay: 5000,
+ /**
+ * Allows Orbit to infinitely loop through the slides
+ * @option
+ * @example true
+ */
+ infiniteWrap: true,
+ /**
+ * Allows the Orbit slides to bind to swipe events for mobile, requires an additional util library
+ * @option
+ * @example true
+ */
+ swipe: true,
+ /**
+ * Allows the timing function to pause animation on hover.
+ * @option
+ * @example true
+ */
+ pauseOnHover: true,
+ /**
+ * Allows Orbit to bind keyboard events to the slider, to animate frames with arrow keys
+ * @option
+ * @example true
+ */
+ accessible: true,
+ /**
+ * Class applied to the container of Orbit
+ * @option
+ * @example 'orbit-container'
+ */
+ containerClass: 'orbit-container',
+ /**
+ * Class applied to individual slides.
+ * @option
+ * @example 'orbit-slide'
+ */
+ slideClass: 'orbit-slide',
+ /**
+ * Class applied to the bullet container. You're welcome.
+ * @option
+ * @example 'orbit-bullets'
+ */
+ boxOfBullets: 'orbit-bullets',
+ /**
+ * Class applied to the `next` navigation button.
+ * @option
+ * @example 'orbit-next'
+ */
+ nextClass: 'orbit-next',
+ /**
+ * Class applied to the `previous` navigation button.
+ * @option
+ * @example 'orbit-previous'
+ */
+ prevClass: 'orbit-previous',
+ /**
+ * Boolean to flag the js to use motion ui classes or not. Default to true for backwards compatability.
+ * @option
+ * @example true
+ */
+ useMUI: true
+ };
+
+ // Window exports
+ Foundation.plugin(Orbit, 'Orbit');
+}(jQuery);
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+!function ($) {
+
+ /**
+ * ResponsiveMenu module.
+ * @module foundation.responsiveMenu
+ * @requires foundation.util.triggers
+ * @requires foundation.util.mediaQuery
+ * @requires foundation.util.accordionMenu
+ * @requires foundation.util.drilldown
+ * @requires foundation.util.dropdown-menu
+ */
+
+ var ResponsiveMenu = function () {
+ /**
+ * Creates a new instance of a responsive menu.
+ * @class
+ * @fires ResponsiveMenu#init
+ * @param {jQuery} element - jQuery object to make into a dropdown menu.
+ * @param {Object} options - Overrides to the default plugin settings.
+ */
+
+ function ResponsiveMenu(element, options) {
+ _classCallCheck(this, ResponsiveMenu);
+
+ this.$element = $(element);
+ this.rules = this.$element.data('responsive-menu');
+ this.currentMq = null;
+ this.currentPlugin = null;
+
+ this._init();
+ this._events();
+
+ Foundation.registerPlugin(this, 'ResponsiveMenu');
+ }
+
+ /**
+ * Initializes the Menu by parsing the classes from the 'data-ResponsiveMenu' attribute on the element.
+ * @function
+ * @private
+ */
+
+
+ _createClass(ResponsiveMenu, [{
+ key: '_init',
+ value: function _init() {
+ // The first time an Interchange plugin is initialized, this.rules is converted from a string of "classes" to an object of rules
+ if (typeof this.rules === 'string') {
+ var rulesTree = {};
+
+ // Parse rules from "classes" pulled from data attribute
+ var rules = this.rules.split(' ');
+
+ // Iterate through every rule found
+ for (var i = 0; i < rules.length; i++) {
+ var rule = rules[i].split('-');
+ var ruleSize = rule.length > 1 ? rule[0] : 'small';
+ var rulePlugin = rule.length > 1 ? rule[1] : rule[0];
+
+ if (MenuPlugins[rulePlugin] !== null) {
+ rulesTree[ruleSize] = MenuPlugins[rulePlugin];
+ }
+ }
+
+ this.rules = rulesTree;
+ }
+
+ if (!$.isEmptyObject(this.rules)) {
+ this._checkMediaQueries();
+ }
+ }
+
+ /**
+ * Initializes events for the Menu.
+ * @function
+ * @private
+ */
+
+ }, {
+ key: '_events',
+ value: function _events() {
+ var _this = this;
+
+ $(window).on('changed.zf.mediaquery', function () {
+ _this._checkMediaQueries();
+ });
+ // $(window).on('resize.zf.ResponsiveMenu', function() {
+ // _this._checkMediaQueries();
+ // });
+ }
+
+ /**
+ * Checks the current screen width against available media queries. If the media query has changed, and the plugin needed has changed, the plugins will swap out.
+ * @function
+ * @private
+ */
+
+ }, {
+ key: '_checkMediaQueries',
+ value: function _checkMediaQueries() {
+ var matchedMq,
+ _this = this;
+ // Iterate through each rule and find the last matching rule
+ $.each(this.rules, function (key) {
+ if (Foundation.MediaQuery.atLeast(key)) {
+ matchedMq = key;
+ }
+ });
+
+ // No match? No dice
+ if (!matchedMq) return;
+
+ // Plugin already initialized? We good
+ if (this.currentPlugin instanceof this.rules[matchedMq].plugin) return;
+
+ // Remove existing plugin-specific CSS classes
+ $.each(MenuPlugins, function (key, value) {
+ _this.$element.removeClass(value.cssClass);
+ });
+
+ // Add the CSS class for the new plugin
+ this.$element.addClass(this.rules[matchedMq].cssClass);
+
+ // Create an instance of the new plugin
+ if (this.currentPlugin) this.currentPlugin.destroy();
+ this.currentPlugin = new this.rules[matchedMq].plugin(this.$element, {});
+ }
+
+ /**
+ * Destroys the instance of the current plugin on this element, as well as the window resize handler that switches the plugins out.
+ * @function
+ */
+
+ }, {
+ key: 'destroy',
+ value: function destroy() {
+ this.currentPlugin.destroy();
+ $(window).off('.zf.ResponsiveMenu');
+ Foundation.unregisterPlugin(this);
+ }
+ }]);
+
+ return ResponsiveMenu;
+ }();
+
+ ResponsiveMenu.defaults = {};
+
+ // The plugin matches the plugin classes with these plugin instances.
+ var MenuPlugins = {
+ dropdown: {
+ cssClass: 'dropdown',
+ plugin: Foundation._plugins['dropdown-menu'] || null
+ },
+ drilldown: {
+ cssClass: 'drilldown',
+ plugin: Foundation._plugins['drilldown'] || null
+ },
+ accordion: {
+ cssClass: 'accordion-menu',
+ plugin: Foundation._plugins['accordion-menu'] || null
+ }
+ };
+
+ // Window exports
+ Foundation.plugin(ResponsiveMenu, 'ResponsiveMenu');
+}(jQuery);
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+!function ($) {
+
+ /**
+ * ResponsiveToggle module.
+ * @module foundation.responsiveToggle
+ * @requires foundation.util.mediaQuery
+ */
+
+ var ResponsiveToggle = function () {
+ /**
+ * Creates a new instance of Tab Bar.
+ * @class
+ * @fires ResponsiveToggle#init
+ * @param {jQuery} element - jQuery object to attach tab bar functionality to.
+ * @param {Object} options - Overrides to the default plugin settings.
+ */
+
+ function ResponsiveToggle(element, options) {
+ _classCallCheck(this, ResponsiveToggle);
+
+ this.$element = $(element);
+ this.options = $.extend({}, ResponsiveToggle.defaults, this.$element.data(), options);
+
+ this._init();
+ this._events();
+
+ Foundation.registerPlugin(this, 'ResponsiveToggle');
+ }
+
+ /**
+ * Initializes the tab bar by finding the target element, toggling element, and running update().
+ * @function
+ * @private
+ */
+
+
+ _createClass(ResponsiveToggle, [{
+ key: '_init',
+ value: function _init() {
+ var targetID = this.$element.data('responsive-toggle');
+ if (!targetID) {
+ console.error('Your tab bar needs an ID of a Menu as the value of data-tab-bar.');
+ }
+
+ this.$targetMenu = $('#' + targetID);
+ this.$toggler = this.$element.find('[data-toggle]');
+
+ this._update();
+ }
+
+ /**
+ * Adds necessary event handlers for the tab bar to work.
+ * @function
+ * @private
+ */
+
+ }, {
+ key: '_events',
+ value: function _events() {
+ var _this = this;
+
+ this._updateMqHandler = this._update.bind(this);
+
+ $(window).on('changed.zf.mediaquery', this._updateMqHandler);
+
+ this.$toggler.on('click.zf.responsiveToggle', this.toggleMenu.bind(this));
+ }
+
+ /**
+ * Checks the current media query to determine if the tab bar should be visible or hidden.
+ * @function
+ * @private
+ */
+
+ }, {
+ key: '_update',
+ value: function _update() {
+ // Mobile
+ if (!Foundation.MediaQuery.atLeast(this.options.hideFor)) {
+ this.$element.show();
+ this.$targetMenu.hide();
+ }
+
+ // Desktop
+ else {
+ this.$element.hide();
+ this.$targetMenu.show();
+ }
+ }
+
+ /**
+ * Toggles the element attached to the tab bar. The toggle only happens if the screen is small enough to allow it.
+ * @function
+ * @fires ResponsiveToggle#toggled
+ */
+
+ }, {
+ key: 'toggleMenu',
+ value: function toggleMenu() {
+ if (!Foundation.MediaQuery.atLeast(this.options.hideFor)) {
+ this.$targetMenu.toggle(0);
+
+ /**
+ * Fires when the element attached to the tab bar toggles.
+ * @event ResponsiveToggle#toggled
+ */
+ this.$element.trigger('toggled.zf.responsiveToggle');
+ }
+ }
+ }, {
+ key: 'destroy',
+ value: function destroy() {
+ this.$element.off('.zf.responsiveToggle');
+ this.$toggler.off('.zf.responsiveToggle');
+
+ $(window).off('changed.zf.mediaquery', this._updateMqHandler);
+
+ Foundation.unregisterPlugin(this);
+ }
+ }]);
+
+ return ResponsiveToggle;
+ }();
+
+ ResponsiveToggle.defaults = {
+ /**
+ * The breakpoint after which the menu is always shown, and the tab bar is hidden.
+ * @option
+ * @example 'medium'
+ */
+ hideFor: 'medium'
+ };
+
+ // Window exports
+ Foundation.plugin(ResponsiveToggle, 'ResponsiveToggle');
+}(jQuery);
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+!function ($) {
+
+ /**
+ * Reveal module.
+ * @module foundation.reveal
+ * @requires foundation.util.keyboard
+ * @requires foundation.util.box
+ * @requires foundation.util.triggers
+ * @requires foundation.util.mediaQuery
+ * @requires foundation.util.motion if using animations
+ */
+
+ var Reveal = function () {
+ /**
+ * Creates a new instance of Reveal.
+ * @class
+ * @param {jQuery} element - jQuery object to use for the modal.
+ * @param {Object} options - optional parameters.
+ */
+
+ function Reveal(element, options) {
+ _classCallCheck(this, Reveal);
+
+ this.$element = element;
+ this.options = $.extend({}, Reveal.defaults, this.$element.data(), options);
+ this._init();
+
+ Foundation.registerPlugin(this, 'Reveal');
+ Foundation.Keyboard.register('Reveal', {
+ 'ENTER': 'open',
+ 'SPACE': 'open',
+ 'ESCAPE': 'close',
+ 'TAB': 'tab_forward',
+ 'SHIFT_TAB': 'tab_backward'
+ });
+ }
+
+ /**
+ * Initializes the modal by adding the overlay and close buttons, (if selected).
+ * @private
+ */
+
+
+ _createClass(Reveal, [{
+ key: '_init',
+ value: function _init() {
+ this.id = this.$element.attr('id');
+ this.isActive = false;
+ this.cached = { mq: Foundation.MediaQuery.current };
+ this.isMobile = mobileSniff();
+
+ this.$anchor = $('[data-open="' + this.id + '"]').length ? $('[data-open="' + this.id + '"]') : $('[data-toggle="' + this.id + '"]');
+ this.$anchor.attr({
+ 'aria-controls': this.id,
+ 'aria-haspopup': true,
+ 'tabindex': 0
+ });
+
+ if (this.options.fullScreen || this.$element.hasClass('full')) {
+ this.options.fullScreen = true;
+ this.options.overlay = false;
+ }
+ if (this.options.overlay && !this.$overlay) {
+ this.$overlay = this._makeOverlay(this.id);
+ }
+
+ this.$element.attr({
+ 'role': 'dialog',
+ 'aria-hidden': true,
+ 'data-yeti-box': this.id,
+ 'data-resize': this.id
+ });
+
+ if (this.$overlay) {
+ this.$element.detach().appendTo(this.$overlay);
+ } else {
+ this.$element.detach().appendTo($('body'));
+ this.$element.addClass('without-overlay');
+ }
+ this._events();
+ if (this.options.deepLink && window.location.hash === '#' + this.id) {
+ $(window).one('load.zf.reveal', this.open.bind(this));
+ }
+ }
+
+ /**
+ * Creates an overlay div to display behind the modal.
+ * @private
+ */
+
+ }, {
+ key: '_makeOverlay',
+ value: function _makeOverlay(id) {
+ var $overlay = $('
').addClass('reveal-overlay').appendTo('body');
+ return $overlay;
+ }
+
+ /**
+ * Updates position of modal
+ * TODO: Figure out if we actually need to cache these values or if it doesn't matter
+ * @private
+ */
+
+ }, {
+ key: '_updatePosition',
+ value: function _updatePosition() {
+ var width = this.$element.outerWidth();
+ var outerWidth = $(window).width();
+ var height = this.$element.outerHeight();
+ var outerHeight = $(window).height();
+ var left, top;
+ if (this.options.hOffset === 'auto') {
+ left = parseInt((outerWidth - width) / 2, 10);
+ } else {
+ left = parseInt(this.options.hOffset, 10);
+ }
+ if (this.options.vOffset === 'auto') {
+ if (height > outerHeight) {
+ top = parseInt(Math.min(100, outerHeight / 10), 10);
+ } else {
+ top = parseInt((outerHeight - height) / 4, 10);
+ }
+ } else {
+ top = parseInt(this.options.vOffset, 10);
+ }
+ this.$element.css({ top: top + 'px' });
+ // only worry about left if we don't have an overlay or we havea horizontal offset,
+ // otherwise we're perfectly in the middle
+ if (!this.$overlay || this.options.hOffset !== 'auto') {
+ this.$element.css({ left: left + 'px' });
+ this.$element.css({ margin: '0px' });
+ }
+ }
+
+ /**
+ * Adds event handlers for the modal.
+ * @private
+ */
+
+ }, {
+ key: '_events',
+ value: function _events() {
+ var _this2 = this;
+
+ var _this = this;
+
+ this.$element.on({
+ 'open.zf.trigger': this.open.bind(this),
+ 'close.zf.trigger': function (event, $element) {
+ if (event.target === _this.$element[0] || $(event.target).parents('[data-closable]')[0] === $element) {
+ // only close reveal when it's explicitly called
+ return _this2.close.apply(_this2);
+ }
+ },
+ 'toggle.zf.trigger': this.toggle.bind(this),
+ 'resizeme.zf.trigger': function () {
+ _this._updatePosition();
+ }
+ });
+
+ if (this.$anchor.length) {
+ this.$anchor.on('keydown.zf.reveal', function (e) {
+ if (e.which === 13 || e.which === 32) {
+ e.stopPropagation();
+ e.preventDefault();
+ _this.open();
+ }
+ });
+ }
+
+ if (this.options.closeOnClick && this.options.overlay) {
+ this.$overlay.off('.zf.reveal').on('click.zf.reveal', function (e) {
+ if (e.target === _this.$element[0] || $.contains(_this.$element[0], e.target)) {
+ return;
+ }
+ _this.close();
+ });
+ }
+ if (this.options.deepLink) {
+ $(window).on('popstate.zf.reveal:' + this.id, this._handleState.bind(this));
+ }
+ }
+
+ /**
+ * Handles modal methods on back/forward button clicks or any other event that triggers popstate.
+ * @private
+ */
+
+ }, {
+ key: '_handleState',
+ value: function _handleState(e) {
+ if (window.location.hash === '#' + this.id && !this.isActive) {
+ this.open();
+ } else {
+ this.close();
+ }
+ }
+
+ /**
+ * Opens the modal controlled by `this.$anchor`, and closes all others by default.
+ * @function
+ * @fires Reveal#closeme
+ * @fires Reveal#open
+ */
+
+ }, {
+ key: 'open',
+ value: function open() {
+ var _this3 = this;
+
+ if (this.options.deepLink) {
+ var hash = '#' + this.id;
+
+ if (window.history.pushState) {
+ window.history.pushState(null, null, hash);
+ } else {
+ window.location.hash = hash;
+ }
+ }
+
+ this.isActive = true;
+
+ // Make elements invisible, but remove display: none so we can get size and positioning
+ this.$element.css({ 'visibility': 'hidden' }).show().scrollTop(0);
+ if (this.options.overlay) {
+ this.$overlay.css({ 'visibility': 'hidden' }).show();
+ }
+
+ this._updatePosition();
+
+ this.$element.hide().css({ 'visibility': '' });
+
+ if (this.$overlay) {
+ this.$overlay.css({ 'visibility': '' }).hide();
+ if (this.$element.hasClass('fast')) {
+ this.$overlay.addClass('fast');
+ } else if (this.$element.hasClass('slow')) {
+ this.$overlay.addClass('slow');
+ }
+ }
+
+ if (!this.options.multipleOpened) {
+ /**
+ * Fires immediately before the modal opens.
+ * Closes any other modals that are currently open
+ * @event Reveal#closeme
+ */
+ this.$element.trigger('closeme.zf.reveal', this.id);
+ }
+ // Motion UI method of reveal
+ if (this.options.animationIn) {
+ var _this;
+
+ (function () {
+ var afterAnimationFocus = function () {
+ _this.$element.attr({
+ 'aria-hidden': false,
+ 'tabindex': -1
+ }).focus();
+ console.log('focus');
+ };
+
+ _this = _this3;
+
+ if (_this3.options.overlay) {
+ Foundation.Motion.animateIn(_this3.$overlay, 'fade-in');
+ }
+ Foundation.Motion.animateIn(_this3.$element, _this3.options.animationIn, function () {
+ _this3.focusableElements = Foundation.Keyboard.findFocusable(_this3.$element);
+ afterAnimationFocus();
+ });
+ })();
+ }
+ // jQuery method of reveal
+ else {
+ if (this.options.overlay) {
+ this.$overlay.show(0);
+ }
+ this.$element.show(this.options.showDelay);
+ }
+
+ // handle accessibility
+ this.$element.attr({
+ 'aria-hidden': false,
+ 'tabindex': -1
+ }).focus();
+
+ /**
+ * Fires when the modal has successfully opened.
+ * @event Reveal#open
+ */
+ this.$element.trigger('open.zf.reveal');
+
+ if (this.isMobile) {
+ this.originalScrollPos = window.pageYOffset;
+ $('html, body').addClass('is-reveal-open');
+ } else {
+ $('body').addClass('is-reveal-open');
+ }
+
+ setTimeout(function () {
+ _this3._extraHandlers();
+ }, 0);
+ }
+
+ /**
+ * Adds extra event handlers for the body and window if necessary.
+ * @private
+ */
+
+ }, {
+ key: '_extraHandlers',
+ value: function _extraHandlers() {
+ var _this = this;
+ this.focusableElements = Foundation.Keyboard.findFocusable(this.$element);
+
+ if (!this.options.overlay && this.options.closeOnClick && !this.options.fullScreen) {
+ $('body').on('click.zf.reveal', function (e) {
+ if (e.target === _this.$element[0] || $.contains(_this.$element[0], e.target)) {
+ return;
+ }
+ _this.close();
+ });
+ }
+
+ if (this.options.closeOnEsc) {
+ $(window).on('keydown.zf.reveal', function (e) {
+ Foundation.Keyboard.handleKey(e, 'Reveal', {
+ close: function () {
+ if (_this.options.closeOnEsc) {
+ _this.close();
+ _this.$anchor.focus();
+ }
+ }
+ });
+ });
+ }
+
+ // lock focus within modal while tabbing
+ this.$element.on('keydown.zf.reveal', function (e) {
+ var $target = $(this);
+ // handle keyboard event with keyboard util
+ Foundation.Keyboard.handleKey(e, 'Reveal', {
+ tab_forward: function () {
+ if (_this.$element.find(':focus').is(_this.focusableElements.eq(-1))) {
+ // left modal downwards, setting focus to first element
+ _this.focusableElements.eq(0).focus();
+ return true;
+ }
+ if (_this.focusableElements.length === 0) {
+ // no focusable elements inside the modal at all, prevent tabbing in general
+ return true;
+ }
+ },
+ tab_backward: function () {
+ if (_this.$element.find(':focus').is(_this.focusableElements.eq(0)) || _this.$element.is(':focus')) {
+ // left modal upwards, setting focus to last element
+ _this.focusableElements.eq(-1).focus();
+ return true;
+ }
+ if (_this.focusableElements.length === 0) {
+ // no focusable elements inside the modal at all, prevent tabbing in general
+ return true;
+ }
+ },
+ open: function () {
+ if (_this.$element.find(':focus').is(_this.$element.find('[data-close]'))) {
+ setTimeout(function () {
+ // set focus back to anchor if close button has been activated
+ _this.$anchor.focus();
+ }, 1);
+ } else if ($target.is(_this.focusableElements)) {
+ // dont't trigger if acual element has focus (i.e. inputs, links, ...)
+ _this.open();
+ }
+ },
+ close: function () {
+ if (_this.options.closeOnEsc) {
+ _this.close();
+ _this.$anchor.focus();
+ }
+ },
+ handled: function (preventDefault) {
+ if (preventDefault) {
+ e.preventDefault();
+ }
+ }
+ });
+ });
+ }
+
+ /**
+ * Closes the modal.
+ * @function
+ * @fires Reveal#closed
+ */
+
+ }, {
+ key: 'close',
+ value: function close() {
+ if (!this.isActive || !this.$element.is(':visible')) {
+ return false;
+ }
+ var _this = this;
+
+ // Motion UI method of hiding
+ if (this.options.animationOut) {
+ if (this.options.overlay) {
+ Foundation.Motion.animateOut(this.$overlay, 'fade-out', finishUp);
+ } else {
+ finishUp();
+ }
+
+ Foundation.Motion.animateOut(this.$element, this.options.animationOut);
+ }
+ // jQuery method of hiding
+ else {
+ if (this.options.overlay) {
+ this.$overlay.hide(0, finishUp);
+ } else {
+ finishUp();
+ }
+
+ this.$element.hide(this.options.hideDelay);
+ }
+
+ // Conditionals to remove extra event listeners added on open
+ if (this.options.closeOnEsc) {
+ $(window).off('keydown.zf.reveal');
+ }
+
+ if (!this.options.overlay && this.options.closeOnClick) {
+ $('body').off('click.zf.reveal');
+ }
+
+ this.$element.off('keydown.zf.reveal');
+
+ function finishUp() {
+ if (_this.isMobile) {
+ $('html, body').removeClass('is-reveal-open');
+ if (_this.originalScrollPos) {
+ $('body').scrollTop(_this.originalScrollPos);
+ _this.originalScrollPos = null;
+ }
+ } else {
+ $('body').removeClass('is-reveal-open');
+ }
+
+ _this.$element.attr('aria-hidden', true);
+
+ /**
+ * Fires when the modal is done closing.
+ * @event Reveal#closed
+ */
+ _this.$element.trigger('closed.zf.reveal');
+ }
+
+ /**
+ * Resets the modal content
+ * This prevents a running video to keep going in the background
+ */
+ if (this.options.resetOnClose) {
+ this.$element.html(this.$element.html());
+ }
+
+ this.isActive = false;
+ if (_this.options.deepLink) {
+ if (window.history.replaceState) {
+ window.history.replaceState("", document.title, window.location.pathname);
+ } else {
+ window.location.hash = '';
+ }
+ }
+ }
+
+ /**
+ * Toggles the open/closed state of a modal.
+ * @function
+ */
+
+ }, {
+ key: 'toggle',
+ value: function toggle() {
+ if (this.isActive) {
+ this.close();
+ } else {
+ this.open();
+ }
+ }
+ }, {
+ key: 'destroy',
+
+
+ /**
+ * Destroys an instance of a modal.
+ * @function
+ */
+ value: function destroy() {
+ if (this.options.overlay) {
+ this.$element.appendTo($('body')); // move $element outside of $overlay to prevent error unregisterPlugin()
+ this.$overlay.hide().off().remove();
+ }
+ this.$element.hide().off();
+ this.$anchor.off('.zf');
+ $(window).off('.zf.reveal:' + this.id);
+
+ Foundation.unregisterPlugin(this);
+ }
+ }]);
+
+ return Reveal;
+ }();
+
+ Reveal.defaults = {
+ /**
+ * Motion-UI class to use for animated elements. If none used, defaults to simple show/hide.
+ * @option
+ * @example 'slide-in-left'
+ */
+ animationIn: '',
+ /**
+ * Motion-UI class to use for animated elements. If none used, defaults to simple show/hide.
+ * @option
+ * @example 'slide-out-right'
+ */
+ animationOut: '',
+ /**
+ * Time, in ms, to delay the opening of a modal after a click if no animation used.
+ * @option
+ * @example 10
+ */
+ showDelay: 0,
+ /**
+ * Time, in ms, to delay the closing of a modal after a click if no animation used.
+ * @option
+ * @example 10
+ */
+ hideDelay: 0,
+ /**
+ * Allows a click on the body/overlay to close the modal.
+ * @option
+ * @example true
+ */
+ closeOnClick: true,
+ /**
+ * Allows the modal to close if the user presses the `ESCAPE` key.
+ * @option
+ * @example true
+ */
+ closeOnEsc: true,
+ /**
+ * If true, allows multiple modals to be displayed at once.
+ * @option
+ * @example false
+ */
+ multipleOpened: false,
+ /**
+ * Distance, in pixels, the modal should push down from the top of the screen.
+ * @option
+ * @example auto
+ */
+ vOffset: 'auto',
+ /**
+ * Distance, in pixels, the modal should push in from the side of the screen.
+ * @option
+ * @example auto
+ */
+ hOffset: 'auto',
+ /**
+ * Allows the modal to be fullscreen, completely blocking out the rest of the view. JS checks for this as well.
+ * @option
+ * @example false
+ */
+ fullScreen: false,
+ /**
+ * Percentage of screen height the modal should push up from the bottom of the view.
+ * @option
+ * @example 10
+ */
+ btmOffsetPct: 10,
+ /**
+ * Allows the modal to generate an overlay div, which will cover the view when modal opens.
+ * @option
+ * @example true
+ */
+ overlay: true,
+ /**
+ * Allows the modal to remove and reinject markup on close. Should be true if using video elements w/o using provider's api, otherwise, videos will continue to play in the background.
+ * @option
+ * @example false
+ */
+ resetOnClose: false,
+ /**
+ * Allows the modal to alter the url on open/close, and allows the use of the `back` button to close modals. ALSO, allows a modal to auto-maniacally open on page load IF the hash === the modal's user-set id.
+ * @option
+ * @example false
+ */
+ deepLink: false
+ };
+
+ // Window exports
+ Foundation.plugin(Reveal, 'Reveal');
+
+ function iPhoneSniff() {
+ return (/iP(ad|hone|od).*OS/.test(window.navigator.userAgent)
+ );
+ }
+
+ function androidSniff() {
+ return (/Android/.test(window.navigator.userAgent)
+ );
+ }
+
+ function mobileSniff() {
+ return iPhoneSniff() || androidSniff();
+ }
+}(jQuery);
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+!function ($) {
+
+ /**
+ * Slider module.
+ * @module foundation.slider
+ * @requires foundation.util.motion
+ * @requires foundation.util.triggers
+ * @requires foundation.util.keyboard
+ * @requires foundation.util.touch
+ */
+
+ var Slider = function () {
+ /**
+ * Creates a new instance of a drilldown menu.
+ * @class
+ * @param {jQuery} element - jQuery object to make into an accordion menu.
+ * @param {Object} options - Overrides to the default plugin settings.
+ */
+
+ function Slider(element, options) {
+ _classCallCheck(this, Slider);
+
+ this.$element = element;
+ this.options = $.extend({}, Slider.defaults, this.$element.data(), options);
+
+ this._init();
+
+ Foundation.registerPlugin(this, 'Slider');
+ Foundation.Keyboard.register('Slider', {
+ 'ltr': {
+ 'ARROW_RIGHT': 'increase',
+ 'ARROW_UP': 'increase',
+ 'ARROW_DOWN': 'decrease',
+ 'ARROW_LEFT': 'decrease',
+ 'SHIFT_ARROW_RIGHT': 'increase_fast',
+ 'SHIFT_ARROW_UP': 'increase_fast',
+ 'SHIFT_ARROW_DOWN': 'decrease_fast',
+ 'SHIFT_ARROW_LEFT': 'decrease_fast'
+ },
+ 'rtl': {
+ 'ARROW_LEFT': 'increase',
+ 'ARROW_RIGHT': 'decrease',
+ 'SHIFT_ARROW_LEFT': 'increase_fast',
+ 'SHIFT_ARROW_RIGHT': 'decrease_fast'
+ }
+ });
+ }
+
+ /**
+ * Initilizes the plugin by reading/setting attributes, creating collections and setting the initial position of the handle(s).
+ * @function
+ * @private
+ */
+
+
+ _createClass(Slider, [{
+ key: '_init',
+ value: function _init() {
+ this.inputs = this.$element.find('input');
+ this.handles = this.$element.find('[data-slider-handle]');
+
+ this.$handle = this.handles.eq(0);
+ this.$input = this.inputs.length ? this.inputs.eq(0) : $('#' + this.$handle.attr('aria-controls'));
+ this.$fill = this.$element.find('[data-slider-fill]').css(this.options.vertical ? 'height' : 'width', 0);
+
+ var isDbl = false,
+ _this = this;
+ if (this.options.disabled || this.$element.hasClass(this.options.disabledClass)) {
+ this.options.disabled = true;
+ this.$element.addClass(this.options.disabledClass);
+ }
+ if (!this.inputs.length) {
+ this.inputs = $().add(this.$input);
+ this.options.binding = true;
+ }
+ this._setInitAttr(0);
+ this._events(this.$handle);
+
+ if (this.handles[1]) {
+ this.options.doubleSided = true;
+ this.$handle2 = this.handles.eq(1);
+ this.$input2 = this.inputs.length > 1 ? this.inputs.eq(1) : $('#' + this.$handle2.attr('aria-controls'));
+
+ if (!this.inputs[1]) {
+ this.inputs = this.inputs.add(this.$input2);
+ }
+ isDbl = true;
+
+ this._setHandlePos(this.$handle, this.options.initialStart, true, function () {
+
+ _this._setHandlePos(_this.$handle2, _this.options.initialEnd, true);
+ });
+ // this.$handle.triggerHandler('click.zf.slider');
+ this._setInitAttr(1);
+ this._events(this.$handle2);
+ }
+
+ if (!isDbl) {
+ this._setHandlePos(this.$handle, this.options.initialStart, true);
+ }
+ }
+
+ /**
+ * Sets the position of the selected handle and fill bar.
+ * @function
+ * @private
+ * @param {jQuery} $hndl - the selected handle to move.
+ * @param {Number} location - floating point between the start and end values of the slider bar.
+ * @param {Function} cb - callback function to fire on completion.
+ * @fires Slider#moved
+ * @fires Slider#changed
+ */
+
+ }, {
+ key: '_setHandlePos',
+ value: function _setHandlePos($hndl, location, noInvert, cb) {
+ // don't move if the slider has been disabled since its initialization
+ if (this.$element.hasClass(this.options.disabledClass)) {
+ return;
+ }
+ //might need to alter that slightly for bars that will have odd number selections.
+ location = parseFloat(location); //on input change events, convert string to number...grumble.
+
+ // prevent slider from running out of bounds, if value exceeds the limits set through options, override the value to min/max
+ if (location < this.options.start) {
+ location = this.options.start;
+ } else if (location > this.options.end) {
+ location = this.options.end;
+ }
+
+ var isDbl = this.options.doubleSided;
+
+ if (isDbl) {
+ //this block is to prevent 2 handles from crossing eachother. Could/should be improved.
+ if (this.handles.index($hndl) === 0) {
+ var h2Val = parseFloat(this.$handle2.attr('aria-valuenow'));
+ location = location >= h2Val ? h2Val - this.options.step : location;
+ } else {
+ var h1Val = parseFloat(this.$handle.attr('aria-valuenow'));
+ location = location <= h1Val ? h1Val + this.options.step : location;
+ }
+ }
+
+ //this is for single-handled vertical sliders, it adjusts the value to account for the slider being "upside-down"
+ //for click and drag events, it's weird due to the scale(-1, 1) css property
+ if (this.options.vertical && !noInvert) {
+ location = this.options.end - location;
+ }
+
+ var _this = this,
+ vert = this.options.vertical,
+ hOrW = vert ? 'height' : 'width',
+ lOrT = vert ? 'top' : 'left',
+ handleDim = $hndl[0].getBoundingClientRect()[hOrW],
+ elemDim = this.$element[0].getBoundingClientRect()[hOrW],
+
+ //percentage of bar min/max value based on click or drag point
+ pctOfBar = percent(location - this.options.start, this.options.end - this.options.start).toFixed(2),
+
+ //number of actual pixels to shift the handle, based on the percentage obtained above
+ pxToMove = (elemDim - handleDim) * pctOfBar,
+
+ //percentage of bar to shift the handle
+ movement = (percent(pxToMove, elemDim) * 100).toFixed(this.options.decimal);
+ //fixing the decimal value for the location number, is passed to other methods as a fixed floating-point value
+ location = parseFloat(location.toFixed(this.options.decimal));
+ // declare empty object for css adjustments, only used with 2 handled-sliders
+ var css = {};
+
+ this._setValues($hndl, location);
+
+ // TODO update to calculate based on values set to respective inputs??
+ if (isDbl) {
+ var isLeftHndl = this.handles.index($hndl) === 0,
+
+ //empty variable, will be used for min-height/width for fill bar
+ dim,
+
+ //percentage w/h of the handle compared to the slider bar
+ handlePct = ~ ~(percent(handleDim, elemDim) * 100);
+ //if left handle, the math is slightly different than if it's the right handle, and the left/top property needs to be changed for the fill bar
+ if (isLeftHndl) {
+ //left or top percentage value to apply to the fill bar.
+ css[lOrT] = movement + '%';
+ //calculate the new min-height/width for the fill bar.
+ dim = parseFloat(this.$handle2[0].style[lOrT]) - movement + handlePct;
+ //this callback is necessary to prevent errors and allow the proper placement and initialization of a 2-handled slider
+ //plus, it means we don't care if 'dim' isNaN on init, it won't be in the future.
+ if (cb && typeof cb === 'function') {
+ cb();
+ } //this is only needed for the initialization of 2 handled sliders
+ } else {
+ //just caching the value of the left/bottom handle's left/top property
+ var handlePos = parseFloat(this.$handle[0].style[lOrT]);
+ //calculate the new min-height/width for the fill bar. Use isNaN to prevent false positives for numbers <= 0
+ //based on the percentage of movement of the handle being manipulated, less the opposing handle's left/top position, plus the percentage w/h of the handle itself
+ dim = movement - (isNaN(handlePos) ? this.options.initialStart / ((this.options.end - this.options.start) / 100) : handlePos) + handlePct;
+ }
+ // assign the min-height/width to our css object
+ css['min-' + hOrW] = dim + '%';
+ }
+
+ this.$element.one('finished.zf.animate', function () {
+ /**
+ * Fires when the handle is done moving.
+ * @event Slider#moved
+ */
+ _this.$element.trigger('moved.zf.slider', [$hndl]);
+ });
+
+ //because we don't know exactly how the handle will be moved, check the amount of time it should take to move.
+ var moveTime = this.$element.data('dragging') ? 1000 / 60 : this.options.moveTime;
+
+ Foundation.Move(moveTime, $hndl, function () {
+ //adjusting the left/top property of the handle, based on the percentage calculated above
+ $hndl.css(lOrT, movement + '%');
+
+ if (!_this.options.doubleSided) {
+ //if single-handled, a simple method to expand the fill bar
+ _this.$fill.css(hOrW, pctOfBar * 100 + '%');
+ } else {
+ //otherwise, use the css object we created above
+ _this.$fill.css(css);
+ }
+ });
+
+ /**
+ * Fires when the value has not been change for a given time.
+ * @event Slider#changed
+ */
+ clearTimeout(_this.timeout);
+ _this.timeout = setTimeout(function () {
+ _this.$element.trigger('changed.zf.slider', [$hndl]);
+ }, _this.options.changedDelay);
+ }
+
+ /**
+ * Sets the initial attribute for the slider element.
+ * @function
+ * @private
+ * @param {Number} idx - index of the current handle/input to use.
+ */
+
+ }, {
+ key: '_setInitAttr',
+ value: function _setInitAttr(idx) {
+ var id = this.inputs.eq(idx).attr('id') || Foundation.GetYoDigits(6, 'slider');
+ this.inputs.eq(idx).attr({
+ 'id': id,
+ 'max': this.options.end,
+ 'min': this.options.start,
+ 'step': this.options.step
+ });
+ this.handles.eq(idx).attr({
+ 'role': 'slider',
+ 'aria-controls': id,
+ 'aria-valuemax': this.options.end,
+ 'aria-valuemin': this.options.start,
+ 'aria-valuenow': idx === 0 ? this.options.initialStart : this.options.initialEnd,
+ 'aria-orientation': this.options.vertical ? 'vertical' : 'horizontal',
+ 'tabindex': 0
+ });
+ }
+
+ /**
+ * Sets the input and `aria-valuenow` values for the slider element.
+ * @function
+ * @private
+ * @param {jQuery} $handle - the currently selected handle.
+ * @param {Number} val - floating point of the new value.
+ */
+
+ }, {
+ key: '_setValues',
+ value: function _setValues($handle, val) {
+ var idx = this.options.doubleSided ? this.handles.index($handle) : 0;
+ this.inputs.eq(idx).val(val);
+ $handle.attr('aria-valuenow', val);
+ }
+
+ /**
+ * Handles events on the slider element.
+ * Calculates the new location of the current handle.
+ * If there are two handles and the bar was clicked, it determines which handle to move.
+ * @function
+ * @private
+ * @param {Object} e - the `event` object passed from the listener.
+ * @param {jQuery} $handle - the current handle to calculate for, if selected.
+ * @param {Number} val - floating point number for the new value of the slider.
+ * TODO clean this up, there's a lot of repeated code between this and the _setHandlePos fn.
+ */
+
+ }, {
+ key: '_handleEvent',
+ value: function _handleEvent(e, $handle, val) {
+ var value, hasVal;
+ if (!val) {
+ //click or drag events
+ e.preventDefault();
+ var _this = this,
+ vertical = this.options.vertical,
+ param = vertical ? 'height' : 'width',
+ direction = vertical ? 'top' : 'left',
+ eventOffset = vertical ? e.pageY : e.pageX,
+ halfOfHandle = this.$handle[0].getBoundingClientRect()[param] / 2,
+ barDim = this.$element[0].getBoundingClientRect()[param],
+ windowScroll = vertical ? $(window).scrollTop() : $(window).scrollLeft();
+
+ var elemOffset = this.$element.offset()[direction];
+
+ // touch events emulated by the touch util give position relative to screen, add window.scroll to event coordinates...
+ // best way to guess this is simulated is if clientY == pageY
+ if (e.clientY === e.pageY) {
+ eventOffset = eventOffset + windowScroll;
+ }
+ var eventFromBar = eventOffset - elemOffset;
+ var barXY;
+ if (eventFromBar < 0) {
+ barXY = 0;
+ } else if (eventFromBar > barDim) {
+ barXY = barDim;
+ } else {
+ barXY = eventFromBar;
+ }
+ offsetPct = percent(barXY, barDim);
+
+ value = (this.options.end - this.options.start) * offsetPct + this.options.start;
+
+ // turn everything around for RTL, yay math!
+ if (Foundation.rtl() && !this.options.vertical) {
+ value = this.options.end - value;
+ }
+
+ value = _this._adjustValue(null, value);
+ //boolean flag for the setHandlePos fn, specifically for vertical sliders
+ hasVal = false;
+
+ if (!$handle) {
+ //figure out which handle it is, pass it to the next function.
+ var firstHndlPos = absPosition(this.$handle, direction, barXY, param),
+ secndHndlPos = absPosition(this.$handle2, direction, barXY, param);
+ $handle = firstHndlPos <= secndHndlPos ? this.$handle : this.$handle2;
+ }
+ } else {
+ //change event on input
+ value = this._adjustValue(null, val);
+ hasVal = true;
+ }
+
+ this._setHandlePos($handle, value, hasVal);
+ }
+
+ /**
+ * Adjustes value for handle in regard to step value. returns adjusted value
+ * @function
+ * @private
+ * @param {jQuery} $handle - the selected handle.
+ * @param {Number} value - value to adjust. used if $handle is falsy
+ */
+
+ }, {
+ key: '_adjustValue',
+ value: function _adjustValue($handle, value) {
+ var val,
+ step = this.options.step,
+ div = parseFloat(step / 2),
+ left,
+ prev_val,
+ next_val;
+ if (!!$handle) {
+ val = parseFloat($handle.attr('aria-valuenow'));
+ } else {
+ val = value;
+ }
+ left = val % step;
+ prev_val = val - left;
+ next_val = prev_val + step;
+ if (left === 0) {
+ return val;
+ }
+ val = val >= prev_val + div ? next_val : prev_val;
+ return val;
+ }
+
+ /**
+ * Adds event listeners to the slider elements.
+ * @function
+ * @private
+ * @param {jQuery} $handle - the current handle to apply listeners to.
+ */
+
+ }, {
+ key: '_events',
+ value: function _events($handle) {
+ var _this = this,
+ curHandle,
+ timer;
+
+ this.inputs.off('change.zf.slider').on('change.zf.slider', function (e) {
+ var idx = _this.inputs.index($(this));
+ _this._handleEvent(e, _this.handles.eq(idx), $(this).val());
+ });
+
+ if (this.options.clickSelect) {
+ this.$element.off('click.zf.slider').on('click.zf.slider', function (e) {
+ if (_this.$element.data('dragging')) {
+ return false;
+ }
+
+ if (!$(e.target).is('[data-slider-handle]')) {
+ if (_this.options.doubleSided) {
+ _this._handleEvent(e);
+ } else {
+ _this._handleEvent(e, _this.$handle);
+ }
+ }
+ });
+ }
+
+ if (this.options.draggable) {
+ this.handles.addTouch();
+
+ var $body = $('body');
+ $handle.off('mousedown.zf.slider').on('mousedown.zf.slider', function (e) {
+ $handle.addClass('is-dragging');
+ _this.$fill.addClass('is-dragging'); //
+ _this.$element.data('dragging', true);
+
+ curHandle = $(e.currentTarget);
+
+ $body.on('mousemove.zf.slider', function (e) {
+ e.preventDefault();
+ _this._handleEvent(e, curHandle);
+ }).on('mouseup.zf.slider', function (e) {
+ _this._handleEvent(e, curHandle);
+
+ $handle.removeClass('is-dragging');
+ _this.$fill.removeClass('is-dragging');
+ _this.$element.data('dragging', false);
+
+ $body.off('mousemove.zf.slider mouseup.zf.slider');
+ });
+ })
+ // prevent events triggered by touch
+ .on('selectstart.zf.slider touchmove.zf.slider', function (e) {
+ e.preventDefault();
+ });
+ }
+
+ $handle.off('keydown.zf.slider').on('keydown.zf.slider', function (e) {
+ var _$handle = $(this),
+ idx = _this.options.doubleSided ? _this.handles.index(_$handle) : 0,
+ oldValue = parseFloat(_this.inputs.eq(idx).val()),
+ newValue;
+
+ // handle keyboard event with keyboard util
+ Foundation.Keyboard.handleKey(e, 'Slider', {
+ decrease: function () {
+ newValue = oldValue - _this.options.step;
+ },
+ increase: function () {
+ newValue = oldValue + _this.options.step;
+ },
+ decrease_fast: function () {
+ newValue = oldValue - _this.options.step * 10;
+ },
+ increase_fast: function () {
+ newValue = oldValue + _this.options.step * 10;
+ },
+ handled: function () {
+ // only set handle pos when event was handled specially
+ e.preventDefault();
+ _this._setHandlePos(_$handle, newValue, true);
+ }
+ });
+ /*if (newValue) { // if pressed key has special function, update value
+ e.preventDefault();
+ _this._setHandlePos(_$handle, newValue);
+ }*/
+ });
+ }
+
+ /**
+ * Destroys the slider plugin.
+ */
+
+ }, {
+ key: 'destroy',
+ value: function destroy() {
+ this.handles.off('.zf.slider');
+ this.inputs.off('.zf.slider');
+ this.$element.off('.zf.slider');
+
+ Foundation.unregisterPlugin(this);
+ }
+ }]);
+
+ return Slider;
+ }();
+
+ Slider.defaults = {
+ /**
+ * Minimum value for the slider scale.
+ * @option
+ * @example 0
+ */
+ start: 0,
+ /**
+ * Maximum value for the slider scale.
+ * @option
+ * @example 100
+ */
+ end: 100,
+ /**
+ * Minimum value change per change event.
+ * @option
+ * @example 1
+ */
+ step: 1,
+ /**
+ * Value at which the handle/input *(left handle/first input)* should be set to on initialization.
+ * @option
+ * @example 0
+ */
+ initialStart: 0,
+ /**
+ * Value at which the right handle/second input should be set to on initialization.
+ * @option
+ * @example 100
+ */
+ initialEnd: 100,
+ /**
+ * Allows the input to be located outside the container and visible. Set to by the JS
+ * @option
+ * @example false
+ */
+ binding: false,
+ /**
+ * Allows the user to click/tap on the slider bar to select a value.
+ * @option
+ * @example true
+ */
+ clickSelect: true,
+ /**
+ * Set to true and use the `vertical` class to change alignment to vertical.
+ * @option
+ * @example false
+ */
+ vertical: false,
+ /**
+ * Allows the user to drag the slider handle(s) to select a value.
+ * @option
+ * @example true
+ */
+ draggable: true,
+ /**
+ * Disables the slider and prevents event listeners from being applied. Double checked by JS with `disabledClass`.
+ * @option
+ * @example false
+ */
+ disabled: false,
+ /**
+ * Allows the use of two handles. Double checked by the JS. Changes some logic handling.
+ * @option
+ * @example false
+ */
+ doubleSided: false,
+ /**
+ * Potential future feature.
+ */
+ // steps: 100,
+ /**
+ * Number of decimal places the plugin should go to for floating point precision.
+ * @option
+ * @example 2
+ */
+ decimal: 2,
+ /**
+ * Time delay for dragged elements.
+ */
+ // dragDelay: 0,
+ /**
+ * Time, in ms, to animate the movement of a slider handle if user clicks/taps on the bar. Needs to be manually set if updating the transition time in the Sass settings.
+ * @option
+ * @example 200
+ */
+ moveTime: 200, //update this if changing the transition time in the sass
+ /**
+ * Class applied to disabled sliders.
+ * @option
+ * @example 'disabled'
+ */
+ disabledClass: 'disabled',
+ /**
+ * Will invert the default layout for a vertical slider.
+ * @option
+ * @example false
+ */
+ invertVertical: false,
+ /**
+ * Milliseconds before the `changed.zf-slider` event is triggered after value change.
+ * @option
+ * @example 500
+ */
+ changedDelay: 500
+ };
+
+ function percent(frac, num) {
+ return frac / num;
+ }
+ function absPosition($handle, dir, clickPos, param) {
+ return Math.abs($handle.position()[dir] + $handle[param]() / 2 - clickPos);
+ }
+
+ // Window exports
+ Foundation.plugin(Slider, 'Slider');
+}(jQuery);
+
+//*********this is in case we go to static, absolute positions instead of dynamic positioning********
+// this.setSteps(function() {
+// _this._events();
+// var initStart = _this.options.positions[_this.options.initialStart - 1] || null;
+// var initEnd = _this.options.initialEnd ? _this.options.position[_this.options.initialEnd - 1] : null;
+// if (initStart || initEnd) {
+// _this._handleEvent(initStart, initEnd);
+// }
+// });
+
+//***********the other part of absolute positions*************
+// Slider.prototype.setSteps = function(cb) {
+// var posChange = this.$element.outerWidth() / this.options.steps;
+// var counter = 0
+// while(counter < this.options.steps) {
+// if (counter) {
+// this.options.positions.push(this.options.positions[counter - 1] + posChange);
+// } else {
+// this.options.positions.push(posChange);
+// }
+// counter++;
+// }
+// cb();
+// };
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+!function ($) {
+
+ /**
+ * Sticky module.
+ * @module foundation.sticky
+ * @requires foundation.util.triggers
+ * @requires foundation.util.mediaQuery
+ */
+
+ var Sticky = function () {
+ /**
+ * Creates a new instance of a sticky thing.
+ * @class
+ * @param {jQuery} element - jQuery object to make sticky.
+ * @param {Object} options - options object passed when creating the element programmatically.
+ */
+
+ function Sticky(element, options) {
+ _classCallCheck(this, Sticky);
+
+ this.$element = element;
+ this.options = $.extend({}, Sticky.defaults, this.$element.data(), options);
+
+ this._init();
+
+ Foundation.registerPlugin(this, 'Sticky');
+ }
+
+ /**
+ * Initializes the sticky element by adding classes, getting/setting dimensions, breakpoints and attributes
+ * @function
+ * @private
+ */
+
+
+ _createClass(Sticky, [{
+ key: '_init',
+ value: function _init() {
+ var $parent = this.$element.parent('[data-sticky-container]'),
+ id = this.$element[0].id || Foundation.GetYoDigits(6, 'sticky'),
+ _this = this;
+
+ if (!$parent.length) {
+ this.wasWrapped = true;
+ }
+ this.$container = $parent.length ? $parent : $(this.options.container).wrapInner(this.$element);
+ this.$container.addClass(this.options.containerClass);
+
+ this.$element.addClass(this.options.stickyClass).attr({ 'data-resize': id });
+
+ this.scrollCount = this.options.checkEvery;
+ this.isStuck = false;
+ $(window).one('load.zf.sticky', function () {
+ if (_this.options.anchor !== '') {
+ _this.$anchor = $('#' + _this.options.anchor);
+ } else {
+ _this._parsePoints();
+ }
+
+ _this._setSizes(function () {
+ _this._calc(false);
+ });
+ _this._events(id.split('-').reverse().join('-'));
+ });
+ }
+
+ /**
+ * If using multiple elements as anchors, calculates the top and bottom pixel values the sticky thing should stick and unstick on.
+ * @function
+ * @private
+ */
+
+ }, {
+ key: '_parsePoints',
+ value: function _parsePoints() {
+ var top = this.options.topAnchor == "" ? 1 : this.options.topAnchor,
+ btm = this.options.btmAnchor == "" ? document.documentElement.scrollHeight : this.options.btmAnchor,
+ pts = [top, btm],
+ breaks = {};
+ for (var i = 0, len = pts.length; i < len && pts[i]; i++) {
+ var pt;
+ if (typeof pts[i] === 'number') {
+ pt = pts[i];
+ } else {
+ var place = pts[i].split(':'),
+ anchor = $('#' + place[0]);
+
+ pt = anchor.offset().top;
+ if (place[1] && place[1].toLowerCase() === 'bottom') {
+ pt += anchor[0].getBoundingClientRect().height;
+ }
+ }
+ breaks[i] = pt;
+ }
+
+ this.points = breaks;
+ return;
+ }
+
+ /**
+ * Adds event handlers for the scrolling element.
+ * @private
+ * @param {String} id - psuedo-random id for unique scroll event listener.
+ */
+
+ }, {
+ key: '_events',
+ value: function _events(id) {
+ var _this = this,
+ scrollListener = this.scrollListener = 'scroll.zf.' + id;
+ if (this.isOn) {
+ return;
+ }
+ if (this.canStick) {
+ this.isOn = true;
+ $(window).off(scrollListener).on(scrollListener, function (e) {
+ if (_this.scrollCount === 0) {
+ _this.scrollCount = _this.options.checkEvery;
+ _this._setSizes(function () {
+ _this._calc(false, window.pageYOffset);
+ });
+ } else {
+ _this.scrollCount--;
+ _this._calc(false, window.pageYOffset);
+ }
+ });
+ }
+
+ this.$element.off('resizeme.zf.trigger').on('resizeme.zf.trigger', function (e, el) {
+ _this._setSizes(function () {
+ _this._calc(false);
+ if (_this.canStick) {
+ if (!_this.isOn) {
+ _this._events(id);
+ }
+ } else if (_this.isOn) {
+ _this._pauseListeners(scrollListener);
+ }
+ });
+ });
+ }
+
+ /**
+ * Removes event handlers for scroll and change events on anchor.
+ * @fires Sticky#pause
+ * @param {String} scrollListener - unique, namespaced scroll listener attached to `window`
+ */
+
+ }, {
+ key: '_pauseListeners',
+ value: function _pauseListeners(scrollListener) {
+ this.isOn = false;
+ $(window).off(scrollListener);
+
+ /**
+ * Fires when the plugin is paused due to resize event shrinking the view.
+ * @event Sticky#pause
+ * @private
+ */
+ this.$element.trigger('pause.zf.sticky');
+ }
+
+ /**
+ * Called on every `scroll` event and on `_init`
+ * fires functions based on booleans and cached values
+ * @param {Boolean} checkSizes - true if plugin should recalculate sizes and breakpoints.
+ * @param {Number} scroll - current scroll position passed from scroll event cb function. If not passed, defaults to `window.pageYOffset`.
+ */
+
+ }, {
+ key: '_calc',
+ value: function _calc(checkSizes, scroll) {
+ if (checkSizes) {
+ this._setSizes();
+ }
+
+ if (!this.canStick) {
+ if (this.isStuck) {
+ this._removeSticky(true);
+ }
+ return false;
+ }
+
+ if (!scroll) {
+ scroll = window.pageYOffset;
+ }
+
+ if (scroll >= this.topPoint) {
+ if (scroll <= this.bottomPoint) {
+ if (!this.isStuck) {
+ this._setSticky();
+ }
+ } else {
+ if (this.isStuck) {
+ this._removeSticky(false);
+ }
+ }
+ } else {
+ if (this.isStuck) {
+ this._removeSticky(true);
+ }
+ }
+ }
+
+ /**
+ * Causes the $element to become stuck.
+ * Adds `position: fixed;`, and helper classes.
+ * @fires Sticky#stuckto
+ * @function
+ * @private
+ */
+
+ }, {
+ key: '_setSticky',
+ value: function _setSticky() {
+ var _this = this,
+ stickTo = this.options.stickTo,
+ mrgn = stickTo === 'top' ? 'marginTop' : 'marginBottom',
+ notStuckTo = stickTo === 'top' ? 'bottom' : 'top',
+ css = {};
+
+ css[mrgn] = this.options[mrgn] + 'em';
+ css[stickTo] = 0;
+ css[notStuckTo] = 'auto';
+ css['left'] = this.$container.offset().left + parseInt(window.getComputedStyle(this.$container[0])["padding-left"], 10);
+ this.isStuck = true;
+ this.$element.removeClass('is-anchored is-at-' + notStuckTo).addClass('is-stuck is-at-' + stickTo).css(css)
+ /**
+ * Fires when the $element has become `position: fixed;`
+ * Namespaced to `top` or `bottom`, e.g. `sticky.zf.stuckto:top`
+ * @event Sticky#stuckto
+ */
+ .trigger('sticky.zf.stuckto:' + stickTo);
+ this.$element.on("transitionend webkitTransitionEnd oTransitionEnd otransitionend MSTransitionEnd", function () {
+ _this._setSizes();
+ });
+ }
+
+ /**
+ * Causes the $element to become unstuck.
+ * Removes `position: fixed;`, and helper classes.
+ * Adds other helper classes.
+ * @param {Boolean} isTop - tells the function if the $element should anchor to the top or bottom of its $anchor element.
+ * @fires Sticky#unstuckfrom
+ * @private
+ */
+
+ }, {
+ key: '_removeSticky',
+ value: function _removeSticky(isTop) {
+ var stickTo = this.options.stickTo,
+ stickToTop = stickTo === 'top',
+ css = {},
+ anchorPt = (this.points ? this.points[1] - this.points[0] : this.anchorHeight) - this.elemHeight,
+ mrgn = stickToTop ? 'marginTop' : 'marginBottom',
+ notStuckTo = stickToTop ? 'bottom' : 'top',
+ topOrBottom = isTop ? 'top' : 'bottom';
+
+ css[mrgn] = 0;
+
+ css['bottom'] = 'auto';
+ if (isTop) {
+ css['top'] = 0;
+ } else {
+ css['top'] = anchorPt;
+ }
+
+ css['left'] = '';
+ this.isStuck = false;
+ this.$element.removeClass('is-stuck is-at-' + stickTo).addClass('is-anchored is-at-' + topOrBottom).css(css)
+ /**
+ * Fires when the $element has become anchored.
+ * Namespaced to `top` or `bottom`, e.g. `sticky.zf.unstuckfrom:bottom`
+ * @event Sticky#unstuckfrom
+ */
+ .trigger('sticky.zf.unstuckfrom:' + topOrBottom);
+ }
+
+ /**
+ * Sets the $element and $container sizes for plugin.
+ * Calls `_setBreakPoints`.
+ * @param {Function} cb - optional callback function to fire on completion of `_setBreakPoints`.
+ * @private
+ */
+
+ }, {
+ key: '_setSizes',
+ value: function _setSizes(cb) {
+ this.canStick = Foundation.MediaQuery.atLeast(this.options.stickyOn);
+ if (!this.canStick) {
+ cb();
+ }
+ var _this = this,
+ newElemWidth = this.$container[0].getBoundingClientRect().width,
+ comp = window.getComputedStyle(this.$container[0]),
+ pdng = parseInt(comp['padding-right'], 10);
+
+ if (this.$anchor && this.$anchor.length) {
+ this.anchorHeight = this.$anchor[0].getBoundingClientRect().height;
+ } else {
+ this._parsePoints();
+ }
+
+ this.$element.css({
+ 'max-width': newElemWidth - pdng + 'px'
+ });
+
+ var newContainerHeight = this.$element[0].getBoundingClientRect().height || this.containerHeight;
+ if (this.$element.css("display") == "none") {
+ newContainerHeight = 0;
+ }
+ this.containerHeight = newContainerHeight;
+ this.$container.css({
+ height: newContainerHeight
+ });
+ this.elemHeight = newContainerHeight;
+
+ if (this.isStuck) {
+ this.$element.css({ "left": this.$container.offset().left + parseInt(comp['padding-left'], 10) });
+ }
+
+ this._setBreakPoints(newContainerHeight, function () {
+ if (cb) {
+ cb();
+ }
+ });
+ }
+
+ /**
+ * Sets the upper and lower breakpoints for the element to become sticky/unsticky.
+ * @param {Number} elemHeight - px value for sticky.$element height, calculated by `_setSizes`.
+ * @param {Function} cb - optional callback function to be called on completion.
+ * @private
+ */
+
+ }, {
+ key: '_setBreakPoints',
+ value: function _setBreakPoints(elemHeight, cb) {
+ if (!this.canStick) {
+ if (cb) {
+ cb();
+ } else {
+ return false;
+ }
+ }
+ var mTop = emCalc(this.options.marginTop),
+ mBtm = emCalc(this.options.marginBottom),
+ topPoint = this.points ? this.points[0] : this.$anchor.offset().top,
+ bottomPoint = this.points ? this.points[1] : topPoint + this.anchorHeight,
+
+ // topPoint = this.$anchor.offset().top || this.points[0],
+ // bottomPoint = topPoint + this.anchorHeight || this.points[1],
+ winHeight = window.innerHeight;
+
+ if (this.options.stickTo === 'top') {
+ topPoint -= mTop;
+ bottomPoint -= elemHeight + mTop;
+ } else if (this.options.stickTo === 'bottom') {
+ topPoint -= winHeight - (elemHeight + mBtm);
+ bottomPoint -= winHeight - mBtm;
+ } else {
+ //this would be the stickTo: both option... tricky
+ }
+
+ this.topPoint = topPoint;
+ this.bottomPoint = bottomPoint;
+
+ if (cb) {
+ cb();
+ }
+ }
+
+ /**
+ * Destroys the current sticky element.
+ * Resets the element to the top position first.
+ * Removes event listeners, JS-added css properties and classes, and unwraps the $element if the JS added the $container.
+ * @function
+ */
+
+ }, {
+ key: 'destroy',
+ value: function destroy() {
+ this._removeSticky(true);
+
+ this.$element.removeClass(this.options.stickyClass + ' is-anchored is-at-top').css({
+ height: '',
+ top: '',
+ bottom: '',
+ 'max-width': ''
+ }).off('resizeme.zf.trigger');
+ if (this.$anchor && this.$anchor.length) {
+ this.$anchor.off('change.zf.sticky');
+ }
+ $(window).off(this.scrollListener);
+
+ if (this.wasWrapped) {
+ this.$element.unwrap();
+ } else {
+ this.$container.removeClass(this.options.containerClass).css({
+ height: ''
+ });
+ }
+ Foundation.unregisterPlugin(this);
+ }
+ }]);
+
+ return Sticky;
+ }();
+
+ Sticky.defaults = {
+ /**
+ * Customizable container template. Add your own classes for styling and sizing.
+ * @option
+ * @example '<div data-sticky-container class="small-6 columns"></div>'
+ */
+ container: '
',
+ /**
+ * Location in the view the element sticks to.
+ * @option
+ * @example 'top'
+ */
+ stickTo: 'top',
+ /**
+ * If anchored to a single element, the id of that element.
+ * @option
+ * @example 'exampleId'
+ */
+ anchor: '',
+ /**
+ * If using more than one element as anchor points, the id of the top anchor.
+ * @option
+ * @example 'exampleId:top'
+ */
+ topAnchor: '',
+ /**
+ * If using more than one element as anchor points, the id of the bottom anchor.
+ * @option
+ * @example 'exampleId:bottom'
+ */
+ btmAnchor: '',
+ /**
+ * Margin, in `em`'s to apply to the top of the element when it becomes sticky.
+ * @option
+ * @example 1
+ */
+ marginTop: 1,
+ /**
+ * Margin, in `em`'s to apply to the bottom of the element when it becomes sticky.
+ * @option
+ * @example 1
+ */
+ marginBottom: 1,
+ /**
+ * Breakpoint string that is the minimum screen size an element should become sticky.
+ * @option
+ * @example 'medium'
+ */
+ stickyOn: 'medium',
+ /**
+ * Class applied to sticky element, and removed on destruction. Foundation defaults to `sticky`.
+ * @option
+ * @example 'sticky'
+ */
+ stickyClass: 'sticky',
+ /**
+ * Class applied to sticky container. Foundation defaults to `sticky-container`.
+ * @option
+ * @example 'sticky-container'
+ */
+ containerClass: 'sticky-container',
+ /**
+ * Number of scroll events between the plugin's recalculating sticky points. Setting it to `0` will cause it to recalc every scroll event, setting it to `-1` will prevent recalc on scroll.
+ * @option
+ * @example 50
+ */
+ checkEvery: -1
+ };
+
+ /**
+ * Helper function to calculate em values
+ * @param Number {em} - number of em's to calculate into pixels
+ */
+ function emCalc(em) {
+ return parseInt(window.getComputedStyle(document.body, null).fontSize, 10) * em;
+ }
+
+ // Window exports
+ Foundation.plugin(Sticky, 'Sticky');
+}(jQuery);
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+!function ($) {
+
+ /**
+ * Tabs module.
+ * @module foundation.tabs
+ * @requires foundation.util.keyboard
+ * @requires foundation.util.timerAndImageLoader if tabs contain images
+ */
+
+ var Tabs = function () {
+ /**
+ * Creates a new instance of tabs.
+ * @class
+ * @fires Tabs#init
+ * @param {jQuery} element - jQuery object to make into tabs.
+ * @param {Object} options - Overrides to the default plugin settings.
+ */
+
+ function Tabs(element, options) {
+ _classCallCheck(this, Tabs);
+
+ this.$element = element;
+ this.options = $.extend({}, Tabs.defaults, this.$element.data(), options);
+
+ this._init();
+ Foundation.registerPlugin(this, 'Tabs');
+ Foundation.Keyboard.register('Tabs', {
+ 'ENTER': 'open',
+ 'SPACE': 'open',
+ 'ARROW_RIGHT': 'next',
+ 'ARROW_UP': 'previous',
+ 'ARROW_DOWN': 'next',
+ 'ARROW_LEFT': 'previous'
+ // 'TAB': 'next',
+ // 'SHIFT_TAB': 'previous'
+ });
+ }
+
+ /**
+ * Initializes the tabs by showing and focusing (if autoFocus=true) the preset active tab.
+ * @private
+ */
+
+
+ _createClass(Tabs, [{
+ key: '_init',
+ value: function _init() {
+ var _this = this;
+
+ this.$tabTitles = this.$element.find('.' + this.options.linkClass);
+ this.$tabContent = $('[data-tabs-content="' + this.$element[0].id + '"]');
+
+ this.$tabTitles.each(function () {
+ var $elem = $(this),
+ $link = $elem.find('a'),
+ isActive = $elem.hasClass('is-active'),
+ hash = $link[0].hash.slice(1),
+ linkId = $link[0].id ? $link[0].id : hash + '-label',
+ $tabContent = $('#' + hash);
+
+ $elem.attr({ 'role': 'presentation' });
+
+ $link.attr({
+ 'role': 'tab',
+ 'aria-controls': hash,
+ 'aria-selected': isActive,
+ 'id': linkId
+ });
+
+ $tabContent.attr({
+ 'role': 'tabpanel',
+ 'aria-hidden': !isActive,
+ 'aria-labelledby': linkId
+ });
+
+ if (isActive && _this.options.autoFocus) {
+ $link.focus();
+ }
+ });
+
+ if (this.options.matchHeight) {
+ var $images = this.$tabContent.find('img');
+
+ if ($images.length) {
+ Foundation.onImagesLoaded($images, this._setHeight.bind(this));
+ } else {
+ this._setHeight();
+ }
+ }
+
+ this._events();
+ }
+
+ /**
+ * Adds event handlers for items within the tabs.
+ * @private
+ */
+
+ }, {
+ key: '_events',
+ value: function _events() {
+ this._addKeyHandler();
+ this._addClickHandler();
+ this._setHeightMqHandler = null;
+
+ if (this.options.matchHeight) {
+ this._setHeightMqHandler = this._setHeight.bind(this);
+
+ $(window).on('changed.zf.mediaquery', this._setHeightMqHandler);
+ }
+ }
+
+ /**
+ * Adds click handlers for items within the tabs.
+ * @private
+ */
+
+ }, {
+ key: '_addClickHandler',
+ value: function _addClickHandler() {
+ var _this = this;
+
+ this.$element.off('click.zf.tabs').on('click.zf.tabs', '.' + this.options.linkClass, function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ if ($(this).hasClass('is-active')) {
+ return;
+ }
+ _this._handleTabChange($(this));
+ });
+ }
+
+ /**
+ * Adds keyboard event handlers for items within the tabs.
+ * @private
+ */
+
+ }, {
+ key: '_addKeyHandler',
+ value: function _addKeyHandler() {
+ var _this = this;
+ var $firstTab = _this.$element.find('li:first-of-type');
+ var $lastTab = _this.$element.find('li:last-of-type');
+
+ this.$tabTitles.off('keydown.zf.tabs').on('keydown.zf.tabs', function (e) {
+ if (e.which === 9) return;
+
+ var $element = $(this),
+ $elements = $element.parent('ul').children('li'),
+ $prevElement,
+ $nextElement;
+
+ $elements.each(function (i) {
+ if ($(this).is($element)) {
+ if (_this.options.wrapOnKeys) {
+ $prevElement = i === 0 ? $elements.last() : $elements.eq(i - 1);
+ $nextElement = i === $elements.length - 1 ? $elements.first() : $elements.eq(i + 1);
+ } else {
+ $prevElement = $elements.eq(Math.max(0, i - 1));
+ $nextElement = $elements.eq(Math.min(i + 1, $elements.length - 1));
+ }
+ return;
+ }
+ });
+
+ // handle keyboard event with keyboard util
+ Foundation.Keyboard.handleKey(e, 'Tabs', {
+ open: function () {
+ $element.find('[role="tab"]').focus();
+ _this._handleTabChange($element);
+ },
+ previous: function () {
+ $prevElement.find('[role="tab"]').focus();
+ _this._handleTabChange($prevElement);
+ },
+ next: function () {
+ $nextElement.find('[role="tab"]').focus();
+ _this._handleTabChange($nextElement);
+ },
+ handled: function () {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ });
+ });
+ }
+
+ /**
+ * Opens the tab `$targetContent` defined by `$target`.
+ * @param {jQuery} $target - Tab to open.
+ * @fires Tabs#change
+ * @function
+ */
+
+ }, {
+ key: '_handleTabChange',
+ value: function _handleTabChange($target) {
+ var $tabLink = $target.find('[role="tab"]'),
+ hash = $tabLink[0].hash,
+ $targetContent = this.$tabContent.find(hash),
+ $oldTab = this.$element.find('.' + this.options.linkClass + '.is-active').removeClass('is-active').find('[role="tab"]').attr({ 'aria-selected': 'false' });
+
+ $('#' + $oldTab.attr('aria-controls')).removeClass('is-active').attr({ 'aria-hidden': 'true' });
+
+ $target.addClass('is-active');
+
+ $tabLink.attr({ 'aria-selected': 'true' });
+
+ $targetContent.addClass('is-active').attr({ 'aria-hidden': 'false' });
+
+ /**
+ * Fires when the plugin has successfully changed tabs.
+ * @event Tabs#change
+ */
+ this.$element.trigger('change.zf.tabs', [$target]);
+ }
+
+ /**
+ * Public method for selecting a content pane to display.
+ * @param {jQuery | String} elem - jQuery object or string of the id of the pane to display.
+ * @function
+ */
+
+ }, {
+ key: 'selectTab',
+ value: function selectTab(elem) {
+ var idStr;
+
+ if (typeof elem === 'object') {
+ idStr = elem[0].id;
+ } else {
+ idStr = elem;
+ }
+
+ if (idStr.indexOf('#') < 0) {
+ idStr = '#' + idStr;
+ }
+
+ var $target = this.$tabTitles.find('[href="' + idStr + '"]').parent('.' + this.options.linkClass);
+
+ this._handleTabChange($target);
+ }
+ }, {
+ key: '_setHeight',
+
+ /**
+ * Sets the height of each panel to the height of the tallest panel.
+ * If enabled in options, gets called on media query change.
+ * If loading content via external source, can be called directly or with _reflow.
+ * @function
+ * @private
+ */
+ value: function _setHeight() {
+ var max = 0;
+ this.$tabContent.find('.' + this.options.panelClass).css('height', '').each(function () {
+ var panel = $(this),
+ isActive = panel.hasClass('is-active');
+
+ if (!isActive) {
+ panel.css({ 'visibility': 'hidden', 'display': 'block' });
+ }
+
+ var temp = this.getBoundingClientRect().height;
+
+ if (!isActive) {
+ panel.css({
+ 'visibility': '',
+ 'display': ''
+ });
+ }
+
+ max = temp > max ? temp : max;
+ }).css('height', max + 'px');
+ }
+
+ /**
+ * Destroys an instance of an tabs.
+ * @fires Tabs#destroyed
+ */
+
+ }, {
+ key: 'destroy',
+ value: function destroy() {
+ this.$element.find('.' + this.options.linkClass).off('.zf.tabs').hide().end().find('.' + this.options.panelClass).hide();
+
+ if (this.options.matchHeight) {
+ if (this._setHeightMqHandler != null) {
+ $(window).off('changed.zf.mediaquery', this._setHeightMqHandler);
+ }
+ }
+
+ Foundation.unregisterPlugin(this);
+ }
+ }]);
+
+ return Tabs;
+ }();
+
+ Tabs.defaults = {
+ /**
+ * Allows the window to scroll to content of active pane on load if set to true.
+ * @option
+ * @example false
+ */
+ autoFocus: false,
+
+ /**
+ * Allows keyboard input to 'wrap' around the tab links.
+ * @option
+ * @example true
+ */
+ wrapOnKeys: true,
+
+ /**
+ * Allows the tab content panes to match heights if set to true.
+ * @option
+ * @example false
+ */
+ matchHeight: false,
+
+ /**
+ * Class applied to `li`'s in tab link list.
+ * @option
+ * @example 'tabs-title'
+ */
+ linkClass: 'tabs-title',
+
+ /**
+ * Class applied to the content containers.
+ * @option
+ * @example 'tabs-panel'
+ */
+ panelClass: 'tabs-panel'
+ };
+
+ function checkClass($elem) {
+ return $elem.hasClass('is-active');
+ }
+
+ // Window exports
+ Foundation.plugin(Tabs, 'Tabs');
+}(jQuery);
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+!function ($) {
+
+ /**
+ * Toggler module.
+ * @module foundation.toggler
+ * @requires foundation.util.motion
+ * @requires foundation.util.triggers
+ */
+
+ var Toggler = function () {
+ /**
+ * Creates a new instance of Toggler.
+ * @class
+ * @fires Toggler#init
+ * @param {Object} element - jQuery object to add the trigger to.
+ * @param {Object} options - Overrides to the default plugin settings.
+ */
+
+ function Toggler(element, options) {
+ _classCallCheck(this, Toggler);
+
+ this.$element = element;
+ this.options = $.extend({}, Toggler.defaults, element.data(), options);
+ this.className = '';
+
+ this._init();
+ this._events();
+
+ Foundation.registerPlugin(this, 'Toggler');
+ }
+
+ /**
+ * Initializes the Toggler plugin by parsing the toggle class from data-toggler, or animation classes from data-animate.
+ * @function
+ * @private
+ */
+
+
+ _createClass(Toggler, [{
+ key: '_init',
+ value: function _init() {
+ var input;
+ // Parse animation classes if they were set
+ if (this.options.animate) {
+ input = this.options.animate.split(' ');
+
+ this.animationIn = input[0];
+ this.animationOut = input[1] || null;
+ }
+ // Otherwise, parse toggle class
+ else {
+ input = this.$element.data('toggler');
+ // Allow for a . at the beginning of the string
+ this.className = input[0] === '.' ? input.slice(1) : input;
+ }
+
+ // Add ARIA attributes to triggers
+ var id = this.$element[0].id;
+ $('[data-open="' + id + '"], [data-close="' + id + '"], [data-toggle="' + id + '"]').attr('aria-controls', id);
+ // If the target is hidden, add aria-hidden
+ this.$element.attr('aria-expanded', this.$element.is(':hidden') ? false : true);
+ }
+
+ /**
+ * Initializes events for the toggle trigger.
+ * @function
+ * @private
+ */
+
+ }, {
+ key: '_events',
+ value: function _events() {
+ this.$element.off('toggle.zf.trigger').on('toggle.zf.trigger', this.toggle.bind(this));
+ }
+
+ /**
+ * Toggles the target class on the target element. An event is fired from the original trigger depending on if the resultant state was "on" or "off".
+ * @function
+ * @fires Toggler#on
+ * @fires Toggler#off
+ */
+
+ }, {
+ key: 'toggle',
+ value: function toggle() {
+ this[this.options.animate ? '_toggleAnimate' : '_toggleClass']();
+ }
+ }, {
+ key: '_toggleClass',
+ value: function _toggleClass() {
+ this.$element.toggleClass(this.className);
+
+ var isOn = this.$element.hasClass(this.className);
+ if (isOn) {
+ /**
+ * Fires if the target element has the class after a toggle.
+ * @event Toggler#on
+ */
+ this.$element.trigger('on.zf.toggler');
+ } else {
+ /**
+ * Fires if the target element does not have the class after a toggle.
+ * @event Toggler#off
+ */
+ this.$element.trigger('off.zf.toggler');
+ }
+
+ this._updateARIA(isOn);
+ }
+ }, {
+ key: '_toggleAnimate',
+ value: function _toggleAnimate() {
+ var _this = this;
+
+ if (this.$element.is(':hidden')) {
+ Foundation.Motion.animateIn(this.$element, this.animationIn, function () {
+ _this._updateARIA(true);
+ this.trigger('on.zf.toggler');
+ });
+ } else {
+ Foundation.Motion.animateOut(this.$element, this.animationOut, function () {
+ _this._updateARIA(false);
+ this.trigger('off.zf.toggler');
+ });
+ }
+ }
+ }, {
+ key: '_updateARIA',
+ value: function _updateARIA(isOn) {
+ this.$element.attr('aria-expanded', isOn ? true : false);
+ }
+
+ /**
+ * Destroys the instance of Toggler on the element.
+ * @function
+ */
+
+ }, {
+ key: 'destroy',
+ value: function destroy() {
+ this.$element.off('.zf.toggler');
+ Foundation.unregisterPlugin(this);
+ }
+ }]);
+
+ return Toggler;
+ }();
+
+ Toggler.defaults = {
+ /**
+ * Tells the plugin if the element should animated when toggled.
+ * @option
+ * @example false
+ */
+ animate: false
+ };
+
+ // Window exports
+ Foundation.plugin(Toggler, 'Toggler');
+}(jQuery);
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+!function ($) {
+
+ /**
+ * Tooltip module.
+ * @module foundation.tooltip
+ * @requires foundation.util.box
+ * @requires foundation.util.triggers
+ */
+
+ var Tooltip = function () {
+ /**
+ * Creates a new instance of a Tooltip.
+ * @class
+ * @fires Tooltip#init
+ * @param {jQuery} element - jQuery object to attach a tooltip to.
+ * @param {Object} options - object to extend the default configuration.
+ */
+
+ function Tooltip(element, options) {
+ _classCallCheck(this, Tooltip);
+
+ this.$element = element;
+ this.options = $.extend({}, Tooltip.defaults, this.$element.data(), options);
+
+ this.isActive = false;
+ this.isClick = false;
+ this._init();
+
+ Foundation.registerPlugin(this, 'Tooltip');
+ }
+
+ /**
+ * Initializes the tooltip by setting the creating the tip element, adding it's text, setting private variables and setting attributes on the anchor.
+ * @private
+ */
+
+
+ _createClass(Tooltip, [{
+ key: '_init',
+ value: function _init() {
+ var elemId = this.$element.attr('aria-describedby') || Foundation.GetYoDigits(6, 'tooltip');
+
+ this.options.positionClass = this.options.positionClass || this._getPositionClass(this.$element);
+ this.options.tipText = this.options.tipText || this.$element.attr('title');
+ this.template = this.options.template ? $(this.options.template) : this._buildTemplate(elemId);
+
+ this.template.appendTo(document.body).text(this.options.tipText).hide();
+
+ this.$element.attr({
+ 'title': '',
+ 'aria-describedby': elemId,
+ 'data-yeti-box': elemId,
+ 'data-toggle': elemId,
+ 'data-resize': elemId
+ }).addClass(this.triggerClass);
+
+ //helper variables to track movement on collisions
+ this.usedPositions = [];
+ this.counter = 4;
+ this.classChanged = false;
+
+ this._events();
+ }
+
+ /**
+ * Grabs the current positioning class, if present, and returns the value or an empty string.
+ * @private
+ */
+
+ }, {
+ key: '_getPositionClass',
+ value: function _getPositionClass(element) {
+ if (!element) {
+ return '';
+ }
+ // var position = element.attr('class').match(/top|left|right/g);
+ var position = element[0].className.match(/\b(top|left|right)\b/g);
+ position = position ? position[0] : '';
+ return position;
+ }
+ }, {
+ key: '_buildTemplate',
+
+ /**
+ * builds the tooltip element, adds attributes, and returns the template.
+ * @private
+ */
+ value: function _buildTemplate(id) {
+ var templateClasses = (this.options.tooltipClass + ' ' + this.options.positionClass + ' ' + this.options.templateClasses).trim();
+ var $template = $('
').addClass(templateClasses).attr({
+ 'role': 'tooltip',
+ 'aria-hidden': true,
+ 'data-is-active': false,
+ 'data-is-focus': false,
+ 'id': id
+ });
+ return $template;
+ }
+
+ /**
+ * Function that gets called if a collision event is detected.
+ * @param {String} position - positioning class to try
+ * @private
+ */
+
+ }, {
+ key: '_reposition',
+ value: function _reposition(position) {
+ this.usedPositions.push(position ? position : 'bottom');
+
+ //default, try switching to opposite side
+ if (!position && this.usedPositions.indexOf('top') < 0) {
+ this.template.addClass('top');
+ } else if (position === 'top' && this.usedPositions.indexOf('bottom') < 0) {
+ this.template.removeClass(position);
+ } else if (position === 'left' && this.usedPositions.indexOf('right') < 0) {
+ this.template.removeClass(position).addClass('right');
+ } else if (position === 'right' && this.usedPositions.indexOf('left') < 0) {
+ this.template.removeClass(position).addClass('left');
+ }
+
+ //if default change didn't work, try bottom or left first
+ else if (!position && this.usedPositions.indexOf('top') > -1 && this.usedPositions.indexOf('left') < 0) {
+ this.template.addClass('left');
+ } else if (position === 'top' && this.usedPositions.indexOf('bottom') > -1 && this.usedPositions.indexOf('left') < 0) {
+ this.template.removeClass(position).addClass('left');
+ } else if (position === 'left' && this.usedPositions.indexOf('right') > -1 && this.usedPositions.indexOf('bottom') < 0) {
+ this.template.removeClass(position);
+ } else if (position === 'right' && this.usedPositions.indexOf('left') > -1 && this.usedPositions.indexOf('bottom') < 0) {
+ this.template.removeClass(position);
+ }
+ //if nothing cleared, set to bottom
+ else {
+ this.template.removeClass(position);
+ }
+ this.classChanged = true;
+ this.counter--;
+ }
+
+ /**
+ * sets the position class of an element and recursively calls itself until there are no more possible positions to attempt, or the tooltip element is no longer colliding.
+ * if the tooltip is larger than the screen width, default to full width - any user selected margin
+ * @private
+ */
+
+ }, {
+ key: '_setPosition',
+ value: function _setPosition() {
+ var position = this._getPositionClass(this.template),
+ $tipDims = Foundation.Box.GetDimensions(this.template),
+ $anchorDims = Foundation.Box.GetDimensions(this.$element),
+ direction = position === 'left' ? 'left' : position === 'right' ? 'left' : 'top',
+ param = direction === 'top' ? 'height' : 'width',
+ offset = param === 'height' ? this.options.vOffset : this.options.hOffset,
+ _this = this;
+
+ if ($tipDims.width >= $tipDims.windowDims.width || !this.counter && !Foundation.Box.ImNotTouchingYou(this.template)) {
+ this.template.offset(Foundation.Box.GetOffsets(this.template, this.$element, 'center bottom', this.options.vOffset, this.options.hOffset, true)).css({
+ // this.$element.offset(Foundation.GetOffsets(this.template, this.$element, 'center bottom', this.options.vOffset, this.options.hOffset, true)).css({
+ 'width': $anchorDims.windowDims.width - this.options.hOffset * 2,
+ 'height': 'auto'
+ });
+ return false;
+ }
+
+ this.template.offset(Foundation.Box.GetOffsets(this.template, this.$element, 'center ' + (position || 'bottom'), this.options.vOffset, this.options.hOffset));
+
+ while (!Foundation.Box.ImNotTouchingYou(this.template) && this.counter) {
+ this._reposition(position);
+ this._setPosition();
+ }
+ }
+
+ /**
+ * reveals the tooltip, and fires an event to close any other open tooltips on the page
+ * @fires Tooltip#closeme
+ * @fires Tooltip#show
+ * @function
+ */
+
+ }, {
+ key: 'show',
+ value: function show() {
+ if (this.options.showOn !== 'all' && !Foundation.MediaQuery.atLeast(this.options.showOn)) {
+ // console.error('The screen is too small to display this tooltip');
+ return false;
+ }
+
+ var _this = this;
+ this.template.css('visibility', 'hidden').show();
+ this._setPosition();
+
+ /**
+ * Fires to close all other open tooltips on the page
+ * @event Closeme#tooltip
+ */
+ this.$element.trigger('closeme.zf.tooltip', this.template.attr('id'));
+
+ this.template.attr({
+ 'data-is-active': true,
+ 'aria-hidden': false
+ });
+ _this.isActive = true;
+ // console.log(this.template);
+ this.template.stop().hide().css('visibility', '').fadeIn(this.options.fadeInDuration, function () {
+ //maybe do stuff?
+ });
+ /**
+ * Fires when the tooltip is shown
+ * @event Tooltip#show
+ */
+ this.$element.trigger('show.zf.tooltip');
+ }
+
+ /**
+ * Hides the current tooltip, and resets the positioning class if it was changed due to collision
+ * @fires Tooltip#hide
+ * @function
+ */
+
+ }, {
+ key: 'hide',
+ value: function hide() {
+ // console.log('hiding', this.$element.data('yeti-box'));
+ var _this = this;
+ this.template.stop().attr({
+ 'aria-hidden': true,
+ 'data-is-active': false
+ }).fadeOut(this.options.fadeOutDuration, function () {
+ _this.isActive = false;
+ _this.isClick = false;
+ if (_this.classChanged) {
+ _this.template.removeClass(_this._getPositionClass(_this.template)).addClass(_this.options.positionClass);
+
+ _this.usedPositions = [];
+ _this.counter = 4;
+ _this.classChanged = false;
+ }
+ });
+ /**
+ * fires when the tooltip is hidden
+ * @event Tooltip#hide
+ */
+ this.$element.trigger('hide.zf.tooltip');
+ }
+
+ /**
+ * adds event listeners for the tooltip and its anchor
+ * TODO combine some of the listeners like focus and mouseenter, etc.
+ * @private
+ */
+
+ }, {
+ key: '_events',
+ value: function _events() {
+ var _this = this;
+ var $template = this.template;
+ var isFocus = false;
+
+ if (!this.options.disableHover) {
+
+ this.$element.on('mouseenter.zf.tooltip', function (e) {
+ if (!_this.isActive) {
+ _this.timeout = setTimeout(function () {
+ _this.show();
+ }, _this.options.hoverDelay);
+ }
+ }).on('mouseleave.zf.tooltip', function (e) {
+ clearTimeout(_this.timeout);
+ if (!isFocus || _this.isClick && !_this.options.clickOpen) {
+ _this.hide();
+ }
+ });
+ }
+
+ if (this.options.clickOpen) {
+ this.$element.on('mousedown.zf.tooltip', function (e) {
+ e.stopImmediatePropagation();
+ if (_this.isClick) {
+ //_this.hide();
+ // _this.isClick = false;
+ } else {
+ _this.isClick = true;
+ if ((_this.options.disableHover || !_this.$element.attr('tabindex')) && !_this.isActive) {
+ _this.show();
+ }
+ }
+ });
+ } else {
+ this.$element.on('mousedown.zf.tooltip', function (e) {
+ e.stopImmediatePropagation();
+ _this.isClick = true;
+ });
+ }
+
+ if (!this.options.disableForTouch) {
+ this.$element.on('tap.zf.tooltip touchend.zf.tooltip', function (e) {
+ _this.isActive ? _this.hide() : _this.show();
+ });
+ }
+
+ this.$element.on({
+ // 'toggle.zf.trigger': this.toggle.bind(this),
+ // 'close.zf.trigger': this.hide.bind(this)
+ 'close.zf.trigger': this.hide.bind(this)
+ });
+
+ this.$element.on('focus.zf.tooltip', function (e) {
+ isFocus = true;
+ if (_this.isClick) {
+ // If we're not showing open on clicks, we need to pretend a click-launched focus isn't
+ // a real focus, otherwise on hover and come back we get bad behavior
+ if (!_this.options.clickOpen) {
+ isFocus = false;
+ }
+ return false;
+ } else {
+ _this.show();
+ }
+ }).on('focusout.zf.tooltip', function (e) {
+ isFocus = false;
+ _this.isClick = false;
+ _this.hide();
+ }).on('resizeme.zf.trigger', function () {
+ if (_this.isActive) {
+ _this._setPosition();
+ }
+ });
+ }
+
+ /**
+ * adds a toggle method, in addition to the static show() & hide() functions
+ * @function
+ */
+
+ }, {
+ key: 'toggle',
+ value: function toggle() {
+ if (this.isActive) {
+ this.hide();
+ } else {
+ this.show();
+ }
+ }
+
+ /**
+ * Destroys an instance of tooltip, removes template element from the view.
+ * @function
+ */
+
+ }, {
+ key: 'destroy',
+ value: function destroy() {
+ this.$element.attr('title', this.template.text()).off('.zf.trigger .zf.tootip')
+ // .removeClass('has-tip')
+ .removeAttr('aria-describedby').removeAttr('data-yeti-box').removeAttr('data-toggle').removeAttr('data-resize');
+
+ this.template.remove();
+
+ Foundation.unregisterPlugin(this);
+ }
+ }]);
+
+ return Tooltip;
+ }();
+
+ Tooltip.defaults = {
+ disableForTouch: false,
+ /**
+ * Time, in ms, before a tooltip should open on hover.
+ * @option
+ * @example 200
+ */
+ hoverDelay: 200,
+ /**
+ * Time, in ms, a tooltip should take to fade into view.
+ * @option
+ * @example 150
+ */
+ fadeInDuration: 150,
+ /**
+ * Time, in ms, a tooltip should take to fade out of view.
+ * @option
+ * @example 150
+ */
+ fadeOutDuration: 150,
+ /**
+ * Disables hover events from opening the tooltip if set to true
+ * @option
+ * @example false
+ */
+ disableHover: false,
+ /**
+ * Optional addtional classes to apply to the tooltip template on init.
+ * @option
+ * @example 'my-cool-tip-class'
+ */
+ templateClasses: '',
+ /**
+ * Non-optional class added to tooltip templates. Foundation default is 'tooltip'.
+ * @option
+ * @example 'tooltip'
+ */
+ tooltipClass: 'tooltip',
+ /**
+ * Class applied to the tooltip anchor element.
+ * @option
+ * @example 'has-tip'
+ */
+ triggerClass: 'has-tip',
+ /**
+ * Minimum breakpoint size at which to open the tooltip.
+ * @option
+ * @example 'small'
+ */
+ showOn: 'small',
+ /**
+ * Custom template to be used to generate markup for tooltip.
+ * @option
+ * @example '<div class="tooltip"></div>'
+ */
+ template: '',
+ /**
+ * Text displayed in the tooltip template on open.
+ * @option
+ * @example 'Some cool space fact here.'
+ */
+ tipText: '',
+ touchCloseText: 'Tap to close.',
+ /**
+ * Allows the tooltip to remain open if triggered with a click or touch event.
+ * @option
+ * @example true
+ */
+ clickOpen: true,
+ /**
+ * Additional positioning classes, set by the JS
+ * @option
+ * @example 'top'
+ */
+ positionClass: '',
+ /**
+ * Distance, in pixels, the template should push away from the anchor on the Y axis.
+ * @option
+ * @example 10
+ */
+ vOffset: 10,
+ /**
+ * Distance, in pixels, the template should push away from the anchor on the X axis, if aligned to a side.
+ * @option
+ * @example 12
+ */
+ hOffset: 12
+ };
+
+ /**
+ * TODO utilize resize event trigger
+ */
+
+ // Window exports
+ Foundation.plugin(Tooltip, 'Tooltip');
+}(jQuery);
\ No newline at end of file
diff --git a/app/assets/stylesheets/foundation.css b/app/assets/stylesheets/foundation.css
new file mode 100644
index 0000000000..66aca962de
--- /dev/null
+++ b/app/assets/stylesheets/foundation.css
@@ -0,0 +1,4194 @@
+@charset "UTF-8";
+/**
+ * Foundation for Sites by ZURB
+ * Version 6.2.3
+ * foundation.zurb.com
+ * Licensed under MIT Open Source
+ */
+/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
+/**
+ * 1. Set default font family to sans-serif.
+ * 2. Prevent iOS and IE text size adjust after device orientation change,
+ * without disabling user zoom.
+ */
+html {
+ font-family: sans-serif;
+ /* 1 */
+ -ms-text-size-adjust: 100%;
+ /* 2 */
+ -webkit-text-size-adjust: 100%;
+ /* 2 */ }
+
+/**
+ * Remove default margin.
+ */
+body {
+ margin: 0; }
+
+/* HTML5 display definitions
+ ========================================================================== */
+/**
+ * Correct `block` display not defined for any HTML5 element in IE 8/9.
+ * Correct `block` display not defined for `details` or `summary` in IE 10/11
+ * and Firefox.
+ * Correct `block` display not defined for `main` in IE 11.
+ */
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+menu,
+nav,
+section,
+summary {
+ display: block; }
+
+/**
+ * 1. Correct `inline-block` display not defined in IE 8/9.
+ * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
+ */
+audio,
+canvas,
+progress,
+video {
+ display: inline-block;
+ /* 1 */
+ vertical-align: baseline;
+ /* 2 */ }
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+audio:not([controls]) {
+ display: none;
+ height: 0; }
+
+/**
+ * Address `[hidden]` styling not present in IE 8/9/10.
+ * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
+ */
+[hidden],
+template {
+ display: none; }
+
+/* Links
+ ========================================================================== */
+/**
+ * Remove the gray background color from active links in IE 10.
+ */
+a {
+ background-color: transparent; }
+
+/**
+ * Improve readability of focused elements when they are also in an
+ * active/hover state.
+ */
+a:active,
+a:hover {
+ outline: 0; }
+
+/* Text-level semantics
+ ========================================================================== */
+/**
+ * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
+ */
+abbr[title] {
+ border-bottom: 1px dotted; }
+
+/**
+ * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
+ */
+b,
+strong {
+ font-weight: bold; }
+
+/**
+ * Address styling not present in Safari and Chrome.
+ */
+dfn {
+ font-style: italic; }
+
+/**
+ * Address variable `h1` font-size and margin within `section` and `article`
+ * contexts in Firefox 4+, Safari, and Chrome.
+ */
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0; }
+
+/**
+ * Address styling not present in IE 8/9.
+ */
+mark {
+ background: #ff0;
+ color: #000; }
+
+/**
+ * Address inconsistent and variable font size in all browsers.
+ */
+small {
+ font-size: 80%; }
+
+/**
+ * Prevent `sub` and `sup` affecting `line-height` in all browsers.
+ */
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline; }
+
+sup {
+ top: -0.5em; }
+
+sub {
+ bottom: -0.25em; }
+
+/* Embedded content
+ ========================================================================== */
+/**
+ * Remove border when inside `a` element in IE 8/9/10.
+ */
+img {
+ border: 0; }
+
+/**
+ * Correct overflow not hidden in IE 9/10/11.
+ */
+svg:not(:root) {
+ overflow: hidden; }
+
+/* Grouping content
+ ========================================================================== */
+/**
+ * Address margin not present in IE 8/9 and Safari.
+ */
+figure {
+ margin: 1em 40px; }
+
+/**
+ * Address differences between Firefox and other browsers.
+ */
+hr {
+ box-sizing: content-box;
+ height: 0; }
+
+/**
+ * Contain overflow in all browsers.
+ */
+pre {
+ overflow: auto; }
+
+/**
+ * Address odd `em`-unit font size rendering in all browsers.
+ */
+code,
+kbd,
+pre,
+samp {
+ font-family: monospace, monospace;
+ font-size: 1em; }
+
+/* Forms
+ ========================================================================== */
+/**
+ * Known limitation: by default, Chrome and Safari on OS X allow very limited
+ * styling of `select`, unless a `border` property is set.
+ */
+/**
+ * 1. Correct color not being inherited.
+ * Known issue: affects color of disabled elements.
+ * 2. Correct font properties not being inherited.
+ * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
+ */
+button,
+input,
+optgroup,
+select,
+textarea {
+ color: inherit;
+ /* 1 */
+ font: inherit;
+ /* 2 */
+ margin: 0;
+ /* 3 */ }
+
+/**
+ * Address `overflow` set to `hidden` in IE 8/9/10/11.
+ */
+button {
+ overflow: visible; }
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
+ * Correct `select` style inheritance in Firefox.
+ */
+button,
+select {
+ text-transform: none; }
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ * and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ * `input` and others.
+ */
+button,
+html input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+ -webkit-appearance: button;
+ /* 2 */
+ cursor: pointer;
+ /* 3 */ }
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+button[disabled],
+html input[disabled] {
+ cursor: not-allowed; }
+
+/**
+ * Remove inner padding and border in Firefox 4+.
+ */
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ border: 0;
+ padding: 0; }
+
+/**
+ * Address Firefox 4+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+input {
+ line-height: normal; }
+
+/**
+ * It's recommended that you don't attempt to style these elements.
+ * Firefox's implementation doesn't respect box-sizing, padding, or width.
+ *
+ * 1. Address box sizing set to `content-box` in IE 8/9/10.
+ * 2. Remove excess padding in IE 8/9/10.
+ */
+input[type="checkbox"],
+input[type="radio"] {
+ box-sizing: border-box;
+ /* 1 */
+ padding: 0;
+ /* 2 */ }
+
+/**
+ * Fix the cursor style for Chrome's increment/decrement buttons. For certain
+ * `font-size` values of the `input`, it causes the cursor style of the
+ * decrement button to change from `default` to `text`.
+ */
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+ height: auto; }
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
+ */
+input[type="search"] {
+ -webkit-appearance: textfield;
+ /* 1 */
+ box-sizing: content-box;
+ /* 2 */ }
+
+/**
+ * Remove inner padding and search cancel button in Safari and Chrome on OS X.
+ * Safari (but not Chrome) clips the cancel button when the search input has
+ * padding (and `textfield` appearance).
+ */
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none; }
+
+/**
+ * Define consistent border, margin, and padding.
+ * [NOTE] We don't enable this ruleset in Foundation, because we want the element to have plain styling.
+ */
+/* fieldset {
+ border: 1px solid #c0c0c0;
+ margin: 0 2px;
+ padding: 0.35em 0.625em 0.75em;
+ } */
+/**
+ * 1. Correct `color` not being inherited in IE 8/9/10/11.
+ * 2. Remove padding so people aren't caught out if they zero out fieldsets.
+ */
+legend {
+ border: 0;
+ /* 1 */
+ padding: 0;
+ /* 2 */ }
+
+/**
+ * Remove default vertical scrollbar in IE 8/9/10/11.
+ */
+textarea {
+ overflow: auto; }
+
+/**
+ * Don't inherit the `font-weight` (applied by a rule above).
+ * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
+ */
+optgroup {
+ font-weight: bold; }
+
+/* Tables
+ ========================================================================== */
+/**
+ * Remove most spacing between table cells.
+ */
+table {
+ border-collapse: collapse;
+ border-spacing: 0; }
+
+td,
+th {
+ padding: 0; }
+
+.foundation-mq {
+ font-family: "small=0em&medium=40em&large=64em&xlarge=75em&xxlarge=90em"; }
+
+html {
+ font-size: 100%;
+ box-sizing: border-box; }
+
+*,
+*::before,
+*::after {
+ box-sizing: inherit; }
+
+body {
+ padding: 0;
+ margin: 0;
+ font-family: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif;
+ font-weight: normal;
+ line-height: 1.5;
+ color: #0a0a0a;
+ background: #fefefe;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale; }
+
+img {
+ max-width: 100%;
+ height: auto;
+ -ms-interpolation-mode: bicubic;
+ display: inline-block;
+ vertical-align: middle; }
+
+textarea {
+ height: auto;
+ min-height: 50px;
+ border-radius: 0; }
+
+select {
+ width: 100%;
+ border-radius: 0; }
+
+#map_canvas img,
+#map_canvas embed,
+#map_canvas object,
+.map_canvas img,
+.map_canvas embed,
+.map_canvas object,
+.mqa-display img,
+.mqa-display embed,
+.mqa-display object {
+ max-width: none !important; }
+
+button {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ background: transparent;
+ padding: 0;
+ border: 0;
+ border-radius: 0;
+ line-height: 1; }
+ [data-whatinput='mouse'] button {
+ outline: 0; }
+
+.is-visible {
+ display: block !important; }
+
+.is-hidden {
+ display: none !important; }
+
+.row {
+ max-width: 75rem;
+ margin-left: auto;
+ margin-right: auto; }
+ .row::before, .row::after {
+ content: ' ';
+ display: table; }
+ .row::after {
+ clear: both; }
+ .row.collapse > .column, .row.collapse > .columns {
+ padding-left: 0;
+ padding-right: 0; }
+ .row .row {
+ max-width: none;
+ margin-left: -0.625rem;
+ margin-right: -0.625rem; }
+ @media screen and (min-width: 40em) {
+ .row .row {
+ margin-left: -0.9375rem;
+ margin-right: -0.9375rem; } }
+ .row .row.collapse {
+ margin-left: 0;
+ margin-right: 0; }
+ .row.expanded {
+ max-width: none; }
+ .row.expanded .row {
+ margin-left: auto;
+ margin-right: auto; }
+
+.column, .columns {
+ width: 100%;
+ float: left;
+ padding-left: 0.625rem;
+ padding-right: 0.625rem; }
+ @media screen and (min-width: 40em) {
+ .column, .columns {
+ padding-left: 0.9375rem;
+ padding-right: 0.9375rem; } }
+ .column:last-child:not(:first-child), .columns:last-child:not(:first-child) {
+ float: right; }
+ .column.end:last-child:last-child, .end.columns:last-child:last-child {
+ float: left; }
+
+.column.row.row, .row.row.columns {
+ float: none; }
+ .row .column.row.row, .row .row.row.columns {
+ padding-left: 0;
+ padding-right: 0;
+ margin-left: 0;
+ margin-right: 0; }
+
+.small-1 {
+ width: 8.33333%; }
+
+.small-push-1 {
+ position: relative;
+ left: 8.33333%; }
+
+.small-pull-1 {
+ position: relative;
+ left: -8.33333%; }
+
+.small-offset-0 {
+ margin-left: 0%; }
+
+.small-2 {
+ width: 16.66667%; }
+
+.small-push-2 {
+ position: relative;
+ left: 16.66667%; }
+
+.small-pull-2 {
+ position: relative;
+ left: -16.66667%; }
+
+.small-offset-1 {
+ margin-left: 8.33333%; }
+
+.small-3 {
+ width: 25%; }
+
+.small-push-3 {
+ position: relative;
+ left: 25%; }
+
+.small-pull-3 {
+ position: relative;
+ left: -25%; }
+
+.small-offset-2 {
+ margin-left: 16.66667%; }
+
+.small-4 {
+ width: 33.33333%; }
+
+.small-push-4 {
+ position: relative;
+ left: 33.33333%; }
+
+.small-pull-4 {
+ position: relative;
+ left: -33.33333%; }
+
+.small-offset-3 {
+ margin-left: 25%; }
+
+.small-5 {
+ width: 41.66667%; }
+
+.small-push-5 {
+ position: relative;
+ left: 41.66667%; }
+
+.small-pull-5 {
+ position: relative;
+ left: -41.66667%; }
+
+.small-offset-4 {
+ margin-left: 33.33333%; }
+
+.small-6 {
+ width: 50%; }
+
+.small-push-6 {
+ position: relative;
+ left: 50%; }
+
+.small-pull-6 {
+ position: relative;
+ left: -50%; }
+
+.small-offset-5 {
+ margin-left: 41.66667%; }
+
+.small-7 {
+ width: 58.33333%; }
+
+.small-push-7 {
+ position: relative;
+ left: 58.33333%; }
+
+.small-pull-7 {
+ position: relative;
+ left: -58.33333%; }
+
+.small-offset-6 {
+ margin-left: 50%; }
+
+.small-8 {
+ width: 66.66667%; }
+
+.small-push-8 {
+ position: relative;
+ left: 66.66667%; }
+
+.small-pull-8 {
+ position: relative;
+ left: -66.66667%; }
+
+.small-offset-7 {
+ margin-left: 58.33333%; }
+
+.small-9 {
+ width: 75%; }
+
+.small-push-9 {
+ position: relative;
+ left: 75%; }
+
+.small-pull-9 {
+ position: relative;
+ left: -75%; }
+
+.small-offset-8 {
+ margin-left: 66.66667%; }
+
+.small-10 {
+ width: 83.33333%; }
+
+.small-push-10 {
+ position: relative;
+ left: 83.33333%; }
+
+.small-pull-10 {
+ position: relative;
+ left: -83.33333%; }
+
+.small-offset-9 {
+ margin-left: 75%; }
+
+.small-11 {
+ width: 91.66667%; }
+
+.small-push-11 {
+ position: relative;
+ left: 91.66667%; }
+
+.small-pull-11 {
+ position: relative;
+ left: -91.66667%; }
+
+.small-offset-10 {
+ margin-left: 83.33333%; }
+
+.small-12 {
+ width: 100%; }
+
+.small-offset-11 {
+ margin-left: 91.66667%; }
+
+.small-up-1 > .column, .small-up-1 > .columns {
+ width: 100%;
+ float: left; }
+ .small-up-1 > .column:nth-of-type(1n), .small-up-1 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .small-up-1 > .column:nth-of-type(1n+1), .small-up-1 > .columns:nth-of-type(1n+1) {
+ clear: both; }
+ .small-up-1 > .column:last-child, .small-up-1 > .columns:last-child {
+ float: left; }
+
+.small-up-2 > .column, .small-up-2 > .columns {
+ width: 50%;
+ float: left; }
+ .small-up-2 > .column:nth-of-type(1n), .small-up-2 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .small-up-2 > .column:nth-of-type(2n+1), .small-up-2 > .columns:nth-of-type(2n+1) {
+ clear: both; }
+ .small-up-2 > .column:last-child, .small-up-2 > .columns:last-child {
+ float: left; }
+
+.small-up-3 > .column, .small-up-3 > .columns {
+ width: 33.33333%;
+ float: left; }
+ .small-up-3 > .column:nth-of-type(1n), .small-up-3 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .small-up-3 > .column:nth-of-type(3n+1), .small-up-3 > .columns:nth-of-type(3n+1) {
+ clear: both; }
+ .small-up-3 > .column:last-child, .small-up-3 > .columns:last-child {
+ float: left; }
+
+.small-up-4 > .column, .small-up-4 > .columns {
+ width: 25%;
+ float: left; }
+ .small-up-4 > .column:nth-of-type(1n), .small-up-4 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .small-up-4 > .column:nth-of-type(4n+1), .small-up-4 > .columns:nth-of-type(4n+1) {
+ clear: both; }
+ .small-up-4 > .column:last-child, .small-up-4 > .columns:last-child {
+ float: left; }
+
+.small-up-5 > .column, .small-up-5 > .columns {
+ width: 20%;
+ float: left; }
+ .small-up-5 > .column:nth-of-type(1n), .small-up-5 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .small-up-5 > .column:nth-of-type(5n+1), .small-up-5 > .columns:nth-of-type(5n+1) {
+ clear: both; }
+ .small-up-5 > .column:last-child, .small-up-5 > .columns:last-child {
+ float: left; }
+
+.small-up-6 > .column, .small-up-6 > .columns {
+ width: 16.66667%;
+ float: left; }
+ .small-up-6 > .column:nth-of-type(1n), .small-up-6 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .small-up-6 > .column:nth-of-type(6n+1), .small-up-6 > .columns:nth-of-type(6n+1) {
+ clear: both; }
+ .small-up-6 > .column:last-child, .small-up-6 > .columns:last-child {
+ float: left; }
+
+.small-up-7 > .column, .small-up-7 > .columns {
+ width: 14.28571%;
+ float: left; }
+ .small-up-7 > .column:nth-of-type(1n), .small-up-7 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .small-up-7 > .column:nth-of-type(7n+1), .small-up-7 > .columns:nth-of-type(7n+1) {
+ clear: both; }
+ .small-up-7 > .column:last-child, .small-up-7 > .columns:last-child {
+ float: left; }
+
+.small-up-8 > .column, .small-up-8 > .columns {
+ width: 12.5%;
+ float: left; }
+ .small-up-8 > .column:nth-of-type(1n), .small-up-8 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .small-up-8 > .column:nth-of-type(8n+1), .small-up-8 > .columns:nth-of-type(8n+1) {
+ clear: both; }
+ .small-up-8 > .column:last-child, .small-up-8 > .columns:last-child {
+ float: left; }
+
+.small-collapse > .column, .small-collapse > .columns {
+ padding-left: 0;
+ padding-right: 0; }
+
+.small-collapse .row,
+.expanded.row .small-collapse.row {
+ margin-left: 0;
+ margin-right: 0; }
+
+.small-uncollapse > .column, .small-uncollapse > .columns {
+ padding-left: 0.625rem;
+ padding-right: 0.625rem; }
+
+.small-centered {
+ float: none;
+ margin-left: auto;
+ margin-right: auto; }
+
+.small-uncentered,
+.small-push-0,
+.small-pull-0 {
+ position: static;
+ margin-left: 0;
+ margin-right: 0;
+ float: left; }
+
+@media screen and (min-width: 40em) {
+ .medium-1 {
+ width: 8.33333%; }
+ .medium-push-1 {
+ position: relative;
+ left: 8.33333%; }
+ .medium-pull-1 {
+ position: relative;
+ left: -8.33333%; }
+ .medium-offset-0 {
+ margin-left: 0%; }
+ .medium-2 {
+ width: 16.66667%; }
+ .medium-push-2 {
+ position: relative;
+ left: 16.66667%; }
+ .medium-pull-2 {
+ position: relative;
+ left: -16.66667%; }
+ .medium-offset-1 {
+ margin-left: 8.33333%; }
+ .medium-3 {
+ width: 25%; }
+ .medium-push-3 {
+ position: relative;
+ left: 25%; }
+ .medium-pull-3 {
+ position: relative;
+ left: -25%; }
+ .medium-offset-2 {
+ margin-left: 16.66667%; }
+ .medium-4 {
+ width: 33.33333%; }
+ .medium-push-4 {
+ position: relative;
+ left: 33.33333%; }
+ .medium-pull-4 {
+ position: relative;
+ left: -33.33333%; }
+ .medium-offset-3 {
+ margin-left: 25%; }
+ .medium-5 {
+ width: 41.66667%; }
+ .medium-push-5 {
+ position: relative;
+ left: 41.66667%; }
+ .medium-pull-5 {
+ position: relative;
+ left: -41.66667%; }
+ .medium-offset-4 {
+ margin-left: 33.33333%; }
+ .medium-6 {
+ width: 50%; }
+ .medium-push-6 {
+ position: relative;
+ left: 50%; }
+ .medium-pull-6 {
+ position: relative;
+ left: -50%; }
+ .medium-offset-5 {
+ margin-left: 41.66667%; }
+ .medium-7 {
+ width: 58.33333%; }
+ .medium-push-7 {
+ position: relative;
+ left: 58.33333%; }
+ .medium-pull-7 {
+ position: relative;
+ left: -58.33333%; }
+ .medium-offset-6 {
+ margin-left: 50%; }
+ .medium-8 {
+ width: 66.66667%; }
+ .medium-push-8 {
+ position: relative;
+ left: 66.66667%; }
+ .medium-pull-8 {
+ position: relative;
+ left: -66.66667%; }
+ .medium-offset-7 {
+ margin-left: 58.33333%; }
+ .medium-9 {
+ width: 75%; }
+ .medium-push-9 {
+ position: relative;
+ left: 75%; }
+ .medium-pull-9 {
+ position: relative;
+ left: -75%; }
+ .medium-offset-8 {
+ margin-left: 66.66667%; }
+ .medium-10 {
+ width: 83.33333%; }
+ .medium-push-10 {
+ position: relative;
+ left: 83.33333%; }
+ .medium-pull-10 {
+ position: relative;
+ left: -83.33333%; }
+ .medium-offset-9 {
+ margin-left: 75%; }
+ .medium-11 {
+ width: 91.66667%; }
+ .medium-push-11 {
+ position: relative;
+ left: 91.66667%; }
+ .medium-pull-11 {
+ position: relative;
+ left: -91.66667%; }
+ .medium-offset-10 {
+ margin-left: 83.33333%; }
+ .medium-12 {
+ width: 100%; }
+ .medium-offset-11 {
+ margin-left: 91.66667%; }
+ .medium-up-1 > .column, .medium-up-1 > .columns {
+ width: 100%;
+ float: left; }
+ .medium-up-1 > .column:nth-of-type(1n), .medium-up-1 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .medium-up-1 > .column:nth-of-type(1n+1), .medium-up-1 > .columns:nth-of-type(1n+1) {
+ clear: both; }
+ .medium-up-1 > .column:last-child, .medium-up-1 > .columns:last-child {
+ float: left; }
+ .medium-up-2 > .column, .medium-up-2 > .columns {
+ width: 50%;
+ float: left; }
+ .medium-up-2 > .column:nth-of-type(1n), .medium-up-2 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .medium-up-2 > .column:nth-of-type(2n+1), .medium-up-2 > .columns:nth-of-type(2n+1) {
+ clear: both; }
+ .medium-up-2 > .column:last-child, .medium-up-2 > .columns:last-child {
+ float: left; }
+ .medium-up-3 > .column, .medium-up-3 > .columns {
+ width: 33.33333%;
+ float: left; }
+ .medium-up-3 > .column:nth-of-type(1n), .medium-up-3 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .medium-up-3 > .column:nth-of-type(3n+1), .medium-up-3 > .columns:nth-of-type(3n+1) {
+ clear: both; }
+ .medium-up-3 > .column:last-child, .medium-up-3 > .columns:last-child {
+ float: left; }
+ .medium-up-4 > .column, .medium-up-4 > .columns {
+ width: 25%;
+ float: left; }
+ .medium-up-4 > .column:nth-of-type(1n), .medium-up-4 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .medium-up-4 > .column:nth-of-type(4n+1), .medium-up-4 > .columns:nth-of-type(4n+1) {
+ clear: both; }
+ .medium-up-4 > .column:last-child, .medium-up-4 > .columns:last-child {
+ float: left; }
+ .medium-up-5 > .column, .medium-up-5 > .columns {
+ width: 20%;
+ float: left; }
+ .medium-up-5 > .column:nth-of-type(1n), .medium-up-5 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .medium-up-5 > .column:nth-of-type(5n+1), .medium-up-5 > .columns:nth-of-type(5n+1) {
+ clear: both; }
+ .medium-up-5 > .column:last-child, .medium-up-5 > .columns:last-child {
+ float: left; }
+ .medium-up-6 > .column, .medium-up-6 > .columns {
+ width: 16.66667%;
+ float: left; }
+ .medium-up-6 > .column:nth-of-type(1n), .medium-up-6 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .medium-up-6 > .column:nth-of-type(6n+1), .medium-up-6 > .columns:nth-of-type(6n+1) {
+ clear: both; }
+ .medium-up-6 > .column:last-child, .medium-up-6 > .columns:last-child {
+ float: left; }
+ .medium-up-7 > .column, .medium-up-7 > .columns {
+ width: 14.28571%;
+ float: left; }
+ .medium-up-7 > .column:nth-of-type(1n), .medium-up-7 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .medium-up-7 > .column:nth-of-type(7n+1), .medium-up-7 > .columns:nth-of-type(7n+1) {
+ clear: both; }
+ .medium-up-7 > .column:last-child, .medium-up-7 > .columns:last-child {
+ float: left; }
+ .medium-up-8 > .column, .medium-up-8 > .columns {
+ width: 12.5%;
+ float: left; }
+ .medium-up-8 > .column:nth-of-type(1n), .medium-up-8 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .medium-up-8 > .column:nth-of-type(8n+1), .medium-up-8 > .columns:nth-of-type(8n+1) {
+ clear: both; }
+ .medium-up-8 > .column:last-child, .medium-up-8 > .columns:last-child {
+ float: left; }
+ .medium-collapse > .column, .medium-collapse > .columns {
+ padding-left: 0;
+ padding-right: 0; }
+ .medium-collapse .row,
+ .expanded.row .medium-collapse.row {
+ margin-left: 0;
+ margin-right: 0; }
+ .medium-uncollapse > .column, .medium-uncollapse > .columns {
+ padding-left: 0.9375rem;
+ padding-right: 0.9375rem; }
+ .medium-centered {
+ float: none;
+ margin-left: auto;
+ margin-right: auto; }
+ .medium-uncentered,
+ .medium-push-0,
+ .medium-pull-0 {
+ position: static;
+ margin-left: 0;
+ margin-right: 0;
+ float: left; } }
+
+@media screen and (min-width: 64em) {
+ .large-1 {
+ width: 8.33333%; }
+ .large-push-1 {
+ position: relative;
+ left: 8.33333%; }
+ .large-pull-1 {
+ position: relative;
+ left: -8.33333%; }
+ .large-offset-0 {
+ margin-left: 0%; }
+ .large-2 {
+ width: 16.66667%; }
+ .large-push-2 {
+ position: relative;
+ left: 16.66667%; }
+ .large-pull-2 {
+ position: relative;
+ left: -16.66667%; }
+ .large-offset-1 {
+ margin-left: 8.33333%; }
+ .large-3 {
+ width: 25%; }
+ .large-push-3 {
+ position: relative;
+ left: 25%; }
+ .large-pull-3 {
+ position: relative;
+ left: -25%; }
+ .large-offset-2 {
+ margin-left: 16.66667%; }
+ .large-4 {
+ width: 33.33333%; }
+ .large-push-4 {
+ position: relative;
+ left: 33.33333%; }
+ .large-pull-4 {
+ position: relative;
+ left: -33.33333%; }
+ .large-offset-3 {
+ margin-left: 25%; }
+ .large-5 {
+ width: 41.66667%; }
+ .large-push-5 {
+ position: relative;
+ left: 41.66667%; }
+ .large-pull-5 {
+ position: relative;
+ left: -41.66667%; }
+ .large-offset-4 {
+ margin-left: 33.33333%; }
+ .large-6 {
+ width: 50%; }
+ .large-push-6 {
+ position: relative;
+ left: 50%; }
+ .large-pull-6 {
+ position: relative;
+ left: -50%; }
+ .large-offset-5 {
+ margin-left: 41.66667%; }
+ .large-7 {
+ width: 58.33333%; }
+ .large-push-7 {
+ position: relative;
+ left: 58.33333%; }
+ .large-pull-7 {
+ position: relative;
+ left: -58.33333%; }
+ .large-offset-6 {
+ margin-left: 50%; }
+ .large-8 {
+ width: 66.66667%; }
+ .large-push-8 {
+ position: relative;
+ left: 66.66667%; }
+ .large-pull-8 {
+ position: relative;
+ left: -66.66667%; }
+ .large-offset-7 {
+ margin-left: 58.33333%; }
+ .large-9 {
+ width: 75%; }
+ .large-push-9 {
+ position: relative;
+ left: 75%; }
+ .large-pull-9 {
+ position: relative;
+ left: -75%; }
+ .large-offset-8 {
+ margin-left: 66.66667%; }
+ .large-10 {
+ width: 83.33333%; }
+ .large-push-10 {
+ position: relative;
+ left: 83.33333%; }
+ .large-pull-10 {
+ position: relative;
+ left: -83.33333%; }
+ .large-offset-9 {
+ margin-left: 75%; }
+ .large-11 {
+ width: 91.66667%; }
+ .large-push-11 {
+ position: relative;
+ left: 91.66667%; }
+ .large-pull-11 {
+ position: relative;
+ left: -91.66667%; }
+ .large-offset-10 {
+ margin-left: 83.33333%; }
+ .large-12 {
+ width: 100%; }
+ .large-offset-11 {
+ margin-left: 91.66667%; }
+ .large-up-1 > .column, .large-up-1 > .columns {
+ width: 100%;
+ float: left; }
+ .large-up-1 > .column:nth-of-type(1n), .large-up-1 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .large-up-1 > .column:nth-of-type(1n+1), .large-up-1 > .columns:nth-of-type(1n+1) {
+ clear: both; }
+ .large-up-1 > .column:last-child, .large-up-1 > .columns:last-child {
+ float: left; }
+ .large-up-2 > .column, .large-up-2 > .columns {
+ width: 50%;
+ float: left; }
+ .large-up-2 > .column:nth-of-type(1n), .large-up-2 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .large-up-2 > .column:nth-of-type(2n+1), .large-up-2 > .columns:nth-of-type(2n+1) {
+ clear: both; }
+ .large-up-2 > .column:last-child, .large-up-2 > .columns:last-child {
+ float: left; }
+ .large-up-3 > .column, .large-up-3 > .columns {
+ width: 33.33333%;
+ float: left; }
+ .large-up-3 > .column:nth-of-type(1n), .large-up-3 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .large-up-3 > .column:nth-of-type(3n+1), .large-up-3 > .columns:nth-of-type(3n+1) {
+ clear: both; }
+ .large-up-3 > .column:last-child, .large-up-3 > .columns:last-child {
+ float: left; }
+ .large-up-4 > .column, .large-up-4 > .columns {
+ width: 25%;
+ float: left; }
+ .large-up-4 > .column:nth-of-type(1n), .large-up-4 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .large-up-4 > .column:nth-of-type(4n+1), .large-up-4 > .columns:nth-of-type(4n+1) {
+ clear: both; }
+ .large-up-4 > .column:last-child, .large-up-4 > .columns:last-child {
+ float: left; }
+ .large-up-5 > .column, .large-up-5 > .columns {
+ width: 20%;
+ float: left; }
+ .large-up-5 > .column:nth-of-type(1n), .large-up-5 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .large-up-5 > .column:nth-of-type(5n+1), .large-up-5 > .columns:nth-of-type(5n+1) {
+ clear: both; }
+ .large-up-5 > .column:last-child, .large-up-5 > .columns:last-child {
+ float: left; }
+ .large-up-6 > .column, .large-up-6 > .columns {
+ width: 16.66667%;
+ float: left; }
+ .large-up-6 > .column:nth-of-type(1n), .large-up-6 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .large-up-6 > .column:nth-of-type(6n+1), .large-up-6 > .columns:nth-of-type(6n+1) {
+ clear: both; }
+ .large-up-6 > .column:last-child, .large-up-6 > .columns:last-child {
+ float: left; }
+ .large-up-7 > .column, .large-up-7 > .columns {
+ width: 14.28571%;
+ float: left; }
+ .large-up-7 > .column:nth-of-type(1n), .large-up-7 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .large-up-7 > .column:nth-of-type(7n+1), .large-up-7 > .columns:nth-of-type(7n+1) {
+ clear: both; }
+ .large-up-7 > .column:last-child, .large-up-7 > .columns:last-child {
+ float: left; }
+ .large-up-8 > .column, .large-up-8 > .columns {
+ width: 12.5%;
+ float: left; }
+ .large-up-8 > .column:nth-of-type(1n), .large-up-8 > .columns:nth-of-type(1n) {
+ clear: none; }
+ .large-up-8 > .column:nth-of-type(8n+1), .large-up-8 > .columns:nth-of-type(8n+1) {
+ clear: both; }
+ .large-up-8 > .column:last-child, .large-up-8 > .columns:last-child {
+ float: left; }
+ .large-collapse > .column, .large-collapse > .columns {
+ padding-left: 0;
+ padding-right: 0; }
+ .large-collapse .row,
+ .expanded.row .large-collapse.row {
+ margin-left: 0;
+ margin-right: 0; }
+ .large-uncollapse > .column, .large-uncollapse > .columns {
+ padding-left: 0.9375rem;
+ padding-right: 0.9375rem; }
+ .large-centered {
+ float: none;
+ margin-left: auto;
+ margin-right: auto; }
+ .large-uncentered,
+ .large-push-0,
+ .large-pull-0 {
+ position: static;
+ margin-left: 0;
+ margin-right: 0;
+ float: left; } }
+
+div,
+dl,
+dt,
+dd,
+ul,
+ol,
+li,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+pre,
+form,
+p,
+blockquote,
+th,
+td {
+ margin: 0;
+ padding: 0; }
+
+p {
+ font-size: inherit;
+ line-height: 1.6;
+ margin-bottom: 1rem;
+ text-rendering: optimizeLegibility; }
+
+em,
+i {
+ font-style: italic;
+ line-height: inherit; }
+
+strong,
+b {
+ font-weight: bold;
+ line-height: inherit; }
+
+small {
+ font-size: 80%;
+ line-height: inherit; }
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-family: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif;
+ font-weight: normal;
+ font-style: normal;
+ color: inherit;
+ text-rendering: optimizeLegibility;
+ margin-top: 0;
+ margin-bottom: 0.5rem;
+ line-height: 1.4; }
+ h1 small,
+ h2 small,
+ h3 small,
+ h4 small,
+ h5 small,
+ h6 small {
+ color: #cacaca;
+ line-height: 0; }
+
+h1 {
+ font-size: 1.5rem; }
+
+h2 {
+ font-size: 1.25rem; }
+
+h3 {
+ font-size: 1.1875rem; }
+
+h4 {
+ font-size: 1.125rem; }
+
+h5 {
+ font-size: 1.0625rem; }
+
+h6 {
+ font-size: 1rem; }
+
+@media screen and (min-width: 40em) {
+ h1 {
+ font-size: 3rem; }
+ h2 {
+ font-size: 2.5rem; }
+ h3 {
+ font-size: 1.9375rem; }
+ h4 {
+ font-size: 1.5625rem; }
+ h5 {
+ font-size: 1.25rem; }
+ h6 {
+ font-size: 1rem; } }
+
+a {
+ color: #2199e8;
+ text-decoration: none;
+ line-height: inherit;
+ cursor: pointer; }
+ a:hover, a:focus {
+ color: #1585cf; }
+ a img {
+ border: 0; }
+
+hr {
+ max-width: 75rem;
+ height: 0;
+ border-right: 0;
+ border-top: 0;
+ border-bottom: 1px solid #cacaca;
+ border-left: 0;
+ margin: 1.25rem auto;
+ clear: both; }
+
+ul,
+ol,
+dl {
+ line-height: 1.6;
+ margin-bottom: 1rem;
+ list-style-position: outside; }
+
+li {
+ font-size: inherit; }
+
+ul {
+ list-style-type: disc;
+ margin-left: 1.25rem; }
+
+ol {
+ margin-left: 1.25rem; }
+
+ul ul, ol ul, ul ol, ol ol {
+ margin-left: 1.25rem;
+ margin-bottom: 0; }
+
+dl {
+ margin-bottom: 1rem; }
+ dl dt {
+ margin-bottom: 0.3rem;
+ font-weight: bold; }
+
+blockquote {
+ margin: 0 0 1rem;
+ padding: 0.5625rem 1.25rem 0 1.1875rem;
+ border-left: 1px solid #cacaca; }
+ blockquote, blockquote p {
+ line-height: 1.6;
+ color: #8a8a8a; }
+
+cite {
+ display: block;
+ font-size: 0.8125rem;
+ color: #8a8a8a; }
+ cite:before {
+ content: '\2014 \0020'; }
+
+abbr {
+ color: #0a0a0a;
+ cursor: help;
+ border-bottom: 1px dotted #0a0a0a; }
+
+code {
+ font-family: Consolas, "Liberation Mono", Courier, monospace;
+ font-weight: normal;
+ color: #0a0a0a;
+ background-color: #e6e6e6;
+ border: 1px solid #cacaca;
+ padding: 0.125rem 0.3125rem 0.0625rem; }
+
+kbd {
+ padding: 0.125rem 0.25rem 0;
+ margin: 0;
+ background-color: #e6e6e6;
+ color: #0a0a0a;
+ font-family: Consolas, "Liberation Mono", Courier, monospace; }
+
+.subheader {
+ margin-top: 0.2rem;
+ margin-bottom: 0.5rem;
+ font-weight: normal;
+ line-height: 1.4;
+ color: #8a8a8a; }
+
+.lead {
+ font-size: 125%;
+ line-height: 1.6; }
+
+.stat {
+ font-size: 2.5rem;
+ line-height: 1; }
+ p + .stat {
+ margin-top: -1rem; }
+
+.no-bullet {
+ margin-left: 0;
+ list-style: none; }
+
+.text-left {
+ text-align: left; }
+
+.text-right {
+ text-align: right; }
+
+.text-center {
+ text-align: center; }
+
+.text-justify {
+ text-align: justify; }
+
+@media screen and (min-width: 40em) {
+ .medium-text-left {
+ text-align: left; }
+ .medium-text-right {
+ text-align: right; }
+ .medium-text-center {
+ text-align: center; }
+ .medium-text-justify {
+ text-align: justify; } }
+
+@media screen and (min-width: 64em) {
+ .large-text-left {
+ text-align: left; }
+ .large-text-right {
+ text-align: right; }
+ .large-text-center {
+ text-align: center; }
+ .large-text-justify {
+ text-align: justify; } }
+
+.show-for-print {
+ display: none !important; }
+
+@media print {
+ * {
+ background: transparent !important;
+ color: black !important;
+ box-shadow: none !important;
+ text-shadow: none !important; }
+ .show-for-print {
+ display: block !important; }
+ .hide-for-print {
+ display: none !important; }
+ table.show-for-print {
+ display: table !important; }
+ thead.show-for-print {
+ display: table-header-group !important; }
+ tbody.show-for-print {
+ display: table-row-group !important; }
+ tr.show-for-print {
+ display: table-row !important; }
+ td.show-for-print {
+ display: table-cell !important; }
+ th.show-for-print {
+ display: table-cell !important; }
+ a,
+ a:visited {
+ text-decoration: underline; }
+ a[href]:after {
+ content: " (" attr(href) ")"; }
+ .ir a:after,
+ a[href^='javascript:']:after,
+ a[href^='#']:after {
+ content: ''; }
+ abbr[title]:after {
+ content: " (" attr(title) ")"; }
+ pre,
+ blockquote {
+ border: 1px solid #8a8a8a;
+ page-break-inside: avoid; }
+ thead {
+ display: table-header-group; }
+ tr,
+ img {
+ page-break-inside: avoid; }
+ img {
+ max-width: 100% !important; }
+ @page {
+ margin: 0.5cm; }
+ p,
+ h2,
+ h3 {
+ orphans: 3;
+ widows: 3; }
+ h2,
+ h3 {
+ page-break-after: avoid; } }
+
+.button {
+ display: inline-block;
+ text-align: center;
+ line-height: 1;
+ cursor: pointer;
+ -webkit-appearance: none;
+ transition: background-color 0.25s ease-out, color 0.25s ease-out;
+ vertical-align: middle;
+ border: 1px solid transparent;
+ border-radius: 0;
+ padding: 0.85em 1em;
+ margin: 0 0 1rem 0;
+ font-size: 0.9rem;
+ background-color: #2199e8;
+ color: #fefefe; }
+ [data-whatinput='mouse'] .button {
+ outline: 0; }
+ .button:hover, .button:focus {
+ background-color: #1583cc;
+ color: #fefefe; }
+ .button.tiny {
+ font-size: 0.6rem; }
+ .button.small {
+ font-size: 0.75rem; }
+ .button.large {
+ font-size: 1.25rem; }
+ .button.expanded {
+ display: block;
+ width: 100%;
+ margin-left: 0;
+ margin-right: 0; }
+ .button.primary {
+ background-color: #2199e8;
+ color: #fefefe; }
+ .button.primary:hover, .button.primary:focus {
+ background-color: #147cc0;
+ color: #fefefe; }
+ .button.secondary {
+ background-color: #777;
+ color: #fefefe; }
+ .button.secondary:hover, .button.secondary:focus {
+ background-color: #5f5f5f;
+ color: #fefefe; }
+ .button.success {
+ background-color: #3adb76;
+ color: #fefefe; }
+ .button.success:hover, .button.success:focus {
+ background-color: #22bb5b;
+ color: #fefefe; }
+ .button.warning {
+ background-color: #ffae00;
+ color: #fefefe; }
+ .button.warning:hover, .button.warning:focus {
+ background-color: #cc8b00;
+ color: #fefefe; }
+ .button.alert {
+ background-color: #ec5840;
+ color: #fefefe; }
+ .button.alert:hover, .button.alert:focus {
+ background-color: #da3116;
+ color: #fefefe; }
+ .button.hollow {
+ border: 1px solid #2199e8;
+ color: #2199e8; }
+ .button.hollow, .button.hollow:hover, .button.hollow:focus {
+ background-color: transparent; }
+ .button.hollow:hover, .button.hollow:focus {
+ border-color: #0c4d78;
+ color: #0c4d78; }
+ .button.hollow.primary {
+ border: 1px solid #2199e8;
+ color: #2199e8; }
+ .button.hollow.primary:hover, .button.hollow.primary:focus {
+ border-color: #0c4d78;
+ color: #0c4d78; }
+ .button.hollow.secondary {
+ border: 1px solid #777;
+ color: #777; }
+ .button.hollow.secondary:hover, .button.hollow.secondary:focus {
+ border-color: #3c3c3c;
+ color: #3c3c3c; }
+ .button.hollow.success {
+ border: 1px solid #3adb76;
+ color: #3adb76; }
+ .button.hollow.success:hover, .button.hollow.success:focus {
+ border-color: #157539;
+ color: #157539; }
+ .button.hollow.warning {
+ border: 1px solid #ffae00;
+ color: #ffae00; }
+ .button.hollow.warning:hover, .button.hollow.warning:focus {
+ border-color: #805700;
+ color: #805700; }
+ .button.hollow.alert {
+ border: 1px solid #ec5840;
+ color: #ec5840; }
+ .button.hollow.alert:hover, .button.hollow.alert:focus {
+ border-color: #881f0e;
+ color: #881f0e; }
+ .button.disabled, .button[disabled] {
+ opacity: 0.25;
+ cursor: not-allowed; }
+ .button.disabled:hover, .button.disabled:focus, .button[disabled]:hover, .button[disabled]:focus {
+ background-color: #2199e8;
+ color: #fefefe; }
+ .button.dropdown::after {
+ content: '';
+ display: block;
+ width: 0;
+ height: 0;
+ border: inset 0.4em;
+ border-color: #fefefe transparent transparent;
+ border-top-style: solid;
+ border-bottom-width: 0;
+ position: relative;
+ top: 0.4em;
+ float: right;
+ margin-left: 1em;
+ display: inline-block; }
+ .button.arrow-only::after {
+ margin-left: 0;
+ float: none;
+ top: -0.1em; }
+
+[type='text'], [type='password'], [type='date'], [type='datetime'], [type='datetime-local'], [type='month'], [type='week'], [type='email'], [type='number'], [type='search'], [type='tel'], [type='time'], [type='url'], [type='color'],
+textarea {
+ display: block;
+ box-sizing: border-box;
+ width: 100%;
+ height: 2.4375rem;
+ padding: 0.5rem;
+ border: 1px solid #cacaca;
+ margin: 0 0 1rem;
+ font-family: inherit;
+ font-size: 1rem;
+ color: #0a0a0a;
+ background-color: #fefefe;
+ box-shadow: inset 0 1px 2px rgba(10, 10, 10, 0.1);
+ border-radius: 0;
+ transition: box-shadow 0.5s, border-color 0.25s ease-in-out;
+ -webkit-appearance: none;
+ -moz-appearance: none; }
+ [type='text']:focus, [type='password']:focus, [type='date']:focus, [type='datetime']:focus, [type='datetime-local']:focus, [type='month']:focus, [type='week']:focus, [type='email']:focus, [type='number']:focus, [type='search']:focus, [type='tel']:focus, [type='time']:focus, [type='url']:focus, [type='color']:focus,
+ textarea:focus {
+ border: 1px solid #8a8a8a;
+ background-color: #fefefe;
+ outline: none;
+ box-shadow: 0 0 5px #cacaca;
+ transition: box-shadow 0.5s, border-color 0.25s ease-in-out; }
+
+textarea {
+ max-width: 100%; }
+ textarea[rows] {
+ height: auto; }
+
+input::-webkit-input-placeholder,
+textarea::-webkit-input-placeholder {
+ color: #cacaca; }
+
+input::-moz-placeholder,
+textarea::-moz-placeholder {
+ color: #cacaca; }
+
+input:-ms-input-placeholder,
+textarea:-ms-input-placeholder {
+ color: #cacaca; }
+
+input::placeholder,
+textarea::placeholder {
+ color: #cacaca; }
+
+input:disabled, input[readonly],
+textarea:disabled,
+textarea[readonly] {
+ background-color: #e6e6e6;
+ cursor: not-allowed; }
+
+[type='submit'],
+[type='button'] {
+ border-radius: 0;
+ -webkit-appearance: none;
+ -moz-appearance: none; }
+
+input[type='search'] {
+ box-sizing: border-box; }
+
+[type='file'],
+[type='checkbox'],
+[type='radio'] {
+ margin: 0 0 1rem; }
+
+[type='checkbox'] + label,
+[type='radio'] + label {
+ display: inline-block;
+ margin-left: 0.5rem;
+ margin-right: 1rem;
+ margin-bottom: 0;
+ vertical-align: baseline; }
+ [type='checkbox'] + label[for],
+ [type='radio'] + label[for] {
+ cursor: pointer; }
+
+label > [type='checkbox'],
+label > [type='radio'] {
+ margin-right: 0.5rem; }
+
+[type='file'] {
+ width: 100%; }
+
+label {
+ display: block;
+ margin: 0;
+ font-size: 0.875rem;
+ font-weight: normal;
+ line-height: 1.8;
+ color: #0a0a0a; }
+ label.middle {
+ margin: 0 0 1rem;
+ padding: 0.5625rem 0; }
+
+.help-text {
+ margin-top: -0.5rem;
+ font-size: 0.8125rem;
+ font-style: italic;
+ color: #0a0a0a; }
+
+.input-group {
+ display: table;
+ width: 100%;
+ margin-bottom: 1rem; }
+ .input-group > :first-child {
+ border-radius: 0 0 0 0; }
+ .input-group > :last-child > * {
+ border-radius: 0 0 0 0; }
+
+.input-group-label, .input-group-field, .input-group-button {
+ margin: 0;
+ white-space: nowrap;
+ display: table-cell;
+ vertical-align: middle; }
+
+.input-group-label {
+ text-align: center;
+ padding: 0 1rem;
+ background: #e6e6e6;
+ color: #0a0a0a;
+ border: 1px solid #cacaca;
+ white-space: nowrap;
+ width: 1%;
+ height: 100%; }
+ .input-group-label:first-child {
+ border-right: 0; }
+ .input-group-label:last-child {
+ border-left: 0; }
+
+.input-group-field {
+ border-radius: 0;
+ height: 2.5rem; }
+
+.input-group-button {
+ padding-top: 0;
+ padding-bottom: 0;
+ text-align: center;
+ height: 100%;
+ width: 1%; }
+ .input-group-button a,
+ .input-group-button input,
+ .input-group-button button {
+ margin: 0; }
+
+.input-group .input-group-button {
+ display: table-cell; }
+
+fieldset {
+ border: 0;
+ padding: 0;
+ margin: 0; }
+
+legend {
+ margin-bottom: 0.5rem;
+ max-width: 100%; }
+
+.fieldset {
+ border: 1px solid #cacaca;
+ padding: 1.25rem;
+ margin: 1.125rem 0; }
+ .fieldset legend {
+ background: #fefefe;
+ padding: 0 0.1875rem;
+ margin: 0;
+ margin-left: -0.1875rem; }
+
+select {
+ height: 2.4375rem;
+ padding: 0.5rem;
+ border: 1px solid #cacaca;
+ margin: 0 0 1rem;
+ font-size: 1rem;
+ font-family: inherit;
+ line-height: normal;
+ color: #0a0a0a;
+ background-color: #fefefe;
+ border-radius: 0;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ background-image: url("data:image/svg+xml;utf8, ");
+ background-size: 9px 6px;
+ background-position: right -1rem center;
+ background-origin: content-box;
+ background-repeat: no-repeat;
+ padding-right: 1.5rem; }
+ @media screen and (min-width: 0\0) {
+ select {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAYCAYAAACbU/80AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIpJREFUeNrEkckNgDAMBBfRkEt0ObRBBdsGXUDgmQfK4XhH2m8czQAAy27R3tsw4Qfe2x8uOO6oYLb6GlOor3GF+swURAOmUJ+RwtEJs9WvTGEYxBXqI1MQAZhCfUQKRzDMVj+TwrAIV6jvSUEkYAr1LSkcyTBb/V+KYfX7xAeusq3sLDtGH3kEGACPWIflNZfhRQAAAABJRU5ErkJggg=="); } }
+ select:disabled {
+ background-color: #e6e6e6;
+ cursor: not-allowed; }
+ select::-ms-expand {
+ display: none; }
+ select[multiple] {
+ height: auto;
+ background-image: none; }
+
+.is-invalid-input:not(:focus) {
+ background-color: rgba(236, 88, 64, 0.1);
+ border-color: #ec5840; }
+
+.is-invalid-label {
+ color: #ec5840; }
+
+.form-error {
+ display: none;
+ margin-top: -0.5rem;
+ margin-bottom: 1rem;
+ font-size: 0.75rem;
+ font-weight: bold;
+ color: #ec5840; }
+ .form-error.is-visible {
+ display: block; }
+
+.accordion {
+ list-style-type: none;
+ background: #fefefe;
+ margin-left: 0; }
+
+.accordion-item:first-child > :first-child {
+ border-radius: 0 0 0 0; }
+
+.accordion-item:last-child > :last-child {
+ border-radius: 0 0 0 0; }
+
+.accordion-title {
+ display: block;
+ padding: 1.25rem 1rem;
+ line-height: 1;
+ font-size: 0.75rem;
+ color: #2199e8;
+ position: relative;
+ border: 1px solid #e6e6e6;
+ border-bottom: 0; }
+ :last-child:not(.is-active) > .accordion-title {
+ border-radius: 0 0 0 0;
+ border-bottom: 1px solid #e6e6e6; }
+ .accordion-title:hover, .accordion-title:focus {
+ background-color: #e6e6e6; }
+ .accordion-title::before {
+ content: '+';
+ position: absolute;
+ right: 1rem;
+ top: 50%;
+ margin-top: -0.5rem; }
+ .is-active > .accordion-title::before {
+ content: '–'; }
+
+.accordion-content {
+ padding: 1rem;
+ display: none;
+ border: 1px solid #e6e6e6;
+ border-bottom: 0;
+ background-color: #fefefe;
+ color: #0a0a0a; }
+ :last-child > .accordion-content:last-child {
+ border-bottom: 1px solid #e6e6e6; }
+
+.is-accordion-submenu-parent > a {
+ position: relative; }
+ .is-accordion-submenu-parent > a::after {
+ content: '';
+ display: block;
+ width: 0;
+ height: 0;
+ border: inset 6px;
+ border-color: #2199e8 transparent transparent;
+ border-top-style: solid;
+ border-bottom-width: 0;
+ position: absolute;
+ top: 50%;
+ margin-top: -4px;
+ right: 1rem; }
+
+.is-accordion-submenu-parent[aria-expanded='true'] > a::after {
+ -webkit-transform-origin: 50% 50%;
+ -ms-transform-origin: 50% 50%;
+ transform-origin: 50% 50%;
+ -webkit-transform: scaleY(-1);
+ -ms-transform: scaleY(-1);
+ transform: scaleY(-1); }
+
+.badge {
+ display: inline-block;
+ padding: 0.3em;
+ min-width: 2.1em;
+ font-size: 0.6rem;
+ text-align: center;
+ border-radius: 50%;
+ background: #2199e8;
+ color: #fefefe; }
+ .badge.secondary {
+ background: #777;
+ color: #fefefe; }
+ .badge.success {
+ background: #3adb76;
+ color: #fefefe; }
+ .badge.warning {
+ background: #ffae00;
+ color: #fefefe; }
+ .badge.alert {
+ background: #ec5840;
+ color: #fefefe; }
+
+.breadcrumbs {
+ list-style: none;
+ margin: 0 0 1rem 0; }
+ .breadcrumbs::before, .breadcrumbs::after {
+ content: ' ';
+ display: table; }
+ .breadcrumbs::after {
+ clear: both; }
+ .breadcrumbs li {
+ float: left;
+ color: #0a0a0a;
+ font-size: 0.6875rem;
+ cursor: default;
+ text-transform: uppercase; }
+ .breadcrumbs li:not(:last-child)::after {
+ color: #cacaca;
+ content: "/";
+ margin: 0 0.75rem;
+ position: relative;
+ top: 1px;
+ opacity: 1; }
+ .breadcrumbs a {
+ color: #2199e8; }
+ .breadcrumbs a:hover {
+ text-decoration: underline; }
+ .breadcrumbs .disabled {
+ color: #cacaca;
+ cursor: not-allowed; }
+
+.button-group {
+ margin-bottom: 1rem;
+ font-size: 0; }
+ .button-group::before, .button-group::after {
+ content: ' ';
+ display: table; }
+ .button-group::after {
+ clear: both; }
+ .button-group .button {
+ margin: 0;
+ margin-right: 1px;
+ margin-bottom: 1px;
+ font-size: 0.9rem; }
+ .button-group .button:last-child {
+ margin-right: 0; }
+ .button-group.tiny .button {
+ font-size: 0.6rem; }
+ .button-group.small .button {
+ font-size: 0.75rem; }
+ .button-group.large .button {
+ font-size: 1.25rem; }
+ .button-group.expanded {
+ margin-right: -1px; }
+ .button-group.expanded::before, .button-group.expanded::after {
+ display: none; }
+ .button-group.expanded .button:first-child:nth-last-child(2), .button-group.expanded .button:first-child:nth-last-child(2):first-child:nth-last-child(2) ~ .button {
+ display: inline-block;
+ width: calc(50% - 1px);
+ margin-right: 1px; }
+ .button-group.expanded .button:first-child:nth-last-child(2):last-child, .button-group.expanded .button:first-child:nth-last-child(2):first-child:nth-last-child(2) ~ .button:last-child {
+ margin-right: -6px; }
+ .button-group.expanded .button:first-child:nth-last-child(3), .button-group.expanded .button:first-child:nth-last-child(3):first-child:nth-last-child(3) ~ .button {
+ display: inline-block;
+ width: calc(33.33333% - 1px);
+ margin-right: 1px; }
+ .button-group.expanded .button:first-child:nth-last-child(3):last-child, .button-group.expanded .button:first-child:nth-last-child(3):first-child:nth-last-child(3) ~ .button:last-child {
+ margin-right: -6px; }
+ .button-group.expanded .button:first-child:nth-last-child(4), .button-group.expanded .button:first-child:nth-last-child(4):first-child:nth-last-child(4) ~ .button {
+ display: inline-block;
+ width: calc(25% - 1px);
+ margin-right: 1px; }
+ .button-group.expanded .button:first-child:nth-last-child(4):last-child, .button-group.expanded .button:first-child:nth-last-child(4):first-child:nth-last-child(4) ~ .button:last-child {
+ margin-right: -6px; }
+ .button-group.expanded .button:first-child:nth-last-child(5), .button-group.expanded .button:first-child:nth-last-child(5):first-child:nth-last-child(5) ~ .button {
+ display: inline-block;
+ width: calc(20% - 1px);
+ margin-right: 1px; }
+ .button-group.expanded .button:first-child:nth-last-child(5):last-child, .button-group.expanded .button:first-child:nth-last-child(5):first-child:nth-last-child(5) ~ .button:last-child {
+ margin-right: -6px; }
+ .button-group.expanded .button:first-child:nth-last-child(6), .button-group.expanded .button:first-child:nth-last-child(6):first-child:nth-last-child(6) ~ .button {
+ display: inline-block;
+ width: calc(16.66667% - 1px);
+ margin-right: 1px; }
+ .button-group.expanded .button:first-child:nth-last-child(6):last-child, .button-group.expanded .button:first-child:nth-last-child(6):first-child:nth-last-child(6) ~ .button:last-child {
+ margin-right: -6px; }
+ .button-group.primary .button {
+ background-color: #2199e8;
+ color: #fefefe; }
+ .button-group.primary .button:hover, .button-group.primary .button:focus {
+ background-color: #147cc0;
+ color: #fefefe; }
+ .button-group.secondary .button {
+ background-color: #777;
+ color: #fefefe; }
+ .button-group.secondary .button:hover, .button-group.secondary .button:focus {
+ background-color: #5f5f5f;
+ color: #fefefe; }
+ .button-group.success .button {
+ background-color: #3adb76;
+ color: #fefefe; }
+ .button-group.success .button:hover, .button-group.success .button:focus {
+ background-color: #22bb5b;
+ color: #fefefe; }
+ .button-group.warning .button {
+ background-color: #ffae00;
+ color: #fefefe; }
+ .button-group.warning .button:hover, .button-group.warning .button:focus {
+ background-color: #cc8b00;
+ color: #fefefe; }
+ .button-group.alert .button {
+ background-color: #ec5840;
+ color: #fefefe; }
+ .button-group.alert .button:hover, .button-group.alert .button:focus {
+ background-color: #da3116;
+ color: #fefefe; }
+ .button-group.stacked .button, .button-group.stacked-for-small .button, .button-group.stacked-for-medium .button {
+ width: 100%; }
+ .button-group.stacked .button:last-child, .button-group.stacked-for-small .button:last-child, .button-group.stacked-for-medium .button:last-child {
+ margin-bottom: 0; }
+ @media screen and (min-width: 40em) {
+ .button-group.stacked-for-small .button {
+ width: auto;
+ margin-bottom: 0; } }
+ @media screen and (min-width: 64em) {
+ .button-group.stacked-for-medium .button {
+ width: auto;
+ margin-bottom: 0; } }
+ @media screen and (max-width: 39.9375em) {
+ .button-group.stacked-for-small.expanded {
+ display: block; }
+ .button-group.stacked-for-small.expanded .button {
+ display: block;
+ margin-right: 0; } }
+
+.callout {
+ margin: 0 0 1rem 0;
+ padding: 1rem;
+ border: 1px solid rgba(10, 10, 10, 0.25);
+ border-radius: 0;
+ position: relative;
+ color: #0a0a0a;
+ background-color: white; }
+ .callout > :first-child {
+ margin-top: 0; }
+ .callout > :last-child {
+ margin-bottom: 0; }
+ .callout.primary {
+ background-color: #def0fc; }
+ .callout.secondary {
+ background-color: #ebebeb; }
+ .callout.success {
+ background-color: #e1faea; }
+ .callout.warning {
+ background-color: #fff3d9; }
+ .callout.alert {
+ background-color: #fce6e2; }
+ .callout.small {
+ padding-top: 0.5rem;
+ padding-right: 0.5rem;
+ padding-bottom: 0.5rem;
+ padding-left: 0.5rem; }
+ .callout.large {
+ padding-top: 3rem;
+ padding-right: 3rem;
+ padding-bottom: 3rem;
+ padding-left: 3rem; }
+
+.close-button {
+ position: absolute;
+ color: #8a8a8a;
+ right: 1rem;
+ top: 0.5rem;
+ font-size: 2em;
+ line-height: 1;
+ cursor: pointer; }
+ [data-whatinput='mouse'] .close-button {
+ outline: 0; }
+ .close-button:hover, .close-button:focus {
+ color: #0a0a0a; }
+
+.menu {
+ margin: 0;
+ list-style-type: none; }
+ .menu > li {
+ display: table-cell;
+ vertical-align: middle; }
+ [data-whatinput='mouse'] .menu > li {
+ outline: 0; }
+ .menu > li > a {
+ display: block;
+ padding: 0.7rem 1rem;
+ line-height: 1; }
+ .menu input,
+ .menu a,
+ .menu button {
+ margin-bottom: 0; }
+ .menu > li > a img,
+ .menu > li > a i,
+ .menu > li > a svg {
+ vertical-align: middle; }
+ .menu > li > a img + span,
+ .menu > li > a i + span,
+ .menu > li > a svg + span {
+ vertical-align: middle; }
+ .menu > li > a img,
+ .menu > li > a i,
+ .menu > li > a svg {
+ margin-right: 0.25rem;
+ display: inline-block; }
+ .menu > li {
+ display: table-cell; }
+ .menu.vertical > li {
+ display: block; }
+ @media screen and (min-width: 40em) {
+ .menu.medium-horizontal > li {
+ display: table-cell; }
+ .menu.medium-vertical > li {
+ display: block; } }
+ @media screen and (min-width: 64em) {
+ .menu.large-horizontal > li {
+ display: table-cell; }
+ .menu.large-vertical > li {
+ display: block; } }
+ .menu.simple li {
+ line-height: 1;
+ display: inline-block;
+ margin-right: 1rem; }
+ .menu.simple a {
+ padding: 0; }
+ .menu.align-right::before, .menu.align-right::after {
+ content: ' ';
+ display: table; }
+ .menu.align-right::after {
+ clear: both; }
+ .menu.align-right > li {
+ float: right; }
+ .menu.expanded {
+ width: 100%;
+ display: table;
+ table-layout: fixed; }
+ .menu.expanded > li:first-child:last-child {
+ width: 100%; }
+ .menu.icon-top > li > a {
+ text-align: center; }
+ .menu.icon-top > li > a img,
+ .menu.icon-top > li > a i,
+ .menu.icon-top > li > a svg {
+ display: block;
+ margin: 0 auto 0.25rem; }
+ .menu.nested {
+ margin-left: 1rem; }
+ .menu .active > a {
+ color: #fefefe;
+ background: #2199e8; }
+
+.menu-text {
+ font-weight: bold;
+ color: inherit;
+ line-height: 1;
+ padding-top: 0;
+ padding-bottom: 0;
+ padding: 0.7rem 1rem; }
+
+.menu-centered {
+ text-align: center; }
+ .menu-centered > .menu {
+ display: inline-block; }
+
+.no-js [data-responsive-menu] ul {
+ display: none; }
+
+.menu-icon {
+ position: relative;
+ display: inline-block;
+ vertical-align: middle;
+ cursor: pointer;
+ width: 20px;
+ height: 16px; }
+ .menu-icon::after {
+ content: '';
+ position: absolute;
+ display: block;
+ width: 100%;
+ height: 2px;
+ background: #fefefe;
+ top: 0;
+ left: 0;
+ box-shadow: 0 7px 0 #fefefe, 0 14px 0 #fefefe; }
+ .menu-icon:hover::after {
+ background: #cacaca;
+ box-shadow: 0 7px 0 #cacaca, 0 14px 0 #cacaca; }
+
+.menu-icon.dark {
+ position: relative;
+ display: inline-block;
+ vertical-align: middle;
+ cursor: pointer;
+ width: 20px;
+ height: 16px; }
+ .menu-icon.dark::after {
+ content: '';
+ position: absolute;
+ display: block;
+ width: 100%;
+ height: 2px;
+ background: #0a0a0a;
+ top: 0;
+ left: 0;
+ box-shadow: 0 7px 0 #0a0a0a, 0 14px 0 #0a0a0a; }
+ .menu-icon.dark:hover::after {
+ background: #8a8a8a;
+ box-shadow: 0 7px 0 #8a8a8a, 0 14px 0 #8a8a8a; }
+
+.is-drilldown {
+ position: relative;
+ overflow: hidden; }
+ .is-drilldown li {
+ display: block !important; }
+
+.is-drilldown-submenu {
+ position: absolute;
+ top: 0;
+ left: 100%;
+ z-index: -1;
+ height: 100%;
+ width: 100%;
+ background: #fefefe;
+ transition: -webkit-transform 0.15s linear;
+ transition: transform 0.15s linear; }
+ .is-drilldown-submenu.is-active {
+ z-index: 1;
+ display: block;
+ -webkit-transform: translateX(-100%);
+ -ms-transform: translateX(-100%);
+ transform: translateX(-100%); }
+ .is-drilldown-submenu.is-closing {
+ -webkit-transform: translateX(100%);
+ -ms-transform: translateX(100%);
+ transform: translateX(100%); }
+
+.is-drilldown-submenu-parent > a {
+ position: relative; }
+ .is-drilldown-submenu-parent > a::after {
+ content: '';
+ display: block;
+ width: 0;
+ height: 0;
+ border: inset 6px;
+ border-color: transparent transparent transparent #2199e8;
+ border-left-style: solid;
+ border-right-width: 0;
+ position: absolute;
+ top: 50%;
+ margin-top: -6px;
+ right: 1rem; }
+
+.js-drilldown-back > a::before {
+ content: '';
+ display: block;
+ width: 0;
+ height: 0;
+ border: inset 6px;
+ border-color: transparent #2199e8 transparent transparent;
+ border-right-style: solid;
+ border-left-width: 0;
+ border-left-width: 0;
+ display: inline-block;
+ vertical-align: middle;
+ margin-right: 0.75rem; }
+
+.dropdown-pane {
+ background-color: #fefefe;
+ border: 1px solid #cacaca;
+ border-radius: 0;
+ display: block;
+ font-size: 1rem;
+ padding: 1rem;
+ position: absolute;
+ visibility: hidden;
+ width: 300px;
+ z-index: 10; }
+ .dropdown-pane.is-open {
+ visibility: visible; }
+
+.dropdown-pane.tiny {
+ width: 100px; }
+
+.dropdown-pane.small {
+ width: 200px; }
+
+.dropdown-pane.large {
+ width: 400px; }
+
+.dropdown.menu > li.opens-left > .is-dropdown-submenu {
+ left: auto;
+ right: 0;
+ top: 100%; }
+
+.dropdown.menu > li.opens-right > .is-dropdown-submenu {
+ right: auto;
+ left: 0;
+ top: 100%; }
+
+.dropdown.menu > li.is-dropdown-submenu-parent > a {
+ padding-right: 1.5rem;
+ position: relative; }
+
+.dropdown.menu > li.is-dropdown-submenu-parent > a::after {
+ content: '';
+ display: block;
+ width: 0;
+ height: 0;
+ border: inset 5px;
+ border-color: #2199e8 transparent transparent;
+ border-top-style: solid;
+ border-bottom-width: 0;
+ right: 5px;
+ margin-top: -2px; }
+
+[data-whatinput='mouse'] .dropdown.menu a {
+ outline: 0; }
+
+.no-js .dropdown.menu ul {
+ display: none; }
+
+.dropdown.menu.vertical > li .is-dropdown-submenu {
+ top: 0; }
+
+.dropdown.menu.vertical > li.opens-left > .is-dropdown-submenu {
+ left: auto;
+ right: 100%; }
+
+.dropdown.menu.vertical > li.opens-right > .is-dropdown-submenu {
+ right: auto;
+ left: 100%; }
+
+.dropdown.menu.vertical > li > a::after {
+ right: 14px;
+ margin-top: -3px; }
+
+.dropdown.menu.vertical > li.opens-left > a::after {
+ content: '';
+ display: block;
+ width: 0;
+ height: 0;
+ border: inset 5px;
+ border-color: transparent #2199e8 transparent transparent;
+ border-right-style: solid;
+ border-left-width: 0; }
+
+.dropdown.menu.vertical > li.opens-right > a::after {
+ content: '';
+ display: block;
+ width: 0;
+ height: 0;
+ border: inset 5px;
+ border-color: transparent transparent transparent #2199e8;
+ border-left-style: solid;
+ border-right-width: 0; }
+
+@media screen and (min-width: 40em) {
+ .dropdown.menu.medium-horizontal > li.opens-left > .is-dropdown-submenu {
+ left: auto;
+ right: 0;
+ top: 100%; }
+ .dropdown.menu.medium-horizontal > li.opens-right > .is-dropdown-submenu {
+ right: auto;
+ left: 0;
+ top: 100%; }
+ .dropdown.menu.medium-horizontal > li.is-dropdown-submenu-parent > a {
+ padding-right: 1.5rem;
+ position: relative; }
+ .dropdown.menu.medium-horizontal > li.is-dropdown-submenu-parent > a::after {
+ content: '';
+ display: block;
+ width: 0;
+ height: 0;
+ border: inset 5px;
+ border-color: #2199e8 transparent transparent;
+ border-top-style: solid;
+ border-bottom-width: 0;
+ right: 5px;
+ margin-top: -2px; }
+ .dropdown.menu.medium-vertical > li .is-dropdown-submenu {
+ top: 0; }
+ .dropdown.menu.medium-vertical > li.opens-left > .is-dropdown-submenu {
+ left: auto;
+ right: 100%; }
+ .dropdown.menu.medium-vertical > li.opens-right > .is-dropdown-submenu {
+ right: auto;
+ left: 100%; }
+ .dropdown.menu.medium-vertical > li > a::after {
+ right: 14px;
+ margin-top: -3px; }
+ .dropdown.menu.medium-vertical > li.opens-left > a::after {
+ content: '';
+ display: block;
+ width: 0;
+ height: 0;
+ border: inset 5px;
+ border-color: transparent #2199e8 transparent transparent;
+ border-right-style: solid;
+ border-left-width: 0; }
+ .dropdown.menu.medium-vertical > li.opens-right > a::after {
+ content: '';
+ display: block;
+ width: 0;
+ height: 0;
+ border: inset 5px;
+ border-color: transparent transparent transparent #2199e8;
+ border-left-style: solid;
+ border-right-width: 0; } }
+
+@media screen and (min-width: 64em) {
+ .dropdown.menu.large-horizontal > li.opens-left > .is-dropdown-submenu {
+ left: auto;
+ right: 0;
+ top: 100%; }
+ .dropdown.menu.large-horizontal > li.opens-right > .is-dropdown-submenu {
+ right: auto;
+ left: 0;
+ top: 100%; }
+ .dropdown.menu.large-horizontal > li.is-dropdown-submenu-parent > a {
+ padding-right: 1.5rem;
+ position: relative; }
+ .dropdown.menu.large-horizontal > li.is-dropdown-submenu-parent > a::after {
+ content: '';
+ display: block;
+ width: 0;
+ height: 0;
+ border: inset 5px;
+ border-color: #2199e8 transparent transparent;
+ border-top-style: solid;
+ border-bottom-width: 0;
+ right: 5px;
+ margin-top: -2px; }
+ .dropdown.menu.large-vertical > li .is-dropdown-submenu {
+ top: 0; }
+ .dropdown.menu.large-vertical > li.opens-left > .is-dropdown-submenu {
+ left: auto;
+ right: 100%; }
+ .dropdown.menu.large-vertical > li.opens-right > .is-dropdown-submenu {
+ right: auto;
+ left: 100%; }
+ .dropdown.menu.large-vertical > li > a::after {
+ right: 14px;
+ margin-top: -3px; }
+ .dropdown.menu.large-vertical > li.opens-left > a::after {
+ content: '';
+ display: block;
+ width: 0;
+ height: 0;
+ border: inset 5px;
+ border-color: transparent #2199e8 transparent transparent;
+ border-right-style: solid;
+ border-left-width: 0; }
+ .dropdown.menu.large-vertical > li.opens-right > a::after {
+ content: '';
+ display: block;
+ width: 0;
+ height: 0;
+ border: inset 5px;
+ border-color: transparent transparent transparent #2199e8;
+ border-left-style: solid;
+ border-right-width: 0; } }
+
+.dropdown.menu.align-right .is-dropdown-submenu.first-sub {
+ top: 100%;
+ left: auto;
+ right: 0; }
+
+.is-dropdown-menu.vertical {
+ width: 100px; }
+ .is-dropdown-menu.vertical.align-right {
+ float: right; }
+
+.is-dropdown-submenu-parent {
+ position: relative; }
+ .is-dropdown-submenu-parent a::after {
+ position: absolute;
+ top: 50%;
+ right: 5px;
+ margin-top: -2px; }
+ .is-dropdown-submenu-parent.opens-inner > .is-dropdown-submenu {
+ top: 100%;
+ left: auto; }
+ .is-dropdown-submenu-parent.opens-left > .is-dropdown-submenu {
+ left: auto;
+ right: 100%; }
+ .is-dropdown-submenu-parent.opens-right > .is-dropdown-submenu {
+ right: auto;
+ left: 100%; }
+
+.is-dropdown-submenu {
+ display: none;
+ position: absolute;
+ top: 0;
+ left: 100%;
+ min-width: 200px;
+ z-index: 1;
+ background: #fefefe;
+ border: 1px solid #cacaca; }
+ .is-dropdown-submenu .is-dropdown-submenu-parent > a::after {
+ right: 14px;
+ margin-top: -3px; }
+ .is-dropdown-submenu .is-dropdown-submenu-parent.opens-left > a::after {
+ content: '';
+ display: block;
+ width: 0;
+ height: 0;
+ border: inset 5px;
+ border-color: transparent #2199e8 transparent transparent;
+ border-right-style: solid;
+ border-left-width: 0; }
+ .is-dropdown-submenu .is-dropdown-submenu-parent.opens-right > a::after {
+ content: '';
+ display: block;
+ width: 0;
+ height: 0;
+ border: inset 5px;
+ border-color: transparent transparent transparent #2199e8;
+ border-left-style: solid;
+ border-right-width: 0; }
+ .is-dropdown-submenu .is-dropdown-submenu {
+ margin-top: -1px; }
+ .is-dropdown-submenu > li {
+ width: 100%; }
+ .is-dropdown-submenu.js-dropdown-active {
+ display: block; }
+
+.flex-video {
+ position: relative;
+ height: 0;
+ padding-bottom: 75%;
+ margin-bottom: 1rem;
+ overflow: hidden; }
+ .flex-video iframe,
+ .flex-video object,
+ .flex-video embed,
+ .flex-video video {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%; }
+ .flex-video.widescreen {
+ padding-bottom: 56.25%; }
+ .flex-video.vimeo {
+ padding-top: 0; }
+
+.label {
+ display: inline-block;
+ padding: 0.33333rem 0.5rem;
+ font-size: 0.8rem;
+ line-height: 1;
+ white-space: nowrap;
+ cursor: default;
+ border-radius: 0;
+ background: #2199e8;
+ color: #fefefe; }
+ .label.secondary {
+ background: #777;
+ color: #fefefe; }
+ .label.success {
+ background: #3adb76;
+ color: #fefefe; }
+ .label.warning {
+ background: #ffae00;
+ color: #fefefe; }
+ .label.alert {
+ background: #ec5840;
+ color: #fefefe; }
+
+.media-object {
+ margin-bottom: 1rem;
+ display: block; }
+ .media-object img {
+ max-width: none; }
+ @media screen and (max-width: 39.9375em) {
+ .media-object.stack-for-small .media-object-section {
+ padding: 0;
+ padding-bottom: 1rem;
+ display: block; }
+ .media-object.stack-for-small .media-object-section img {
+ width: 100%; } }
+
+.media-object-section {
+ display: table-cell;
+ vertical-align: top; }
+ .media-object-section:first-child {
+ padding-right: 1rem; }
+ .media-object-section:last-child:not(:nth-child(2)) {
+ padding-left: 1rem; }
+ .media-object-section > :last-child {
+ margin-bottom: 0; }
+ .media-object-section.middle {
+ vertical-align: middle; }
+ .media-object-section.bottom {
+ vertical-align: bottom; }
+
+html,
+body {
+ height: 100%; }
+
+.off-canvas-wrapper {
+ width: 100%;
+ overflow-x: hidden;
+ position: relative;
+ -webkit-backface-visibility: hidden;
+ backface-visibility: hidden;
+ -webkit-overflow-scrolling: auto; }
+
+.off-canvas-wrapper-inner {
+ position: relative;
+ width: 100%;
+ transition: -webkit-transform 0.5s ease;
+ transition: transform 0.5s ease; }
+ .off-canvas-wrapper-inner::before, .off-canvas-wrapper-inner::after {
+ content: ' ';
+ display: table; }
+ .off-canvas-wrapper-inner::after {
+ clear: both; }
+
+.off-canvas-content,
+.off-canvas-content {
+ min-height: 100%;
+ background: #fefefe;
+ transition: -webkit-transform 0.5s ease;
+ transition: transform 0.5s ease;
+ -webkit-backface-visibility: hidden;
+ backface-visibility: hidden;
+ z-index: 1;
+ padding-bottom: 0.1px;
+ box-shadow: 0 0 10px rgba(10, 10, 10, 0.5); }
+
+.js-off-canvas-exit {
+ display: none;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(254, 254, 254, 0.25);
+ cursor: pointer;
+ transition: background 0.5s ease; }
+
+.off-canvas {
+ position: absolute;
+ background: #e6e6e6;
+ z-index: -1;
+ max-height: 100%;
+ overflow-y: auto;
+ -webkit-transform: translateX(0);
+ -ms-transform: translateX(0);
+ transform: translateX(0); }
+ [data-whatinput='mouse'] .off-canvas {
+ outline: 0; }
+ .off-canvas.position-left {
+ left: -250px;
+ top: 0;
+ width: 250px; }
+ .is-open-left {
+ -webkit-transform: translateX(250px);
+ -ms-transform: translateX(250px);
+ transform: translateX(250px); }
+ .off-canvas.position-right {
+ right: -250px;
+ top: 0;
+ width: 250px; }
+ .is-open-right {
+ -webkit-transform: translateX(-250px);
+ -ms-transform: translateX(-250px);
+ transform: translateX(-250px); }
+
+@media screen and (min-width: 40em) {
+ .position-left.reveal-for-medium {
+ left: 0;
+ z-index: auto;
+ position: fixed; }
+ .position-left.reveal-for-medium ~ .off-canvas-content {
+ margin-left: 250px; }
+ .position-right.reveal-for-medium {
+ right: 0;
+ z-index: auto;
+ position: fixed; }
+ .position-right.reveal-for-medium ~ .off-canvas-content {
+ margin-right: 250px; } }
+
+@media screen and (min-width: 64em) {
+ .position-left.reveal-for-large {
+ left: 0;
+ z-index: auto;
+ position: fixed; }
+ .position-left.reveal-for-large ~ .off-canvas-content {
+ margin-left: 250px; }
+ .position-right.reveal-for-large {
+ right: 0;
+ z-index: auto;
+ position: fixed; }
+ .position-right.reveal-for-large ~ .off-canvas-content {
+ margin-right: 250px; } }
+
+.orbit {
+ position: relative; }
+
+.orbit-container {
+ position: relative;
+ margin: 0;
+ overflow: hidden;
+ list-style: none; }
+
+.orbit-slide {
+ width: 100%;
+ max-height: 100%; }
+ .orbit-slide.no-motionui.is-active {
+ top: 0;
+ left: 0; }
+
+.orbit-figure {
+ margin: 0; }
+
+.orbit-image {
+ margin: 0;
+ width: 100%;
+ max-width: 100%; }
+
+.orbit-caption {
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+ padding: 1rem;
+ margin-bottom: 0;
+ color: #fefefe;
+ background-color: rgba(10, 10, 10, 0.5); }
+
+.orbit-previous, .orbit-next {
+ position: absolute;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%);
+ z-index: 10;
+ padding: 1rem;
+ color: #fefefe; }
+ [data-whatinput='mouse'] .orbit-previous, [data-whatinput='mouse'] .orbit-next {
+ outline: 0; }
+ .orbit-previous:hover, .orbit-next:hover, .orbit-previous:active, .orbit-next:active, .orbit-previous:focus, .orbit-next:focus {
+ background-color: rgba(10, 10, 10, 0.5); }
+
+.orbit-previous {
+ left: 0; }
+
+.orbit-next {
+ left: auto;
+ right: 0; }
+
+.orbit-bullets {
+ position: relative;
+ margin-top: 0.8rem;
+ margin-bottom: 0.8rem;
+ text-align: center; }
+ [data-whatinput='mouse'] .orbit-bullets {
+ outline: 0; }
+ .orbit-bullets button {
+ width: 1.2rem;
+ height: 1.2rem;
+ margin: 0.1rem;
+ background-color: #cacaca;
+ border-radius: 50%; }
+ .orbit-bullets button:hover {
+ background-color: #8a8a8a; }
+ .orbit-bullets button.is-active {
+ background-color: #8a8a8a; }
+
+.pagination {
+ margin-left: 0;
+ margin-bottom: 1rem; }
+ .pagination::before, .pagination::after {
+ content: ' ';
+ display: table; }
+ .pagination::after {
+ clear: both; }
+ .pagination li {
+ font-size: 0.875rem;
+ margin-right: 0.0625rem;
+ border-radius: 0;
+ display: none; }
+ .pagination li:last-child, .pagination li:first-child {
+ display: inline-block; }
+ @media screen and (min-width: 40em) {
+ .pagination li {
+ display: inline-block; } }
+ .pagination a,
+ .pagination button {
+ color: #0a0a0a;
+ display: block;
+ padding: 0.1875rem 0.625rem;
+ border-radius: 0; }
+ .pagination a:hover,
+ .pagination button:hover {
+ background: #e6e6e6; }
+ .pagination .current {
+ padding: 0.1875rem 0.625rem;
+ background: #2199e8;
+ color: #fefefe;
+ cursor: default; }
+ .pagination .disabled {
+ padding: 0.1875rem 0.625rem;
+ color: #cacaca;
+ cursor: not-allowed; }
+ .pagination .disabled:hover {
+ background: transparent; }
+ .pagination .ellipsis::after {
+ content: '\2026';
+ padding: 0.1875rem 0.625rem;
+ color: #0a0a0a; }
+
+.pagination-previous a::before,
+.pagination-previous.disabled::before {
+ content: '\00ab';
+ display: inline-block;
+ margin-right: 0.5rem; }
+
+.pagination-next a::after,
+.pagination-next.disabled::after {
+ content: '\00bb';
+ display: inline-block;
+ margin-left: 0.5rem; }
+
+.progress {
+ background-color: #cacaca;
+ height: 1rem;
+ margin-bottom: 1rem;
+ border-radius: 0; }
+ .progress.primary .progress-meter {
+ background-color: #2199e8; }
+ .progress.secondary .progress-meter {
+ background-color: #777; }
+ .progress.success .progress-meter {
+ background-color: #3adb76; }
+ .progress.warning .progress-meter {
+ background-color: #ffae00; }
+ .progress.alert .progress-meter {
+ background-color: #ec5840; }
+
+.progress-meter {
+ position: relative;
+ display: block;
+ width: 0%;
+ height: 100%;
+ background-color: #2199e8; }
+
+.progress-meter-text {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ -webkit-transform: translate(-50%, -50%);
+ -ms-transform: translate(-50%, -50%);
+ transform: translate(-50%, -50%);
+ position: absolute;
+ margin: 0;
+ font-size: 0.75rem;
+ font-weight: bold;
+ color: #fefefe;
+ white-space: nowrap; }
+
+body.is-reveal-open {
+ overflow: hidden; }
+
+html.is-reveal-open,
+html.is-reveal-open body {
+ height: 100%;
+ overflow: hidden;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none; }
+
+.reveal-overlay {
+ display: none;
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ z-index: 1005;
+ background-color: rgba(10, 10, 10, 0.45);
+ overflow-y: scroll; }
+
+.reveal {
+ display: none;
+ z-index: 1006;
+ padding: 1rem;
+ border: 1px solid #cacaca;
+ background-color: #fefefe;
+ border-radius: 0;
+ position: relative;
+ top: 100px;
+ margin-left: auto;
+ margin-right: auto;
+ overflow-y: auto; }
+ [data-whatinput='mouse'] .reveal {
+ outline: 0; }
+ @media screen and (min-width: 40em) {
+ .reveal {
+ min-height: 0; } }
+ .reveal .column, .reveal .columns,
+ .reveal .columns {
+ min-width: 0; }
+ .reveal > :last-child {
+ margin-bottom: 0; }
+ @media screen and (min-width: 40em) {
+ .reveal {
+ width: 600px;
+ max-width: 75rem; } }
+ @media screen and (min-width: 40em) {
+ .reveal .reveal {
+ left: auto;
+ right: auto;
+ margin: 0 auto; } }
+ .reveal.collapse {
+ padding: 0; }
+ @media screen and (min-width: 40em) {
+ .reveal.tiny {
+ width: 30%;
+ max-width: 75rem; } }
+ @media screen and (min-width: 40em) {
+ .reveal.small {
+ width: 50%;
+ max-width: 75rem; } }
+ @media screen and (min-width: 40em) {
+ .reveal.large {
+ width: 90%;
+ max-width: 75rem; } }
+ .reveal.full {
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ height: 100vh;
+ min-height: 100vh;
+ max-width: none;
+ margin-left: 0;
+ border: 0;
+ border-radius: 0; }
+ @media screen and (max-width: 39.9375em) {
+ .reveal {
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ height: 100vh;
+ min-height: 100vh;
+ max-width: none;
+ margin-left: 0;
+ border: 0;
+ border-radius: 0; } }
+ .reveal.without-overlay {
+ position: fixed; }
+
+.slider {
+ position: relative;
+ height: 0.5rem;
+ margin-top: 1.25rem;
+ margin-bottom: 2.25rem;
+ background-color: #e6e6e6;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ -ms-touch-action: none;
+ touch-action: none; }
+
+.slider-fill {
+ position: absolute;
+ top: 0;
+ left: 0;
+ display: inline-block;
+ max-width: 100%;
+ height: 0.5rem;
+ background-color: #cacaca;
+ transition: all 0.2s ease-in-out; }
+ .slider-fill.is-dragging {
+ transition: all 0s linear; }
+
+.slider-handle {
+ position: absolute;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%);
+ position: absolute;
+ left: 0;
+ z-index: 1;
+ display: inline-block;
+ width: 1.4rem;
+ height: 1.4rem;
+ background-color: #2199e8;
+ transition: all 0.2s ease-in-out;
+ -ms-touch-action: manipulation;
+ touch-action: manipulation;
+ border-radius: 0; }
+ [data-whatinput='mouse'] .slider-handle {
+ outline: 0; }
+ .slider-handle:hover {
+ background-color: #1583cc; }
+ .slider-handle.is-dragging {
+ transition: all 0s linear; }
+
+.slider.disabled,
+.slider[disabled] {
+ opacity: 0.25;
+ cursor: not-allowed; }
+
+.slider.vertical {
+ display: inline-block;
+ width: 0.5rem;
+ height: 12.5rem;
+ margin: 0 1.25rem;
+ -webkit-transform: scale(1, -1);
+ -ms-transform: scale(1, -1);
+ transform: scale(1, -1); }
+ .slider.vertical .slider-fill {
+ top: 0;
+ width: 0.5rem;
+ max-height: 100%; }
+ .slider.vertical .slider-handle {
+ position: absolute;
+ top: 0;
+ left: 50%;
+ width: 1.4rem;
+ height: 1.4rem;
+ -webkit-transform: translateX(-50%);
+ -ms-transform: translateX(-50%);
+ transform: translateX(-50%); }
+
+.sticky-container {
+ position: relative; }
+
+.sticky {
+ position: absolute;
+ z-index: 0;
+ -webkit-transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0); }
+
+.sticky.is-stuck {
+ position: fixed;
+ z-index: 5; }
+ .sticky.is-stuck.is-at-top {
+ top: 0; }
+ .sticky.is-stuck.is-at-bottom {
+ bottom: 0; }
+
+.sticky.is-anchored {
+ position: absolute;
+ left: auto;
+ right: auto; }
+ .sticky.is-anchored.is-at-bottom {
+ bottom: 0; }
+
+.switch {
+ margin-bottom: 1rem;
+ outline: 0;
+ position: relative;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ color: #fefefe;
+ font-weight: bold;
+ font-size: 0.875rem; }
+
+.switch-input {
+ opacity: 0;
+ position: absolute; }
+
+.switch-paddle {
+ background: #cacaca;
+ cursor: pointer;
+ display: block;
+ position: relative;
+ width: 4rem;
+ height: 2rem;
+ transition: all 0.25s ease-out;
+ border-radius: 0;
+ color: inherit;
+ font-weight: inherit; }
+ input + .switch-paddle {
+ margin: 0; }
+ .switch-paddle::after {
+ background: #fefefe;
+ content: '';
+ display: block;
+ position: absolute;
+ height: 1.5rem;
+ left: 0.25rem;
+ top: 0.25rem;
+ width: 1.5rem;
+ transition: all 0.25s ease-out;
+ -webkit-transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0);
+ border-radius: 0; }
+ input:checked ~ .switch-paddle {
+ background: #2199e8; }
+ input:checked ~ .switch-paddle::after {
+ left: 2.25rem; }
+ [data-whatinput='mouse'] input:focus ~ .switch-paddle {
+ outline: 0; }
+
+.switch-active, .switch-inactive {
+ position: absolute;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%); }
+
+.switch-active {
+ left: 8%;
+ display: none; }
+ input:checked + label > .switch-active {
+ display: block; }
+
+.switch-inactive {
+ right: 15%; }
+ input:checked + label > .switch-inactive {
+ display: none; }
+
+.switch.tiny .switch-paddle {
+ width: 3rem;
+ height: 1.5rem;
+ font-size: 0.625rem; }
+
+.switch.tiny .switch-paddle::after {
+ width: 1rem;
+ height: 1rem; }
+
+.switch.tiny input:checked ~ .switch-paddle::after {
+ left: 1.75rem; }
+
+.switch.small .switch-paddle {
+ width: 3.5rem;
+ height: 1.75rem;
+ font-size: 0.75rem; }
+
+.switch.small .switch-paddle::after {
+ width: 1.25rem;
+ height: 1.25rem; }
+
+.switch.small input:checked ~ .switch-paddle::after {
+ left: 2rem; }
+
+.switch.large .switch-paddle {
+ width: 5rem;
+ height: 2.5rem;
+ font-size: 1rem; }
+
+.switch.large .switch-paddle::after {
+ width: 2rem;
+ height: 2rem; }
+
+.switch.large input:checked ~ .switch-paddle::after {
+ left: 2.75rem; }
+
+table {
+ width: 100%;
+ margin-bottom: 1rem;
+ border-radius: 0; }
+ table thead,
+ table tbody,
+ table tfoot {
+ border: 1px solid #f1f1f1;
+ background-color: #fefefe; }
+ table caption {
+ font-weight: bold;
+ padding: 0.5rem 0.625rem 0.625rem; }
+ table thead,
+ table tfoot {
+ background: #f8f8f8;
+ color: #0a0a0a; }
+ table thead tr,
+ table tfoot tr {
+ background: transparent; }
+ table thead th,
+ table thead td,
+ table tfoot th,
+ table tfoot td {
+ padding: 0.5rem 0.625rem 0.625rem;
+ font-weight: bold;
+ text-align: left; }
+ table tbody tr:nth-child(even) {
+ background-color: #f1f1f1; }
+ table tbody th,
+ table tbody td {
+ padding: 0.5rem 0.625rem 0.625rem; }
+
+@media screen and (max-width: 63.9375em) {
+ table.stack thead {
+ display: none; }
+ table.stack tfoot {
+ display: none; }
+ table.stack tr,
+ table.stack th,
+ table.stack td {
+ display: block; }
+ table.stack td {
+ border-top: 0; } }
+
+table.scroll {
+ display: block;
+ width: 100%;
+ overflow-x: auto; }
+
+table.hover tr:hover {
+ background-color: #f9f9f9; }
+
+table.hover tr:nth-of-type(even):hover {
+ background-color: #ececec; }
+
+.table-scroll {
+ overflow-x: auto; }
+ .table-scroll table {
+ width: auto; }
+
+.tabs {
+ margin: 0;
+ list-style-type: none;
+ background: #fefefe;
+ border: 1px solid #e6e6e6; }
+ .tabs::before, .tabs::after {
+ content: ' ';
+ display: table; }
+ .tabs::after {
+ clear: both; }
+
+.tabs.vertical > li {
+ width: auto;
+ float: none;
+ display: block; }
+
+.tabs.simple > li > a {
+ padding: 0; }
+ .tabs.simple > li > a:hover {
+ background: transparent; }
+
+.tabs.primary {
+ background: #2199e8; }
+ .tabs.primary > li > a {
+ color: #fefefe; }
+ .tabs.primary > li > a:hover, .tabs.primary > li > a:focus {
+ background: #1893e4; }
+
+.tabs-title {
+ float: left; }
+ .tabs-title > a {
+ display: block;
+ padding: 1.25rem 1.5rem;
+ line-height: 1;
+ font-size: 0.75rem; }
+ .tabs-title > a:hover {
+ background: #fefefe; }
+ .tabs-title > a:focus, .tabs-title > a[aria-selected='true'] {
+ background: #e6e6e6; }
+
+.tabs-content {
+ background: #fefefe;
+ transition: all 0.5s ease;
+ border: 1px solid #e6e6e6;
+ border-top: 0; }
+
+.tabs-content.vertical {
+ border: 1px solid #e6e6e6;
+ border-left: 0; }
+
+.tabs-panel {
+ display: none;
+ padding: 1rem; }
+ .tabs-panel.is-active {
+ display: block; }
+
+.thumbnail {
+ border: solid 4px #fefefe;
+ box-shadow: 0 0 0 1px rgba(10, 10, 10, 0.2);
+ display: inline-block;
+ line-height: 0;
+ max-width: 100%;
+ transition: box-shadow 200ms ease-out;
+ border-radius: 0;
+ margin-bottom: 1rem; }
+ .thumbnail:hover, .thumbnail:focus {
+ box-shadow: 0 0 6px 1px rgba(33, 153, 232, 0.5); }
+
+.title-bar {
+ background: #0a0a0a;
+ color: #fefefe;
+ padding: 0.5rem; }
+ .title-bar::before, .title-bar::after {
+ content: ' ';
+ display: table; }
+ .title-bar::after {
+ clear: both; }
+ .title-bar .menu-icon {
+ margin-left: 0.25rem;
+ margin-right: 0.25rem; }
+
+.title-bar-left {
+ float: left; }
+
+.title-bar-right {
+ float: right;
+ text-align: right; }
+
+.title-bar-title {
+ font-weight: bold;
+ vertical-align: middle;
+ display: inline-block; }
+
+.menu-icon.dark {
+ position: relative;
+ display: inline-block;
+ vertical-align: middle;
+ cursor: pointer;
+ width: 20px;
+ height: 16px; }
+ .menu-icon.dark::after {
+ content: '';
+ position: absolute;
+ display: block;
+ width: 100%;
+ height: 2px;
+ background: #0a0a0a;
+ top: 0;
+ left: 0;
+ box-shadow: 0 7px 0 #0a0a0a, 0 14px 0 #0a0a0a; }
+ .menu-icon.dark:hover::after {
+ background: #8a8a8a;
+ box-shadow: 0 7px 0 #8a8a8a, 0 14px 0 #8a8a8a; }
+
+.has-tip {
+ border-bottom: dotted 1px #8a8a8a;
+ font-weight: bold;
+ position: relative;
+ display: inline-block;
+ cursor: help; }
+
+.tooltip {
+ background-color: #0a0a0a;
+ color: #fefefe;
+ font-size: 80%;
+ padding: 0.75rem;
+ position: absolute;
+ z-index: 10;
+ top: calc(100% + 0.6495rem);
+ max-width: 10rem !important;
+ border-radius: 0; }
+ .tooltip::before {
+ content: '';
+ display: block;
+ width: 0;
+ height: 0;
+ border: inset 0.75rem;
+ border-color: transparent transparent #0a0a0a;
+ border-bottom-style: solid;
+ border-top-width: 0;
+ bottom: 100%;
+ position: absolute;
+ left: 50%;
+ -webkit-transform: translateX(-50%);
+ -ms-transform: translateX(-50%);
+ transform: translateX(-50%); }
+ .tooltip.top::before {
+ content: '';
+ display: block;
+ width: 0;
+ height: 0;
+ border: inset 0.75rem;
+ border-color: #0a0a0a transparent transparent;
+ border-top-style: solid;
+ border-bottom-width: 0;
+ top: 100%;
+ bottom: auto; }
+ .tooltip.left::before {
+ content: '';
+ display: block;
+ width: 0;
+ height: 0;
+ border: inset 0.75rem;
+ border-color: transparent transparent transparent #0a0a0a;
+ border-left-style: solid;
+ border-right-width: 0;
+ bottom: auto;
+ left: 100%;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%); }
+ .tooltip.right::before {
+ content: '';
+ display: block;
+ width: 0;
+ height: 0;
+ border: inset 0.75rem;
+ border-color: transparent #0a0a0a transparent transparent;
+ border-right-style: solid;
+ border-left-width: 0;
+ bottom: auto;
+ left: auto;
+ right: 100%;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%); }
+
+.top-bar {
+ padding: 0.5rem; }
+ .top-bar::before, .top-bar::after {
+ content: ' ';
+ display: table; }
+ .top-bar::after {
+ clear: both; }
+ .top-bar,
+ .top-bar ul {
+ background-color: #e6e6e6; }
+ .top-bar input {
+ max-width: 200px;
+ margin-right: 1rem; }
+ .top-bar .input-group-field {
+ width: 100%;
+ margin-right: 0; }
+ .top-bar input.button {
+ width: auto; }
+ .top-bar .top-bar-left,
+ .top-bar .top-bar-right {
+ width: 100%; }
+ @media screen and (min-width: 40em) {
+ .top-bar .top-bar-left,
+ .top-bar .top-bar-right {
+ width: auto; } }
+ @media screen and (max-width: 63.9375em) {
+ .top-bar.stacked-for-medium .top-bar-left,
+ .top-bar.stacked-for-medium .top-bar-right {
+ width: 100%; } }
+ @media screen and (max-width: 74.9375em) {
+ .top-bar.stacked-for-large .top-bar-left,
+ .top-bar.stacked-for-large .top-bar-right {
+ width: 100%; } }
+
+.top-bar-title {
+ float: left;
+ margin-right: 1rem; }
+
+.top-bar-left {
+ float: left; }
+
+.top-bar-right {
+ float: right; }
+
+.hide {
+ display: none !important; }
+
+.invisible {
+ visibility: hidden; }
+
+@media screen and (max-width: 39.9375em) {
+ .hide-for-small-only {
+ display: none !important; } }
+
+@media screen and (max-width: 0em), screen and (min-width: 40em) {
+ .show-for-small-only {
+ display: none !important; } }
+
+@media screen and (min-width: 40em) {
+ .hide-for-medium {
+ display: none !important; } }
+
+@media screen and (max-width: 39.9375em) {
+ .show-for-medium {
+ display: none !important; } }
+
+@media screen and (min-width: 40em) and (max-width: 63.9375em) {
+ .hide-for-medium-only {
+ display: none !important; } }
+
+@media screen and (max-width: 39.9375em), screen and (min-width: 64em) {
+ .show-for-medium-only {
+ display: none !important; } }
+
+@media screen and (min-width: 64em) {
+ .hide-for-large {
+ display: none !important; } }
+
+@media screen and (max-width: 63.9375em) {
+ .show-for-large {
+ display: none !important; } }
+
+@media screen and (min-width: 64em) and (max-width: 74.9375em) {
+ .hide-for-large-only {
+ display: none !important; } }
+
+@media screen and (max-width: 63.9375em), screen and (min-width: 75em) {
+ .show-for-large-only {
+ display: none !important; } }
+
+.show-for-sr,
+.show-on-focus {
+ position: absolute !important;
+ width: 1px;
+ height: 1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0); }
+
+.show-on-focus:active, .show-on-focus:focus {
+ position: static !important;
+ height: auto;
+ width: auto;
+ overflow: visible;
+ clip: auto; }
+
+.show-for-landscape,
+.hide-for-portrait {
+ display: block !important; }
+ @media screen and (orientation: landscape) {
+ .show-for-landscape,
+ .hide-for-portrait {
+ display: block !important; } }
+ @media screen and (orientation: portrait) {
+ .show-for-landscape,
+ .hide-for-portrait {
+ display: none !important; } }
+
+.hide-for-landscape,
+.show-for-portrait {
+ display: none !important; }
+ @media screen and (orientation: landscape) {
+ .hide-for-landscape,
+ .show-for-portrait {
+ display: none !important; } }
+ @media screen and (orientation: portrait) {
+ .hide-for-landscape,
+ .show-for-portrait {
+ display: block !important; } }
+
+.float-left {
+ float: left !important; }
+
+.float-right {
+ float: right !important; }
+
+.float-center {
+ display: block;
+ margin-left: auto;
+ margin-right: auto; }
+
+.clearfix::before, .clearfix::after {
+ content: ' ';
+ display: table; }
+
+.clearfix::after {
+ clear: both; }
+
+.slide-in-down.mui-enter {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: translateY(-100%);
+ -ms-transform: translateY(-100%);
+ transform: translateY(-100%);
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ -webkit-backface-visibility: hidden;
+ backface-visibility: hidden; }
+
+.slide-in-down.mui-enter.mui-enter-active {
+ -webkit-transform: translateY(0);
+ -ms-transform: translateY(0);
+ transform: translateY(0); }
+
+.slide-in-left.mui-enter {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: translateX(-100%);
+ -ms-transform: translateX(-100%);
+ transform: translateX(-100%);
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ -webkit-backface-visibility: hidden;
+ backface-visibility: hidden; }
+
+.slide-in-left.mui-enter.mui-enter-active {
+ -webkit-transform: translateX(0);
+ -ms-transform: translateX(0);
+ transform: translateX(0); }
+
+.slide-in-up.mui-enter {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: translateY(100%);
+ -ms-transform: translateY(100%);
+ transform: translateY(100%);
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ -webkit-backface-visibility: hidden;
+ backface-visibility: hidden; }
+
+.slide-in-up.mui-enter.mui-enter-active {
+ -webkit-transform: translateY(0);
+ -ms-transform: translateY(0);
+ transform: translateY(0); }
+
+.slide-in-right.mui-enter {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: translateX(100%);
+ -ms-transform: translateX(100%);
+ transform: translateX(100%);
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ -webkit-backface-visibility: hidden;
+ backface-visibility: hidden; }
+
+.slide-in-right.mui-enter.mui-enter-active {
+ -webkit-transform: translateX(0);
+ -ms-transform: translateX(0);
+ transform: translateX(0); }
+
+.slide-out-down.mui-leave {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: translateY(0);
+ -ms-transform: translateY(0);
+ transform: translateY(0);
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ -webkit-backface-visibility: hidden;
+ backface-visibility: hidden; }
+
+.slide-out-down.mui-leave.mui-leave-active {
+ -webkit-transform: translateY(100%);
+ -ms-transform: translateY(100%);
+ transform: translateY(100%); }
+
+.slide-out-right.mui-leave {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: translateX(0);
+ -ms-transform: translateX(0);
+ transform: translateX(0);
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ -webkit-backface-visibility: hidden;
+ backface-visibility: hidden; }
+
+.slide-out-right.mui-leave.mui-leave-active {
+ -webkit-transform: translateX(100%);
+ -ms-transform: translateX(100%);
+ transform: translateX(100%); }
+
+.slide-out-up.mui-leave {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: translateY(0);
+ -ms-transform: translateY(0);
+ transform: translateY(0);
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ -webkit-backface-visibility: hidden;
+ backface-visibility: hidden; }
+
+.slide-out-up.mui-leave.mui-leave-active {
+ -webkit-transform: translateY(-100%);
+ -ms-transform: translateY(-100%);
+ transform: translateY(-100%); }
+
+.slide-out-left.mui-leave {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: translateX(0);
+ -ms-transform: translateX(0);
+ transform: translateX(0);
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ -webkit-backface-visibility: hidden;
+ backface-visibility: hidden; }
+
+.slide-out-left.mui-leave.mui-leave-active {
+ -webkit-transform: translateX(-100%);
+ -ms-transform: translateX(-100%);
+ transform: translateX(-100%); }
+
+.fade-in.mui-enter {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ opacity: 0;
+ transition-property: opacity; }
+
+.fade-in.mui-enter.mui-enter-active {
+ opacity: 1; }
+
+.fade-out.mui-leave {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ opacity: 1;
+ transition-property: opacity; }
+
+.fade-out.mui-leave.mui-leave-active {
+ opacity: 0; }
+
+.hinge-in-from-top.mui-enter {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: perspective(2000px) rotateX(-90deg);
+ transform: perspective(2000px) rotateX(-90deg);
+ -webkit-transform-origin: top;
+ -ms-transform-origin: top;
+ transform-origin: top;
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ opacity: 0; }
+
+.hinge-in-from-top.mui-enter.mui-enter-active {
+ -webkit-transform: perspective(2000px) rotate(0deg);
+ transform: perspective(2000px) rotate(0deg);
+ opacity: 1; }
+
+.hinge-in-from-right.mui-enter {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: perspective(2000px) rotateY(-90deg);
+ transform: perspective(2000px) rotateY(-90deg);
+ -webkit-transform-origin: right;
+ -ms-transform-origin: right;
+ transform-origin: right;
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ opacity: 0; }
+
+.hinge-in-from-right.mui-enter.mui-enter-active {
+ -webkit-transform: perspective(2000px) rotate(0deg);
+ transform: perspective(2000px) rotate(0deg);
+ opacity: 1; }
+
+.hinge-in-from-bottom.mui-enter {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: perspective(2000px) rotateX(90deg);
+ transform: perspective(2000px) rotateX(90deg);
+ -webkit-transform-origin: bottom;
+ -ms-transform-origin: bottom;
+ transform-origin: bottom;
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ opacity: 0; }
+
+.hinge-in-from-bottom.mui-enter.mui-enter-active {
+ -webkit-transform: perspective(2000px) rotate(0deg);
+ transform: perspective(2000px) rotate(0deg);
+ opacity: 1; }
+
+.hinge-in-from-left.mui-enter {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: perspective(2000px) rotateY(90deg);
+ transform: perspective(2000px) rotateY(90deg);
+ -webkit-transform-origin: left;
+ -ms-transform-origin: left;
+ transform-origin: left;
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ opacity: 0; }
+
+.hinge-in-from-left.mui-enter.mui-enter-active {
+ -webkit-transform: perspective(2000px) rotate(0deg);
+ transform: perspective(2000px) rotate(0deg);
+ opacity: 1; }
+
+.hinge-in-from-middle-x.mui-enter {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: perspective(2000px) rotateX(-90deg);
+ transform: perspective(2000px) rotateX(-90deg);
+ -webkit-transform-origin: center;
+ -ms-transform-origin: center;
+ transform-origin: center;
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ opacity: 0; }
+
+.hinge-in-from-middle-x.mui-enter.mui-enter-active {
+ -webkit-transform: perspective(2000px) rotate(0deg);
+ transform: perspective(2000px) rotate(0deg);
+ opacity: 1; }
+
+.hinge-in-from-middle-y.mui-enter {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: perspective(2000px) rotateY(-90deg);
+ transform: perspective(2000px) rotateY(-90deg);
+ -webkit-transform-origin: center;
+ -ms-transform-origin: center;
+ transform-origin: center;
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ opacity: 0; }
+
+.hinge-in-from-middle-y.mui-enter.mui-enter-active {
+ -webkit-transform: perspective(2000px) rotate(0deg);
+ transform: perspective(2000px) rotate(0deg);
+ opacity: 1; }
+
+.hinge-out-from-top.mui-leave {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: perspective(2000px) rotate(0deg);
+ transform: perspective(2000px) rotate(0deg);
+ -webkit-transform-origin: top;
+ -ms-transform-origin: top;
+ transform-origin: top;
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ opacity: 1; }
+
+.hinge-out-from-top.mui-leave.mui-leave-active {
+ -webkit-transform: perspective(2000px) rotateX(-90deg);
+ transform: perspective(2000px) rotateX(-90deg);
+ opacity: 0; }
+
+.hinge-out-from-right.mui-leave {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: perspective(2000px) rotate(0deg);
+ transform: perspective(2000px) rotate(0deg);
+ -webkit-transform-origin: right;
+ -ms-transform-origin: right;
+ transform-origin: right;
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ opacity: 1; }
+
+.hinge-out-from-right.mui-leave.mui-leave-active {
+ -webkit-transform: perspective(2000px) rotateY(-90deg);
+ transform: perspective(2000px) rotateY(-90deg);
+ opacity: 0; }
+
+.hinge-out-from-bottom.mui-leave {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: perspective(2000px) rotate(0deg);
+ transform: perspective(2000px) rotate(0deg);
+ -webkit-transform-origin: bottom;
+ -ms-transform-origin: bottom;
+ transform-origin: bottom;
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ opacity: 1; }
+
+.hinge-out-from-bottom.mui-leave.mui-leave-active {
+ -webkit-transform: perspective(2000px) rotateX(90deg);
+ transform: perspective(2000px) rotateX(90deg);
+ opacity: 0; }
+
+.hinge-out-from-left.mui-leave {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: perspective(2000px) rotate(0deg);
+ transform: perspective(2000px) rotate(0deg);
+ -webkit-transform-origin: left;
+ -ms-transform-origin: left;
+ transform-origin: left;
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ opacity: 1; }
+
+.hinge-out-from-left.mui-leave.mui-leave-active {
+ -webkit-transform: perspective(2000px) rotateY(90deg);
+ transform: perspective(2000px) rotateY(90deg);
+ opacity: 0; }
+
+.hinge-out-from-middle-x.mui-leave {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: perspective(2000px) rotate(0deg);
+ transform: perspective(2000px) rotate(0deg);
+ -webkit-transform-origin: center;
+ -ms-transform-origin: center;
+ transform-origin: center;
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ opacity: 1; }
+
+.hinge-out-from-middle-x.mui-leave.mui-leave-active {
+ -webkit-transform: perspective(2000px) rotateX(-90deg);
+ transform: perspective(2000px) rotateX(-90deg);
+ opacity: 0; }
+
+.hinge-out-from-middle-y.mui-leave {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: perspective(2000px) rotate(0deg);
+ transform: perspective(2000px) rotate(0deg);
+ -webkit-transform-origin: center;
+ -ms-transform-origin: center;
+ transform-origin: center;
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ opacity: 1; }
+
+.hinge-out-from-middle-y.mui-leave.mui-leave-active {
+ -webkit-transform: perspective(2000px) rotateY(-90deg);
+ transform: perspective(2000px) rotateY(-90deg);
+ opacity: 0; }
+
+.scale-in-up.mui-enter {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: scale(0.5);
+ -ms-transform: scale(0.5);
+ transform: scale(0.5);
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ opacity: 0; }
+
+.scale-in-up.mui-enter.mui-enter-active {
+ -webkit-transform: scale(1);
+ -ms-transform: scale(1);
+ transform: scale(1);
+ opacity: 1; }
+
+.scale-in-down.mui-enter {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: scale(1.5);
+ -ms-transform: scale(1.5);
+ transform: scale(1.5);
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ opacity: 0; }
+
+.scale-in-down.mui-enter.mui-enter-active {
+ -webkit-transform: scale(1);
+ -ms-transform: scale(1);
+ transform: scale(1);
+ opacity: 1; }
+
+.scale-out-up.mui-leave {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: scale(1);
+ -ms-transform: scale(1);
+ transform: scale(1);
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ opacity: 1; }
+
+.scale-out-up.mui-leave.mui-leave-active {
+ -webkit-transform: scale(1.5);
+ -ms-transform: scale(1.5);
+ transform: scale(1.5);
+ opacity: 0; }
+
+.scale-out-down.mui-leave {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: scale(1);
+ -ms-transform: scale(1);
+ transform: scale(1);
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ opacity: 1; }
+
+.scale-out-down.mui-leave.mui-leave-active {
+ -webkit-transform: scale(0.5);
+ -ms-transform: scale(0.5);
+ transform: scale(0.5);
+ opacity: 0; }
+
+.spin-in.mui-enter {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: rotate(-0.75turn);
+ -ms-transform: rotate(-0.75turn);
+ transform: rotate(-0.75turn);
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ opacity: 0; }
+
+.spin-in.mui-enter.mui-enter-active {
+ -webkit-transform: rotate(0);
+ -ms-transform: rotate(0);
+ transform: rotate(0);
+ opacity: 1; }
+
+.spin-out.mui-leave {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: rotate(0);
+ -ms-transform: rotate(0);
+ transform: rotate(0);
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ opacity: 1; }
+
+.spin-out.mui-leave.mui-leave-active {
+ -webkit-transform: rotate(0.75turn);
+ -ms-transform: rotate(0.75turn);
+ transform: rotate(0.75turn);
+ opacity: 0; }
+
+.spin-in-ccw.mui-enter {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: rotate(0.75turn);
+ -ms-transform: rotate(0.75turn);
+ transform: rotate(0.75turn);
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ opacity: 0; }
+
+.spin-in-ccw.mui-enter.mui-enter-active {
+ -webkit-transform: rotate(0);
+ -ms-transform: rotate(0);
+ transform: rotate(0);
+ opacity: 1; }
+
+.spin-out-ccw.mui-leave {
+ transition-duration: 500ms;
+ transition-timing-function: linear;
+ -webkit-transform: rotate(0);
+ -ms-transform: rotate(0);
+ transform: rotate(0);
+ transition-property: -webkit-transform, opacity;
+ transition-property: transform, opacity;
+ opacity: 1; }
+
+.spin-out-ccw.mui-leave.mui-leave-active {
+ -webkit-transform: rotate(-0.75turn);
+ -ms-transform: rotate(-0.75turn);
+ transform: rotate(-0.75turn);
+ opacity: 0; }
+
+.slow {
+ transition-duration: 750ms !important; }
+
+.fast {
+ transition-duration: 250ms !important; }
+
+.linear {
+ transition-timing-function: linear !important; }
+
+.ease {
+ transition-timing-function: ease !important; }
+
+.ease-in {
+ transition-timing-function: ease-in !important; }
+
+.ease-out {
+ transition-timing-function: ease-out !important; }
+
+.ease-in-out {
+ transition-timing-function: ease-in-out !important; }
+
+.bounce-in {
+ transition-timing-function: cubic-bezier(0.485, 0.155, 0.24, 1.245) !important; }
+
+.bounce-out {
+ transition-timing-function: cubic-bezier(0.485, 0.155, 0.515, 0.845) !important; }
+
+.bounce-in-out {
+ transition-timing-function: cubic-bezier(0.76, -0.245, 0.24, 1.245) !important; }
+
+.short-delay {
+ transition-delay: 300ms !important; }
+
+.long-delay {
+ transition-delay: 700ms !important; }
+
+.shake {
+ -webkit-animation-name: shake-7;
+ animation-name: shake-7; }
+
+@-webkit-keyframes shake-7 {
+ 0%, 10%, 20%, 30%, 40%, 50%, 60%, 70%, 80%, 90% {
+ -webkit-transform: translateX(7%);
+ transform: translateX(7%); }
+ 5%, 15%, 25%, 35%, 45%, 55%, 65%, 75%, 85%, 95% {
+ -webkit-transform: translateX(-7%);
+ transform: translateX(-7%); } }
+
+@keyframes shake-7 {
+ 0%, 10%, 20%, 30%, 40%, 50%, 60%, 70%, 80%, 90% {
+ -webkit-transform: translateX(7%);
+ transform: translateX(7%); }
+ 5%, 15%, 25%, 35%, 45%, 55%, 65%, 75%, 85%, 95% {
+ -webkit-transform: translateX(-7%);
+ transform: translateX(-7%); } }
+
+.spin-cw {
+ -webkit-animation-name: spin-cw-1turn;
+ animation-name: spin-cw-1turn; }
+
+@-webkit-keyframes spin-cw-1turn {
+ 0% {
+ -webkit-transform: rotate(-1turn);
+ transform: rotate(-1turn); }
+ 100% {
+ -webkit-transform: rotate(0);
+ transform: rotate(0); } }
+
+@keyframes spin-cw-1turn {
+ 0% {
+ -webkit-transform: rotate(-1turn);
+ transform: rotate(-1turn); }
+ 100% {
+ -webkit-transform: rotate(0);
+ transform: rotate(0); } }
+
+.spin-ccw {
+ -webkit-animation-name: spin-cw-1turn;
+ animation-name: spin-cw-1turn; }
+
+@keyframes spin-cw-1turn {
+ 0% {
+ -webkit-transform: rotate(0);
+ transform: rotate(0); }
+ 100% {
+ -webkit-transform: rotate(1turn);
+ transform: rotate(1turn); } }
+
+.wiggle {
+ -webkit-animation-name: wiggle-7deg;
+ animation-name: wiggle-7deg; }
+
+@-webkit-keyframes wiggle-7deg {
+ 40%, 50%, 60% {
+ -webkit-transform: rotate(7deg);
+ transform: rotate(7deg); }
+ 35%, 45%, 55%, 65% {
+ -webkit-transform: rotate(-7deg);
+ transform: rotate(-7deg); }
+ 0%, 30%, 70%, 100% {
+ -webkit-transform: rotate(0);
+ transform: rotate(0); } }
+
+@keyframes wiggle-7deg {
+ 40%, 50%, 60% {
+ -webkit-transform: rotate(7deg);
+ transform: rotate(7deg); }
+ 35%, 45%, 55%, 65% {
+ -webkit-transform: rotate(-7deg);
+ transform: rotate(-7deg); }
+ 0%, 30%, 70%, 100% {
+ -webkit-transform: rotate(0);
+ transform: rotate(0); } }
+
+.shake,
+.spin-cw,
+.spin-ccw,
+.wiggle {
+ -webkit-animation-duration: 500ms;
+ animation-duration: 500ms; }
+
+.infinite {
+ -webkit-animation-iteration-count: infinite;
+ animation-iteration-count: infinite; }
+
+.slow {
+ -webkit-animation-duration: 750ms !important;
+ animation-duration: 750ms !important; }
+
+.fast {
+ -webkit-animation-duration: 250ms !important;
+ animation-duration: 250ms !important; }
+
+.linear {
+ -webkit-animation-timing-function: linear !important;
+ animation-timing-function: linear !important; }
+
+.ease {
+ -webkit-animation-timing-function: ease !important;
+ animation-timing-function: ease !important; }
+
+.ease-in {
+ -webkit-animation-timing-function: ease-in !important;
+ animation-timing-function: ease-in !important; }
+
+.ease-out {
+ -webkit-animation-timing-function: ease-out !important;
+ animation-timing-function: ease-out !important; }
+
+.ease-in-out {
+ -webkit-animation-timing-function: ease-in-out !important;
+ animation-timing-function: ease-in-out !important; }
+
+.bounce-in {
+ -webkit-animation-timing-function: cubic-bezier(0.485, 0.155, 0.24, 1.245) !important;
+ animation-timing-function: cubic-bezier(0.485, 0.155, 0.24, 1.245) !important; }
+
+.bounce-out {
+ -webkit-animation-timing-function: cubic-bezier(0.485, 0.155, 0.515, 0.845) !important;
+ animation-timing-function: cubic-bezier(0.485, 0.155, 0.515, 0.845) !important; }
+
+.bounce-in-out {
+ -webkit-animation-timing-function: cubic-bezier(0.76, -0.245, 0.24, 1.245) !important;
+ animation-timing-function: cubic-bezier(0.76, -0.245, 0.24, 1.245) !important; }
+
+.short-delay {
+ -webkit-animation-delay: 300ms !important;
+ animation-delay: 300ms !important; }
+
+.long-delay {
+ -webkit-animation-delay: 700ms !important;
+ animation-delay: 700ms !important; }
From eb5dc90126473a0d2a385fec50a71c291d014b3f Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Mon, 24 Oct 2016 16:35:54 -0700
Subject: [PATCH 069/144] edited routes
---
app/controllers/products_controller.rb | 24 ++++++++++--------------
config/routes.rb | 21 +++++++++------------
2 files changed, 19 insertions(+), 26 deletions(-)
diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb
index 0eab65a23e..f1ccf0b61e 100644
--- a/app/controllers/products_controller.rb
+++ b/app/controllers/products_controller.rb
@@ -1,23 +1,11 @@
class ProductsController < ApplicationController
-
-# products products#index
-# merchant_products products#index
-# " " products#create
-# new_merchant_product products#new
-# edit_merchant_product products#edit
-# product products#show
-# merchant_product products#show
-# " " products#update
-# " " products#update
-# " " products#destroy
+ before_action :find_product, only: [:show, :edit, :update]
def index
@product = Product.all
end
- def show
- @product = Product.find(params[:id])
- end
+ def show; end
# should be limited to merchants
# def new
@@ -42,4 +30,12 @@ def product_params
params.require(:product).permit(:name, :description, :stock, :price,:photo_url, :merchant_id)
end
+ def find_product
+ begin
+ @product = Product.find(params[:id])
+ rescue ActiveRecord::RecordNotFound
+ render file: "#{Rails.root}/public/404.html", layout: false, status: :not_found
+ end
+ end
+
end
diff --git a/config/routes.rb b/config/routes.rb
index 21e20ce8fa..8946ecb014 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -2,31 +2,28 @@
root 'home#index'
- resources :merchants, only: [:show] do
- resources :products
- resources :orders, except: [:new, :create, :delete]
- # NESTED? do
- # resources :order_items, only: [:update]
- # end
- end
+ resources :merchants, only: [:index, :show]
- resources :products, only: [:index, :show] do
+ resources :products do
resources :reviews, only: [:new, :create]
end
+
+ resources :orders, except: [:new, :create, :delete]
+ resources :order_items, except: [:index, :show]
resources :categories, only: [:index, :new, :create, :show]
# We're going to talk about this more if any of us needs to edit this. :)
- resources :orders, only: [:new, :create, :show] do
- resources :order_items, except: [:index, :show]
- end
+ # resources :orders, only: [:new, :create, :show] do
+ # # resources :order_items, except: [:index, :show]
+ # end
# Sessions routes - can be further flushed out...
get '/auth/:provider/callback' => 'sessions#create'
get "/sessions/login_failure", to: "sessions#login_failure", as: "login_failure"
- get '/sessions', to: 'sessions#index', as: 'sessions'
+ get '/sessions', to: 'sessions#index', as: 'portal'
delete '/sessions', to: 'sessions#destroy'
From 46f5ca4b4015f4ecfe95128250daa9cef5d58856 Mon Sep 17 00:00:00 2001
From: Shari Meggs
Date: Mon, 24 Oct 2016 16:52:17 -0700
Subject: [PATCH 070/144] Fixed an order list
---
test/models/order_test.rb | 20 +++-----------------
1 file changed, 3 insertions(+), 17 deletions(-)
diff --git a/test/models/order_test.rb b/test/models/order_test.rb
index 035829f465..7b03395764 100644
--- a/test/models/order_test.rb
+++ b/test/models/order_test.rb
@@ -27,24 +27,10 @@ class OrderTest < ActiveSupport::TestCase
test "confirming the private method update_total occurs on an order object" do
o = Order.new(cc_number: 6789, cc_exp_year: 2016, cc_exp_month: 12)
assert_equal(0, o.total)
-
- p = OrderItem.new(quantity: 4, product: products(:cat_suit), shipped?: true, order_id: o)
- puts "Here are o's attributes: #{o.attributes}."
- # o[:order_item] = p
- o.save
- puts "Did o save? #{o.save}."
+ assert o.save
+ p = o.order_items.new(quantity: 4, product: products(:cat_suit), shipped?: true)
+ assert p.save
assert_equal(4936, o.total)
-
- # assert_difference("o.order_items = order_items(:four_unit)", 0) do
- # o.save
- # end
end
end
-
-# test
-# create order model object
-# add order items to it (fixtures)
-#
-# assert_difference
-# end
From 2b7087a1fc6876a1b99cf94a4851f125b25b8c97 Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Mon, 24 Oct 2016 16:55:33 -0700
Subject: [PATCH 071/144] schema.rb
---
db/schema.rb | 11 +----------
1 file changed, 1 insertion(+), 10 deletions(-)
diff --git a/db/schema.rb b/db/schema.rb
index a97fbc0b0c..fc09d7da5e 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,16 +11,8 @@
#
# It's strongly recommended that you check this file into your version control system.
-
ActiveRecord::Schema.define(version: 20161024221053) do
-
-ActiveRecord::Schema.define(version: 20161024184627) do
-
-ActiveRecord::Schema.define(version: 20161021201059) do
-
-
-
create_table "categories", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
@@ -89,6 +81,5 @@
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
-end
-end
+
end
From e85cb5242048d53add748cbeee41082d9c7a5240 Mon Sep 17 00:00:00 2001
From: Shari Meggs
Date: Tue, 25 Oct 2016 11:18:30 -0700
Subject: [PATCH 072/144] Fixed a few errors on homepage
---
app/helpers/application_helper.rb | 1 +
app/views/sessions/login.html.erb | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 51210a4ae3..8e0c941ee9 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -10,6 +10,7 @@ def show_dollars(price)
def date_format(time)
return time.strftime("%B %-d, %Y at %l:%M:%S %p")
+ end
def user_name
begin
diff --git a/app/views/sessions/login.html.erb b/app/views/sessions/login.html.erb
index 7882fcfdf9..e4b46df29c 100644
--- a/app/views/sessions/login.html.erb
+++ b/app/views/sessions/login.html.erb
@@ -1 +1 @@
-<%= link_to "Merchant Portal", sessions_path %>
+<%= link_to "Merchant Portal", merchants_path %>
From 132f56ffdb123824f764734a71b1814633199984 Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Tue, 25 Oct 2016 11:41:17 -0700
Subject: [PATCH 073/144] temporarily commented out require login, updated path
names for portal_path in sessions controller and tests
---
app/controllers/application_controller.rb | 4 +-
app/controllers/sessions_controller.rb | 2 +-
test/controllers/products_controller_test.rb | 121 ++++++++++++++++++-
test/controllers/sessions_controller_test.rb | 4 +-
4 files changed, 123 insertions(+), 8 deletions(-)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 1963c5e5bb..a4d3c73e8c 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -3,7 +3,7 @@ class ApplicationController < ActionController::Base
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
helper_method :current_order, :shopping_cart
- before_action :require_login
+ # before_action :require_login
private
def current_user
@@ -28,7 +28,7 @@ def current_order
Order.new
end
end
-
+
def shopping_cart
if session[:cart]
@cart = session[:cart]
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 213138b9d3..0f0f46d144 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -17,7 +17,7 @@ def create
end
session[:user_id] = @merchant.id
- redirect_to sessions_path
+ redirect_to portal_path
end
def index
diff --git a/test/controllers/products_controller_test.rb b/test/controllers/products_controller_test.rb
index 46de3ddb10..30d6fb5a8e 100644
--- a/test/controllers/products_controller_test.rb
+++ b/test/controllers/products_controller_test.rb
@@ -1,9 +1,6 @@
require 'test_helper'
class ProductsControllerTest < ActionController::TestCase
- # test "the truth" do
- # assert true
- # end
test "should show the index page" do
get :index
assert_template :index
@@ -16,4 +13,122 @@ class ProductsControllerTest < ActionController::TestCase
assert_template :show
assert_equal assigns(:product), products(:cat_suit)
end
+
+ test "should return 404 for a product that doesn't exist" do
+ product_id = 123448658
+ assert_raises ActiveRecord::RecordNotFound do
+ Product.find(product_id)
+ end
+
+ end
+
end
+
+# get :show, {id: movie_id}
+# assert_response :not_found
+# end
+#
+# test "should get the new form" do
+# get :new
+# assert_template :new
+# assert_template partial: '_form'
+# assert_response :success
+# end
+#
+# test "add a new movie to the database" do
+# post_params = {movie: {name: "The Sound of Music", director: "no idea"} }
+# assert_difference("Movie.count", 1) do
+# post :create, post_params
+# end
+#
+# assert_redirected_to movies_path
+# end
+#
+# test "a movie with no title can't change the database" do
+# post_params = { movie: {director: "someone", description: "empty values"}}
+#
+# assert_no_difference("Movie.count") do
+# post :create, post_params
+# end
+#
+# assert_template :new
+# end
+#
+# test "a movie with no author can't change the database" do
+# post_params = { movie: {name: "something", description: "empty values"}}
+#
+# assert_no_difference("Movie.count") do
+# post :create, post_params
+# end
+#
+# assert_template :new
+# end
+#
+# test "should get the edit form" do
+# movie_id = movies(:schindlers_list).id
+# get :edit, {id: movie_id}
+# assert_template :edit
+# assert_template partial: '_form'
+# assert_response :success
+#
+# movie = assigns(:movie)
+# assert_not_nil movie
+# assert_equal movie.id, movie_id
+# end
+#
+# test "update should change the movie" do
+# movie_id = movies(:galaxy_quest).id
+# patch :update, {id: movie_id, movie: {name: "Galaxy Quest (ick)"} }
+# assert_equal "Galaxy Quest (ick)", Movie.find(movie_id).name
+#
+# assert_redirected_to movie_path
+# end
+#
+# test "update should not allow nil name" do
+# movie_id = movies(:toy_story).id
+# patch :update, {id: movie_id, movie: {name: nil} }
+#
+# assert_equal "Toy Story", Movie.find(movie_id).name
+#
+# assert_template :edit
+# end
+#
+# test "update should not allow nil director" do
+# movie_id = movies(:toy_story).id
+# patch :update, {id: movie_id, movie: {director: nil} }
+#
+# assert_equal "Pixar", Movie.find(movie_id).director
+# assert_template :edit
+# end
+#
+# test "destroy should delete the item" do
+# movie_id = movies(:galaxy_quest).id
+#
+# assert_difference("Movie.count", -1) do
+# delete :destroy, {id: movie_id}
+# end
+#
+# assert_raises ActiveRecord::RecordNotFound do
+# Movie.find(movie_id)
+# end
+#
+# assert_redirected_to movies_path
+# end
+#
+# test "upvote should increment rank by one" do
+# movie_id = movies(:schindlers_list).id
+#
+# assert_difference("Movie.find(movie_id).rank", 1) do
+# patch :upvote, {id: movie_id}
+# end
+#
+# assert_redirected_to movie_path(movie_id)
+# end
+#
+# test "upvote should set nil ranks to one" do
+# movie_id = movies(:nil_rank).id
+#
+# assert_difference("Movie.find(movie_id).rank", 1) do
+# patch :upvote, {id: movie_id}
+# end
+# end
diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb
index 49799a552a..8123e778ec 100644
--- a/test/controllers/sessions_controller_test.rb
+++ b/test/controllers/sessions_controller_test.rb
@@ -10,7 +10,7 @@ def login_a_user
assert_difference('Merchant.count', 1) do
login_a_user
assert_response :redirect
- assert_redirected_to sessions_path
+ assert_redirected_to portal_path
end
end
@@ -21,7 +21,7 @@ def login_a_user
assert_no_difference('Merchant.count') do
login_a_user
assert_response :redirect
- assert_redirected_to sessions_path
+ assert_redirected_to portal_path
assert_not_nil session[:merchant_id]
end
end
From 0ae47c79e410acece94b701f286ea7d8a0956e10 Mon Sep 17 00:00:00 2001
From: Shari Meggs
Date: Tue, 25 Oct 2016 11:41:28 -0700
Subject: [PATCH 074/144] Fixed error in shopping cart in helper as well as
index page of cart
---
app/controllers/application_controller.rb | 7 ++++---
app/controllers/carts_controller.rb | 24 ++++++++++++++---------
2 files changed, 19 insertions(+), 12 deletions(-)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 1963c5e5bb..db10ba629b 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -3,7 +3,7 @@ class ApplicationController < ActionController::Base
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
helper_method :current_order, :shopping_cart
- before_action :require_login
+
private
def current_user
@@ -28,12 +28,13 @@ def current_order
Order.new
end
end
-
+
def shopping_cart
if session[:cart]
@cart = session[:cart]
else
- @cart ={}
+ session[:cart]= {}
+ @cart = session[:cart]
end
end
diff --git a/app/controllers/carts_controller.rb b/app/controllers/carts_controller.rb
index ed050dc8fb..237945cc7f 100644
--- a/app/controllers/carts_controller.rb
+++ b/app/controllers/carts_controller.rb
@@ -1,14 +1,20 @@
class CartsController < ApplicationController
before_action :shopping_cart
def index
- @cart.each do | k, v|
- @id = k
- end
- @product = Product.find(@id)
- @order_item = OrderItem.new
- @product.order_items << @order_item
- @product.save
- return @cart
+ # if !@cart.empty?
+ @cart.each do | k, v|
+ @id = k
+ end
+ @product = Product.find(@id)
+ @order_item = OrderItem.new
+ @product.order_items << @order_item
+ @product.save
+ # return @cart
+ # else
+
+
+ return @cart
+ # end
end
def add_to_cart
@@ -17,7 +23,7 @@ def add_to_cart
@order_item = OrderItem.new
@product.order_items << @order_item
@product.save
-
+ @order_item.save
if @cart[id]
@cart[id] = @cart[id] + 1
From eee534def5e9f7626fc7f2625609c0c4baa15943 Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Tue, 25 Oct 2016 12:33:24 -0700
Subject: [PATCH 075/144] WIP product controller
---
app/controllers/products_controller.rb | 13 +++---
app/models/product.rb | 1 +
app/views/products/_form.html.erb | 44 ++++++++++++++++++++
app/views/products/new.html.erb | 5 +++
app/views/reviews/_form.html.erb | 2 +-
test/controllers/products_controller_test.rb | 24 ++++++-----
6 files changed, 72 insertions(+), 17 deletions(-)
create mode 100644 app/views/products/_form.html.erb
create mode 100644 app/views/products/new.html.erb
diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb
index 9f02a99772..ee87575889 100644
--- a/app/controllers/products_controller.rb
+++ b/app/controllers/products_controller.rb
@@ -9,13 +9,16 @@ def index
def show; end
# should be limited to merchants
- # def new
- # end
- #
+ def new
+ @product = Product.new
+ end
+
+
# def create
- # end
- #
#
+ # end
+
+
# def edit
# end
#
diff --git a/app/models/product.rb b/app/models/product.rb
index eb832ef917..ce872377e1 100644
--- a/app/models/product.rb
+++ b/app/models/product.rb
@@ -5,6 +5,7 @@ class Product < ActiveRecord::Base
belongs_to :merchant
validates :name, presence: true, uniqueness: true
validates :price, presence: true, numericality: {only_integer: true, greater_than: 0}
+ validates :stock, presence: true, numericality: {only_integer: true, greater_than_or_equal_to: 0}
default_scope { where(active?: true) }
diff --git a/app/views/products/_form.html.erb b/app/views/products/_form.html.erb
new file mode 100644
index 0000000000..361d9c143a
--- /dev/null
+++ b/app/views/products/_form.html.erb
@@ -0,0 +1,44 @@
+
+ <% if @product.errors.any? %>
+
+ <% @product.errors.each do |column, message| %>
+
+
+ <%= column.capitalize %>
+ <%= message %>
+
+ <% end %>
+
+ <% end %>
+
+ <%= form_for @product, url: local_path do |f| %>
+
+ <%= f.label :name %>:
+ <%= f.text_field :name %>
+
+
+
+ <%= f.label :description %>:
+ <%= f.text_field :description %>
+
+
+
+ <%= f.label :stock, "How many are you selling? (max 500)" %>:
+ <%= f.number_field :stock, in: 1..500 %>
+
+
+
+ <%= f.label :price %>:
+ <%= f.text_field :price %>
+
+
+
+ <%= f.label :photo_url, "URL of photo (sorry, we can't host photos)" %>:
+ <%= f.url_field :photo_url %>
+
+
+
+ <%= f.submit "Submit" %>
+
+ <% end %>
+
diff --git a/app/views/products/new.html.erb b/app/views/products/new.html.erb
new file mode 100644
index 0000000000..3ef63c2f9b
--- /dev/null
+++ b/app/views/products/new.html.erb
@@ -0,0 +1,5 @@
+Add a New Product
+
+<%= render partial: "form",
+ locals: { http_method: :post,
+ local_path: products_path } %>
diff --git a/app/views/reviews/_form.html.erb b/app/views/reviews/_form.html.erb
index 0e0ea482e7..4eb0e2050b 100644
--- a/app/views/reviews/_form.html.erb
+++ b/app/views/reviews/_form.html.erb
@@ -16,7 +16,7 @@
<%= f.label :rating, "Rating (1 (worst) to 5 (best)): " %>
- <%= f.number_field :rating, in: 1..5, size: "30x1" %>
+ <%= f.number_field :rating, in: 1..5 %>
diff --git a/test/controllers/products_controller_test.rb b/test/controllers/products_controller_test.rb
index 30d6fb5a8e..836a118e0b 100644
--- a/test/controllers/products_controller_test.rb
+++ b/test/controllers/products_controller_test.rb
@@ -20,21 +20,23 @@ class ProductsControllerTest < ActionController::TestCase
Product.find(product_id)
end
+ get :show, {id: product_id}
+ assert_response :not_found
+ end
+
+ test "should get the new form" do
+ get :new
+ assert_template :new
+ assert_template partial: '_form'
+ assert_response :success
+ end
+
+ test "create should add a new product to the database" do
+ post_params = {product: {name: "dog sunglasses", description: "too cool!"}, }
end
end
-# get :show, {id: movie_id}
-# assert_response :not_found
-# end
-#
-# test "should get the new form" do
-# get :new
-# assert_template :new
-# assert_template partial: '_form'
-# assert_response :success
-# end
-#
# test "add a new movie to the database" do
# post_params = {movie: {name: "The Sound of Music", director: "no idea"} }
# assert_difference("Movie.count", 1) do
From af3a6e73ea502a69ac264772fc8856e3d2a6b9c4 Mon Sep 17 00:00:00 2001
From: Shari Meggs
Date: Tue, 25 Oct 2016 12:54:08 -0700
Subject: [PATCH 076/144] Working on the destroy method
---
app/controllers/carts_controller.rb | 32 +++++++++++++++--------------
app/models/order_item.rb | 8 ++++----
2 files changed, 21 insertions(+), 19 deletions(-)
diff --git a/app/controllers/carts_controller.rb b/app/controllers/carts_controller.rb
index 237945cc7f..7c17819d0d 100644
--- a/app/controllers/carts_controller.rb
+++ b/app/controllers/carts_controller.rb
@@ -1,30 +1,32 @@
class CartsController < ApplicationController
before_action :shopping_cart
def index
- # if !@cart.empty?
- @cart.each do | k, v|
- @id = k
- end
- @product = Product.find(@id)
- @order_item = OrderItem.new
- @product.order_items << @order_item
- @product.save
- # return @cart
- # else
+ @cart.each do | k, v|
+ @id = k
+ end
+ @order_item = OrderItem.find_by(product_id: @id)
+ raise
+
+ if @order_item != nil
+ @product = Product.find(@id)
+ # @product.order_items << @order_item
+ # @product.save
+ # @order_item.save
+ # return @cart
+ else
return @cart
- # end
+ end
end
def add_to_cart
id = params[:id]
@product = Product.find(id)
- @order_item = OrderItem.new
+ @order_item = OrderItem.create
@product.order_items << @order_item
@product.save
@order_item.save
-
if @cart[id]
@cart[id] = @cart[id] + 1
else
@@ -41,8 +43,8 @@ def sub_cart
def destroy
- @product = Product.find(params[:id])
- session[:cart].delete(@product)
+ @order_item = OrderItem.find_by(product_id: params[:id])
+ @order_item.destroy
# @cart = shopping_cart
# @product = @cart.product.find(params[:id]).destroy
redirect_to carts_path
diff --git a/app/models/order_item.rb b/app/models/order_item.rb
index aaa04b2d96..651d05a39a 100644
--- a/app/models/order_item.rb
+++ b/app/models/order_item.rb
@@ -2,11 +2,11 @@ class OrderItem < ActiveRecord::Base
belongs_to :product
belongs_to :order
- validates :quantity, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 1 }
+ # validates :quantity, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 1 }
validates :product_id, presence: true
- validates :order_id, presence: true
- validates :shipped?, inclusion: { in: [true, false] }
- validate :valid_quantity
+ # validates :order_id, presence: true
+ # validates :shipped?, inclusion: { in: [true, false] }
+ # validate :valid_quantity
def total_price
self.product.price * quantity
From b0d82799b255e721f624fa018340c88c4072754c Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Tue, 25 Oct 2016 13:43:29 -0700
Subject: [PATCH 077/144] wrote home_controller test that ought to pass when
require login is disabled before the home index method
---
test/controllers/home_controller_test.rb | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/test/controllers/home_controller_test.rb b/test/controllers/home_controller_test.rb
index 730478d380..a3909c4d50 100644
--- a/test/controllers/home_controller_test.rb
+++ b/test/controllers/home_controller_test.rb
@@ -1,7 +1,10 @@
+# INTERNAL_NOTE: this test ought to pass once we make the home index page available to a guest user. 100% simplecov on this controller as of 10.25.2016.
+
require 'test_helper'
class HomeControllerTest < ActionController::TestCase
- # test "the truth" do
- # assert true
- # end
+ test "should get index" do
+ get :index
+ assert_response :success
+ end
end
From ae56187c28d6efcc081cc7b0768b4f598a0104c8 Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Tue, 25 Oct 2016 14:40:35 -0700
Subject: [PATCH 078/144] CATEGORIES controller: added index; created
CATEGORIES index view; tested the index controller; and added
skip_before_action so that all category controller tests are passing
---
app/controllers/categories_controller.rb | 21 ++++++----
app/views/categories/index.html.erb | 0
.../controllers/categories_controller_test.rb | 38 +++++++++++--------
3 files changed, 36 insertions(+), 23 deletions(-)
create mode 100644 app/views/categories/index.html.erb
diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb
index 5ee0e80115..ff71c374a5 100644
--- a/app/controllers/categories_controller.rb
+++ b/app/controllers/categories_controller.rb
@@ -1,10 +1,17 @@
class CategoriesController < ApplicationController
- def show
- begin
- @category = Category.find(params[:id])
- @products = @category.products
- rescue ActiveRecord::RecordNotFound => err
- render file: "#{Rails.root}/public/404.html", layout: false, status: :not_found
+ # Will likely need to remove this line (or further customize this), once we've narrowed down which pages require login. This line allows our tests to pass.
+ skip_before_action :require_login
+
+ def index
+ @categories = Category.all
+ end
+
+ def show
+ begin
+ @category = Category.find(params[:id])
+ @products = @category.products
+ rescue ActiveRecord::RecordNotFound
+ render file: "#{Rails.root}/public/404.html", layout: false, status: :not_found
+ end
end
- end
end
diff --git a/app/views/categories/index.html.erb b/app/views/categories/index.html.erb
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/controllers/categories_controller_test.rb b/test/controllers/categories_controller_test.rb
index 159fac6006..7fdce519a4 100644
--- a/test/controllers/categories_controller_test.rb
+++ b/test/controllers/categories_controller_test.rb
@@ -1,24 +1,30 @@
require 'test_helper'
class CategoriesControllerTest < ActionController::TestCase
- test "show page should show the proper category" do
- cat_id = categories(:cat).id
- get :show, {id: cat_id}
- assert_response :success
- assert_template :show
- category = assigns(:category)
- assert_not_nil category
- assert_equal category.id, cat_id
- end
+ test "should get category index" do
+ get :index
+ assert_response :success
+ end
+
+ test "show page should show the proper category" do
+ cat_id = categories(:cat).id
+ get :show, {id: cat_id}
+ assert_response :success
+ assert_template :show
- test "can't show a category that doesn't exist" do
- cat_id = 12345
- assert_raises ActiveRecord::RecordNotFound do
- Category.find(cat_id)
+ category = assigns(:category)
+ assert_not_nil category
+ assert_equal category.id, cat_id
end
- get :show, {id: cat_id}
- assert_response :not_found
- end
+ test "can't show a category that doesn't exist" do
+ cat_id = 12345
+ assert_raises ActiveRecord::RecordNotFound do
+ Category.find(cat_id)
+ end
+
+ get :show, {id: cat_id}
+ assert_response :not_found
+ end
end
From 3df17704ce08c9473b2486cde3f8d9673ff8e469 Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Tue, 25 Oct 2016 14:42:43 -0700
Subject: [PATCH 079/144] updated 5 controllers with a skip_before_action so
that all of our tests are passing, given the :require_login status across our
app. Also modified sessions_controller_test to correct changed routes so all
tests now pass. Finally, uncommented the test in home_controller_test; tests
in this file pass.
---
app/controllers/home_controller.rb | 3 +++
app/controllers/merchants_controller.rb | 3 +++
app/controllers/products_controller.rb | 5 +++++
app/controllers/reviews_controller.rb | 4 ++++
app/controllers/sessions_controller.rb | 2 +-
test/controllers/home_controller_test.rb | 2 --
test/controllers/sessions_controller_test.rb | 7 ++++---
7 files changed, 20 insertions(+), 6 deletions(-)
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
index 3930f023d2..e3c9b97d26 100644
--- a/app/controllers/home_controller.rb
+++ b/app/controllers/home_controller.rb
@@ -1,3 +1,6 @@
class HomeController < ApplicationController
+ # Will likely need to remove this line (or further customize this), once we've narrowed down which pages require login. This line allows our tests to pass.
+ skip_before_action :require_login
+
def index; end
end
diff --git a/app/controllers/merchants_controller.rb b/app/controllers/merchants_controller.rb
index 030610c038..fa67a22300 100644
--- a/app/controllers/merchants_controller.rb
+++ b/app/controllers/merchants_controller.rb
@@ -1,5 +1,8 @@
class MerchantsController < ApplicationController
+ # Will likely need to remove this line (or further customize this), once we've narrowed down which pages require login. This line allows our tests to pass.
+ skip_before_action :require_login, only: [:show]
+
def show
begin
@merchant = Merchant.find(params[:id])
diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb
index 9f02a99772..0f1490be21 100644
--- a/app/controllers/products_controller.rb
+++ b/app/controllers/products_controller.rb
@@ -1,4 +1,9 @@
class ProductsController < ApplicationController
+
+ # Will likely need to remove this line (or further customize this), once we've narrowed down which pages require login. This line allows our tests to pass.
+ skip_before_action :require_login, only: [:show, :index]
+
+
before_action :find_product, only: [:show, :edit, :update]
def index
diff --git a/app/controllers/reviews_controller.rb b/app/controllers/reviews_controller.rb
index 4a1eb591d8..5237925f64 100644
--- a/app/controllers/reviews_controller.rb
+++ b/app/controllers/reviews_controller.rb
@@ -1,4 +1,8 @@
class ReviewsController < ApplicationController
+
+ # Will likely need to remove this line (or further customize this), once we've narrowed down which pages require login. This line allows our tests to pass.
+ skip_before_action :require_login
+
def new
@product = Product.find(params[:product_id])
@review = Review.new
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 213138b9d3..0f0f46d144 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -17,7 +17,7 @@ def create
end
session[:user_id] = @merchant.id
- redirect_to sessions_path
+ redirect_to portal_path
end
def index
diff --git a/test/controllers/home_controller_test.rb b/test/controllers/home_controller_test.rb
index a3909c4d50..b230be25cc 100644
--- a/test/controllers/home_controller_test.rb
+++ b/test/controllers/home_controller_test.rb
@@ -1,5 +1,3 @@
-# INTERNAL_NOTE: this test ought to pass once we make the home index page available to a guest user. 100% simplecov on this controller as of 10.25.2016.
-
require 'test_helper'
class HomeControllerTest < ActionController::TestCase
diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb
index 49799a552a..f839fe5faf 100644
--- a/test/controllers/sessions_controller_test.rb
+++ b/test/controllers/sessions_controller_test.rb
@@ -10,7 +10,7 @@ def login_a_user
assert_difference('Merchant.count', 1) do
login_a_user
assert_response :redirect
- assert_redirected_to sessions_path
+ assert_redirected_to portal_path
end
end
@@ -18,11 +18,12 @@ def login_a_user
assert_difference('Merchant.count', 1) do
login_a_user
end
+ assert_not_nil session[:user_id]
assert_no_difference('Merchant.count') do
login_a_user
assert_response :redirect
- assert_redirected_to sessions_path
- assert_not_nil session[:merchant_id]
+ assert_redirected_to portal_path
+ assert_not_nil session[:user_id]
end
end
end
From f0d5247600c311c97575b8a82a573d0f0f9f9d91 Mon Sep 17 00:00:00 2001
From: "Johna N. Morris"
Date: Tue, 25 Oct 2016 15:43:20 -0700
Subject: [PATCH 080/144] wrote tests and implemented the remaining Product
controller actions
---
app/controllers/application_controller.rb | 2 +-
app/controllers/products_controller.rb | 36 ++--
app/helpers/application_helper.rb | 2 +-
app/views/products/edit.html.erb | 5 +
test/controllers/products_controller_test.rb | 186 +++++++++----------
test/models/product_test.rb | 29 ++-
6 files changed, 139 insertions(+), 121 deletions(-)
create mode 100644 app/views/products/edit.html.erb
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index a4d3c73e8c..e169ae1eb0 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -3,7 +3,7 @@ class ApplicationController < ActionController::Base
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
helper_method :current_order, :shopping_cart
- # before_action :require_login
+ before_action :require_login
private
def current_user
diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb
index ee87575889..d51617eaa7 100644
--- a/app/controllers/products_controller.rb
+++ b/app/controllers/products_controller.rb
@@ -1,5 +1,5 @@
class ProductsController < ApplicationController
- before_action :find_product, only: [:show, :edit, :update]
+ before_action :find_product, only: [:show, :edit, :update, :destroy]
def index
@products = Product.all
@@ -13,21 +13,31 @@ def new
@product = Product.new
end
+ def create
+ @product = Product.new(product_params)
+ @product.merchant_id = session[:user_id]
+ if @product.save
+ redirect_to product_path(@product)
+ else
+ render :new
+ end
+ end
- # def create
- #
- # end
-
+ def edit; end
- # def edit
- # end
- #
- # def update
- # end
- #
- # def destroy
- # end
+ def update
+ if @product.update(product_params)
+ redirect_to product_path(@product)
+ else
+ render :edit
+ end
+ end
+ def destroy
+ @product.destroy
+ redirect_to portal_path
+ end
+
private
def product_params
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 8e0c941ee9..b8a01f8948 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -38,7 +38,7 @@ def login_status
def login_button(**kwargs)
if session[:user_id]
text = "Log Out"
- path = '/sessions'
+ path = '/auth/logout'
method = :delete
else
text = "Log In"
diff --git a/app/views/products/edit.html.erb b/app/views/products/edit.html.erb
new file mode 100644
index 0000000000..86b3669939
--- /dev/null
+++ b/app/views/products/edit.html.erb
@@ -0,0 +1,5 @@
+Edit <%= @product.name %>
+
+<%= render partial: "form",
+ locals: { http_method: :patch,
+ local_path: product_path(@product) } %>
diff --git a/test/controllers/products_controller_test.rb b/test/controllers/products_controller_test.rb
index 836a118e0b..caa48ab1e3 100644
--- a/test/controllers/products_controller_test.rb
+++ b/test/controllers/products_controller_test.rb
@@ -32,105 +32,93 @@ class ProductsControllerTest < ActionController::TestCase
end
test "create should add a new product to the database" do
- post_params = {product: {name: "dog sunglasses", description: "too cool!"}, }
+ post_params = {product: {name: "dog sunglasses", description: "too cool!", price: 549, stock: 10}}
+ assert_difference("Product.count", 1) do
+ post :create, post_params
+ end
+
+ product = assigns(:product)
+ assert_redirected_to product_path(product.id)
end
-end
+ test "product with no name can't change the database" do
+ post_params = {product: {description: "too cool!", price: 549, stock: 10}}
+ assert_no_difference("Product.count") do
+ post :create, post_params
+ end
+ assert_template :new
+ end
+
+ test "product with no price can't change the database" do
+ post_params = {product: {name: "dog sunglasses", description: "too cool!", stock: 10}}
+ assert_no_difference("Product.count") do
+ post :create, post_params
+ end
+ assert_template :new
+ end
+
+ test "product with no stock can't change the database" do
+ post_params = {product: {name: "dog sunglasses", description: "too cool!"}}
+ assert_no_difference("Product.count") do
+ post :create, post_params
+ end
+ assert_template :new
+ end
+
+ test "should get the product edit form" do
+ product_id = products(:cat_suit).id
+ get :edit, {id: product_id}
+ assert_template :edit
+ assert_template partial: '_form'
+ assert_response :success
+
+ product = assigns(:product)
+ assert_not_nil product
+ assert_equal product.id, product_id
+ end
+
+ test "update should change the product" do
+ product_id = products(:cat_suit).id
+ patch :update, {id: product_id, product: {name: "cat fancy suit"}}
+ assert_equal "cat fancy suit", Product.find(product_id).name
-# test "add a new movie to the database" do
-# post_params = {movie: {name: "The Sound of Music", director: "no idea"} }
-# assert_difference("Movie.count", 1) do
-# post :create, post_params
-# end
-#
-# assert_redirected_to movies_path
-# end
-#
-# test "a movie with no title can't change the database" do
-# post_params = { movie: {director: "someone", description: "empty values"}}
-#
-# assert_no_difference("Movie.count") do
-# post :create, post_params
-# end
-#
-# assert_template :new
-# end
-#
-# test "a movie with no author can't change the database" do
-# post_params = { movie: {name: "something", description: "empty values"}}
-#
-# assert_no_difference("Movie.count") do
-# post :create, post_params
-# end
-#
-# assert_template :new
-# end
-#
-# test "should get the edit form" do
-# movie_id = movies(:schindlers_list).id
-# get :edit, {id: movie_id}
-# assert_template :edit
-# assert_template partial: '_form'
-# assert_response :success
-#
-# movie = assigns(:movie)
-# assert_not_nil movie
-# assert_equal movie.id, movie_id
-# end
-#
-# test "update should change the movie" do
-# movie_id = movies(:galaxy_quest).id
-# patch :update, {id: movie_id, movie: {name: "Galaxy Quest (ick)"} }
-# assert_equal "Galaxy Quest (ick)", Movie.find(movie_id).name
-#
-# assert_redirected_to movie_path
-# end
-#
-# test "update should not allow nil name" do
-# movie_id = movies(:toy_story).id
-# patch :update, {id: movie_id, movie: {name: nil} }
-#
-# assert_equal "Toy Story", Movie.find(movie_id).name
-#
-# assert_template :edit
-# end
-#
-# test "update should not allow nil director" do
-# movie_id = movies(:toy_story).id
-# patch :update, {id: movie_id, movie: {director: nil} }
-#
-# assert_equal "Pixar", Movie.find(movie_id).director
-# assert_template :edit
-# end
-#
-# test "destroy should delete the item" do
-# movie_id = movies(:galaxy_quest).id
-#
-# assert_difference("Movie.count", -1) do
-# delete :destroy, {id: movie_id}
-# end
-#
-# assert_raises ActiveRecord::RecordNotFound do
-# Movie.find(movie_id)
-# end
-#
-# assert_redirected_to movies_path
-# end
-#
-# test "upvote should increment rank by one" do
-# movie_id = movies(:schindlers_list).id
-#
-# assert_difference("Movie.find(movie_id).rank", 1) do
-# patch :upvote, {id: movie_id}
-# end
-#
-# assert_redirected_to movie_path(movie_id)
-# end
-#
-# test "upvote should set nil ranks to one" do
-# movie_id = movies(:nil_rank).id
-#
-# assert_difference("Movie.find(movie_id).rank", 1) do
-# patch :upvote, {id: movie_id}
-# end
-# end
+ assert_redirected_to product_path
+ end
+
+ test "update should not allow empty name" do
+ product_id = products(:cat_suit).id
+ patch :update, {id: product_id, product: {name: nil}}
+ assert_equal "cat suit", Product.find(product_id).name
+
+ assert_template :edit
+ end
+
+ test "update should not allow invalid price" do
+ product_id = products(:cat_suit).id
+ patch :update, {id: product_id, product: {price: 0}}
+ assert_equal 1234, Product.find(product_id).price
+
+ assert_template :edit
+ end
+
+ test "update should not allow empty stock" do
+ product_id = products(:cat_suit).id
+ patch :update, {id: product_id, product: {stock: nil}}
+ assert_equal 4, Product.find(product_id).stock
+
+ assert_template :edit
+ end
+
+ test "destroy should delete the product" do
+ product_id = products(:cat_suit).id
+ assert_difference("Product.count", -1) do
+ delete :destroy, {id: product_id}
+ end
+
+ assert_raises ActiveRecord::RecordNotFound do
+ Product.find(product_id)
+ end
+
+ assert_redirected_to portal_path
+ end
+end
diff --git a/test/models/product_test.rb b/test/models/product_test.rb
index afe47c7246..a5d0d008da 100644
--- a/test/models/product_test.rb
+++ b/test/models/product_test.rb
@@ -2,19 +2,19 @@
class ProductTest < ActiveSupport::TestCase
test "Cannot create a product without a name" do
- product = Product.new
+ product = Product.new(stock: 2, price: 400)
assert_not product.valid?
assert_includes product.errors, :name
end
test "Cannot create a product without a price" do
- product = Product.new(name: "kitten tux")
+ product = Product.new(name: "kitten tux", stock: 2)
assert_not product.valid?
assert_includes product.errors, :price
end
test "Cannot create a product with a duplicate name" do
- product = Product.new(name: "cat suit", price: 224)
+ product = Product.new(name: "cat suit", price: 224, stock: 5)
assert_not product.valid?
assert_not product.save
assert_includes product.errors, :name
@@ -22,32 +22,47 @@ class ProductTest < ActiveSupport::TestCase
end
test "Price must be an integer" do
- product = Product.new(name: "dog sunglasses", price: "foo")
+ product = Product.new(name: "dog sunglasses", price: "foo", stock: 1)
assert_not product.valid?
assert_not product.save
assert_equal ["is not a number"], product.errors.messages[:price]
end
test "Price can't be 0" do
- product = Product.new(name: "foobar", price: 0)
+ product = Product.new(name: "foobar", price: 0, stock: 1)
assert_not product.valid?
assert_not product.save
assert_equal ["must be greater than 0"], product.errors.messages[:price]
end
test "Price can't be less than 0" do
- product = Product.new(name: "foobar", price: -1)
+ product = Product.new(name: "foobar", price: -1, stock: 1)
assert_not product.valid?
assert_not product.save
assert_equal ["must be greater than 0"], product.errors.messages[:price]
end
+ test "Stock can't be less than 0" do
+ product = Product.new(name: "foobar", price: 500, stock: -10)
+ assert_not product.valid?
+ assert_not product.save
+ assert_equal ["must be greater than or equal to 0"], product.errors.messages[:stock]
+ end
+
+ test "Stock can be zero" do
+ product = Product.new(name: "foobar", price: 120, stock: 0)
+ assert product.valid?
+ assert_not_nil product.stock
+ assert product.save
+ end
+
test "create Product with valid data" do
product = products(:cat_suit)
assert product.valid?
assert_not_nil product.name
assert_not_nil product.price
+ assert_not_nil product.stock
end
test "create products with different names" do
@@ -58,7 +73,7 @@ class ProductTest < ActiveSupport::TestCase
end
test "Product belongs to a merchant" do
- product = Product.create!(name: "mouse hat", price: 1240)
+ product = Product.create!(name: "mouse hat", price: 1240, stock: 5)
merchant = Merchant.create!(user_name: "testing", email: "test@test.com", uid: 124, provider: "github")
product.merchant = merchant
From 9fc18fbd52e285dacfc66e000a85b7ce9cb1c24e Mon Sep 17 00:00:00 2001
From: Suzannah Kirk-Daligcon
Date: Tue, 25 Oct 2016 15:45:32 -0700
Subject: [PATCH 081/144] added content to categories index view, minor updates
elsewhere
---
app/assets/stylesheets/application.css | 5 +++++
app/controllers/categories_controller.rb | 2 +-
app/helpers/application_helper.rb | 2 +-
app/views/categories/index.html.erb | 9 +++++++++
app/views/categories/show.html.erb | 2 +-
5 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css
index 19d085d894..5344a6da78 100644
--- a/app/assets/stylesheets/application.css
+++ b/app/assets/stylesheets/application.css
@@ -1,3 +1,8 @@
+/*
+*= require_tree .
+*= require_self
+*/
+
.nav-bar li {
display: inline;
font-family: 'Julius Sans One', sans-serif;
diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb
index ff71c374a5..cbc37b27c4 100644
--- a/app/controllers/categories_controller.rb
+++ b/app/controllers/categories_controller.rb
@@ -3,7 +3,7 @@ class CategoriesController < ApplicationController
skip_before_action :require_login
def index
- @categories = Category.all
+ @categories = Category.all.order(:name)
end
def show
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 8e0c941ee9..b8a01f8948 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -38,7 +38,7 @@ def login_status
def login_button(**kwargs)
if session[:user_id]
text = "Log Out"
- path = '/sessions'
+ path = '/auth/logout'
method = :delete
else
text = "Log In"
diff --git a/app/views/categories/index.html.erb b/app/views/categories/index.html.erb
index e69de29bb2..1bf747ebb5 100644
--- a/app/views/categories/index.html.erb
+++ b/app/views/categories/index.html.erb
@@ -0,0 +1,9 @@
+All Categories
+
+
+<% @categories.each do |category| %>
+
+ <%= link_to capitalize_each_word(category.name), category_path(category) %>
+
+<%end%>
+
diff --git a/app/views/categories/show.html.erb b/app/views/categories/show.html.erb
index 713e2c9ab6..e150956518 100644
--- a/app/views/categories/show.html.erb
+++ b/app/views/categories/show.html.erb
@@ -1,4 +1,4 @@
-<%= capitalize_each_word(@category.name) %>
+Category: <%= capitalize_each_word(@category.name) %>
<% if @products.empty? %>
No products in this category!
From d1ab31b3f4cf22d965199e473ef845a3cf312713 Mon Sep 17 00:00:00 2001
From: Shari Meggs
Date: Tue, 25 Oct 2016 16:03:36 -0700
Subject: [PATCH 082/144] destroying the cart
---
app/controllers/application_controller.rb | 16 +++++-----
app/controllers/carts_controller.rb | 39 +++++++++++++----------
app/controllers/products_controller.rb | 2 +-
app/views/carts/index.html.erb | 3 +-
app/views/products/index.html.erb | 4 +--
config/routes.rb | 1 +
6 files changed, 35 insertions(+), 30 deletions(-)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index db10ba629b..b729d004b8 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -21,16 +21,16 @@ def require_login
end
end
- def current_order
- if !session[:order_id].nil?
- Order.find(session[:order_id])
- else
- Order.new
- end
- end
+ # def current_order
+ # if !session[:order_id].nil?
+ # Order.find(session[:order_id])
+ # else
+ # Order.new
+ # end
+ # end
def shopping_cart
- if session[:cart]
+ if !session[:cart].nil?
@cart = session[:cart]
else
session[:cart]= {}
diff --git a/app/controllers/carts_controller.rb b/app/controllers/carts_controller.rb
index 7c17819d0d..52ee4021cf 100644
--- a/app/controllers/carts_controller.rb
+++ b/app/controllers/carts_controller.rb
@@ -1,15 +1,18 @@
class CartsController < ApplicationController
before_action :shopping_cart
- def index
- @cart.each do | k, v|
- @id = k
+ def index
+ @cart.each do | cart_item|
+ @id = cart_item.to_i
end
- @order_item = OrderItem.find_by(product_id: @id)
- raise
+ # @order_item = OrderItem.find_by(product_id: @id)
- if @order_item != nil
+raise
+
+ if @id != nil
@product = Product.find(@id)
+ @order_item = OrderItem.find_by(product_id: @id)
+
# @product.order_items << @order_item
# @product.save
@@ -23,14 +26,18 @@ def index
def add_to_cart
id = params[:id]
@product = Product.find(id)
- @order_item = OrderItem.create
- @product.order_items << @order_item
- @product.save
- @order_item.save
- if @cart[id]
- @cart[id] = @cart[id] + 1
- else
- @cart[id] = 1
+ begin
+ @order_item = OrderItem.find(params[:product_id])
+ rescue ActiveRecord::RecordNotFound
+ nil
+ end
+ if @order_item.nil?
+
+ @order_item = OrderItem.create
+ @product.order_items << @order_item
+ @product.save
+ # @cart { @product.id => @order_item.quantity })
+ @order_item.save
end
redirect_to carts_path
end
@@ -42,9 +49,9 @@ def sub_cart
end
def destroy
+ @order_item = OrderItem.find_by(params[:product_id])
+
- @order_item = OrderItem.find_by(product_id: params[:id])
- @order_item.destroy
# @cart = shopping_cart
# @product = @cart.product.find(params[:id]).destroy
redirect_to carts_path
diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb
index 9f02a99772..da063fd81d 100644
--- a/app/controllers/products_controller.rb
+++ b/app/controllers/products_controller.rb
@@ -3,7 +3,7 @@ class ProductsController < ApplicationController
def index
@products = Product.all
- @order_item = current_order.order_items.new
+ # @order_item = current_order.order_items.new
end
def show; end
diff --git a/app/views/carts/index.html.erb b/app/views/carts/index.html.erb
index 528aa196bd..a159061282 100644
--- a/app/views/carts/index.html.erb
+++ b/app/views/carts/index.html.erb
@@ -7,9 +7,8 @@
<% @cart.each do |id, quantity| %>
-
-
+ raise
<%=@product.name%>
<%=link_to "Learn More", product_path(id)%>
<%=number_to_currency(@product.price, :unit => '$')%>
diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb
index 8d15fce1c0..27b7ca6e86 100644
--- a/app/views/products/index.html.erb
+++ b/app/views/products/index.html.erb
@@ -11,9 +11,7 @@
<%= product.description %>
-
- Add to Cart
-
+ Add to Cart
<%end%>
diff --git a/config/routes.rb b/config/routes.rb
index 25036b9d51..ac1a951452 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -35,6 +35,7 @@
get '/carts' => 'carts#index'
get 'carts/empty_cart' =>'carts#empty_cart'
get '/carts/:id', to: 'carts#add_to_cart', as: "add_cart"
+
get '/carts/:id', to: 'carts#sub_cart', as: "sub_cart"
delete '/carts/products/:id', to: 'carts#destroy', as: 'delete_cart'
From 8dacb59e28500cba17544530ff30cc2a32434afa Mon Sep 17 00:00:00 2001
From: Yeni
Date: Tue, 25 Oct 2016 17:11:29 -0700
Subject: [PATCH 083/144] worked on product show page css and html foundation
---
app/assets/javascripts/application.js | 1 +
app/assets/stylesheets/application.css | 25 ------
app/assets/stylesheets/application.scss | 67 +++++++++++++++
app/assets/stylesheets/products.scss | 31 +++++++
app/views/layouts/application.html.erb | 29 ++++---
app/views/products/index.html.erb | 7 +-
app/views/products/show.html.erb | 109 +++++++++++++-----------
7 files changed, 177 insertions(+), 92 deletions(-)
delete mode 100644 app/assets/stylesheets/application.css
create mode 100644 app/assets/stylesheets/application.scss
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index e07c5a830f..a71cf35238 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -13,4 +13,5 @@
//= require jquery
//= require jquery_ujs
//= require turbolinks
+//= require foundation
//= require_tree .
diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css
deleted file mode 100644
index 19d085d894..0000000000
--- a/app/assets/stylesheets/application.css
+++ /dev/null
@@ -1,25 +0,0 @@
-.nav-bar li {
- display: inline;
- font-family: 'Julius Sans One', sans-serif;
- border-bottom: 2pt solid #F5B19A;
- color: #666699;
- /*previous color #39324b*/
- margin-right: 8%;
- margin-left: 8%;
- padding: 5px 25px;
- font-size: 9pt;
-}
-
-nav a {
- color: grey;
-}
-
-a:hover {
- color: #666699;
-}
-
-a {
- color: #F5B19A;
- font-family: 'Quicksand';
- text-decoration: none;
-}
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
new file mode 100644
index 0000000000..102f05a63f
--- /dev/null
+++ b/app/assets/stylesheets/application.scss
@@ -0,0 +1,67 @@
+/*
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
+ * listed below.
+ *
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
+ *
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
+ * compiled file so the styles you add here take precedence over styles defined in any styles
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
+ * file per style scope.
+ *
+ *= require_tree .
+ *= require_self
+ */
+
+ body {
+ margin: 0 5%;
+ }
+
+.top-bar {
+ background-color: white;
+}
+
+.top-bar-left li, .top-bar-right li {
+ font-family: 'Julius Sans One', sans-serif;
+ border-bottom: 2pt solid #F5B19A;
+ color: #666699;
+ background-color: white;
+ margin-right: 8%;
+ margin-left: 8%;
+ padding: 5px 25px;
+ font-size: 9pt;
+}
+
+.title-area {
+ color: #F5B19A;
+ font-family: 'Quicksand';
+ text-align: center;
+}
+/*
+nav a {
+ color: grey;
+}*/
+
+a:hover {
+ color: #666699;
+}
+
+a, ul li {
+ color: #F5B19A;
+ font-family: 'Quicksand';
+ text-decoration: none;
+}
+
+footer {
+ font-family: 'Quicksand', sans-serif;
+ color: #F5B19A;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ background-color: #efefef;
+ /*text-align: center;*/
+ margin-top: 5%;
+ padding-left: 5%;
+ position: fixed;
+}
diff --git a/app/assets/stylesheets/products.scss b/app/assets/stylesheets/products.scss
index 89e2e8db07..f1da003c0a 100644
--- a/app/assets/stylesheets/products.scss
+++ b/app/assets/stylesheets/products.scss
@@ -1,3 +1,34 @@
// Place all the styles related to the products controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
+.product-image {
+ border-radius: 25px;
+ width: 300px;
+}
+
+.prod-dets {
+ margin: 5%;
+}
+
+.details {
+ margin-top: 5%;
+ padding: 5% 5% 0 5%;
+ border: 2pt solid #F5B19A;
+ border-radius: 25px;
+ color: #66CDAA;
+}
+
+ul li {
+ display: inline;
+}
+
+.category-link {
+ text-decoration: underline;
+ margin: 1%;
+}
+
+.button {
+ border-radius: 25px;
+ // background-color: white;
+
+}
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index ae43ba0a78..b43e944988 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -9,17 +9,26 @@
-
- <%= @page_title %>
-
-
- <%= link_to "HOME", root_path %>
- <%= login_status %>
- <%= login_button %>
-
-
+
+ <%= @page_title %>
+
+
+
+
+
<%= yield %>
-
+